175 lines
5.9 KiB
Python
175 lines
5.9 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
from pydantic import ValidationError
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.imessage.config_schema import (
|
||
|
|
DmPolicyEnum,
|
||
|
|
GroupOverrideConfig,
|
||
|
|
GroupPolicyEnum,
|
||
|
|
IMessageAccountConfig,
|
||
|
|
IMessageConnectionConfig,
|
||
|
|
IMessageFullConfig,
|
||
|
|
IMessageSecurityConfigSchema,
|
||
|
|
validate_config,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestDmPolicyEnum:
|
||
|
|
def test_values(self):
|
||
|
|
assert DmPolicyEnum.PAIRING.value == "pairing"
|
||
|
|
assert DmPolicyEnum.ALLOWLIST.value == "allowlist"
|
||
|
|
assert DmPolicyEnum.OPEN.value == "open"
|
||
|
|
assert DmPolicyEnum.DISABLED.value == "disabled"
|
||
|
|
|
||
|
|
def test_coercion(self):
|
||
|
|
schema = IMessageSecurityConfigSchema(dm_policy="open")
|
||
|
|
assert schema.dm_policy == DmPolicyEnum.OPEN
|
||
|
|
|
||
|
|
def test_coercion_invalid(self):
|
||
|
|
schema = IMessageSecurityConfigSchema(dm_policy="invalid_policy")
|
||
|
|
assert schema.dm_policy == DmPolicyEnum.PAIRING
|
||
|
|
|
||
|
|
|
||
|
|
class TestGroupPolicyEnum:
|
||
|
|
def test_values(self):
|
||
|
|
assert GroupPolicyEnum.OPEN.value == "open"
|
||
|
|
assert GroupPolicyEnum.ALLOWLIST.value == "allowlist"
|
||
|
|
assert GroupPolicyEnum.DISABLED.value == "disabled"
|
||
|
|
|
||
|
|
def test_coercion_invalid(self):
|
||
|
|
schema = IMessageSecurityConfigSchema(group_policy="invalid")
|
||
|
|
assert schema.group_policy == GroupPolicyEnum.ALLOWLIST
|
||
|
|
|
||
|
|
|
||
|
|
class TestIMessageConnectionConfig:
|
||
|
|
def test_defaults(self):
|
||
|
|
cfg = IMessageConnectionConfig()
|
||
|
|
assert cfg.server_url == "http://localhost:1234"
|
||
|
|
assert cfg.password == ""
|
||
|
|
assert cfg.probe_timeout_ms == 10000
|
||
|
|
assert cfg.max_retries == 3
|
||
|
|
assert cfg.http_timeout_s == 30.0
|
||
|
|
|
||
|
|
def test_custom_values(self):
|
||
|
|
cfg = IMessageConnectionConfig(
|
||
|
|
server_url="http://mac.local:4321",
|
||
|
|
password="secret",
|
||
|
|
probe_timeout_ms=5000,
|
||
|
|
max_retries=5,
|
||
|
|
http_timeout_s=60.0,
|
||
|
|
)
|
||
|
|
assert cfg.server_url == "http://mac.local:4321"
|
||
|
|
assert cfg.password == "secret"
|
||
|
|
assert cfg.probe_timeout_ms == 5000
|
||
|
|
assert cfg.max_retries == 5
|
||
|
|
assert cfg.http_timeout_s == 60.0
|
||
|
|
|
||
|
|
def test_probe_timeout_out_of_range(self):
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
IMessageConnectionConfig(probe_timeout_ms=500)
|
||
|
|
|
||
|
|
def test_max_retries_out_of_range(self):
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
IMessageConnectionConfig(max_retries=0)
|
||
|
|
|
||
|
|
|
||
|
|
class TestIMessageSecurityConfigSchema:
|
||
|
|
def test_defaults(self):
|
||
|
|
cfg = IMessageSecurityConfigSchema()
|
||
|
|
assert cfg.dm_policy == DmPolicyEnum.PAIRING
|
||
|
|
assert cfg.group_policy == GroupPolicyEnum.ALLOWLIST
|
||
|
|
assert cfg.allow_from == []
|
||
|
|
assert cfg.group_allow_from == []
|
||
|
|
assert cfg.require_mention is False
|
||
|
|
|
||
|
|
def test_custom_values(self):
|
||
|
|
cfg = IMessageSecurityConfigSchema(
|
||
|
|
dm_policy=DmPolicyEnum.OPEN,
|
||
|
|
group_policy=GroupPolicyEnum.OPEN,
|
||
|
|
allow_from=["+8613800138000"],
|
||
|
|
group_allow_from=["iMessage;-;group001"],
|
||
|
|
require_mention=True,
|
||
|
|
)
|
||
|
|
assert cfg.dm_policy == DmPolicyEnum.OPEN
|
||
|
|
assert cfg.group_policy == GroupPolicyEnum.OPEN
|
||
|
|
assert "+8613800138000" in cfg.allow_from
|
||
|
|
assert "iMessage;-;group001" in cfg.group_allow_from
|
||
|
|
assert cfg.require_mention is True
|
||
|
|
|
||
|
|
|
||
|
|
class TestIMessageFullConfig:
|
||
|
|
def test_defaults(self):
|
||
|
|
cfg = IMessageFullConfig()
|
||
|
|
assert cfg.name == "iMessage"
|
||
|
|
assert cfg.block_streaming is False
|
||
|
|
assert cfg.text_chunk_limit == 4096
|
||
|
|
assert cfg.media_max_mb == 100
|
||
|
|
assert cfg.history_limit == 20
|
||
|
|
assert cfg.include_attachments is False
|
||
|
|
assert cfg.accounts == {}
|
||
|
|
assert cfg.groups == {}
|
||
|
|
assert cfg.loop_rate_limit == 5
|
||
|
|
|
||
|
|
def test_text_chunk_limit_out_of_range(self):
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
IMessageFullConfig(text_chunk_limit=50)
|
||
|
|
|
||
|
|
def test_accounts_from_list(self):
|
||
|
|
cfg = IMessageFullConfig(
|
||
|
|
accounts=[
|
||
|
|
IMessageAccountConfig(account_id="acct1"),
|
||
|
|
IMessageAccountConfig(account_id="acct2"),
|
||
|
|
]
|
||
|
|
)
|
||
|
|
assert len(cfg.accounts) == 2
|
||
|
|
assert "acct1" in cfg.accounts
|
||
|
|
assert "acct2" in cfg.accounts
|
||
|
|
|
||
|
|
def test_accounts_none_coerced_to_dict(self):
|
||
|
|
cfg = IMessageFullConfig(accounts=None)
|
||
|
|
assert cfg.accounts == {}
|
||
|
|
|
||
|
|
|
||
|
|
class TestGroupOverrideConfig:
|
||
|
|
def test_defaults(self):
|
||
|
|
cfg = GroupOverrideConfig()
|
||
|
|
assert cfg.enabled is None
|
||
|
|
assert cfg.require_mention is None
|
||
|
|
assert cfg.tools is None
|
||
|
|
|
||
|
|
def test_custom_values(self):
|
||
|
|
cfg = GroupOverrideConfig(enabled=True, require_mention=True, tools=["calc", "weather"])
|
||
|
|
assert cfg.enabled is True
|
||
|
|
assert cfg.require_mention is True
|
||
|
|
assert cfg.tools == ["calc", "weather"]
|
||
|
|
|
||
|
|
|
||
|
|
class TestValidateConfig:
|
||
|
|
def test_basic_valid_config(self):
|
||
|
|
result = validate_config({"server_url": "http://localhost:1234", "password": "test"})
|
||
|
|
assert isinstance(result, IMessageFullConfig)
|
||
|
|
|
||
|
|
def test_empty_config(self):
|
||
|
|
result = validate_config({})
|
||
|
|
assert isinstance(result, IMessageFullConfig)
|
||
|
|
|
||
|
|
def test_dm_policy_string(self):
|
||
|
|
result = validate_config({"security": {"dm_policy": "open"}})
|
||
|
|
assert result.security.dm_policy == DmPolicyEnum.OPEN
|
||
|
|
|
||
|
|
def test_block_streaming(self):
|
||
|
|
result = validate_config({"block_streaming": True})
|
||
|
|
assert result.block_streaming is True
|
||
|
|
|
||
|
|
def test_ws_reconnect_params(self):
|
||
|
|
result = validate_config({"ws_reconnect_initial_delay": 10.0})
|
||
|
|
assert result.ws_reconnect_initial_delay == 10.0
|
||
|
|
|
||
|
|
def test_loop_rate_params(self):
|
||
|
|
result = validate_config(
|
||
|
|
{"loop_rate_limit": 10, "loop_rate_window_s": 120.0, "loop_cooldown_s": 300.0}
|
||
|
|
)
|
||
|
|
assert result.loop_rate_limit == 10
|
||
|
|
assert result.loop_rate_window_s == 120.0
|
||
|
|
assert result.loop_cooldown_s == 300.0
|