from __future__ import annotations from yuxi.channels.adapters.twitch.setup import ( SetupWizardState, SetupWizardStep, TwitchSetupWizard, create_setup_wizard_steps, setup_wizard_to_config, ) class TestSetupWizardStep: def test_default_values(self): step = SetupWizardStep(step_id="test") assert step.step_id == "test" assert step.required is True assert step.completed is False assert step.config_key == "" assert step.config_value is None def test_custom_values(self): step = SetupWizardStep( step_id="username", title="Bot Username", description="Enter username", required=True, completed=True, config_key="bot_username", config_value="mybot", ) assert step.step_id == "username" assert step.title == "Bot Username" assert step.completed is True assert step.config_value == "mybot" class TestSetupWizardState: def test_empty_state(self): state = SetupWizardState() assert state.steps == [] assert state.current_step == 0 assert state.current is None assert state.is_complete is True def test_with_steps_not_complete(self): step1 = SetupWizardStep(step_id="s1", required=True) step2 = SetupWizardStep(step_id="s2", required=True) state = SetupWizardState(steps=[step1, step2]) assert state.is_complete is False assert state.current == step1 def test_advance_marks_completed(self): step1 = SetupWizardStep(step_id="s1", required=True) step2 = SetupWizardStep(step_id="s2", required=True) state = SetupWizardState(steps=[step1, step2]) state.advance() assert step1.completed is True assert state.current_step == 1 assert state.current == step2 def test_state_to_config(self): step = SetupWizardStep(step_id="s1", config_key="bot_username", config_value="mybot") state = SetupWizardState(steps=[step]) config = state.to_config() assert config["bot_username"] == "mybot" def test_skips_step_with_none_value(self): step = SetupWizardStep(step_id="s1", config_key="key", config_value=None) state = SetupWizardState(steps=[step]) config = state.to_config() assert "key" not in config def test_optional_step_not_required_for_complete(self): step1 = SetupWizardStep(step_id="s1", required=True, completed=True) step2 = SetupWizardStep(step_id="s2", required=False) state = SetupWizardState(steps=[step1, step2]) assert state.is_complete is True def test_advance_beyond_last_step(self): step = SetupWizardStep(step_id="s1", required=True) state = SetupWizardState(steps=[step]) state.advance() state.advance() assert state.current_step == 2 assert state.current is None class TestCreateSetupWizardSteps: def test_returns_steps_list(self): steps = create_setup_wizard_steps() assert len(steps) > 0 def test_contains_required_steps(self): steps = create_setup_wizard_steps() step_ids = [s.step_id for s in steps] assert "username" in step_ids assert "token" in step_ids assert "client_id" in step_ids assert "channels" in step_ids assert "group_access" in step_ids def test_required_steps_are_required(self): steps = create_setup_wizard_steps() for step in steps: if step.step_id in ("username", "token", "client_id", "channels", "group_access"): assert step.required is True def test_optional_steps_not_required(self): steps = create_setup_wizard_steps() for step in steps: if step.step_id in ("client_secret", "refresh_token", "dm_policy", "allowed_roles", "silent", "probe_timeout"): assert step.required is False def test_completed_reuses_existing_config(self): steps = create_setup_wizard_steps({"bot_username": "existing_bot", "client_id": "existing_cid"}) username_step = next(s for s in steps if s.step_id == "username") assert username_step.completed is True client_id_step = next(s for s in steps if s.step_id == "client_id") assert client_id_step.completed is True token_step = next(s for s in steps if s.step_id == "token") assert token_step.completed is False class TestSetupWizardToConfig: def test_converts_state_to_dict(self): step = SetupWizardStep(step_id="s1", config_key="bot_username", config_value="testbot") state = SetupWizardState(steps=[step]) result = setup_wizard_to_config(state) assert result == {"bot_username": "testbot"} class TestTwitchSetupWizard: def test_initial_state(self): wizard = TwitchSetupWizard() assert wizard.config == {} def test_initial_with_existing_config(self): wizard = TwitchSetupWizard({"bot_username": "existing_bot"}) assert wizard.config["bot_username"] == "existing_bot" def test_resolve_account_id(self): wizard = TwitchSetupWizard() result = wizard.resolve_account_id("my_account") assert result == "my_account" def test_resolve_account_id_default(self): wizard = TwitchSetupWizard() result = wizard.resolve_account_id() assert result == "default" def test_prompt_username_valid(self): wizard = TwitchSetupWizard() result = wizard.prompt_username("TestBot") assert result is True assert wizard.config["bot_username"] == "testbot" def test_prompt_username_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_username("") assert result is False def test_prompt_username_whitespace(self): wizard = TwitchSetupWizard() result = wizard.prompt_username(" ") assert result is False def test_prompt_token_with_value(self): wizard = TwitchSetupWizard() result = wizard.prompt_token("mytoken") assert result is True assert wizard.config["access_token"].startswith("oauth:") def test_prompt_token_with_oauth_prefix(self): wizard = TwitchSetupWizard() result = wizard.prompt_token("oauth:existing") assert result is True assert wizard.config["access_token"] == "oauth:existing" def test_prompt_token_from_env(self, monkeypatch): import os monkeypatch.setitem(os.environ, "TWITCH_ACCESS_TOKEN", "env_token") wizard = TwitchSetupWizard() result = wizard.prompt_token("") assert result is True assert "env_token" in wizard.config["access_token"] def test_prompt_token_no_value_or_env(self, monkeypatch): monkeypatch.delenv("TWITCH_ACCESS_TOKEN", raising=False) monkeypatch.delenv("OPENCLAW_TWITCH_ACCESS_TOKEN", raising=False) wizard = TwitchSetupWizard() result = wizard.prompt_token("") assert result is False def test_prompt_client_id_valid(self): wizard = TwitchSetupWizard() result = wizard.prompt_client_id("my_client_id") assert result is True assert wizard.config["client_id"] == "my_client_id" def test_prompt_client_id_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_client_id("") assert result is False def test_prompt_client_secret_with_value(self): wizard = TwitchSetupWizard() result = wizard.prompt_client_secret("my_secret") assert result is True assert wizard.config["client_secret"] == "my_secret" def test_prompt_client_secret_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_client_secret("") assert result is True def test_prompt_channels_string(self): wizard = TwitchSetupWizard() result = wizard.prompt_channels("ch1, ch2, CH3") assert result is True assert wizard.config["channels"] == ["ch1", "ch2", "ch3"] def test_prompt_channels_list(self): wizard = TwitchSetupWizard() result = wizard.prompt_channels(["ch1", "CH2"]) assert result is True assert wizard.config["channels"] == ["ch1", "CH2"] def test_prompt_channels_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_channels("") assert result is False def test_prompt_refresh_token_with_value(self): wizard = TwitchSetupWizard() result = wizard.prompt_refresh_token("refresh123") assert result is True assert wizard.config["refresh_token"] == "refresh123" def test_prompt_refresh_token_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_refresh_token("") assert result is True def test_prompt_dm_policy_open(self): wizard = TwitchSetupWizard() result = wizard.prompt_dm_policy("open") assert result is True assert wizard.config["dm_policy"] == "open" def test_prompt_dm_policy_pairing(self): wizard = TwitchSetupWizard() result = wizard.prompt_dm_policy("pairing") assert result is True assert wizard.config["dm_policy"] == "pairing" def test_prompt_dm_policy_invalid(self): wizard = TwitchSetupWizard() result = wizard.prompt_dm_policy("block") assert result is False def test_prompt_group_access_valid(self): wizard = TwitchSetupWizard() for policy in ("open", "allowlist", "disabled", "mention_only"): result = wizard.prompt_group_access(policy) assert result is True def test_prompt_group_access_invalid(self): wizard = TwitchSetupWizard() result = wizard.prompt_group_access("invalid") assert result is False def test_prompt_allowed_roles_string(self): wizard = TwitchSetupWizard() result = wizard.prompt_allowed_roles("moderator, vip, subscriber") assert result is True assert "moderator" in wizard.config["allowedRoles"] assert "vip" in wizard.config["allowedRoles"] assert "subscriber" in wizard.config["allowedRoles"] def test_prompt_allowed_roles_list(self): wizard = TwitchSetupWizard() result = wizard.prompt_allowed_roles(["moderator", "vip"]) assert result is True assert "moderator" in wizard.config["allowedRoles"] def test_prompt_allowed_roles_invalid_defaults_to_all(self): wizard = TwitchSetupWizard() result = wizard.prompt_allowed_roles("admin, super") assert result is True assert wizard.config["allowedRoles"] == ["all"] def test_prompt_silent_yes(self): wizard = TwitchSetupWizard() for val in ("yes", "true", "1", "y", "on"): result = wizard.prompt_silent(val) assert result is True assert wizard.config["silent"] is True def test_prompt_silent_no(self): wizard = TwitchSetupWizard() result = wizard.prompt_silent("no") assert result is True assert wizard.config["silent"] is False def test_prompt_probe_timeout_valid(self): wizard = TwitchSetupWizard() result = wizard.prompt_probe_timeout(5000) assert result is True assert wizard.config["probe_timeout_ms"] == 5000 def test_prompt_probe_timeout_string_valid(self): wizard = TwitchSetupWizard() result = wizard.prompt_probe_timeout("5000") assert result is True assert wizard.config["probe_timeout_ms"] == 5000 def test_prompt_probe_timeout_empty(self): wizard = TwitchSetupWizard() result = wizard.prompt_probe_timeout("") assert result is True assert wizard.config["probe_timeout_ms"] == 10000 def test_prompt_probe_timeout_too_small(self): wizard = TwitchSetupWizard() result = wizard.prompt_probe_timeout(500) assert result is False def test_prompt_probe_timeout_invalid_string(self): wizard = TwitchSetupWizard() result = wizard.prompt_probe_timeout("not_a_number") assert result is False def test_disable(self): wizard = TwitchSetupWizard() result = wizard.disable() assert result == {"enabled": False} def test_finalize_complete(self): wizard = TwitchSetupWizard() wizard.prompt_username("testbot") wizard.prompt_token("mytoken") wizard.prompt_client_id("cid123") wizard.prompt_channels("test_channel") wizard.prompt_group_access("open") result = wizard.finalize() assert result["enabled"] is True assert result["config"]["bot_username"] == "testbot" assert result["validation"]["valid"] is True def test_finalize_incomplete(self): wizard = TwitchSetupWizard() result = wizard.finalize() assert result["enabled"] is True assert result["validation"]["valid"] is False def test_finalize_disabled(self): wizard = TwitchSetupWizard() wizard.disable() result = wizard.finalize() assert result == {"enabled": False} def test_finalize_and_write_no_path(self): wizard = TwitchSetupWizard() wizard.prompt_username("testbot") wizard.prompt_token("token") wizard.prompt_client_id("cid") wizard.prompt_channels("ch") wizard.prompt_group_access("open") result = wizard.finalize_and_write() assert result["storage"]["mode"] == "memory" assert result["storage"]["success"] is True def test_finalize_and_write_with_path(self, tmp_path): import json wizard = TwitchSetupWizard() wizard.prompt_username("testbot") wizard.prompt_token("token") wizard.prompt_client_id("cid") wizard.prompt_channels("ch") wizard.prompt_group_access("open") result = wizard.finalize_and_write(str(tmp_path)) assert result["storage"]["success"] is True config_path = tmp_path / "twitch_config.json" assert config_path.exists() saved = json.loads(config_path.read_text(encoding="utf-8")) assert saved["bot_username"] == "testbot" def test_state_property(self): wizard = TwitchSetupWizard() assert isinstance(wizard.state, SetupWizardState) def test_config_property(self): wizard = TwitchSetupWizard({"bot_username": "mybot"}) assert wizard.config["bot_username"] == "mybot" def test_validate_no_client_secret_warning(self): wizard = TwitchSetupWizard() wizard.prompt_username("bot") wizard.prompt_token("tok") wizard.prompt_client_id("cid") wizard.prompt_channels("ch") wizard.prompt_group_access("open") result = wizard.finalize() assert any("EventSub" in w for w in result["validation"]["warnings"]) def test_validate_invalid_group_policy_error(self): wizard = TwitchSetupWizard() wizard._config["group_policy"] = "invalid" result = wizard.finalize() assert result["validation"]["valid"] is False def test_validate_unknown_dm_policy_warns(self): wizard = TwitchSetupWizard() wizard.prompt_username("bot") wizard.prompt_token("tok") wizard.prompt_client_id("cid") wizard.prompt_channels("ch") wizard.prompt_group_access("open") wizard._config["dm_policy"] = "unknown_policy" result = wizard.finalize() assert any("dm_policy" in w for w in result["validation"]["warnings"])