test_scan.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. from pathlib import Path
  2. import json
  3. from ops.scan import classify_token_file
  4. from platforms.chatgpt.constants import (
  5. OPENAI_SEC_CH_UA,
  6. OPENAI_SEC_CH_UA_MOBILE,
  7. OPENAI_SEC_CH_UA_PLATFORM,
  8. OPENAI_USER_AGENT,
  9. )
  10. def test_classify_token_file_retries_transient_transport_error_with_fresh_session(
  11. monkeypatch,
  12. tmp_path: Path,
  13. ) -> None:
  14. token_file = tmp_path / "fresh@example.com.json"
  15. token_file.write_text(
  16. json.dumps({"access_token": "token-1", "account_id": "acct-1"}),
  17. encoding="utf-8",
  18. )
  19. class FailingSession:
  20. def __init__(self) -> None:
  21. self.calls = 0
  22. def get(self, *args, **kwargs): # type: ignore[no-untyped-def]
  23. del args, kwargs
  24. self.calls += 1
  25. raise RuntimeError(
  26. "Failed to perform, curl: (35) TLS connect error: "
  27. "error:00000000:OPENSSL_internal:invalid library (0)."
  28. )
  29. class HealthySession:
  30. def __init__(self) -> None:
  31. self.calls = 0
  32. def get(self, *args, **kwargs): # type: ignore[no-untyped-def]
  33. del args, kwargs
  34. self.calls += 1
  35. return type("Response", (), {"status_code": 200, "text": "{\"ok\":true}"})()
  36. sessions = [FailingSession(), HealthySession()]
  37. def fake_get_session(): # type: ignore[no-untyped-def]
  38. return sessions.pop(0)
  39. monkeypatch.setattr("ops.scan.get_session", fake_get_session)
  40. monkeypatch.setattr("ops.scan.reset_session", lambda: None)
  41. result = classify_token_file(token_file, proxy="http://127.0.0.1:7899", timeout=15)
  42. assert result.category == "normal"
  43. assert result.status_code == 200
  44. assert "ok" in result.detail
  45. def test_classify_token_file_reports_missing_file_separately(tmp_path: Path) -> None:
  46. token_file = tmp_path / "missing@example.com.json"
  47. result = classify_token_file(token_file, proxy=None, timeout=15)
  48. assert result.category == "missing"
  49. assert result.status_code is None
  50. assert result.detail.startswith("missing_file:")
  51. def test_classify_token_file_requires_responses_path_when_enabled(monkeypatch, tmp_path: Path) -> None:
  52. token_file = tmp_path / "service@example.com.json"
  53. token_file.write_text(
  54. json.dumps({"access_token": "token-1", "account_id": "acct-1"}),
  55. encoding="utf-8",
  56. )
  57. class Session:
  58. def get(self, *args, **kwargs): # type: ignore[no-untyped-def]
  59. del args, kwargs
  60. return type("Response", (), {"status_code": 200, "text": '{"ok":true}'})()
  61. def post(self, *args, **kwargs): # type: ignore[no-untyped-def]
  62. del args, kwargs
  63. return type("Response", (), {"status_code": 500, "text": '{"error":{"message":"unexpected EOF","type":"server_error"}}'})()
  64. monkeypatch.setattr("ops.scan.get_session", lambda: Session())
  65. result = classify_token_file(token_file, proxy=None, timeout=15, require_response_path=True)
  66. assert result.category == "service_error"
  67. assert result.status_code == 500
  68. assert "unexpected EOF" in result.detail
  69. def test_classify_token_file_uses_browser_fingerprint_headers_for_usage_and_responses(
  70. monkeypatch,
  71. tmp_path: Path,
  72. ) -> None:
  73. token_file = tmp_path / "fingerprint@example.com.json"
  74. token_file.write_text(
  75. json.dumps({"access_token": "token-1", "account_id": "acct-1"}),
  76. encoding="utf-8",
  77. )
  78. calls: list[tuple[str, dict[str, str]]] = []
  79. class Session:
  80. def get(self, url: str, **kwargs): # type: ignore[no-untyped-def]
  81. del url
  82. calls.append(("GET", dict(kwargs.get("headers") or {})))
  83. return type("Response", (), {"status_code": 200, "text": '{"ok":true}'})()
  84. def post(self, url: str, **kwargs): # type: ignore[no-untyped-def]
  85. del url
  86. calls.append(("POST", dict(kwargs.get("headers") or {})))
  87. return type("Response", (), {"status_code": 200, "text": "data: ok"})()
  88. monkeypatch.setattr("ops.scan.get_session", lambda: Session())
  89. result = classify_token_file(token_file, proxy=None, timeout=15, require_response_path=True)
  90. assert result.category == "normal"
  91. assert [method for method, _headers in calls] == ["GET", "POST"]
  92. for _method, headers in calls:
  93. assert headers["User-Agent"] == OPENAI_USER_AGENT
  94. assert headers["sec-ch-ua"] == OPENAI_SEC_CH_UA
  95. assert headers["sec-ch-ua-mobile"] == OPENAI_SEC_CH_UA_MOBILE
  96. assert headers["sec-ch-ua-platform"] == OPENAI_SEC_CH_UA_PLATFORM