import test from 'node:test'; import assert from 'node:assert/strict'; import { detectSessionDeliverables, detectWorkspaceSyncedDeliverables, hasRecoverableSessionDeliverables, isRecoverableFinishError, mergeDeliverableSummaries, prepareAndDetectSessionDeliverables, tryRecoverRunFromDeliverables, tryRecoverRunFromSessionDeliverables, } from './agent-run-deliverable-check.mjs'; test('isRecoverableFinishError recognizes finish and run timeout codes', () => { assert.equal(isRecoverableFinishError({ code: 'SESSION_REPLY_INCOMPLETE' }), true); assert.equal(isRecoverableFinishError({ code: 'SESSION_REPLY_TIMEOUT' }), true); assert.equal(isRecoverableFinishError({ code: 'AGENT_RUN_TIMEOUT' }), true); assert.equal(isRecoverableFinishError({ code: 'TOOL_GATEWAY_VALIDATION_FAILED' }), false); }); test('detectSessionDeliverables counts pages and online publications for a session', async () => { const pool = { async query(sql, params) { assert.match(sql, /source_session_id/); assert.deepEqual(params, ['user-1', 'session-1']); return [[ { page_id: 'page-front', title: '前台', publication_id: 'pub-front', publication_status: 'online', public_url: 'https://example.com/u/john/pages/page-front', }, { page_id: 'page-admin', title: '后台', publication_id: null, publication_status: null, public_url: null, }, ]]; }, }; const summary = await detectSessionDeliverables(pool, 'user-1', 'session-1'); assert.equal(summary.pageCount, 2); assert.equal(summary.publicationCount, 1); assert.equal(summary.pages[0].publicUrl, 'https://example.com/u/john/pages/page-front'); }); test('tryRecoverRunFromSessionDeliverables succeeds when pages exist despite finish timeout', async () => { const pool = { async query() { return [[{ page_id: 'page-1', title: 'Survey', publication_id: 'pub-1', public_url: '/u/j/x' }]]; }, }; const recovered = await tryRecoverRunFromSessionDeliverables({ pool, userId: 'user-1', sessionId: 'session-1', error: Object.assign(new Error('session reply timed out'), { code: 'SESSION_REPLY_TIMEOUT' }), }); assert.ok(recovered); assert.equal(recovered.deliverables.pageCount, 1); assert.equal(recovered.originalError.code, 'SESSION_REPLY_TIMEOUT'); }); test('tryRecoverRunFromSessionDeliverables returns null when no deliverables exist', async () => { const pool = { async query() { return [[]]; } }; const recovered = await tryRecoverRunFromSessionDeliverables({ pool, userId: 'user-1', sessionId: 'session-1', error: Object.assign(new Error('stream ended'), { code: 'SESSION_REPLY_INCOMPLETE' }), }); assert.equal(recovered, null); }); test('hasRecoverableSessionDeliverables accepts pages even without online publication', () => { assert.equal(hasRecoverableSessionDeliverables({ pageCount: 1, publicationCount: 0 }), true); assert.equal(hasRecoverableSessionDeliverables({ pageCount: 0, publicationCount: 0 }), false); }); test('detectWorkspaceSyncedDeliverables filters auto-synced public html pages', async () => { const pool = { async query(sql, params) { assert.match(sql, /auto_synced/); assert.deepEqual(params, ['user-1', 1000]); return [[{ page_id: 'page-html', title: '攻略', publication_id: null, publication_status: null, public_url: null, }]]; }, }; const summary = await detectWorkspaceSyncedDeliverables(pool, 'user-1', { sinceMs: 1000 }); assert.equal(summary.pageCount, 1); }); test('detectWorkspaceSyncedDeliverables stays inside current run artifact paths', async () => { const pool = { async query(sql, params) { assert.match(sql, /workspace_relative_path IN \(\?, \?\)/); assert.deepEqual(params, [ 'user-1', 1000, 'public/current.html', 'public/current-admin.html', ]); return [[{ page_id: 'page-current', title: 'Current', workspace_relative_path: 'public/current.html', }]]; }, }; const summary = await detectWorkspaceSyncedDeliverables(pool, 'user-1', { sinceMs: 1000, onlyRelativePaths: ['public/current.html', 'public/current-admin.html'], }); assert.equal(summary.pageCount, 1); assert.equal(summary.pages[0].workspaceRelativePath, 'public/current.html'); }); test('detectWorkspaceSyncedDeliverables skips workspace sweep for an empty run scope', async () => { const pool = { async query() { throw new Error('must not query'); } }; const summary = await detectWorkspaceSyncedDeliverables(pool, 'user-1', { sinceMs: 1000, onlyRelativePaths: [], }); assert.equal(summary.pageCount, 0); }); test('prepareAndDetectSessionDeliverables merges session and workspace pages after prepare hook', async () => { const prepareCalls = []; const pool = { async query(sql, params) { if (sql.includes('source_session_id')) { return [[{ page_id: 'page-session', title: 'Session Page', publication_id: null, publication_status: null, public_url: null, }]]; } if (sql.includes('auto_synced')) { return [[{ page_id: 'page-workspace', title: 'Workspace Page', publication_id: 'pub-workspace', publication_status: 'online', public_url: '/u/john/pages/page-workspace', }]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const summary = await prepareAndDetectSessionDeliverables({ pool, userId: 'user-1', sessionId: 'session-1', runStartedAtMs: 2000, prepareDeliverables: async ({ userId, sessionId }) => { prepareCalls.push({ userId, sessionId }); }, }); assert.equal(prepareCalls.length, 1); assert.equal(summary.pageCount, 2); assert.equal(summary.publicationCount, 1); }); test('tryRecoverRunFromDeliverables succeeds for stale recovery without recoverable finish error', async () => { const pool = { async query(sql) { if (sql.includes('source_session_id')) return [[]]; if (sql.includes('auto_synced')) { return [[{ page_id: 'page-1', title: 'Survey', publication_id: 'pub-1', public_url: '/u/j/x', }]]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; const recovered = await tryRecoverRunFromDeliverables({ pool, userId: 'user-1', sessionId: 'session-1', error: Object.assign(new Error('stale'), { code: 'AGENT_RUN_STALE_RECOVERY' }), requireRecoverableError: false, }); assert.ok(recovered); assert.equal(recovered.deliverables.pageCount, 1); }); test('mergeDeliverableSummaries deduplicates pages by id', () => { const merged = mergeDeliverableSummaries( { pageCount: 1, publicationCount: 0, pages: [{ pageId: 'page-1', title: 'A', publicationId: null, publicationStatus: null, publicUrl: null }], }, { pageCount: 1, publicationCount: 1, pages: [{ pageId: 'page-1', title: 'A', publicationId: 'pub-1', publicationStatus: 'online', publicUrl: '/u/j/x', }], }, ); assert.equal(merged.pageCount, 1); assert.equal(merged.publicationCount, 1); });