mirror of
https://github.com/usestrix/strix.git
synced 2026-08-17 09:29:49 +02:00
134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
"""Tests for OpenCode (Zen/Go) subscription auth: prefix parsing and key store."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
from strix.config import codex, opencode
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _tmp_store(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
path = tmp_path / "home" / ".strix" / "subscription-auth.json"
|
|
monkeypatch.setattr(codex, "AUTH_PATH", path)
|
|
return path
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("model", "slug", "base_url", "uses_responses"),
|
|
[
|
|
("opencode/claude-sonnet-5", "claude-sonnet-5", opencode.ZEN_BASE_URL, False),
|
|
("opencode/gpt-5.4", "gpt-5.4", opencode.ZEN_BASE_URL, True),
|
|
("opencode/grok-4.5", "grok-4.5", opencode.ZEN_BASE_URL, True),
|
|
("OpenCode/Kimi-K3", "Kimi-K3", opencode.ZEN_BASE_URL, False),
|
|
("opencode-go/kimi-k3", "kimi-k3", opencode.GO_BASE_URL, False),
|
|
("opencode-go/gpt-5.6-luna", "gpt-5.6-luna", opencode.GO_BASE_URL, True),
|
|
("opencode-go/grok-4.5", "grok-4.5", opencode.GO_BASE_URL, False),
|
|
],
|
|
)
|
|
def test_subscription_model_parses_prefixes(
|
|
model: str, slug: str, base_url: str, uses_responses: bool
|
|
) -> None:
|
|
parsed = opencode.subscription_model(model)
|
|
assert parsed is not None
|
|
assert parsed.slug == slug
|
|
assert parsed.base_url == base_url
|
|
assert parsed.uses_responses == uses_responses
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"model",
|
|
["openai/gpt-5.4", "chatgpt/gpt-5.4", "opencode/", "opencode-go/", "opencode", "", None],
|
|
)
|
|
def test_subscription_model_rejects_non_opencode(model: str | None) -> None:
|
|
assert opencode.subscription_model(model) is None
|
|
|
|
|
|
def test_store_roundtrip_and_logout() -> None:
|
|
assert opencode.read_record() is None
|
|
assert opencode.is_authenticated() is False
|
|
|
|
opencode.save_api_key("sk-oc-test")
|
|
record = opencode.read_record()
|
|
assert record is not None
|
|
assert record["key"] == "sk-oc-test"
|
|
assert opencode.is_authenticated() is True
|
|
assert opencode.get_api_key() == "sk-oc-test"
|
|
|
|
opencode.logout()
|
|
assert opencode.read_record() is None
|
|
opencode.logout() # no-op when already gone
|
|
|
|
|
|
def test_store_coexists_with_chatgpt_record() -> None:
|
|
codex.save_record({"type": "oauth", "access": "a", "refresh": "r", "account_id": "acct"})
|
|
opencode.save_api_key("sk-oc-test")
|
|
|
|
assert codex.read_record() is not None
|
|
assert opencode.get_api_key() == "sk-oc-test"
|
|
|
|
opencode.logout()
|
|
assert codex.read_record() is not None
|
|
assert opencode.read_record() is None
|
|
|
|
|
|
def test_get_api_key_raises_when_not_signed_in() -> None:
|
|
with pytest.raises(opencode.OpencodeAuthError) as exc:
|
|
opencode.get_api_key()
|
|
assert exc.value.code == "not_authenticated"
|
|
|
|
|
|
def test_auth_mode_covers_both_subscriptions() -> None:
|
|
assert opencode.auth_mode("opencode/claude-sonnet-5") == "subscription"
|
|
assert opencode.auth_mode("opencode-go/kimi-k3") == "subscription"
|
|
assert opencode.auth_mode("chatgpt/gpt-5.4") == "subscription"
|
|
assert opencode.auth_mode("openai/gpt-5.4") == "api_key"
|
|
assert opencode.auth_mode(None) == "api_key"
|
|
|
|
|
|
def test_subscription_provider() -> None:
|
|
assert opencode.subscription_provider("opencode/claude-sonnet-5") == "opencode"
|
|
assert opencode.subscription_provider("opencode-go/kimi-k3") == "opencode"
|
|
assert opencode.subscription_provider("chatgpt/gpt-5.4") == "chatgpt"
|
|
assert opencode.subscription_provider("openai/gpt-5.4") is None
|
|
assert opencode.subscription_provider(None) is None
|
|
|
|
|
|
def _response(status_code: int, text: str = "") -> mock.MagicMock:
|
|
response = mock.MagicMock()
|
|
response.status_code = status_code
|
|
response.text = text
|
|
return response
|
|
|
|
|
|
def test_validate_api_key_accepts_ok() -> None:
|
|
with mock.patch.object(requests, "get", return_value=_response(200)) as get:
|
|
opencode.validate_api_key("sk-oc-test")
|
|
assert get.call_args.kwargs["headers"]["Authorization"] == "Bearer sk-oc-test"
|
|
|
|
|
|
def test_validate_api_key_rejects_unauthorized() -> None:
|
|
with (
|
|
mock.patch.object(requests, "get", return_value=_response(401)),
|
|
pytest.raises(opencode.OpencodeAuthError) as exc,
|
|
):
|
|
opencode.validate_api_key("bad-key")
|
|
assert exc.value.code == "invalid_key"
|
|
|
|
|
|
def test_validate_api_key_maps_network_errors() -> None:
|
|
with (
|
|
mock.patch.object(requests, "get", side_effect=requests.ConnectionError("boom")),
|
|
pytest.raises(opencode.OpencodeAuthError) as exc,
|
|
):
|
|
opencode.validate_api_key("sk-oc-test")
|
|
assert exc.value.code == "unavailable"
|