254 lines
7.6 KiB
JavaScript
254 lines
7.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
import {
|
|
createPageDataDeliveryCodeReviewService,
|
|
inspectPageDataHtmlScripts,
|
|
} from './page-data-delivery-code-review.mjs';
|
|
|
|
const VALID_PAGE = `<!doctype html>
|
|
<script src="/assets/page-data-client.js"></script>
|
|
<script>
|
|
const client = MindSpacePageData.createClient({ apiBase: '/api' });
|
|
async function loadRows() {
|
|
return client.listRows('checkin_records', { limit: 50 });
|
|
}
|
|
</script>`;
|
|
|
|
const BROKEN_PAGE = `<!doctype html>
|
|
<script src="/assets/page-data-client.js"></script>
|
|
<script>
|
|
async function tryAuth() {
|
|
try {
|
|
await client.authenticate('888');
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
loadRows();
|
|
})();
|
|
</script>`;
|
|
|
|
async function createWorkspace(html = VALID_PAGE) {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'page-data-code-review-'));
|
|
await fs.mkdir(path.join(root, 'public'), { recursive: true });
|
|
await fs.writeFile(path.join(root, 'public', 'checkin.html'), html);
|
|
return root;
|
|
}
|
|
|
|
function createGateway(workspace, {
|
|
executor = 'aider',
|
|
receiptStatus = 'passed',
|
|
fixHtml = null,
|
|
removeHtml = false,
|
|
} = {}) {
|
|
const jobs = [];
|
|
return {
|
|
jobs,
|
|
getStatus() {
|
|
return {
|
|
enabled: true,
|
|
executors: ['aider', 'openhands'],
|
|
};
|
|
},
|
|
async executeJob(job) {
|
|
jobs.push(job);
|
|
if (fixHtml) {
|
|
await fs.writeFile(path.join(workspace, 'public', 'checkin.html'), fixHtml);
|
|
}
|
|
if (removeHtml) {
|
|
await fs.rm(path.join(workspace, 'public', 'checkin.html'));
|
|
}
|
|
const receiptPath = job.userMessage.metadata.memindRun.aiderContextFiles
|
|
.find((item) => item.startsWith('.memind/page-data-reviews/'));
|
|
await fs.writeFile(
|
|
path.join(workspace, receiptPath),
|
|
JSON.stringify({
|
|
requestId: job.requestId,
|
|
sourceChannel: 'wechat_mp',
|
|
executor: 'aider',
|
|
status: receiptStatus,
|
|
reviewedFiles: ['public/checkin.html'],
|
|
issues: fixHtml ? ['fixed_javascript_syntax'] : [],
|
|
}),
|
|
);
|
|
return {
|
|
ok: true,
|
|
executor,
|
|
exitCode: 0,
|
|
cwd: workspace,
|
|
};
|
|
},
|
|
};
|
|
}
|
|
|
|
test('inline Page Data syntax inspection catches the broken check-in script', () => {
|
|
const failures = inspectPageDataHtmlScripts(BROKEN_PAGE, {
|
|
relativePath: 'public/checkin.html',
|
|
});
|
|
assert.equal(failures.length, 1);
|
|
assert.match(failures[0].message, /Unexpected token/);
|
|
assert.equal(inspectPageDataHtmlScripts(VALID_PAGE).length, 0);
|
|
});
|
|
|
|
test('Page Data delivery review skips ordinary static HTML', async (t) => {
|
|
const workspace = await createWorkspace('<!doctype html><h1>Static page</h1>');
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace);
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = await service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-static',
|
|
relativePaths: ['public/checkin.html'],
|
|
});
|
|
|
|
assert.deepEqual(result, { action: 'skip', reason: 'not_page_data' });
|
|
assert.equal(gateway.jobs.length, 0);
|
|
});
|
|
|
|
test('Page Data delivery review requires Aider receipt and final syntax pass', async (t) => {
|
|
const workspace = await createWorkspace(BROKEN_PAGE);
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace, { fixHtml: VALID_PAGE });
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
const result = await service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-broken-page',
|
|
sessionId: 'session-1',
|
|
sourceMessageId: 'wx-message-1',
|
|
originalUserText: '打卡页面提交失败,请修复',
|
|
relativePaths: ['public/checkin.html'],
|
|
forcePageData: true,
|
|
});
|
|
|
|
assert.equal(result.action, 'passed');
|
|
assert.equal(result.executor, 'aider');
|
|
assert.deepEqual(result.reviewedFiles, ['public/checkin.html']);
|
|
assert.equal(gateway.jobs.length, 1);
|
|
assert.equal(gateway.jobs[0].taskType, 'page_data_dev');
|
|
assert.match(gateway.jobs[0].userMessage.content[0].text, /Initial JavaScript syntax failures/);
|
|
assert.match(gateway.jobs[0].userMessage.content[0].text, /untrusted input/);
|
|
});
|
|
|
|
test('Page Data delivery review fails closed on executor mismatch', async (t) => {
|
|
const workspace = await createWorkspace();
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace, { executor: 'openhands' });
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-wrong-executor',
|
|
relativePaths: ['public/checkin.html'],
|
|
forcePageData: true,
|
|
}),
|
|
(error) => error?.code === 'PAGE_DATA_REVIEW_EXECUTOR_MISMATCH',
|
|
);
|
|
});
|
|
|
|
test('Page Data delivery review fails closed when Aider leaves a pending receipt', async (t) => {
|
|
const workspace = await createWorkspace();
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace, { receiptStatus: 'pending' });
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-pending',
|
|
relativePaths: ['public/checkin.html'],
|
|
forcePageData: true,
|
|
}),
|
|
(error) => error?.code === 'PAGE_DATA_REVIEW_NOT_PASSED',
|
|
);
|
|
});
|
|
|
|
test('Page Data delivery review fails closed when Aider removes the reviewed artifact', async (t) => {
|
|
const workspace = await createWorkspace();
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace, { removeHtml: true });
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-removed',
|
|
relativePaths: ['public/checkin.html'],
|
|
forcePageData: true,
|
|
}),
|
|
(error) => error?.code === 'PAGE_DATA_REVIEW_ARTIFACT_MISSING',
|
|
);
|
|
});
|
|
|
|
test('Page Data delivery review rejects removal of the Page Data client contract', async (t) => {
|
|
const workspace = await createWorkspace();
|
|
t.after(() => fs.rm(workspace, { recursive: true, force: true }));
|
|
const gateway = createGateway(workspace, {
|
|
fixHtml: '<!doctype html><script>console.log("static")</script>',
|
|
});
|
|
const service = createPageDataDeliveryCodeReviewService({
|
|
toolGateway: gateway,
|
|
userAuth: {
|
|
async resolveWorkingDir() {
|
|
return workspace;
|
|
},
|
|
},
|
|
});
|
|
|
|
await assert.rejects(
|
|
service.reviewIfNeeded({
|
|
sourceChannel: 'wechat_mp',
|
|
userId: 'user-1',
|
|
requestId: 'request-contract-removed',
|
|
relativePaths: ['public/checkin.html'],
|
|
forcePageData: true,
|
|
}),
|
|
(error) => error?.code === 'PAGE_DATA_REVIEW_CONTRACT_REMOVED',
|
|
);
|
|
});
|