Spaces:
Paused
Paused
| import threading | |
| import pytest | |
| from fastapi import HTTPException | |
| from autoteam import api | |
| def _reset_main_codex_state(): | |
| yield | |
| api._admin_login_api = None | |
| api._admin_login_step = None | |
| api._main_codex_flow = None | |
| api._main_codex_step = None | |
| api._main_codex_action = None | |
| if api._playwright_lock.locked(): | |
| api._playwright_lock.release() | |
| def test_finish_admin_login_refreshes_local_main_auth_without_remote_sync(monkeypatch): | |
| events = [] | |
| class FakeAdminLoginAPI: | |
| def complete_admin_login(self): | |
| events.append("admin_complete") | |
| return { | |
| "email": "owner@example.com", | |
| "session_token": "session-token", | |
| "account_id": "acc-1", | |
| "workspace_name": "Idapro", | |
| } | |
| def stop(self): | |
| events.append("admin_stop") | |
| lock = threading.Lock() | |
| assert lock.acquire(blocking=False) is True | |
| def fake_refresh_main_auth_file(): | |
| events.append("refresh_main_auth") | |
| return {"email": "owner@example.com", "auth_file": "/tmp/codex-main-acc-1.json", "plan_type": "team"} | |
| def fail_remote_sync(_filepath): | |
| raise AssertionError("admin login must not sync main Codex auth to remote targets") | |
| monkeypatch.setattr(api, "_playwright_lock", lock) | |
| monkeypatch.setattr(api, "_admin_login_api", FakeAdminLoginAPI()) | |
| monkeypatch.setattr(api, "_admin_login_step", "workspace_required") | |
| monkeypatch.setattr(api, "_main_codex_flow", None) | |
| monkeypatch.setattr(api, "_main_codex_step", None) | |
| monkeypatch.setattr(api, "_main_codex_action", None) | |
| monkeypatch.setattr(api._pw_executor, "run", lambda func, *args, **kwargs: func(*args, **kwargs)) | |
| monkeypatch.setattr("autoteam.codex_auth.refresh_main_auth_file", fake_refresh_main_auth_file) | |
| monkeypatch.setattr("autoteam.sync_targets.sync_main_codex_to_configured_targets", fail_remote_sync) | |
| monkeypatch.setattr( | |
| "autoteam.admin_state.get_admin_state_summary", | |
| lambda: { | |
| "configured": False, | |
| "email": "", | |
| "password_saved": False, | |
| "session_present": False, | |
| "account_id": "", | |
| "workspace_name": "", | |
| }, | |
| ) | |
| result = api._finish_admin_login({"step": "completed"}) | |
| assert result["status"] == "completed" | |
| assert result["codex"] == {"in_progress": False, "step": None, "action": None} | |
| assert result["info"]["main_auth"] == { | |
| "email": "owner@example.com", | |
| "auth_file": "/tmp/codex-main-acc-1.json", | |
| "plan_type": "team", | |
| } | |
| assert api._admin_login_api is None | |
| assert api._admin_login_step is None | |
| assert lock.locked() is False | |
| assert events == ["admin_complete", "admin_stop", "refresh_main_auth"] | |
| def test_finish_main_codex_flow_returns_login_message(monkeypatch): | |
| events = [] | |
| class FakeMainCodexFlow: | |
| def complete(self): | |
| events.append("complete") | |
| return { | |
| "email": "owner@example.com", | |
| "auth_file": "/tmp/codex-main-acc-1.json", | |
| "plan_type": "team", | |
| } | |
| def stop(self): | |
| events.append("stop") | |
| lock = threading.Lock() | |
| assert lock.acquire(blocking=False) is True | |
| monkeypatch.setattr(api, "_playwright_lock", lock) | |
| monkeypatch.setattr(api, "_main_codex_flow", FakeMainCodexFlow()) | |
| monkeypatch.setattr(api, "_main_codex_step", "code_required") | |
| monkeypatch.setattr(api, "_main_codex_action", "login") | |
| monkeypatch.setattr(api._pw_executor, "run", lambda func, *args, **kwargs: func(*args, **kwargs)) | |
| result = api._finish_main_codex_flow() | |
| assert result == { | |
| "status": "completed", | |
| "message": "主号 Codex 已登录", | |
| "codex": {"in_progress": False, "step": None, "action": None}, | |
| "info": { | |
| "email": "owner@example.com", | |
| "auth_file": "/tmp/codex-main-acc-1.json", | |
| "plan_type": "team", | |
| }, | |
| } | |
| assert api._main_codex_flow is None | |
| assert api._main_codex_step is None | |
| assert api._main_codex_action is None | |
| assert lock.locked() is False | |
| assert events == ["complete", "stop"] | |
| def test_post_main_codex_login_starts_login_flow(monkeypatch): | |
| events = [] | |
| lock = threading.Lock() | |
| monkeypatch.setattr(api, "_playwright_lock", lock) | |
| monkeypatch.setattr(api, "_main_codex_flow", None) | |
| monkeypatch.setattr(api, "_main_codex_step", None) | |
| monkeypatch.setattr(api, "_main_codex_action", None) | |
| def fake_start(action="sync"): | |
| events.append(action) | |
| if lock.locked(): | |
| lock.release() | |
| return "completed", { | |
| "status": "completed", | |
| "message": "主号 Codex 已登录", | |
| "codex": {"in_progress": False, "step": None, "action": None}, | |
| "info": {"auth_file": "/tmp/codex-main-acc-1.json"}, | |
| } | |
| monkeypatch.setattr(api, "_start_main_codex_flow", fake_start) | |
| result = api.post_main_codex_login() | |
| assert events == ["login"] | |
| assert result["message"] == "主号 Codex 已登录" | |
| assert lock.locked() is False | |
| def test_main_codex_sync_requires_enabled_target(monkeypatch): | |
| monkeypatch.setenv("SYNC_TARGET_CPA", "false") | |
| monkeypatch.setenv("SYNC_TARGET_SUB2API", "false") | |
| monkeypatch.delenv("CPA_URL", raising=False) | |
| monkeypatch.delenv("CPA_KEY", raising=False) | |
| monkeypatch.delenv("SUB2API_URL", raising=False) | |
| monkeypatch.delenv("SUB2API_EMAIL", raising=False) | |
| monkeypatch.delenv("SUB2API_PASSWORD", raising=False) | |
| with pytest.raises(HTTPException) as exc_info: | |
| api._require_sync_target_configs("同步主号 Codex") | |
| assert exc_info.value.status_code == 400 | |
| assert "启用至少一个远端同步目标" in exc_info.value.detail | |
| def test_post_main_codex_delete_remote_files_returns_deleted_names(monkeypatch): | |
| monkeypatch.setattr(api, "_require_sync_target_configs", lambda *_args, **_kwargs: None) | |
| monkeypatch.setattr( | |
| "autoteam.sync_targets.get_enabled_sync_targets", | |
| lambda env=None: ["cpa", "sub2api"], | |
| ) | |
| monkeypatch.setattr( | |
| "autoteam.sync_targets.delete_main_codex_from_configured_targets", | |
| lambda: { | |
| "cpa": {"deleted": ["codex-main-acc-1.json"], "count": 1}, | |
| "sub2api": {"deleted": ["sub2api-codex-main-acc-1.json"], "count": 1}, | |
| }, | |
| ) | |
| result = api.post_main_codex_delete_remote_files() | |
| assert result == { | |
| "message": "已从 CPA + Sub2API 删除 2 个主号认证文件", | |
| "deleted": ["codex-main-acc-1.json", "sub2api-codex-main-acc-1.json"], | |
| "results": { | |
| "cpa": {"deleted": ["codex-main-acc-1.json"], "count": 1}, | |
| "sub2api": {"deleted": ["sub2api-codex-main-acc-1.json"], "count": 1}, | |
| }, | |
| } | |
| def test_post_main_codex_delete_cpa_requires_enabled_target(monkeypatch): | |
| def fake_require(*_args, **_kwargs): | |
| raise HTTPException( | |
| status_code=400, | |
| detail="删除主号 Codex 远端文件 前请先在配置面板启用至少一个远端同步目标(CPA 或 Sub2API)", | |
| ) | |
| monkeypatch.setattr(api, "_require_sync_target_configs", fake_require) | |
| with pytest.raises(HTTPException) as exc_info: | |
| api.post_main_codex_delete_cpa() | |
| assert exc_info.value.status_code == 400 | |
| assert "启用至少一个远端同步目标" in exc_info.value.detail | |