Files
memind/agent-run-deliverable-check.test.mjs

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);
});