Files
wehub-resource-sync a7d6d88f6f
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:37:18 +08:00

265 lines
8.8 KiB
Python

from __future__ import annotations
import pytest
from langchain_protocol import (
Channel as ProtocolChannel,
)
from langchain_protocol import (
Event as ProtocolEvent,
)
from langchain_protocol import (
Namespace as ProtocolNamespace,
)
from langchain_protocol import (
SubscribeParams as ProtocolSubscribeParams,
)
from langgraph_sdk.stream import (
Channel,
Event,
Namespace,
SubscribeParams,
)
from langgraph_sdk.stream.subscription import (
compute_union_filter,
filter_covers,
infer_channel,
is_prefix_match,
matches_subscription,
namespace_matches,
normalize_segment,
)
from streaming._events import (
custom_event,
lifecycle_event,
values_event,
)
def test_normalize_segment_strips_suffix_after_colon():
assert normalize_segment("fetcher:abc-uuid") == "fetcher"
def test_normalize_segment_passes_through_when_no_colon():
assert normalize_segment("fetcher") == "fetcher"
def test_is_prefix_match_empty_prefix_matches_anything():
assert is_prefix_match(["a", "b"], []) is True
def test_is_prefix_match_exact_literal_match():
assert is_prefix_match(["fetcher", "inner"], ["fetcher"]) is True
def test_is_prefix_match_strips_runtime_suffix_when_prefix_is_static():
assert is_prefix_match(["fetcher:abc-uuid", "inner"], ["fetcher"]) is True
def test_is_prefix_match_keeps_colons_in_prefix_literal():
# When the prefix itself contains a colon it is treated as exact.
assert is_prefix_match(["fetcher:abc"], ["fetcher:abc"]) is True
assert is_prefix_match(["fetcher:xyz"], ["fetcher:abc"]) is False
def test_is_prefix_match_returns_false_when_prefix_longer():
assert is_prefix_match(["a"], ["a", "b"]) is False
def test_namespace_matches_no_prefixes_matches_anything():
assert namespace_matches(["any", "ns"], None, None) is True
assert namespace_matches(["any", "ns"], [], None) is True
def test_namespace_matches_with_depth_limit():
assert namespace_matches(["a", "b"], [["a"]], 1) is True
assert namespace_matches(["a", "b", "c"], [["a"]], 1) is False
@pytest.mark.parametrize(
("method", "expected_channel"),
[
("values", "values"),
("checkpoints", "checkpoints"),
("updates", "updates"),
("messages", "messages"),
("tools", "tools"),
("lifecycle", "lifecycle"),
("tasks", "tasks"),
("input.requested", "input"),
],
)
def test_infer_channel_for_each_method(method, expected_channel):
event = {
"type": "event",
"method": method,
"params": {"namespace": [], "data": {}},
"seq": 0,
"event_id": "e",
}
assert infer_channel(event) == expected_channel # ty: ignore[invalid-argument-type]
def test_infer_channel_custom_with_name_produces_namespaced_channel():
assert infer_channel(custom_event(name="my_ext")) == "custom:my_ext" # ty:ignore[invalid-argument-type]
def test_infer_channel_custom_without_name_falls_back_to_bare_custom():
assert infer_channel(custom_event(name="")) == "custom" # ty:ignore[invalid-argument-type]
def test_infer_channel_unknown_method_returns_none():
assert (
infer_channel(
{
"type": "event",
"method": "unknown",
"params": {"namespace": [], "data": {}},
"seq": 0,
"event_id": "e",
} # ty:ignore[invalid-argument-type]
)
is None
)
def test_matches_subscription_channel_in_filter():
sub = {"channels": ["values"]}
assert matches_subscription(values_event(), sub) is True # ty:ignore[invalid-argument-type]
assert matches_subscription(lifecycle_event(), sub) is False # ty:ignore[invalid-argument-type]
def test_matches_subscription_bare_custom_covers_namespaced_custom():
sub = {"channels": ["custom"]}
assert matches_subscription(custom_event(name="my_ext"), sub) is True # ty:ignore[invalid-argument-type]
def test_matches_subscription_namespace_filter_applied():
sub = {"channels": ["values"], "namespaces": [["fetcher"]]}
assert (
matches_subscription(values_event(namespace=["fetcher", "inner"]), sub) is True # ty:ignore[invalid-argument-type]
)
assert matches_subscription(values_event(namespace=["other"]), sub) is False # ty:ignore[invalid-argument-type]
def test_matches_subscription_bare_custom_event_matches_bare_custom_filter():
sub = {"channels": ["custom"]}
assert matches_subscription(custom_event(name=""), sub) is True # ty: ignore[invalid-argument-type]
def test_compute_union_filter_merges_channels():
a = {"channels": ["values"]}
b = {"channels": ["messages", "lifecycle"]}
result = compute_union_filter([a, b])
assert set(result["channels"]) == {"values", "messages", "lifecycle"}
def test_compute_union_filter_drops_namespaces_when_any_subscription_is_unscoped():
a = {"channels": ["values"], "namespaces": [["fetcher"]]}
b = {"channels": ["messages"]} # no namespaces == wildcard
result = compute_union_filter([a, b])
# If any subscription is unscoped, the union must be unscoped.
assert "namespaces" not in result or result.get("namespaces") is None
def test_compute_union_filter_unions_namespaces_when_all_scoped():
a = {"channels": ["values"], "namespaces": [["fetcher"]]}
b = {"channels": ["messages"], "namespaces": [["scorer"]]}
result = compute_union_filter([a, b])
assert sorted(result["namespaces"]) == [["fetcher"], ["scorer"]]
def test_compute_union_filter_takes_max_depth():
a = {"channels": ["values"], "depth": 1}
b = {"channels": ["messages"], "depth": 3}
result = compute_union_filter([a, b])
assert result["depth"] == 3
def test_compute_union_filter_omits_depth_when_any_subscription_omits():
a = {"channels": ["values"], "depth": 2}
b = {"channels": ["messages"]} # no depth == unbounded
result = compute_union_filter([a, b])
assert "depth" not in result or result.get("depth") is None
def test_compute_union_filter_empty_input_returns_empty_channel_filter():
result = compute_union_filter([])
assert result == {"channels": []}
def test_filter_covers_same_filter():
f = {"channels": ["values", "messages"]}
assert filter_covers(f, f) is True
def test_filter_covers_superset_channels():
coverer = {"channels": ["values", "messages", "lifecycle"]}
target = {"channels": ["values", "messages"]}
assert filter_covers(coverer, target) is True
def test_filter_covers_missing_channel():
coverer = {"channels": ["values"]}
target = {"channels": ["values", "messages"]}
assert filter_covers(coverer, target) is False
def test_filter_covers_unscoped_covers_scoped():
coverer = {"channels": ["values"]} # wildcard namespaces
target = {"channels": ["values"], "namespaces": [["fetcher"]]}
assert filter_covers(coverer, target) is True
def test_filter_covers_scoped_does_not_cover_unscoped():
coverer = {"channels": ["values"], "namespaces": [["fetcher"]]}
target = {"channels": ["values"]} # wildcard
assert filter_covers(coverer, target) is False
def test_filter_covers_depth_with_namespace_offset():
# Coverer at depth 1 from ["agent"] reaches ["agent", X].
# Target needs depth 1 from ["agent", "tool"] — i.e., ["agent", "tool", X].
# That's 2 levels past coverer's prefix, but coverer only allows 1.
coverer = {"channels": ["values"], "namespaces": [["agent"]], "depth": 1}
target = {
"channels": ["values"],
"namespaces": [["agent", "tool"]],
"depth": 1,
}
assert filter_covers(coverer, target) is False
def test_filter_covers_depth_with_offset_enough_depth():
# Same setup but coverer depth=2 absorbs the offset.
coverer = {"channels": ["values"], "namespaces": [["agent"]], "depth": 2}
target = {
"channels": ["values"],
"namespaces": [["agent", "tool"]],
"depth": 1,
}
assert filter_covers(coverer, target) is True
def test_filter_covers_unscoped_coverer_with_bounded_depth():
# Coverer is unscoped; depth comparison is the simple scalar form.
coverer = {"channels": ["values"], "depth": 2}
target = {"channels": ["values"], "depth": 1}
assert filter_covers(coverer, target) is True
target_too_deep = {"channels": ["values"], "depth": 3}
assert filter_covers(coverer, target_too_deep) is False
def test_filter_covers_bounded_coverer_does_not_cover_unbounded_target():
coverer = {"channels": ["values"], "depth": 2}
target = {"channels": ["values"]} # unbounded
assert filter_covers(coverer, target) is False
def test_protocol_types_are_importable_from_stream_module():
"""Test that v3 protocol types are re-exported from langgraph_sdk.stream."""
assert Channel is ProtocolChannel
assert Event is ProtocolEvent
assert Namespace is ProtocolNamespace
assert SubscribeParams is ProtocolSubscribeParams