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