Phase2 dry-run: pending A, probe, build-config client, gated task/add

Still default dry-run; ALLOW_LIVE_SUBMIT=0. Live gates closed.
This commit is contained in:
Victor Cao
2026-10-16 19:00:15 +08:00
parent 0d0f3b611a
commit bed3bc11ca
25 changed files with 952 additions and 110 deletions

View File

@@ -0,0 +1,73 @@
import runpy
import io
import json
import os
import unittest
import urllib.error
from unittest.mock import MagicMock, patch
from urllib.parse import urlparse, parse_qs
from src.build_config_client import build_config, BuildConfigError
from scripts.build_config_dry import main
class TestBuildConfigClient(unittest.TestCase):
def response(self, body, status=200):
response = MagicMock()
response.__enter__.return_value = response
response.status = status
response.read.return_value = json.dumps(body).encode()
return response
def test_success_request_and_auth(self):
for authorization in ('0', '1'):
with patch.dict(os.environ, {'XC_TOKEN': 'test-secret', 'BUILD_CONFIG_USE_AUTHORIZATION': authorization}, clear=True), patch('urllib.request.urlopen', return_value=self.response({'code': 0, 'data': 'framework: vllm\n'})) as call:
self.assertEqual(build_config('Sunrise_pt-200-x1'), 'framework: vllm\n')
request = call.call_args.args[0]
self.assertEqual(request.get_method(), 'POST')
self.assertIsNone(request.data)
parsed = urlparse(request.full_url)
self.assertEqual(parsed.path, '/api/adapt/task/build-config')
self.assertNotIn('/task/add', request.full_url)
self.assertEqual(parse_qs(parsed.query), {'gpuType': ['Sunrise_pt-200-x1'], 'framework': ['vllm'], 'taskType': ['text-generation']})
self.assertEqual(request.get_header('Authorization' if authorization == '1' else 'Xc-token'), 'test-secret')
self.assertIsNone(request.get_header('Xc-token' if authorization == '1' else 'Authorization'))
def test_missing_invalid_no_network(self):
for env in ({}, {'XC_TOKEN': 'placeholder'}, {'XC_TOKEN_FILE': '/nonexistent/token'}):
with patch.dict(os.environ, env, clear=True), patch('urllib.request.urlopen') as call:
with self.assertRaisesRegex(BuildConfigError, 'missing/invalid credential'):
build_config('gpu')
with patch('sys.argv', ['build_config_dry']), patch('sys.stderr', io.StringIO()):
self.assertEqual(main(), 1)
call.assert_not_called()
def test_platform_and_data_errors(self):
for body, reason in [({'code': 40100}, '40100'), ({'code': 40101}, '40101'), ({'code': 9}, 'other HTTP/platform'), ({'code': 0, 'data': ''}, 'empty/non-str'), ({'code': 0, 'data': []}, 'empty/non-str'), ({'code': 0, 'data': 'test-secret'}, 'credential echoed')]:
with patch.dict(os.environ, {'XC_TOKEN': 'test-secret'}, clear=True), patch('urllib.request.urlopen', return_value=self.response(body)):
with self.assertRaisesRegex(BuildConfigError, reason):
build_config('gpu')
def test_http_transport_invalid_response(self):
for code in (40100, 40101):
error = urllib.error.HTTPError('https://example.test', 403, '', {}, io.BytesIO(json.dumps({'code': code}).encode()))
with patch.dict(os.environ, {'XC_TOKEN': 'test-secret'}, clear=True), patch('urllib.request.urlopen', side_effect=error):
with self.assertRaisesRegex(BuildConfigError, str(code)):
build_config('gpu')
with patch.dict(os.environ, {'XC_TOKEN': 'test-secret'}, clear=True):
for error in (TimeoutError('secret'), urllib.error.URLError('secret')):
with patch('urllib.request.urlopen', side_effect=error), self.assertRaisesRegex(BuildConfigError, 'transport/timeout'):
build_config('gpu')
for response in (self.response([], 200), self.response({'code': 0, 'data': 'yaml'}, 500)):
with patch('urllib.request.urlopen', return_value=response), self.assertRaises(BuildConfigError):
build_config('gpu')
response = self.response({})
response.read.return_value = b'not-json'
with patch('urllib.request.urlopen', return_value=response), self.assertRaisesRegex(BuildConfigError, 'invalid JSON'):
build_config('gpu')
def test_import_no_io(self):
import src.build_config_client
with patch('urllib.request.urlopen') as call, patch('pathlib.Path.read_text') as read:
runpy.run_path('src/build_config_client.py')
call.assert_not_called()
read.assert_not_called()

