fix: guard WeChat Page Data delivery with Aider review
Memind CI / Test, build, and release guards (push) Has been cancelled
Memind CI / Test, build, and release guards (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
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',
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user