import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { Readable, Writable } from 'node:stream'; import { LearningControlClient, main, readControlInput } from './control.mjs'; // Isolated transport fixtures only: no browser, real accounts or development API. const calls = []; const secret = 'Transport-fixture-password-51'; let csrf = 'fixture-csrf-before-login'; let actions = 0; let redirect = false; const server = createServer(async (request, response) => { const buffers = []; for await (const chunk of request) buffers.push(chunk); const body = buffers.length ? JSON.parse(Buffer.concat(buffers).toString()) : undefined; calls.push({ method: request.method, path: request.url }); const send = (status, data, cookie) => { response.writeHead(status, { 'Content-Type': 'application/json', ...(cookie ? { 'Set-Cookie': cookie } : {}) }); response.end(JSON.stringify(data)); }; if (request.url === '/api/health') return send(200, { status: 'ok' }); if (request.url === '/api/auth/status') return send(200, { csrf_token: csrf, authenticated: false }, 'assistant_maintainer=anonymous; HttpOnly; SameSite=Strict'); if (request.url === '/api/auth/login') { assert.equal(request.headers['x-csrf-token'], csrf); assert.match(request.headers.cookie, /assistant_maintainer=anonymous/); assert.equal(body.password, secret); if (redirect) { response.writeHead(302, { Location: '/unexpected-redirect' }); response.end(); return; } csrf = 'fixture-csrf-after-login'; return send(200, { csrf_token: csrf, authenticated: true }, 'assistant_maintainer=authenticated; HttpOnly; SameSite=Strict'); } assert.equal(request.headers.origin, `http://127.0.0.1:${server.address().port}`); assert.match(request.headers.cookie, /assistant_maintainer=authenticated/); if (request.method === 'POST') assert.equal(request.headers['x-csrf-token'], csrf); if (request.url === '/api/learning/tasks/fail/browser/action') return send(409, { detail: secret }); if (request.url === '/api/learning/tasks/fail_code/browser/action') return send(409, { detail: { code: body.fixture_code, message: secret, csrf_token: csrf } }); if (request.url.endsWith('/browser/action')) { actions += 1; return send(200, { result: { kind: body.kind } }); } return send(200, { items: [] }); }); await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); const baseUrl = `http://127.0.0.1:${server.address().port}`; const environment = { ASSISTANT_CONTROL_URL: baseUrl, ASSISTANT_MAINTAINER_USERNAME: 'fixture-maintainer', ASSISTANT_MAINTAINER_PASSWORD: secret }; function outputBuffer() { let value = ''; return { output: new Writable({ write(chunk, encoding, next) { value += chunk; next(); } }), text: () => value }; } try { for (const base of ['https://127.0.0.1:8810', 'http://example.com', 'http://user:pass@127.0.0.1:8810', baseUrl + '/api', baseUrl + '?token=secret']) { assert.throws(() => new LearningControlClient({ baseUrl: base }), error => error.code === 'INVALID_BASE_URL'); } const client = new LearningControlClient({ baseUrl, username: 'fixture-maintainer', password: secret }); assert.deepEqual(await client.execute({ operation: 'health' }), { status: 'ok' }); const beforeInvalid = calls.length; await assert.rejects(client.execute({ operation: 'shell.run' }), error => error.code === 'UNKNOWN_OPERATION'); await assert.rejects(client.execute({ operation: 'tasks.get', task_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'discovery.get', run_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'readings.list', run_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'readings.create', run_id: '../auth/status', data: {} }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'readings.get', reading_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'analyses.list', reading_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'analyses.create', reading_id: '../auth/status', data: {} }), error => error.code === 'INVALID_REQUEST'); await assert.rejects(client.execute({ operation: 'analyses.get', analysis_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); for (const operation of ['briefs.get', 'briefs.confirm', 'briefs.discover']) { await assert.rejects(client.execute({ operation, brief_id: '../auth/status', data: {} }), error => error.code === 'INVALID_REQUEST'); } for (const operation of ['reviews.list', 'reviews.create', 'reviews.summary']) { await assert.rejects(client.execute({ operation, analysis_id: '../auth/status', data: {} }), error => error.code === 'INVALID_REQUEST'); } await assert.rejects(client.execute({ operation: 'reviews.get', review_id: '../auth/status' }), error => error.code === 'INVALID_REQUEST'); assert.equal(calls.length, beforeInvalid); const discoveryStart = calls.length; await client.execute({ operation: 'discovery.list' }); await client.execute({ operation: 'discovery.get', run_id: 'discovery_fixture' }); await client.execute({ operation: 'discovery.create', data: { search_candidate_id: 'candidate_fixture', keyword: '装修', max_selected: 2 } }); assert.deepEqual(calls.slice(discoveryStart).map(({method, path}) => ({method, path})), [ { method: 'GET', path: '/api/auth/status' }, { method: 'POST', path: '/api/auth/login' }, { method: 'GET', path: '/api/discovery/runs' }, { method: 'GET', path: '/api/discovery/runs/discovery_fixture' }, { method: 'POST', path: '/api/discovery/runs' }, ]); const routeStart = calls.length; await client.execute({ operation: 'routes.list' }); await client.execute({ operation: 'routes.resolve', data: { platform: 'demo', task_type: 'search_note_comments' } }); await client.execute({ operation: 'routes.verify', data: { platform: 'demo', task_type: 'search_note_comments', keyword: '换参样例' } }); assert.deepEqual(calls.slice(routeStart), [ { method: 'GET', path: '/api/learning/routes' }, { method: 'POST', path: '/api/learning/routes/resolve' }, { method: 'POST', path: '/api/learning/routes/verify' }, ]); const actualReadingStart = calls.length; await client.execute({ operation: 'readings.list', run_id: 'discovery_fixture' }); await client.execute({ operation: 'readings.get', reading_id: 'reading_fixture' }); await client.execute({ operation: 'readings.create', run_id: 'discovery_fixture', data: { detail_candidate_id: 'candidate_fixture', note_id: 'a'.repeat(24) } }); assert.deepEqual(calls.slice(actualReadingStart), [ { method: 'GET', path: '/api/discovery/runs/discovery_fixture/readings' }, { method: 'GET', path: '/api/discovery/readings/reading_fixture' }, { method: 'POST', path: '/api/discovery/runs/discovery_fixture/readings' }, ]); const analysisStart = calls.length; await client.execute({ operation: 'model.config' }); await client.execute({ operation: 'analyses.list', reading_id: 'reading_fixture' }); await client.execute({ operation: 'analyses.create', reading_id: 'reading_fixture', data: {} }); await client.execute({ operation: 'analyses.get', analysis_id: 'analysis_fixture' }); assert.deepEqual(calls.slice(analysisStart), [ { method: 'GET', path: '/api/analysis/config' }, { method: 'GET', path: '/api/discovery/readings/reading_fixture/analyses' }, { method: 'POST', path: '/api/discovery/readings/reading_fixture/analyses' }, { method: 'GET', path: '/api/discovery/analyses/analysis_fixture' }, ]); const briefStart = calls.length; await client.execute({ operation: 'briefs.list' }); await client.execute({ operation: 'briefs.create', data: { request_text: '小红书寻找装修业主' } }); await client.execute({ operation: 'briefs.get', brief_id: 'brief_fixture' }); await client.execute({ operation: 'briefs.confirm', brief_id: 'brief_fixture', data: {} }); await client.execute({ operation: 'briefs.discover', brief_id: 'brief_fixture', data: { search_candidate_id: 'candidate_fixture', max_selected: 2 } }); assert.deepEqual(calls.slice(briefStart), [ { method: 'GET', path: '/api/business/briefs' }, { method: 'POST', path: '/api/business/briefs' }, { method: 'GET', path: '/api/business/briefs/brief_fixture' }, { method: 'POST', path: '/api/business/briefs/brief_fixture/confirm' }, { method: 'POST', path: '/api/business/briefs/brief_fixture/discover' }, ]); const reviewStart = calls.length; await client.execute({ operation: 'reviews.list', analysis_id: 'analysis_fixture' }); await client.execute({ operation: 'reviews.create', analysis_id: 'analysis_fixture', data: { source_ref: 'note', previous_review_id: null, decision: 'uncertain', labels: null, note: '需要更多上下文' } }); await client.execute({ operation: 'reviews.summary', analysis_id: 'analysis_fixture' }); await client.execute({ operation: 'reviews.get', review_id: 'review_fixture' }); assert.deepEqual(calls.slice(reviewStart), [ { method: 'GET', path: '/api/discovery/analyses/analysis_fixture/reviews' }, { method: 'POST', path: '/api/discovery/analyses/analysis_fixture/reviews' }, { method: 'GET', path: '/api/discovery/analyses/analysis_fixture/review-summary' }, { method: 'GET', path: '/api/discovery/reviews/review_fixture' }, ]); await client.execute({ operation: 'browser.action', task_id: 'fixture_task', data: { kind: 'scroll', selector: 'body', direction: 'down', pixels: 500, max_scrolls: 1 } }); assert.equal(actions, 1); const rejection = await client.execute({ operation: 'browser.action', task_id: 'fail', data: { kind: 'click', selector: 'button' } }).catch(error => error); assert.equal(rejection.code, 'API_REJECTED'); assert.equal(rejection.status, 409); assert.equal(rejection.apiCode, undefined); assert.ok(!rejection.message.includes(secret)); assert.equal(actions, 1, 'A rejected write must not be retried'); const codedRejection = await client.execute({ operation: 'browser.action', task_id: 'fail_code', data: { kind: 'click', selector: 'button', fixture_code: 'WRITE_REQUEST_BLOCKED' } }).catch(error => error); assert.equal(codedRejection.code, 'API_REJECTED'); assert.equal(codedRejection.status, 409); assert.equal(codedRejection.apiCode, 'WRITE_REQUEST_BLOCKED'); assert.equal(codedRejection.message, rejection.message); assert.ok(!JSON.stringify(codedRejection).includes(secret) && !JSON.stringify(codedRejection).includes(csrf)); const coded = outputBuffer(); assert.equal(await main(Readable.from([JSON.stringify({ operation: 'browser.action', task_id: 'fail_code', data: { kind: 'click', selector: 'button', fixture_code: 'WRITE_REQUEST_BLOCKED' } })]), coded.output, environment), 1); assert.deepEqual(JSON.parse(coded.text()).error, { code: 'API_REJECTED', message: rejection.message, status: 409, api_code: 'WRITE_REQUEST_BLOCKED' }); for (const fixtureCode of [null, 409, {}, 'X', 'A'.repeat(65), 'browser_session_required', 'WRITE REQUEST BLOCKED', 'WRITE_REQUEST_BLOCKED\n', 'WRITE_REQUEST_BLOCKED', secret, 'API_SECRET_LEAK', 'PASSWORD_123456', 'CSRF_TOKEN_VALUE']) { const unsafe = outputBuffer(); assert.equal(await main(Readable.from([JSON.stringify({ operation: 'browser.action', task_id: 'fail_code', data: { kind: 'click', selector: 'button', fixture_code: fixtureCode } })]), unsafe.output, environment), 1); const error = JSON.parse(unsafe.text()).error; assert.deepEqual(error, { code: 'API_REJECTED', message: rejection.message, status: 409 }); assert.ok(!unsafe.text().includes(secret) && !unsafe.text().includes(csrf)); } assert.equal(actions, 1, 'Diagnostic failures must not retry writes'); await assert.rejects(readControlInput(Readable.from(['x'.repeat(64 * 1024 + 1)])), error => error.code === 'INPUT_TOO_LARGE'); await assert.rejects(readControlInput(Readable.from(['[]'])), error => error.code === 'INVALID_REQUEST'); const batch = outputBuffer(); assert.equal(await main(Readable.from([JSON.stringify([ { operation: 'tasks.list' }, { operation: 'browser.action', task_id: 'fail', data: { kind: 'click', selector: 'button' } }, { operation: 'browser.action', task_id: 'must_not_execute', data: { kind: 'click', selector: 'button' } }, ])]), batch.output, environment), 1); const results = JSON.parse(batch.text()); assert.equal(results.length, 2); assert.equal(results[0].ok, true); assert.equal(results[1].error.status, 409); assert.ok(!batch.text().includes(secret) && !batch.text().includes(csrf) && !batch.text().includes('authenticated;')); assert.ok(!calls.some(call => call.path.includes('must_not_execute'))); redirect = true; const redirected = new LearningControlClient({ baseUrl, username: 'fixture-maintainer', password: secret }); await assert.rejects(redirected.execute({ operation: 'tasks.list' }), error => error.code === 'CONNECTION_FAILED'); assert.ok(!calls.some(call => call.path === '/unexpected-redirect')); process.stdout.write(JSON.stringify({ result: 'passed', local_only: true, csrf_rotation: true, operation_allowlist: true, no_redirect: true, no_write_retry: true, no_credentials_in_output: true, bounded_input: true, safe_api_code: true }) + '\n'); } finally { await new Promise(resolve => server.close(resolve)); }