View File

@@ -0,0 +1,47 @@
import json
import re
import unittest
import os
from unittest.mock import patch
from pathlib import Path
from src.config_params import build_config_params, config_params_source, GAP
from src.build_config_client import BuildConfigError
from scripts.dry_run import _render
class TestConfigParams(unittest.TestCase):
def setUp(self):
env = patch.dict(os.environ, {}, clear=True)
env.start()
self.addCleanup(env.stop)
def test_source_selection_and_failure(self):
with patch("urllib.request.urlopen") as network:
self.assertEqual(config_params_source(), "sample")
build_config_params()
network.assert_not_called()
self.assertIn("discovered via capture", GAP)
with patch.dict(os.environ, {"CONFIG_PARAMS_SOURCE": "build-config"}):
with self.assertRaisesRegex(BuildConfigError, "missing/invalid credential"):
build_config_params("gpu")
with patch.dict(os.environ, {"ALLOW_BUILD_CONFIG": "1", "XC_TOKEN": "test-secret"}), patch("src.config_params.build_config", return_value="api: yaml") as client:
self.assertEqual(config_params_source(), "build-config")
self.assertEqual(build_config_params("gpu"), "api: yaml")
client.assert_called_once_with("gpu", "vllm", "text-generation")
with patch.dict(os.environ, {"CONFIG_PARAMS_SOURCE": "build-config"}), patch("src.config_params.build_config", side_effect=BuildConfigError("transport/timeout")):
with self.assertRaisesRegex(BuildConfigError, "transport/timeout"):
build_config_params("gpu")
def test_official_sample_exact(self):
scrape = Path('/workspace/modelhub-adapt/logs/api-docs-scrape-2026-10-16.md')
if scrape.is_file():
body = json.loads(re.search(r'```json\n(.*?)\n```', scrape.read_text(), re.S)[1])
self.assertEqual(build_config_params(), body['configParams'])
self.assertIn('max_model_len: 4096', build_config_params())
self.assertIn('framework: vllm', build_config_params())
self.assertIn('NOT production-ready', GAP)
def test_report_draft_and_gap(self):
report = _render([{'model_address': 'org/a', 'try_next_gpus': ['壁砺']}], 'test', 'fixture')
for value in ('代提候选 / 需 Victor 确认', 'configParams source', 'configParams_source=sample', 'GAP:', 'truncated', 'max_model_len: 4096', 'perf_order=unset', 'order_source=experience'):
self.assertIn(value, report)

View File

