fix(llm): make logout-all atomic and persist provider in run record

Second review pass:
- strix auth logout (all providers) now holds the shared store lock across every provider removal, so a concurrent save/refresh cannot leave one credential behind while reporting all removed.
- _persist_run_record writes subscription_provider alongside auth_mode, matching ReportState, so resumed runs keep their original provider label.
This commit is contained in:
yoni
2026-07-29 17:48:33 +00:00
parent 9fd11eedec
commit 7289153f9b
4 changed files with 67 additions and 4 deletions
+25
View File
@@ -95,6 +95,31 @@ def test_model_subcommand_removed() -> None:
assert auth_cli.run_auth(["model", "gpt-5.5"]) == 2
def _sign_in_both() -> None:
codex.save_record({"type": "oauth", "access": "c", "refresh": "r", "account_id": "a"})
grok.save_record({"type": "oauth", "access": "g", "refresh": "r"})
def test_logout_all_removes_every_provider() -> None:
_sign_in_both()
assert codex.is_authenticated()
assert grok.is_authenticated()
assert auth_cli.run_auth(["logout"]) == 0
assert not codex.is_authenticated()
assert not grok.is_authenticated()
def test_logout_single_provider_leaves_the_other() -> None:
_sign_in_both()
assert auth_cli.run_auth(["logout", "grok"]) == 0
assert codex.is_authenticated()
assert not grok.is_authenticated()
@pytest.mark.parametrize("provider", ["chatgpt", "codex", "ChatGPT"])
def test_login_accepts_provider_aliases(provider: str, monkeypatch: pytest.MonkeyPatch) -> None:
reached = {"flow": False}
+33
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
import argparse
import importlib
from unittest import mock
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
@@ -12,6 +14,11 @@ from strix.interface import utils
from strix.report import state as state_mod
# ``strix.interface.__init__`` binds ``main`` to the entrypoint function, so
# ``from strix.interface import main`` returns that function, not the module.
main_mod = importlib.import_module("strix.interface.main")
def test_grok_prefix_routes_to_chat_completions(monkeypatch) -> None: # type: ignore[no-untyped-def]
client = mock.MagicMock()
monkeypatch.setattr(grok, "get_subscription_client", lambda: client)
@@ -69,3 +76,29 @@ def test_subscription_label_prefers_persisted_provider(monkeypatch) -> None: #
# hardcoded default).
fresh = mock.MagicMock(run_record={})
assert utils._subscription_label(fresh) == "ChatGPT subscription"
def test_persisted_run_record_carries_provider(tmp_path, monkeypatch) -> None: # type: ignore[no-untyped-def]
settings = mock.MagicMock()
settings.llm.model = "grok/grok-4"
monkeypatch.setattr(main_mod, "load_settings", lambda: settings)
monkeypatch.setattr(main_mod, "run_dir_for", lambda _name: tmp_path)
captured: dict[str, object] = {}
monkeypatch.setattr(main_mod, "write_run_record", lambda _dir, rec: captured.update(rec))
args = argparse.Namespace(
run_name="run-test",
targets_info=[],
scan_mode="scan",
instruction=None,
non_interactive=True,
local_sources=[],
diff_scope={"active": False},
scope_mode="mode",
diff_base=None,
)
main_mod._persist_run_record(args)
# The resume/viewer record must carry the provider so resumed runs stay labeled.
assert captured["auth_mode"] == "subscription"
assert captured["subscription_provider"] == "Grok"