251 lines
8.6 KiB
Python
251 lines
8.6 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from yuxi.channels.adapters.nextcloudtalk.config import (
|
||
|
|
resolve_history_limit,
|
||
|
|
resolve_room_allow_from,
|
||
|
|
resolve_room_config,
|
||
|
|
resolve_room_enabled,
|
||
|
|
resolve_room_require_mention,
|
||
|
|
resolve_room_skills,
|
||
|
|
resolve_room_system_prompt,
|
||
|
|
)
|
||
|
|
from yuxi.channels.adapters.nextcloudtalk.config_schema import (
|
||
|
|
NextcloudTalkWebhookConfig,
|
||
|
|
validate_config,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomRequireMention:
|
||
|
|
def test_default_true_when_no_config(self):
|
||
|
|
assert resolve_room_require_mention("room-1", {}) is True
|
||
|
|
|
||
|
|
def test_camel_case_key(self):
|
||
|
|
config = {"rooms": {"room-1": {"requireMention": False}}}
|
||
|
|
assert resolve_room_require_mention("room-1", config) is False
|
||
|
|
|
||
|
|
def test_snake_case_key(self):
|
||
|
|
config = {"rooms": {"room-1": {"require_mention": False}}}
|
||
|
|
assert resolve_room_require_mention("room-1", config) is False
|
||
|
|
|
||
|
|
def test_wildcard_fallback(self):
|
||
|
|
config = {"rooms": {"*": {"requireMention": False}}}
|
||
|
|
assert resolve_room_require_mention("unknown-room", config) is False
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomAllowFrom:
|
||
|
|
def test_empty_default(self):
|
||
|
|
assert resolve_room_allow_from("room-1", {}) == []
|
||
|
|
|
||
|
|
def test_camel_case(self):
|
||
|
|
config = {"rooms": {"room-1": {"allowFrom": ["user-1", "user-2"]}}}
|
||
|
|
assert resolve_room_allow_from("room-1", config) == ["user-1", "user-2"]
|
||
|
|
|
||
|
|
def test_snake_case(self):
|
||
|
|
config = {"rooms": {"room-1": {"allow_from": ["user-3"]}}}
|
||
|
|
assert resolve_room_allow_from("room-1", config) == ["user-3"]
|
||
|
|
|
||
|
|
def test_wildcard(self):
|
||
|
|
config = {"rooms": {"*": {"allowFrom": ["global-user"]}}}
|
||
|
|
assert resolve_room_allow_from("unknown-room", config) == ["global-user"]
|
||
|
|
|
||
|
|
def test_direct_overrides_wildcard(self):
|
||
|
|
config = {
|
||
|
|
"rooms": {
|
||
|
|
"room-1": {"allowFrom": ["specific-user"]},
|
||
|
|
"*": {"allowFrom": ["global-user"]},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
assert resolve_room_allow_from("room-1", config) == ["specific-user"]
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomSkills:
|
||
|
|
def test_empty_default(self):
|
||
|
|
assert resolve_room_skills("room-1", {}) == []
|
||
|
|
|
||
|
|
def test_exact_match(self):
|
||
|
|
config = {"rooms": {"room-1": {"skills": ["skill-a"]}}}
|
||
|
|
assert resolve_room_skills("room-1", config) == ["skill-a"]
|
||
|
|
|
||
|
|
def test_wildcard(self):
|
||
|
|
config = {"rooms": {"*": {"skills": ["base-skill"]}}}
|
||
|
|
assert resolve_room_skills("any-room", config) == ["base-skill"]
|
||
|
|
|
||
|
|
def test_direct_overrides(self):
|
||
|
|
config = {
|
||
|
|
"rooms": {
|
||
|
|
"room-1": {"skills": ["premium-skill"]},
|
||
|
|
"*": {"skills": ["base-skill"]},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
assert resolve_room_skills("room-1", config) == ["premium-skill"]
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomSystemPromptNoneEmpty:
|
||
|
|
def test_none_when_no_config(self):
|
||
|
|
assert resolve_room_system_prompt("room-1", {}) is None
|
||
|
|
|
||
|
|
def test_empty_string_when_empty_value(self):
|
||
|
|
config = {"rooms": {"room-1": {"systemPrompt": ""}}}
|
||
|
|
assert resolve_room_system_prompt("room-1", config) == ""
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomEnabledEdgeCases:
|
||
|
|
def test_enabled_key(self):
|
||
|
|
config = {"rooms": {"room-1": {"enabled": True}}}
|
||
|
|
assert resolve_room_enabled("room-1", config) is True
|
||
|
|
|
||
|
|
def test_enabled_underscore_key(self):
|
||
|
|
config = {"rooms": {"room-1": {"enabled_": False}}}
|
||
|
|
assert resolve_room_enabled("room-1", config) is False
|
||
|
|
|
||
|
|
def test_wildcard_fallback(self):
|
||
|
|
config = {"rooms": {"*": {"enabled": False}}}
|
||
|
|
assert resolve_room_enabled("unknown-room", config) is False
|
||
|
|
|
||
|
|
def test_default_true_when_no_rooms(self):
|
||
|
|
assert resolve_room_enabled("room-1", {}) is True
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveHistoryLimitEdgeCases:
|
||
|
|
def test_group_wildcard_room(self):
|
||
|
|
config = {"rooms": {"*": {"historyLimit": 10}}}
|
||
|
|
assert resolve_history_limit("any-room", config, "group") == 10
|
||
|
|
|
||
|
|
def test_group_global_and_room_both_set(self):
|
||
|
|
config = {
|
||
|
|
"historyLimit": 50,
|
||
|
|
"rooms": {"room-1": {"historyLimit": 25}},
|
||
|
|
}
|
||
|
|
assert resolve_history_limit("room-1", config, "group") == 25
|
||
|
|
|
||
|
|
def test_group_global_only(self):
|
||
|
|
assert resolve_history_limit("room-1", {"historyLimit": 50}, "group") == 50
|
||
|
|
|
||
|
|
def test_dm_global_snake_case(self):
|
||
|
|
assert resolve_history_limit("user-1", {"dm_history_limit": 30}, "direct") == 30
|
||
|
|
|
||
|
|
def test_dm_per_user_overrides_global(self):
|
||
|
|
config = {
|
||
|
|
"dmHistoryLimit": 30,
|
||
|
|
"dms": {"user-1": {"historyLimit": 10}},
|
||
|
|
}
|
||
|
|
assert resolve_history_limit("user-1", config, "direct") == 10
|
||
|
|
|
||
|
|
def test_dm_default_zero(self):
|
||
|
|
assert resolve_history_limit("user-1", {}, "direct") == 0
|
||
|
|
|
||
|
|
|
||
|
|
class TestResolveRoomConfigEdgeCases:
|
||
|
|
def test_no_rooms_key(self):
|
||
|
|
result = resolve_room_config("token", {})
|
||
|
|
assert result["_match_source"] == "none"
|
||
|
|
|
||
|
|
def test_empty_rooms_returns_none_source(self):
|
||
|
|
result = resolve_room_config("token", {"rooms": {}})
|
||
|
|
assert result["_match_source"] == "none"
|
||
|
|
|
||
|
|
def test_no_wildcard_no_match(self):
|
||
|
|
result = resolve_room_config("token", {"rooms": {"other": {"enabled": True}}})
|
||
|
|
assert result["_match_source"] == "wildcard"
|
||
|
|
|
||
|
|
|
||
|
|
class TestWebhookConfigSchema:
|
||
|
|
def test_default_values(self):
|
||
|
|
cfg = NextcloudTalkWebhookConfig()
|
||
|
|
assert cfg.enabled is False
|
||
|
|
assert cfg.port == 8788
|
||
|
|
assert cfg.host == "0.0.0.0"
|
||
|
|
assert cfg.path == "/nextcloud-talk-webhook"
|
||
|
|
assert cfg.rate_limit_max_requests == 10
|
||
|
|
assert cfg.rate_limit_window_s == 60
|
||
|
|
assert cfg.rate_limit_lockout_s == 300
|
||
|
|
assert cfg.max_body_size == 1 * 1024 * 1024
|
||
|
|
assert cfg.pre_auth_max_body_size == 64 * 1024
|
||
|
|
|
||
|
|
def test_custom_values(self):
|
||
|
|
cfg = NextcloudTalkWebhookConfig(
|
||
|
|
port=9999,
|
||
|
|
host="127.0.0.1",
|
||
|
|
path="/custom-webhook",
|
||
|
|
rateLimitMaxRequests=5,
|
||
|
|
rateLimitWindowS=30,
|
||
|
|
rateLimitLockoutS=60,
|
||
|
|
maxBodySize=2 * 1024 * 1024,
|
||
|
|
preAuthMaxBodySize=128 * 1024,
|
||
|
|
)
|
||
|
|
assert cfg.port == 9999
|
||
|
|
assert cfg.host == "127.0.0.1"
|
||
|
|
assert cfg.path == "/custom-webhook"
|
||
|
|
assert cfg.rate_limit_max_requests == 5
|
||
|
|
assert cfg.rate_limit_window_s == 30
|
||
|
|
assert cfg.rate_limit_lockout_s == 60
|
||
|
|
assert cfg.max_body_size == 2 * 1024 * 1024
|
||
|
|
assert cfg.pre_auth_max_body_size == 128 * 1024
|
||
|
|
|
||
|
|
|
||
|
|
class TestValidateConfigEdgeCases:
|
||
|
|
def test_invalid_group_policy(self):
|
||
|
|
with pytest.raises(ValueError, match="group_policy"):
|
||
|
|
validate_config({
|
||
|
|
"serverUrl": "https://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
"groupPolicy": "unknown",
|
||
|
|
})
|
||
|
|
|
||
|
|
def test_invalid_chunk_mode(self):
|
||
|
|
with pytest.raises(ValueError, match="chunk_mode"):
|
||
|
|
validate_config({
|
||
|
|
"serverUrl": "https://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
"chunkMode": "invalid_mode",
|
||
|
|
})
|
||
|
|
|
||
|
|
def test_empty_server_url_passes_validation(self):
|
||
|
|
config = validate_config({
|
||
|
|
"serverUrl": "",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
})
|
||
|
|
assert config.server_url == ""
|
||
|
|
|
||
|
|
def test_http_url_allowed(self):
|
||
|
|
config = validate_config({
|
||
|
|
"serverUrl": "http://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
})
|
||
|
|
assert config.server_url == "http://nc.example.com"
|
||
|
|
|
||
|
|
def test_all_policies_valid(self):
|
||
|
|
for policy in ("open", "pairing", "allowlist", "disabled"):
|
||
|
|
config = validate_config({
|
||
|
|
"serverUrl": "https://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
"dmPolicy": policy,
|
||
|
|
})
|
||
|
|
assert config.dm_policy == policy
|
||
|
|
|
||
|
|
def test_all_group_policies_valid(self):
|
||
|
|
for policy in ("open", "allowlist", "disabled"):
|
||
|
|
config = validate_config({
|
||
|
|
"serverUrl": "https://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
"groupPolicy": policy,
|
||
|
|
})
|
||
|
|
assert config.group_policy == policy
|
||
|
|
|
||
|
|
def test_all_chunk_modes_valid(self):
|
||
|
|
for mode in ("split", "stop", "truncate"):
|
||
|
|
config = validate_config({
|
||
|
|
"serverUrl": "https://nc.example.com",
|
||
|
|
"botUser": "bot",
|
||
|
|
"appPassword": "secret",
|
||
|
|
"chunkMode": mode,
|
||
|
|
})
|
||
|
|
assert config.chunk_mode == mode
|