@@ -19,6 +19,27 @@ class FakeClient:
class TestGate(unittest.TestCase):
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)
def _clear_gate_env(self):
return mock.patch.dict(
os.environ,
@@ -78,7 +99,7 @@ class TestGate(unittest.TestCase):
self.assertEqual(d.mode, "live")
def test_open_gate_without_client_still_blocked(self) -> None:
"""Phase1: no real HTTP client even when gate is open."""
"""Phase2: an open gate never constructs a network client."""
with mock.patch.dict(
os.environ,
{

View File

@@ -0,0 +1,49 @@
import tempfile
import unittest
from pathlib import Path
from src.pending_pool import build_ordered_candidates, parse_pending_txt
class TestPendingPool(unittest.TestCase):
def test_order_filters_and_first_gpu(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
md, txt, dnr, banned = [root / n for n in ('pending.md', 'pending.txt', 'dnr.md', 'banned.md')]
md.write_text('''## Section B
| 1 | `org/cold` | 50 | | |
## Section A
| 1 | `org/good` | 曦望 | 壁砺, 天垓150 | — |
| 2 | `org/exact` | 曦望 | 壁砺 | — |
| 3 | `org/Heretic-model` | 曦望 | 壁砺 | — |
| 4 | `org/OLMo-2-model` | 曦望 | 壁砺 | — |
''')
dnr.write_text('| 1 | `org/exact` | fail |\n## Uniqueness collisions\n- `org/collision`\n## Overnight\n- `org/overnight`')
banned.write_text('`abliterated` `heretic` `uncensored` `unsensored`')
rows = build_ordered_candidates(md, txt, dnr, banned)
self.assertEqual([r['section'] for r in rows], ['A'] * 4 + ['B'])
self.assertEqual(rows[0]['try_next_gpus'][0], '壁砺')
self.assertEqual(rows[1]['skip_reason'], 'do-not-resubmit')
self.assertEqual(rows[2]['status'], 'skipped')
self.assertIn('family', rows[3]['skip_reason'])
self.assertTrue(rows[-1]['needs_human'])
txt.write_text('org/b\t#B cold\norg/a\t#A done:曦望; still try:壁砺+天垓150\norg/c\\t#A try:天垓150\n')
self.assertEqual(parse_pending_txt(txt)[1]['try_next_gpus'], ['壁砺', '天垓150'])
fallback = build_ordered_candidates(root/'absent', txt, dnr, banned)
self.assertEqual([r['model_address'] for r in fallback], ['org/a', 'org/c', 'org/b'])
self.assertEqual(len(build_ordered_candidates(md, txt, dnr, banned)), 5)
def test_experience_order_missing_only_and_ties(self):
from src.pending_pool import experience_order, parse_pending_md
self.assertEqual(experience_order(['智铠 mrv-100', '天垓100', '曦望', '天垓150', '壁砺']),
['曦望', '壁砺', '天垓150', '天垓100', '智铠 mrv-100'])
self.assertEqual(experience_order(['天垓150']), ['天垓150'])
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'pending.md'
path.write_text('## Section A\n| 1 | `org/a` | 壁砺:验证失败; 曦望:正在验证 | 天垓150 | no 壁砺 retry |\n## Section B\n| 1 | `org/b` | 0 | 壁砺 |\n')
from unittest.mock import patch
import os
with patch.dict(os.environ, {'PENDING_SECTION': 'A'}):
rows = build_ordered_candidates(path, dnr_path=Path(directory)/'none', banned_path=Path(directory)/'none')
self.assertEqual(len(rows), 1)
self.assertEqual(rows[0]['try_next_gpus'], ['天垓150'])
self.assertEqual(rows[0]['perf_order'], 'unset')

View File

@@ -0,0 +1,61 @@
import io
import json
import os
import unittest
import urllib.error
from unittest.mock import patch, MagicMock
from src.readonly_probe import probe_readonly
from scripts.probe_readonly import main
class TestReadonlyProbe(unittest.TestCase):
def test_missing_token_no_network(self):
with patch.dict(os.environ, {}, clear=True), patch('urllib.request.urlopen') as call:
self.assertFalse(probe_readonly()['auth_ok'])
with patch('sys.stdout', io.StringIO()):
self.assertEqual(main(), 1)
call.assert_not_called()
def test_success_and_exact_request(self):
response = MagicMock()
response.__enter__.return_value = response
response.status = 200
response.read.return_value = json.dumps({'code': 0, 'data': {
'records': [{'modelId': 'secret-value'}], 'total': 10}}).encode()
with patch.dict(os.environ, {'EXTERNAL_SERVICE_TOKEN': 'secret-value'}, clear=True), patch('urllib.request.urlopen', return_value=response) as call:
result = probe_readonly()
request = call.call_args.args[0]
self.assertEqual(request.get_method(), 'GET')
self.assertEqual(request.full_url, 'https://modelhub.org.cn/api/adapt/task/page?onlyMine=true&pageSize=5&current=1')
self.assertEqual(request.get_header('Xc-token'), 'secret-value')
self.assertTrue(result['auth_ok'])
self.assertEqual(result['record_summary'], {'count': 1, 'total': 10})
self.assertNotIn('secret-value', json.dumps(result))
def test_errors_no_fallback_or_echo(self):
for code in (40100, 40101, 50000):
error = urllib.error.HTTPError('https://example.test', 403, '', {}, io.BytesIO(json.dumps({'code': code, 'message': 'secret-value'}).encode()))
with patch.dict(os.environ, {'XC_TOKEN': 'secret-value'}, clear=True), patch('urllib.request.urlopen', side_effect=error) as call:
result = probe_readonly()
self.assertEqual(call.call_count, 1)
self.assertFalse(result['auth_ok'])
self.assertEqual(result['platform_code'], code)
self.assertIn(str(code) if code != 50000 else 'other', result['error_attribution'])
self.assertNotIn('secret-value', json.dumps(result))
def test_transport_and_invalid_json(self):
for error in (OSError('secret-value'), urllib.error.URLError('secret-value')):
with patch.dict(os.environ, {'XC_TOKEN': 'secret-value'}, clear=True), patch('urllib.request.urlopen', side_effect=error):
self.assertEqual(probe_readonly()['error_attribution'], 'transport/timeout')
response = MagicMock()
response.__enter__.return_value = response
response.status, response.read.return_value = 200, b'invalid secret-value'
with patch.dict(os.environ, {'XC_TOKEN': 'secret-value'}, clear=True), patch('urllib.request.urlopen', return_value=response):
self.assertFalse(probe_readonly()['auth_ok'])
def test_import_no_network(self):
import importlib
import src.readonly_probe
with patch('urllib.request.urlopen') as call:
importlib.reload(src.readonly_probe)
call.assert_not_called()

View File

@@ -21,9 +21,23 @@ class TestReportSafety(unittest.TestCase):
}, clear=True):
report = _render([], "test-time", Path("example.json"))
self.assertNotIn(sentinel, report)
for required in ("待对接 pending-submit", "A→B", "需 Victor 人工提交", "积分:待平台核实", "≥95"):
for required in ("A→B", "需 Victor 人工提交", "积分:待平台核实", "≥95"):
self.assertIn(required, report)
def test_pending_draft_queue_banner_and_skips(self):
rows = [dict(model_address='org/a', section='A', try_next_gpus=['壁砺', '天垓150'],
status='skipped', skip_reason='do-not-resubmit', needs_human=False),
dict(model_address='org/b', section='B', try_next_gpus=[],
status='needs_human', needs_human=True)]
with mock.patch.dict(os.environ, {'ASYNC_QUEUE_DEPTH': '95'}, clear=True):
report = _render(rows, 'test', Path('pending.md'))
self.assertIn('禁 live / 队列≥95', report)
self.assertIn('do-not-resubmit', report)
self.assertIn('needs_human', report)
self.assertIn('"targetGpu": "壁砺"', report)
self.assertNotIn('"targetGpu": "天垓150"', report)
self.assertLess(report.index('Section A'), report.index('Section B'))
def test_http_log_omits_request_details(self):
output = io.StringIO()
with contextlib.redirect_stderr(output):

View File

@@ -0,0 +1,69 @@
import importlib
import io
import os
import tempfile
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
from src import task_add_client as module
class TestTaskAddClient(unittest.TestCase):
def setUp(self):
patch = mock.patch.dict(os.environ, {'STRATEGY_ID': 'sid', 'XC_TOKEN': 'test-secret-credential'}, clear=True)
patch.start()
self.addCleanup(patch.stop)
self.payload = module.build_payload('org/model', '壁砺', 'vllm', config_params={ 'max_model_len': 4096 })
def test_import_no_network(self):
with mock.patch('urllib.request.urlopen') as urlopen:
importlib.reload(module)
urlopen.assert_not_called()
def test_success(self):
response = mock.MagicMock()
response.__enter__.return_value = response
response.status = 200
response.read.return_value = b'{"code":0,"message":"test-secret-credential"}'
with mock.patch('urllib.request.urlopen', return_value=response) as urlopen:
result = module.TaskAddClient().submit(self.payload)
self.assertEqual(result['http_status'], 200)
self.assertNotIn(os.environ['XC_TOKEN'], str(result))
request = urlopen.call_args.args[0]
self.assertEqual(request.get_header('Xc-token'), os.environ['XC_TOKEN'])
self.assertEqual(request.method, 'POST')
urlopen.assert_called_once()
def test_http_4xx_60014(self):
error = urllib.error.HTTPError('https://example.invalid', 403, 'forbidden', {}, io.BytesIO(b'{"code":60014}'))
with mock.patch('urllib.request.urlopen', side_effect=error) as urlopen:
result = module.TaskAddClient().submit(self.payload)
self.assertEqual(result['http_status'], 403)
self.assertEqual(result['platform_code'], 60014)
self.assertIn('D (unverified)', result['error_attribution'])
urlopen.assert_called_once()
def test_transport_no_retry(self):
with mock.patch('urllib.request.urlopen', side_effect=TimeoutError('test-secret-credential')) as urlopen:
result = module.TaskAddClient().submit(self.payload)
self.assertNotIn(os.environ['XC_TOKEN'], str(result))
self.assertEqual(result['error_attribution'], 'transport/timeout')
urlopen.assert_called_once()
def test_token_precedence_and_rejections(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory)/'credential'
path.write_text(' file-credential \n')
with mock.patch.dict(os.environ, {'XC_TOKEN_FILE': str(path)}):
self.assertEqual(module.resolve_token(), 'file-credential')
for token in ('', 'tmp', 'PLACEHOLDER', 'changeme', 'tmp-token'):
with self.subTest(token=token), mock.patch.dict(os.environ, {'XC_TOKEN': token}):
with self.assertRaises(ValueError):
module.resolve_token()
def test_placeholder_never_requests(self):
with mock.patch('urllib.request.urlopen') as urlopen:
with self.assertRaises(ValueError):
module.TaskAddClient().submit(module.build_payload('org/model'))
urlopen.assert_not_called()