2026-10-16 15:47:10 +08:00
|
|
|
"""Gate defaults to dry-run / blocked; live only with dual confirm + STRATEGY_ID."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import unittest
|
|
|
|
|
from unittest import mock
|
|
|
|
|
|
|
|
|
|
from src.gate import LIVE_CONFIRM_VALUE, attempt_live_submit, evaluate_gate
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeClient:
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.calls = []
|
|
|
|
|
|
|
|
|
|
def submit(self, payload):
|
|
|
|
|
self.calls.append(payload)
|
|
|
|
|
return {"task_id": "fake-1"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGate(unittest.TestCase):
|
2026-10-16 19:00:15 +08:00
|
|
|
def setUp(self):
|
|
|
|
|
self.counter = mock.patch('src.gate._submit_count', 0)
|
|
|
|
|
self.counter.start()
|
|
|
|
|
self.addCleanup(self.counter.stop)
|
|
|
|
|
self.env = mock.patch.dict(os.environ, {}, clear=True)
|
|
|
|
|
self.env.start()
|
|
|
|
|
self.addCleanup(self.env.stop)
|
|
|
|
|
|
|
|
|
|
def test_queue_and_process_limit(self):
|
|
|
|
|
client = FakeClient()
|
|
|
|
|
with mock.patch.dict(os.environ, {
|
|
|
|
|
'STRATEGY_ID': 'sid', 'ALLOW_LIVE_SUBMIT': '1',
|
|
|
|
|
'LIVE_CONFIRM': LIVE_CONFIRM_VALUE, 'ASYNC_QUEUE_DEPTH': '95'
|
|
|
|
|
}):
|
|
|
|
|
self.assertIn('≥95', attempt_live_submit({}, client)['reason'])
|
|
|
|
|
self.assertEqual(client.calls, [])
|
|
|
|
|
os.environ['ASYNC_QUEUE_DEPTH'] = '94'
|
|
|
|
|
self.assertTrue(attempt_live_submit({}, client)['submitted'])
|
|
|
|
|
self.assertFalse(attempt_live_submit({}, client)['submitted'])
|
|
|
|
|
self.assertEqual(len(client.calls), 1)
|
|
|
|
|
|
2026-10-16 15:47:10 +08:00
|
|
|
def _clear_gate_env(self):
|
|
|
|
|
return mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{
|
|
|
|
|
"STRATEGY_ID": "sid-1",
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "0",
|
|
|
|
|
"LIVE_CONFIRM": "",
|
|
|
|
|
},
|
|
|
|
|
clear=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_default_dry_run(self) -> None:
|
|
|
|
|
with self._clear_gate_env():
|
|
|
|
|
d = evaluate_gate()
|
|
|
|
|
self.assertFalse(d.allowed)
|
|
|
|
|
self.assertEqual(d.mode, "dry-run")
|
|
|
|
|
|
|
|
|
|
def test_missing_strategy_blocked(self) -> None:
|
|
|
|
|
env = {
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "1",
|
|
|
|
|
"LIVE_CONFIRM": LIVE_CONFIRM_VALUE,
|
|
|
|
|
}
|
|
|
|
|
# Remove STRATEGY_ID
|
|
|
|
|
base = {k: v for k, v in os.environ.items() if k != "STRATEGY_ID"}
|
|
|
|
|
base.update(env)
|
|
|
|
|
with mock.patch.dict(os.environ, base, clear=True):
|
|
|
|
|
d = evaluate_gate()
|
|
|
|
|
self.assertFalse(d.allowed)
|
|
|
|
|
self.assertEqual(d.mode, "blocked")
|
|
|
|
|
|
|
|
|
|
def test_allow_without_confirm_blocked(self) -> None:
|
|
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{
|
|
|
|
|
"STRATEGY_ID": "sid-1",
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "1",
|
|
|
|
|
"LIVE_CONFIRM": "YES",
|
|
|
|
|
},
|
|
|
|
|
clear=False,
|
|
|
|
|
):
|
|
|
|
|
d = evaluate_gate()
|
|
|
|
|
self.assertFalse(d.allowed)
|
|
|
|
|
self.assertEqual(d.mode, "blocked")
|
|
|
|
|
|
|
|
|
|
def test_dual_gate_opens(self) -> None:
|
|
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{
|
|
|
|
|
"STRATEGY_ID": "sid-1",
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "1",
|
|
|
|
|
"LIVE_CONFIRM": LIVE_CONFIRM_VALUE,
|
|
|
|
|
},
|
|
|
|
|
clear=False,
|
|
|
|
|
):
|
|
|
|
|
d = evaluate_gate()
|
|
|
|
|
self.assertTrue(d.allowed)
|
|
|
|
|
self.assertEqual(d.mode, "live")
|
|
|
|
|
|
|
|
|
|
def test_open_gate_without_client_still_blocked(self) -> None:
|
2026-10-16 19:00:15 +08:00
|
|
|
"""Phase2: an open gate never constructs a network client."""
|
2026-10-16 15:47:10 +08:00
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{
|
|
|
|
|
"STRATEGY_ID": "sid-1",
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "1",
|
|
|
|
|
"LIVE_CONFIRM": LIVE_CONFIRM_VALUE,
|
|
|
|
|
},
|
|
|
|
|
clear=False,
|
|
|
|
|
):
|
|
|
|
|
result = attempt_live_submit({"modelAddress": "x/y"})
|
|
|
|
|
self.assertFalse(result["submitted"])
|
|
|
|
|
self.assertIn("no real", result["reason"].lower())
|
|
|
|
|
self.assertEqual(result["mode"], "blocked")
|
|
|
|
|
self.assertFalse(result["ok"])
|
|
|
|
|
|
|
|
|
|
def test_prd_example_and_whitespace_id_never_submit(self) -> None:
|
|
|
|
|
cases = [
|
|
|
|
|
{"ALLOW_LIVE_SUBMIT": "true", "LIVE_CONFIRM": LIVE_CONFIRM_VALUE},
|
|
|
|
|
{"ALLOW_LIVE_SUBMIT": "1", "LIVE_CONFIRM": "", "CONFIRM_LIVE": "YES"},
|
|
|
|
|
{"ALLOW_LIVE_SUBMIT": "1", "LIVE_CONFIRM": LIVE_CONFIRM_VALUE, "STRATEGY_ID": " "},
|
|
|
|
|
]
|
|
|
|
|
for overrides in cases:
|
|
|
|
|
with self.subTest(overrides=overrides):
|
|
|
|
|
client = FakeClient()
|
|
|
|
|
env = {"STRATEGY_ID": "sid-test", **overrides}
|
|
|
|
|
with mock.patch.dict(os.environ, env, clear=True):
|
|
|
|
|
result = attempt_live_submit({}, client=client)
|
|
|
|
|
self.assertFalse(result["submitted"])
|
|
|
|
|
self.assertFalse(result["ok"])
|
|
|
|
|
self.assertEqual(client.calls, [])
|
|
|
|
|
self.assertTrue(result["reason"])
|
|
|
|
|
|
|
|
|
|
def test_injected_fake_client(self) -> None:
|
|
|
|
|
client = FakeClient()
|
|
|
|
|
with mock.patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{
|
|
|
|
|
"STRATEGY_ID": "sid-1",
|
|
|
|
|
"ALLOW_LIVE_SUBMIT": "1",
|
|
|
|
|
"LIVE_CONFIRM": LIVE_CONFIRM_VALUE,
|
|
|
|
|
},
|
|
|
|
|
clear=False,
|
|
|
|
|
):
|
|
|
|
|
result = attempt_live_submit({"modelAddress": "x/y"}, client=client)
|
|
|
|
|
self.assertTrue(result["submitted"])
|
|
|
|
|
self.assertEqual(len(client.calls), 1)
|
|
|
|
|
|
|
|
|
|
def test_default_attempt_rejects(self) -> None:
|
|
|
|
|
client = FakeClient()
|
|
|
|
|
with self._clear_gate_env():
|
|
|
|
|
result = attempt_live_submit({"modelAddress": "x/y"}, client=client)
|
|
|
|
|
self.assertFalse(result["submitted"])
|
|
|
|
|
self.assertEqual(len(client.calls), 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|