fix(register): align live sentinel flow with successful HAR
This commit is contained in:
146
tests/test_sentinel_solver.py
Normal file
146
tests/test_sentinel_solver.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import json
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, *, text="", status_code=200, json_data=None, headers=None):
|
||||
self.text = text
|
||||
self.status_code = status_code
|
||||
self._json_data = json_data
|
||||
self.headers = headers or {}
|
||||
|
||||
def json(self):
|
||||
if self._json_data is None:
|
||||
raise ValueError("no json")
|
||||
return self._json_data
|
||||
|
||||
|
||||
class FakeHTTP:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
self.device_id = "device-fallback-id"
|
||||
self.session = SimpleNamespace(
|
||||
headers={
|
||||
"User-Agent": "Mozilla/5.0 Test",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
},
|
||||
cookies=SimpleNamespace(
|
||||
jar=[SimpleNamespace(name="oai-did", value="cookie-did", domain="chatgpt.com")]
|
||||
),
|
||||
)
|
||||
self.req_payloads = []
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
self.calls.append((method, url, kwargs))
|
||||
if url.endswith("/backend-api/sentinel/sdk.js"):
|
||||
return FakeResponse(
|
||||
text=(
|
||||
"window.SentinelSDK = {};"
|
||||
"script.src = 'https://sentinel.openai.com/sentinel/20260219f9f6/sdk.js';"
|
||||
)
|
||||
)
|
||||
if "/backend-api/sentinel/frame.html?sv=" in url:
|
||||
return FakeResponse(text="<html></html>")
|
||||
if url.endswith("/backend-api/sentinel/req"):
|
||||
data = kwargs.get("data")
|
||||
if data:
|
||||
payload = json.loads(data)
|
||||
else:
|
||||
payload = {}
|
||||
self.req_payloads.append(payload)
|
||||
return FakeResponse(
|
||||
json_data={
|
||||
"token": "collector-token",
|
||||
"proofofwork": {"required": True, "seed": "seed-1", "difficulty": "0"},
|
||||
"turnstile": {"required": True, "dx": "encoded-dx"},
|
||||
"so": {"required": True, "snapshot_dx": "snapshot-dx"},
|
||||
}
|
||||
)
|
||||
raise AssertionError(f"unexpected request: {method} {url}")
|
||||
|
||||
|
||||
class SentinelSolverTests(unittest.TestCase):
|
||||
@patch("subprocess.run")
|
||||
def test_build_token_uses_enforcement_p_not_prepare_p(self, run_mock):
|
||||
from src.sentinel_solver import SentinelSolver
|
||||
|
||||
def fake_run(command, **kwargs):
|
||||
mode = command[-1]
|
||||
payload = json.loads(kwargs["input"])
|
||||
if mode == "prepare":
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "prepare-proof"}), stderr="")
|
||||
if mode == "turnstile":
|
||||
self.assertEqual(payload["p"], "prepare-proof")
|
||||
self.assertEqual(payload["dx"], "encoded-dx")
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"t": "wire-turnstile"}), stderr="")
|
||||
if mode == "enforcement":
|
||||
self.assertEqual(payload["chat_req"]["proofofwork"]["seed"], "seed-1")
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "enforcement-proof"}), stderr="")
|
||||
raise AssertionError(command)
|
||||
|
||||
run_mock.side_effect = fake_run
|
||||
http = FakeHTTP()
|
||||
solver = SentinelSolver(http)
|
||||
|
||||
token = solver.build_token("username_password_create")
|
||||
parsed = json.loads(token)
|
||||
|
||||
self.assertEqual(
|
||||
parsed,
|
||||
{
|
||||
"p": "enforcement-proof",
|
||||
"t": "wire-turnstile",
|
||||
"c": "collector-token",
|
||||
"id": "cookie-did",
|
||||
"flow": "username_password_create",
|
||||
},
|
||||
)
|
||||
self.assertEqual(http.calls[0][0:2], ("GET", "https://sentinel.openai.com/backend-api/sentinel/sdk.js"))
|
||||
self.assertTrue(http.calls[1][1].startswith("https://sentinel.openai.com/backend-api/sentinel/frame.html?sv="))
|
||||
self.assertEqual(http.calls[2][0:2], ("POST", "https://sentinel.openai.com/backend-api/sentinel/req"))
|
||||
self.assertEqual(http.req_payloads[-1]["p"], "prepare-proof")
|
||||
self.assertEqual(http.req_payloads[-1]["flow"], "username_password_create")
|
||||
|
||||
@patch("subprocess.run")
|
||||
def test_build_session_observer_token_uses_cached_chat_req_snapshot(self, run_mock):
|
||||
from src.sentinel_solver import SentinelSolver
|
||||
|
||||
def fake_run(command, **kwargs):
|
||||
mode = command[-1]
|
||||
payload = json.loads(kwargs["input"])
|
||||
if mode == "prepare":
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "prepare-proof"}), stderr="")
|
||||
if mode == "turnstile":
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"t": "wire-turnstile"}), stderr="")
|
||||
if mode == "enforcement":
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"p": "enforcement-proof"}), stderr="")
|
||||
if mode == "session-observer":
|
||||
self.assertEqual(payload["flow"], "oauth_create_account")
|
||||
self.assertEqual(payload["c"], "collector-token")
|
||||
self.assertEqual(payload["snapshot_dx"], "snapshot-dx")
|
||||
return SimpleNamespace(returncode=0, stdout=json.dumps({"so": "wire-so"}), stderr="")
|
||||
raise AssertionError(command)
|
||||
|
||||
run_mock.side_effect = fake_run
|
||||
http = FakeHTTP()
|
||||
solver = SentinelSolver(http)
|
||||
|
||||
so_token = solver.build_session_observer_token("oauth_create_account")
|
||||
|
||||
self.assertEqual(
|
||||
json.loads(so_token),
|
||||
{
|
||||
"so": "wire-so",
|
||||
"c": "collector-token",
|
||||
"id": "cookie-did",
|
||||
"flow": "oauth_create_account",
|
||||
},
|
||||
)
|
||||
self.assertEqual(http.req_payloads[-1]["flow"], "oauth_create_account")
|
||||
self.assertEqual(http.req_payloads[-1]["p"], "prepare-proof")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user