323 lines
11 KiB
Python
323 lines
11 KiB
Python
|
|
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"
|