Fixes bug 2 from issue #329. current_is_oauth flag; confirmation card for OAuth providers; KeyError fix in _build_setup_catalog. 15 new tests, 791 total.
163 lines
6.9 KiB
Python
163 lines
6.9 KiB
Python
"""
|
|
Sprint 40 Tests: OAuth provider onboarding path (PR B of issue #329).
|
|
|
|
Covers:
|
|
- _build_setup_catalog sets current_is_oauth=True for OAuth providers
|
|
- _build_setup_catalog sets current_is_oauth=False for API-key providers
|
|
- _build_setup_catalog sets current_is_oauth=False when no provider configured
|
|
- apply_onboarding_setup with unsupported provider marks onboarding complete directly
|
|
- i18n.js contains all required OAuth onboarding keys in both English and Spanish
|
|
"""
|
|
import pathlib
|
|
import re
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
import api.onboarding as mod
|
|
|
|
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
|
I18N_JS = (REPO_ROOT / "static" / "i18n.js").read_text()
|
|
ONBOARDING_JS = (REPO_ROOT / "static" / "onboarding.js").read_text()
|
|
|
|
|
|
# ── Backend: _build_setup_catalog ──────────────────────────────────────────
|
|
|
|
|
|
class TestBuildSetupCatalog(unittest.TestCase):
|
|
|
|
def _catalog(self, provider, model="gpt-4o", base_url=""):
|
|
cfg = {}
|
|
if provider:
|
|
cfg = {"model": {"provider": provider, "default": model, "base_url": base_url}}
|
|
with patch.object(mod, "get_config", return_value=cfg):
|
|
return mod._build_setup_catalog(cfg)
|
|
|
|
def test_oauth_provider_sets_current_is_oauth_true(self):
|
|
"""openai-codex is not in _SUPPORTED_PROVIDER_SETUPS → current_is_oauth=True."""
|
|
catalog = self._catalog("openai-codex", "gpt-5.4")
|
|
self.assertTrue(catalog["current_is_oauth"],
|
|
"current_is_oauth must be True for openai-codex")
|
|
|
|
def test_copilot_provider_sets_current_is_oauth_true(self):
|
|
"""copilot is also OAuth."""
|
|
catalog = self._catalog("copilot")
|
|
self.assertTrue(catalog["current_is_oauth"])
|
|
|
|
def test_openai_provider_sets_current_is_oauth_false(self):
|
|
"""openai is in _SUPPORTED_PROVIDER_SETUPS → current_is_oauth=False."""
|
|
catalog = self._catalog("openai", "gpt-4o")
|
|
self.assertFalse(catalog["current_is_oauth"],
|
|
"current_is_oauth must be False for API-key provider openai")
|
|
|
|
def test_anthropic_provider_sets_current_is_oauth_false(self):
|
|
catalog = self._catalog("anthropic", "claude-sonnet-4.6")
|
|
self.assertFalse(catalog["current_is_oauth"])
|
|
|
|
def test_no_provider_sets_current_is_oauth_false(self):
|
|
"""Empty config → current_is_oauth=False."""
|
|
catalog = self._catalog("")
|
|
self.assertFalse(catalog["current_is_oauth"])
|
|
|
|
def test_catalog_includes_current_is_oauth_key(self):
|
|
"""current_is_oauth must always be present in the catalog dict."""
|
|
catalog = self._catalog("openrouter")
|
|
self.assertIn("current_is_oauth", catalog)
|
|
|
|
|
|
# ── Backend: apply_onboarding_setup for OAuth providers ────────────────────
|
|
|
|
|
|
class TestApplyOnboardingOAuthPath(unittest.TestCase):
|
|
|
|
def test_unsupported_provider_skips_to_complete(self):
|
|
"""apply_onboarding_setup with an OAuth provider just marks onboarding done."""
|
|
saved = {}
|
|
|
|
def _save(d):
|
|
saved.update(d)
|
|
|
|
mock_status = {"completed": True, "system": {"chat_ready": True}}
|
|
|
|
with patch.object(mod, "save_settings", side_effect=_save), \
|
|
patch.object(mod, "get_onboarding_status", return_value=mock_status):
|
|
result = mod.apply_onboarding_setup({"provider": "openai-codex", "model": "gpt-5.4"})
|
|
|
|
self.assertTrue(saved.get("onboarding_completed"),
|
|
"save_settings must set onboarding_completed=True for OAuth provider")
|
|
self.assertEqual(result, mock_status)
|
|
|
|
def test_unsupported_provider_does_not_write_config_yaml(self):
|
|
"""OAuth path must not call _save_yaml_config — no config mutation."""
|
|
with patch.object(mod, "save_settings"), \
|
|
patch.object(mod, "get_onboarding_status", return_value={}), \
|
|
patch.object(mod, "_save_yaml_config") as mock_save_yaml:
|
|
mod.apply_onboarding_setup({"provider": "copilot", "model": "gpt-4o"})
|
|
|
|
mock_save_yaml.assert_not_called()
|
|
|
|
|
|
# ── Frontend: i18n keys ────────────────────────────────────────────────────
|
|
|
|
|
|
_REQUIRED_OAUTH_KEYS = [
|
|
"onboarding_oauth_provider_ready_title",
|
|
"onboarding_oauth_provider_ready_body",
|
|
"onboarding_oauth_provider_not_ready_title",
|
|
"onboarding_oauth_provider_not_ready_body",
|
|
"onboarding_oauth_switch_hint",
|
|
]
|
|
|
|
|
|
class TestOAuthI18nKeys(unittest.TestCase):
|
|
|
|
def test_english_locale_has_all_oauth_keys(self):
|
|
"""All OAuth onboarding i18n keys must be present in the English locale."""
|
|
missing = [k for k in _REQUIRED_OAUTH_KEYS if k not in I18N_JS]
|
|
self.assertFalse(missing,
|
|
f"English locale missing OAuth keys: {missing}")
|
|
|
|
def test_spanish_locale_has_all_oauth_keys(self):
|
|
"""All OAuth onboarding i18n keys must be present in the Spanish locale."""
|
|
# Spanish locale is the second occurrence of each key
|
|
counts = {k: I18N_JS.count(k) for k in _REQUIRED_OAUTH_KEYS}
|
|
under = [k for k, c in counts.items() if c < 2]
|
|
self.assertFalse(under,
|
|
f"Spanish locale missing OAuth keys (need 2 occurrences each): {under}")
|
|
|
|
def test_oauth_body_strings_contain_provider_placeholder(self):
|
|
"""Body strings must contain {provider} so JS can substitute the provider name."""
|
|
for key in ["onboarding_oauth_provider_ready_body",
|
|
"onboarding_oauth_provider_not_ready_body"]:
|
|
self.assertIn("{provider}", I18N_JS,
|
|
f"{key} must contain {{provider}} placeholder")
|
|
|
|
|
|
# ── Frontend: onboarding.js uses current_is_oauth ─────────────────────────
|
|
|
|
|
|
class TestOAuthOnboardingJs(unittest.TestCase):
|
|
|
|
def test_onboarding_js_reads_current_is_oauth(self):
|
|
"""onboarding.js must check current_is_oauth from the status payload."""
|
|
self.assertIn("current_is_oauth", ONBOARDING_JS,
|
|
"onboarding.js must read current_is_oauth from ONBOARDING.status.setup")
|
|
|
|
def test_onboarding_js_renders_oauth_ready_card(self):
|
|
"""onboarding.js must render the oauth-ready card class."""
|
|
self.assertIn("onboarding-oauth-ready", ONBOARDING_JS)
|
|
|
|
def test_onboarding_js_renders_oauth_pending_card(self):
|
|
"""onboarding.js must render the oauth-pending card class."""
|
|
self.assertIn("onboarding-oauth-pending", ONBOARDING_JS)
|
|
|
|
def test_style_css_has_oauth_card_rules(self):
|
|
"""style.css must contain the .onboarding-oauth-card rules."""
|
|
css = (REPO_ROOT / "static" / "style.css").read_text()
|
|
self.assertIn("onboarding-oauth-card", css)
|
|
self.assertIn("onboarding-oauth-ready", css)
|
|
self.assertIn("onboarding-oauth-pending", css)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|