222 lines
7.2 KiB
JavaScript
222 lines
7.2 KiB
JavaScript
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);
|
|
});
|