Files
Mujoco_WASM/decision_server/tests/test_codex.py
T
chenlin f3a8a38acd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Waiting to run
web-platform-ci / TypeScript, lint, unit, build (push) Waiting to run
web-platform-ci / Playwright E2E (push) Waiting to run
lekiwi-compatibility / cpu-compatibility (push) Waiting to run
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Waiting to run
web-platform-ci / TypeScript, lint, unit, build (pull_request) Waiting to run
web-platform-ci / Playwright E2E (pull_request) Waiting to run
lekiwi-compatibility / cpu-compatibility (pull_request) Waiting to run
feat: release v1.0.1 CADWorld 网站与 LeKiwi 智能抓放
集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
2026-09-24 09:57:41 +08:00

281 lines
12 KiB
Python

import asyncio
import json
import os
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
from aiohttp import web
from aiohttp.test_utils import TestServer
from decision_server.credentials import deepseek_llm, openrouter_jev
from decision_server.protocol import DecisionError
from decision_server.providers.codex import CodexAccount
from decision_server.providers.codex_gate import verify_no_tools
from decision_server.providers.jev import answer
from decision_server.tests.test_service import plan_value, request_value
class CredentialTests(unittest.TestCase):
def test_explicit_single_variable_no_eval(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "env"
path.write_text(
"OTHER_SECRET=do-not-import\n"
'export OPENROUTER_API_KEY="fixture-key-at-least-16" # note\n'
)
conn = openrouter_jev(path)
self.assertEqual(conn.protocol, "openrouter-decisions")
self.assertEqual(conn.model, "typesafe/jev-1.13")
self.assertNotIn("OTHER_SECRET", os.environ)
path.write_text("DEEPSEEK_API_KEY=another-fixture-key-16\n")
self.assertEqual(deepseek_llm(path).model, "deepseek-flash")
self.assertEqual(deepseek_llm(path).base_url, "https://api.deepseek.com")
for value in ("$(touch PWNED)", "`some-command`", "short"):
path.write_text("OPENROUTER_API_KEY=" + value)
with self.assertRaises(DecisionError):
openrouter_jev(path)
path.write_text(
"OPENROUTER_API_KEY=fixture-key-at-least-16\nOPENROUTER_API_KEY=duplicate-fixture-16"
)
with self.assertRaises(DecisionError):
openrouter_jev(path)
def test_official_choice_not_reordered(self):
result = {"answers": {"test": {"choice": "a", "probabilities": {"a": 0.1, "b": 0.9}}}}
self.assertEqual(answer(result, "test", ["a", "b"]), "a")
for value in (float("nan"), True, -1, 1.01):
result["answers"]["test"]["probabilities"]["a"] = value
with self.assertRaises(DecisionError):
answer(result, "test", ["a", "b"])
class CodexTests(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.temp = tempfile.TemporaryDirectory()
self.client = CodexAccount(Path(self.temp.name))
self.client.status = AsyncMock(return_value={"loggedIn": True})
self.client.check_model = AsyncMock(return_value={"toolGatePassed": True})
self.client.close = AsyncMock()
self.calls = []
self.mode = "success"
self.started = asyncio.Event()
async def rpc(method, params):
self.calls.append((method, params))
if method == "thread/start":
return {"thread": {"id": "thread"}}
if method == "turn/start":
queue = self.client.queues["thread"]
self.started.set()
if self.mode != "wait":
item = {"type": "agentMessage", "text": json.dumps(plan_value())}
if self.mode == "tool":
item = {"type": "commandExecution"}
queue.put_nowait(
{
"method": "item/completed",
"params": {
"threadId": "thread",
"turnId": "turn",
"item": item,
},
}
)
queue.put_nowait(
{
"method": "turn/completed",
"params": {
"threadId": "thread",
"turn": {
"id": "turn",
"status": self.mode if self.mode == "failed" else "completed",
},
},
}
)
return {"turn": {"id": "turn"}}
return {}
self.client.rpc = AsyncMock(side_effect=rpc)
self.client.process = SimpleNamespace(returncode=None)
async def asyncTearDown(self):
self.temp.cleanup()
async def test_plan_is_structured_and_ephemeral(self):
value, _ = await self.client.plan(request_value(), "allowed")
self.assertEqual(value, plan_value())
self.assertTrue(self.calls[0][1]["ephemeral"])
self.assertIn("outputSchema", self.calls[1][1])
self.assertEqual(self.calls[-1][0], "thread/unsubscribe")
self.assertFalse(self.client.queues)
async def test_tool_error_and_failed_turn_interrupt(self):
for mode in ("tool", "failed"):
self.mode = mode
self.calls.clear()
with self.assertRaises(DecisionError):
await self.client.plan(request_value(), "allowed")
self.assertIn("turn/interrupt", [method for method, _ in self.calls])
async def test_cancel_uses_exact_turn_interrupt(self):
self.mode = "wait"
task = asyncio.create_task(self.client.plan(request_value(), "allowed"))
await self.started.wait()
task.cancel()
with self.assertRaises(asyncio.CancelledError):
await task
self.assertIn(("turn/interrupt", {"threadId": "thread", "turnId": "turn"}), self.calls)
self.assertFalse(self.client.queues)
async def test_failed_gate_never_starts_turn(self):
self.client.check_model.side_effect = DecisionError("codex_tool_gate_failed")
with self.assertRaises(DecisionError):
await self.client.plan(request_value(), "allowed")
self.assertFalse(self.calls)
async def test_no_login_no_turn(self):
self.client.status.return_value = {"loggedIn": False}
with self.assertRaises(DecisionError):
await self.client.plan(request_value(), "allowed")
self.assertFalse(self.calls)
async def test_login_cancel_and_logout_are_named_operations(self):
self.client.start = AsyncMock()
self.client.rpc = AsyncMock(
return_value={
"authUrl": "https://auth.openai.com/oauth/authorize?state=fixture",
"loginId": "login",
}
)
result = await self.client.login()
self.assertEqual(result["storage"], "session-only")
self.client.rpc.assert_awaited_with("account/login/start", {"type": "chatgpt"})
await self.client.cancel_login()
self.client.rpc.assert_awaited_with("account/login/cancel", {"loginId": "login"})
self.assertIsNone(self.client.login_id)
await self.client.logout()
self.client.rpc.assert_awaited_with("account/logout", {})
self.client.close.assert_awaited()
async def test_device_login_uses_official_protocol(self):
self.client.start = AsyncMock()
self.client.rpc = AsyncMock(
return_value={
"verificationUrl": "https://auth.openai.com/codex/device",
"userCode": "ABCD-1234",
"loginId": "device-login",
}
)
result = await self.client.login(device=True)
self.client.rpc.assert_awaited_with("account/login/start", {"type": "chatgptDeviceCode"})
self.assertEqual(result["userCode"], "ABCD-1234")
self.assertNotIn("authUrl", result)
self.assertEqual(self.client.login_id, "device-login")
async def test_unexpected_auth_url_rejected(self):
self.client.start = AsyncMock()
self.client.rpc = AsyncMock(return_value={"authUrl": "https://evil.test/login"})
with self.assertRaisesRegex(DecisionError, "codex_unexpected_login_url"):
await self.client.login()
self.client.close.assert_awaited()
async def test_hidden_or_unknown_model_rejected_before_gate(self):
native = CodexAccount(Path(self.temp.name))
native.models = AsyncMock(return_value={"models": [{"id": "current"}]})
with self.assertRaisesRegex(DecisionError, "codex_model_unavailable"):
await native.check_model("gpt-5.4")
self.assertFalse(native.checked)
async def test_unknown_rpc_forbidden(self):
native = CodexAccount(Path(self.temp.name))
with self.assertRaises(DecisionError):
await native.rpc("command/exec", {})
with (
patch("decision_server.providers.codex.shutil.which", return_value=None),
self.assertRaisesRegex(DecisionError, "codex_not_installed"),
):
await native.start()
class NativeGates(unittest.IsolatedAsyncioTestCase):
@unittest.skipUnless(
os.environ.get("DECISION_CODEX_SMOKE") == "1", "native fake inference is opt-in"
)
async def test_native_structured_turn_and_unsubscribe_without_login(self):
async def respond(request):
incoming = await request.json()
self.assertEqual(incoming.get("tools", []), [])
item = {
"type": "message",
"role": "assistant",
"id": "msg_plan",
"status": "completed",
"phase": "final_answer",
"content": [{"type": "output_text", "text": json.dumps(plan_value())}],
}
events = [
{
"type": "response.created",
"response": {"id": "resp_plan", "status": "in_progress"},
},
{"type": "response.output_item.done", "output_index": 0, "item": item},
{
"type": "response.completed",
"response": {
"id": "resp_plan",
"status": "completed",
"output": [item],
"usage": {"input_tokens": 1, "output_tokens": 1, "total_tokens": 2},
},
},
]
return web.Response(
content_type="text/event-stream",
text="".join(
"event: " + e["type"] + "\ndata: " + json.dumps(e) + "\n\n" for e in events
),
)
app = web.Application()
app.router.add_post("/v1/responses", respond)
server = TestServer(app)
await server.start_server()
try:
with tempfile.TemporaryDirectory() as directory:
client = CodexAccount(Path(directory), probe_url=str(server.make_url("/v1")))
try:
await client.start()
# Fake-inference fixture only: no OAuth or cloud inference.
with (
patch.object(client, "status", AsyncMock(return_value={"loggedIn": True})),
patch.object(client, "check_model", AsyncMock()),
):
result, _ = await client.plan(request_value(), "gpt-5.6-terra")
self.assertEqual(result, plan_value())
self.assertIsNotNone(client.process)
self.assertIsNone(client.process.returncode)
finally:
await client.close()
finally:
await server.close()
@unittest.skipUnless(
os.environ.get("DECISION_CODEX_SMOKE") == "1", "native offline gate is opt-in"
)
async def test_all_visible_models_no_tools_and_injected_calls_rejected(self):
catalog = Path(__file__).parents[1] / "providers/codex_models_0_147.json"
models = json.loads(catalog.read_text())["models"]
with tempfile.TemporaryDirectory() as directory:
for model in models:
if model["visibility"] != "list":
continue
with self.subTest(model=model["slug"]):
evidence = {}
await verify_no_tools(Path(directory), model["slug"], evidence)
self.assertTrue(evidence["passed"])
self.assertEqual(evidence["tools"], [])