308 lines
11 KiB
Python
308 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
from yuxi.channel.capabilities import CapabilityMatrix
|
|
from yuxi.channel.capabilities.levels import (
|
|
EditSupport,
|
|
InteractiveSupport,
|
|
MarkdownSupport,
|
|
MediaSupport,
|
|
StreamingSupport,
|
|
ThreadSupport,
|
|
)
|
|
from yuxi.channel.capabilities.negotiation import (
|
|
CapabilityRequirement,
|
|
get_required_ports,
|
|
negotiate,
|
|
supports,
|
|
)
|
|
from yuxi.channel.plugins.protocol import ChannelCapability, ChannelMeta
|
|
from yuxi.channel.ports import AuthPort, InboundPort, OutboundPort, SessionPort
|
|
|
|
|
|
class TestCapabilityMatrixDefaults:
|
|
def test_defaults(self):
|
|
matrix = CapabilityMatrix()
|
|
assert matrix.text is True
|
|
assert matrix.markdown == MarkdownSupport.NONE
|
|
assert matrix.media == {MediaSupport.NONE}
|
|
assert matrix.interactive == InteractiveSupport.NONE
|
|
assert matrix.reactions is False
|
|
assert matrix.threads == ThreadSupport.NONE
|
|
assert matrix.edits == EditSupport.NONE
|
|
assert matrix.streaming == StreamingSupport.NONE
|
|
assert matrix.batch_send is False
|
|
assert matrix.qr_login is False
|
|
assert matrix.scan_pairing is False
|
|
assert matrix.pin is False
|
|
assert matrix.directory is False
|
|
|
|
|
|
class TestCapabilityMatrixFromChannelCapability:
|
|
def test_text_only(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.TEXT)
|
|
assert matrix.text is True
|
|
assert matrix.markdown == MarkdownSupport.NONE
|
|
assert matrix.media == {MediaSupport.NONE}
|
|
|
|
def test_markdown(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.MARKDOWN)
|
|
assert matrix.markdown == MarkdownSupport.BASIC
|
|
|
|
def test_image_and_file(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.IMAGE | ChannelCapability.FILE)
|
|
assert matrix.media == {MediaSupport.IMAGE, MediaSupport.FILE}
|
|
|
|
def test_interactive(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.INTERACTIVE)
|
|
assert matrix.interactive == InteractiveSupport.STATIC
|
|
|
|
def test_reaction(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.REACTION)
|
|
assert matrix.reactions is True
|
|
|
|
def test_thread(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.THREAD)
|
|
assert matrix.threads == ThreadSupport.REPLY
|
|
|
|
def test_edit(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.EDIT)
|
|
assert matrix.edits == EditSupport.WITHIN_MINUTE
|
|
|
|
def test_streaming(self):
|
|
matrix = CapabilityMatrix.from_channel_capability(ChannelCapability.STREAMING)
|
|
assert matrix.streaming == StreamingSupport.TYPING
|
|
|
|
def test_combined_flags(self):
|
|
caps = (
|
|
ChannelCapability.TEXT
|
|
| ChannelCapability.MARKDOWN
|
|
| ChannelCapability.IMAGE
|
|
| ChannelCapability.INTERACTIVE
|
|
| ChannelCapability.THREAD
|
|
)
|
|
matrix = CapabilityMatrix.from_channel_capability(caps)
|
|
assert matrix.text is True
|
|
assert matrix.markdown == MarkdownSupport.BASIC
|
|
assert MediaSupport.IMAGE in matrix.media
|
|
assert matrix.interactive == InteractiveSupport.STATIC
|
|
assert matrix.threads == ThreadSupport.REPLY
|
|
|
|
|
|
class TestCapabilityMatrixSupports:
|
|
def test_text_supported_by_default(self):
|
|
matrix = CapabilityMatrix()
|
|
assert matrix.supports("text") is True
|
|
|
|
def test_markdown_when_none(self):
|
|
matrix = CapabilityMatrix()
|
|
assert matrix.supports("markdown") is False
|
|
|
|
def test_markdown_when_basic(self):
|
|
matrix = CapabilityMatrix(markdown=MarkdownSupport.BASIC)
|
|
assert matrix.supports("markdown") is True
|
|
|
|
def test_media_when_none(self):
|
|
matrix = CapabilityMatrix()
|
|
assert matrix.supports("media") is False
|
|
|
|
def test_media_when_image(self):
|
|
matrix = CapabilityMatrix(media={MediaSupport.IMAGE})
|
|
assert matrix.supports("media") is True
|
|
|
|
def test_interactive_when_static(self):
|
|
matrix = CapabilityMatrix(interactive=InteractiveSupport.STATIC)
|
|
assert matrix.supports("interactive") is True
|
|
|
|
def test_reactions(self):
|
|
matrix = CapabilityMatrix(reactions=True)
|
|
assert matrix.supports("reactions") is True
|
|
|
|
def test_threads(self):
|
|
matrix = CapabilityMatrix(threads=ThreadSupport.REPLY)
|
|
assert matrix.supports("threads") is True
|
|
|
|
def test_edits(self):
|
|
matrix = CapabilityMatrix(edits=EditSupport.FOREVER)
|
|
assert matrix.supports("edits") is True
|
|
|
|
def test_streaming(self):
|
|
matrix = CapabilityMatrix(streaming=StreamingSupport.FULL)
|
|
assert matrix.supports("streaming") is True
|
|
|
|
def test_batch_send(self):
|
|
matrix = CapabilityMatrix(batch_send=True)
|
|
assert matrix.supports("batch_send") is True
|
|
|
|
def test_qr_login(self):
|
|
matrix = CapabilityMatrix(qr_login=True)
|
|
assert matrix.supports("qr_login") is True
|
|
|
|
def test_scan_pairing(self):
|
|
matrix = CapabilityMatrix(scan_pairing=True)
|
|
assert matrix.supports("scan_pairing") is True
|
|
|
|
def test_unknown_capability(self):
|
|
matrix = CapabilityMatrix()
|
|
assert matrix.supports("unknown") is False
|
|
|
|
|
|
class TestCapabilityMatrixOverrides:
|
|
def test_override_disabled(self):
|
|
matrix = CapabilityMatrix(text=True, markdown=MarkdownSupport.BASIC)
|
|
config = {"capability_overrides": {"text": "disabled", "markdown": "disabled"}}
|
|
assert matrix.supports("text", config) is False
|
|
assert matrix.supports("markdown", config) is False
|
|
|
|
def test_override_enabled(self):
|
|
matrix = CapabilityMatrix()
|
|
config = {"capability_overrides": {"text": "enabled", "markdown": "enabled"}}
|
|
assert matrix.supports("text", config) is True
|
|
assert matrix.supports("markdown", config) is True
|
|
|
|
def test_override_level(self):
|
|
matrix = CapabilityMatrix(markdown=MarkdownSupport.BASIC)
|
|
config = {"capability_overrides": {"markdown": "extended"}}
|
|
assert matrix.supports("markdown", config) is True
|
|
|
|
def test_override_invalid_level_returns_false(self):
|
|
matrix = CapabilityMatrix(markdown=MarkdownSupport.BASIC)
|
|
config = {"capability_overrides": {"markdown": "not-a-level"}}
|
|
assert matrix.supports("markdown", config) is False
|
|
|
|
def test_override_media(self):
|
|
matrix = CapabilityMatrix()
|
|
config = {"capability_overrides": {"media": "image"}}
|
|
assert matrix.supports("media", config) is True
|
|
|
|
def test_override_ignores_unrelated_keys(self):
|
|
matrix = CapabilityMatrix(text=True)
|
|
config = {"capability_overrides": {"unknown": "enabled"}}
|
|
assert matrix.supports("text", config) is True
|
|
|
|
|
|
class TestSupportsFunction:
|
|
def test_supports_when_matrix_present(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(text=True),
|
|
)
|
|
|
|
assert supports(FakeChannel(), "text") is True
|
|
|
|
def test_supports_false_when_not_supported(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(),
|
|
)
|
|
|
|
assert supports(FakeChannel(), "markdown") is False
|
|
|
|
def test_supports_with_config_override(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(markdown=MarkdownSupport.BASIC),
|
|
)
|
|
|
|
config = {"capability_overrides": {"markdown": "disabled"}}
|
|
assert supports(FakeChannel(), "markdown", config) is False
|
|
|
|
def test_supports_returns_false_without_meta(self):
|
|
class FakeChannel:
|
|
pass
|
|
|
|
assert supports(FakeChannel(), "text") is False # type: ignore[arg-type]
|
|
|
|
def test_supports_returns_false_without_matrix(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
meta = ChannelMeta(channel_type="test", display_name="Test")
|
|
meta.capability_matrix = None # type: ignore[assignment]
|
|
return meta
|
|
|
|
assert supports(FakeChannel(), "text") is False
|
|
|
|
|
|
class TestNegotiateFunction:
|
|
def test_negotiate_supported(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(text=True),
|
|
)
|
|
|
|
req = CapabilityRequirement(capability="text")
|
|
result = negotiate(FakeChannel(), req)
|
|
assert result.supported is True
|
|
assert result.fallback == "ignore"
|
|
|
|
def test_negotiate_unsupported(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(),
|
|
)
|
|
|
|
req = CapabilityRequirement(capability="markdown", fallback="text")
|
|
result = negotiate(FakeChannel(), req)
|
|
assert result.supported is False
|
|
assert result.fallback == "text"
|
|
|
|
def test_negotiate_level_met(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(markdown=MarkdownSupport.EXTENDED),
|
|
)
|
|
|
|
req = CapabilityRequirement(capability="markdown", level="basic")
|
|
result = negotiate(FakeChannel(), req)
|
|
assert result.supported is True
|
|
|
|
def test_negotiate_level_not_met(self):
|
|
class FakeChannel:
|
|
def get_meta(self) -> ChannelMeta:
|
|
return ChannelMeta(
|
|
channel_type="test",
|
|
display_name="Test",
|
|
capability_matrix=CapabilityMatrix(markdown=MarkdownSupport.BASIC),
|
|
)
|
|
|
|
req = CapabilityRequirement(capability="markdown", level="extended")
|
|
result = negotiate(FakeChannel(), req)
|
|
assert result.supported is False
|
|
|
|
|
|
class TestGetRequiredPorts:
|
|
def test_text_requires_outbound(self):
|
|
assert get_required_ports("text") == {OutboundPort}
|
|
|
|
def test_media_requires_outbound(self):
|
|
assert get_required_ports("media") == {OutboundPort}
|
|
|
|
def test_threads_requires_outbound_and_session(self):
|
|
assert get_required_ports("threads") == {OutboundPort, SessionPort}
|
|
|
|
def test_qr_login_requires_auth(self):
|
|
assert get_required_ports("qr_login") == {AuthPort}
|
|
|
|
def test_scan_pairing_requires_inbound(self):
|
|
assert get_required_ports("scan_pairing") == {InboundPort}
|
|
|
|
def test_unknown_capability_returns_empty(self):
|
|
assert get_required_ports("unknown") == set()
|