from __future__ import annotations from yuxi.channels.adapters.twitch.config_schema import ( TwitchAccountSchema, TwitchConfigSchema, super_refine_twitch_config, validate_twitch_config, ) class TestTwitchConfigSchemaDefaults: def test_defaults(self): schema = TwitchConfigSchema() assert schema.bot_username == "" assert schema.access_token == "" assert schema.client_id == "" assert schema.channels == [] assert schema.group_policy == "open" assert schema.rate_limit == 20 assert schema.rate_window == 30 assert schema.irc_host == "irc.chat.twitch.tv" assert schema.irc_port == 6697 assert schema.strip_markdown is True assert schema.dm_policy == "pairing" assert schema.silent is False assert schema.prefer_helix_send is False def test_account_schema_defaults(self): schema = TwitchAccountSchema() assert schema.bot_username == "" assert schema.access_token == "" assert schema.channels == [] assert schema.group_policy == "open" assert schema.rate_limit == 20 class TestTwitchConfigSchemaValidation: def test_valid_group_policy_accepted(self): schema = TwitchConfigSchema(group_policy="allowlist") assert schema.group_policy == "allowlist" def test_rate_limit_minimum(self): schema = TwitchConfigSchema(rate_limit=1) assert schema.rate_limit == 1 def test_valid_irc_port(self): schema = TwitchConfigSchema(irc_port=6697) assert schema.irc_port == 6697 def test_accounts_dict(self): schema = TwitchConfigSchema(accounts={"default": TwitchAccountSchema(bot_username="bot1")}) assert "default" in schema.accounts assert schema.accounts["default"].bot_username == "bot1" def test_require_mention_default(self): schema = TwitchConfigSchema() assert schema.require_mention is True def test_pairing_disabled_default(self): schema = TwitchConfigSchema() assert schema.pairing_enabled is False def test_response_prefix_default(self): schema = TwitchConfigSchema() assert schema.response_prefix == "" class TestValidateTwitchConfig: def test_valid_config_no_errors(self): config = { "bot_username": "testbot", "access_token": "oauth:abc", "client_id": "client123", "channels": ["test_channel"], } errors = validate_twitch_config(config) assert errors == [] def test_missing_client_id(self): errors = validate_twitch_config({"bot_username": "bot", "access_token": "tok", "channels": ["ch"]}) assert any("client_id" in e for e in errors) def test_missing_access_token(self): errors = validate_twitch_config({"bot_username": "bot", "client_id": "cid", "channels": ["ch"]}) assert any("access_token" in e for e in errors) def test_missing_bot_username(self): errors = validate_twitch_config({"access_token": "tok", "client_id": "cid", "channels": ["ch"]}) assert any("bot_username" in e for e in errors) def test_missing_channels(self): errors = validate_twitch_config({"bot_username": "bot", "access_token": "tok", "client_id": "cid"}) assert any("channel" in e for e in errors) def test_invalid_group_policy(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "group_policy": "invalid_policy", } errors = validate_twitch_config(config) assert any("group_policy" in e for e in errors) def test_allowlist_with_empty_allow_from(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "group_policy": "allowlist", } errors = validate_twitch_config(config) assert any("allowlist" in e for e in errors) def test_allowlist_with_per_channel_allow_from_is_ok(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "group_policy": "allowlist", "channels_config": {"ch": {"allow_from": ["user1"]}}, } errors = validate_twitch_config(config) assert not any("allowlist" in e for e in errors) def test_invalid_role_in_allowed_roles(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "allowedRoles": ["admin"], } errors = validate_twitch_config(config) assert any("allowedRoles" in e for e in errors) def test_all_roles_with_allowlist_redundant(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "allowedRoles": ["all"], "group_policy": "allowlist", "group_allow_from": ["user1"], } errors = validate_twitch_config(config) assert any("redundant" in e for e in errors) def test_invalid_rate_limit(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "rate_limit": 0, } errors = validate_twitch_config(config) assert any("rate_limit" in e for e in errors) def test_invalid_irc_port(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "irc_port": 99999, } errors = validate_twitch_config(config) assert any("irc_port" in e for e in errors) def test_account_missing_bot_username(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "accounts": {"acct1": {"access_token": "tok"}}, } errors = validate_twitch_config(config) assert any("bot_username" in e for e in errors) def test_account_missing_access_token(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "accounts": {"acct1": {"bot_username": "bot2"}}, } errors = validate_twitch_config(config) assert any("access_token" in e for e in errors) def test_default_account_not_found(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "accounts": {"acct1": {"bot_username": "b", "access_token": "t"}}, "defaultAccount": "nonexistent", } errors = validate_twitch_config(config) assert any("defaultAccount" in e for e in errors) def test_invalid_dm_policy(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "dm_policy": "block_all", } errors = validate_twitch_config(config) assert any("dm_policy" in e for e in errors) def test_probe_timeout_too_small(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "probe_timeout_ms": 500, } errors = validate_twitch_config(config) assert any("probe_timeout" in e for e in errors) def test_valid_roles_accepted(self): config = { "bot_username": "bot", "access_token": "tok", "client_id": "cid", "channels": ["ch"], "allowedRoles": ["moderator", "vip", "subscriber"], } errors = validate_twitch_config(config) assert errors == [] def test_empty_config_all_errors(self): errors = validate_twitch_config({}) assert len(errors) >= 4 class TestSuperRefineTwitchConfig: def test_converts_allowall_to_open(self): config = {"group_policy": "allowall"} refined = super_refine_twitch_config(config) assert refined["group_policy"] == "open" def test_converts_mention_only_to_mention(self): config = {"group_policy": "mention_only"} refined = super_refine_twitch_config(config) assert refined["group_policy"] == "mention" def test_adds_oauth_prefix(self): config = {"access_token": "mytoken123"} refined = super_refine_twitch_config(config) assert refined["access_token"] == "oauth:mytoken123" def test_preserves_existing_oauth_prefix(self): config = {"access_token": "oauth:mytoken"} refined = super_refine_twitch_config(config) assert refined["access_token"] == "oauth:mytoken" def test_strips_channel_hash_prefix(self): config = {"channels": ["#testchannel", "@otherchannel", "plainchannel"]} refined = super_refine_twitch_config(config) assert refined["channels"] == ["testchannel", "otherchannel", "plainchannel"] def test_account_tokens_normalized(self): config = { "access_token": "oauth:master", "client_id": "master_cid", "accounts": { "acct1": {"bot_username": "b1", "access_token": "rawtoken"}, }, } refined = super_refine_twitch_config(config) assert refined["accounts"]["acct1"]["access_token"] == "oauth:rawtoken" def test_account_channels_normalized(self): config = { "accounts": { "acct1": {"bot_username": "b1", "access_token": "tok", "channels": ["#CH1", "@CH2"]}, }, } refined = super_refine_twitch_config(config) assert refined["accounts"]["acct1"]["channels"] == ["ch1", "ch2"] def test_account_inherits_parent_fields(self): config = { "client_id": "parent_cid", "rate_limit": 50, "accounts": { "acct1": {"bot_username": "b1", "access_token": "tok"}, }, } refined = super_refine_twitch_config(config) assert refined["accounts"]["acct1"]["client_id"] == "parent_cid" assert refined["accounts"]["acct1"]["rate_limit"] == 50 def test_account_own_field_not_overwritten(self): config = { "client_id": "parent_cid", "accounts": { "acct1": {"bot_username": "b1", "access_token": "tok", "client_id": "own_cid"}, }, } refined = super_refine_twitch_config(config) assert refined["accounts"]["acct1"]["client_id"] == "own_cid" def test_wildcard_allow_from_with_open_policy(self): config = {"group_policy": "open", "group_allow_from": ["*"]} refined = super_refine_twitch_config(config) assert refined["group_policy"] == "open" def test_skip_non_dict_account(self): config = {"accounts": {"acct1": "not_a_dict"}} refined = super_refine_twitch_config(config) assert refined["accounts"]["acct1"] == "not_a_dict"