"""Tests for Data Truthfulness (P0-1) and OAuth Security Fail-Closed Invariants (P0-2). Verifies: 1. Quota snapshots without live provider metrics are honestly marked as source='baseline' (never '*_api'). 2. Quota buckets do not return fabricated percentages when unmeasured by API. 3. Quota snapshots report is_estimated=True. 4. Codex and Grok OAuth device flows fail immediately on network errors when not in DEV_MODE. 5. Zero background polling is launched on device flow initialization failure. 6. Fake code generation is strictly gated behind HERMES_HUB_DEV_MODE=1. 7. Claude token exchange returns failure on invalid codes rather than silently accepting them. """ from __future__ import annotations import os from unittest.mock import patch, MagicMock import pytest from antigravity_provider.router.quota_collector import AccountQuotaService from antigravity_provider.router.account_identity import QuotaSnapshot, QuotaBucket from antigravity_provider.router.codex_oauth import CodexOAuthSession from antigravity_provider.router.grok_oauth import GrokOAuthSession from antigravity_provider.router.claude_oauth import ClaudeOAuthSession # ── TEST P0-1: Data Truthfulness in Quota Collection ── def test_quota_collector_never_fakes_api_source_without_network(): """P0-1: Quota snapshots without live network endpoints must NOT claim '*_api' sources.""" service = AccountQuotaService.get() providers = ["antigravity", "openai-codex", "opencode-go", "claude", "grok"] for prov in providers: with patch("antigravity_provider.router.profile_manager.ProfileAuthManager.load_profile_auth", return_value={"token": "mock_tok"}): snap = service.fetch_account_quota(prov, f"{prov}-slot-1", force=True) # Invariant: source must be 'baseline' or 'estimated', never '*_api' assert not snap.source.endswith("_api"), f"Provider {prov} falsely claimed API source '{snap.source}'" assert snap.source in ("baseline", "estimated", "unconfigured", "runtime_event") assert snap.is_estimated is True # Invariant: no fabricated non-zero used percentages for b in snap.buckets: if b.status == "healthy": assert b.used_percent is None or b.used_percent == 0.0, ( f"Bucket {b.id} returned fabricated used_percent {b.used_percent}" ) def test_quota_bucket_formatted_remaining_honesty(): """P0-1: Missing values are unknown, not evidence that quota is available.""" b = QuotaBucket( id="test.bucket", display_name="Test Bucket", used_percent=None, remaining_percent=None, status="unknown", ) assert b.formatted_remaining() == "Н/Д" assert b.status == "unknown" # ── TEST P0-2: OAuth Fail-Closed & DEV_MODE Gating ── def test_codex_oauth_fails_immediately_on_network_error(monkeypatch): """P0-2: Codex device flow must fail immediately on network error without HERMES_HUB_DEV_MODE.""" monkeypatch.delenv("HERMES_HUB_DEV_MODE", raising=False) session = CodexOAuthSession("codex-slot-1") with patch("antigravity_provider.router.codex_oauth._post_json", side_effect=ConnectionError("DNS failure")): url, code = session.start() # Must fail immediately assert url == "" assert code == "" assert session.status == "failed" assert session.error_msg is not None assert "DNS failure" in session.error_msg or "Не удалось подключиться" in session.error_msg # Must NOT launch background polling thread assert session.poll_thread is None assert session.user_code is None assert session.is_dev_mode is False def test_codex_oauth_dev_mode_fallback(monkeypatch): """P0-2: Codex device flow allows local mock session ONLY when HERMES_HUB_DEV_MODE=1.""" monkeypatch.setenv("HERMES_HUB_DEV_MODE", "1") session = CodexOAuthSession("codex-slot-1") with patch("antigravity_provider.router.codex_oauth._post_json", side_effect=ConnectionError("Offline")): url, code = session.start() assert code.startswith("CDX-") assert session.status == "pending" assert session.is_dev_mode is True assert session.poll_thread is not None session.cancel() def test_grok_oauth_fails_immediately_on_network_error(monkeypatch): """P0-2: Grok device flow must fail immediately on network error without HERMES_HUB_DEV_MODE.""" monkeypatch.delenv("HERMES_HUB_DEV_MODE", raising=False) session = GrokOAuthSession("grok-slot-1") with patch("antigravity_provider.router.grok_oauth._post_form", side_effect=TimeoutError("xAI unreachable")): url, code = session.start() assert url == "" assert code == "" assert session.status == "failed" assert session.error_msg is not None assert "xAI unreachable" in session.error_msg or "Не удалось подключиться" in session.error_msg assert session.poll_thread is None assert session.user_code is None assert session.is_dev_mode is False def test_grok_oauth_dev_mode_fallback(monkeypatch): """P0-2: Grok device flow allows local mock session ONLY when HERMES_HUB_DEV_MODE=1.""" monkeypatch.setenv("HERMES_HUB_DEV_MODE", "1") session = GrokOAuthSession("grok-slot-1") with patch("antigravity_provider.router.grok_oauth._post_form", side_effect=TimeoutError("Offline")): url, code = session.start() assert code.startswith("GRK-") assert session.status == "pending" assert session.is_dev_mode is True assert session.poll_thread is not None session.cancel() def test_claude_oauth_rejects_invalid_code_on_network_failure(monkeypatch): """P0-2: Claude OAuth rejects invalid raw code when token endpoint fails.""" monkeypatch.delenv("HERMES_HUB_DEV_MODE", raising=False) session = ClaudeOAuthSession("claude-slot-1") with patch("urllib.request.urlopen", side_effect=ConnectionRefusedError("Endpoint down")): ok, msg = session.handle_auth_code("fake_temporary_auth_code_1234567890") assert ok is False assert session.status == "failed" assert "Ошибка обмена кода" in msg