Skip to content

Commit 6374550

Browse files
committed
test: a member's treatAsHistorical import cannot land a foreign comment pair; a row re-pointed mid-backfill keeps its re-pointed pair (ADR-0139 E7)
Claude-Session: https://claude.ai/code/session_01NX4Woa8XhvMs6xpqQpx8kK Co-authored-by: Claude <noreply@anthropic.com>
1 parent 32b6f53 commit 6374550

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

‎packages/plugins/plugin-audit/src/comment-parent-pair.integration.test.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,12 +392,13 @@ function findOr(where: any): any[] {
392392
describe('[ADR-0139 E7] backfillCommentParentPair', () => {
393393
let kernel: ObjectKernel;
394394
let engine: ObjectQL;
395+
let driver: SqliteWasmDriver;
395396
const rowIds: string[] = [];
396397
const stampedAt = new Map<string, unknown>();
397398
const logger = () => ({ info: vi.fn(), warn: vi.fn() });
398399

399400
beforeAll(async () => {
400-
const driver = new SqliteWasmDriver({ filename: ':memory:' });
401+
driver = new SqliteWasmDriver({ filename: ':memory:' });
401402
({ kernel, engine } = await bootKernel(driver));
402403
engine.registry.registerObject(objectOf(DOC) as any, HARNESS_PACKAGE);
403404
await engine.syncSchemas();
@@ -451,6 +452,26 @@ describe('[ADR-0139 E7] backfillCommentParentPair', () => {
451452
expect(await all()).toEqual(before);
452453
});
453454

455+
it('a row re-pointed between the page read and the write keeps the re-pointed pair', async () => {
456+
const doc = String((await engine.insert(DOC, { name: 'race' }, { context: SYS })).id);
457+
const moved = String((await engine.insert(DOC, { name: 'race target' }, { context: SYS })).id);
458+
const id = String((await engine.insert(COMMENT, commentOn(DOC, doc, 'race'), { context: SYS })).id);
459+
await driver.update(COMMENT, id, { object_name: null, record_id: null });
460+
// The engine the backfill drives re-points the row right after reading the page.
461+
const racing = {
462+
find: async (...args: any[]) => {
463+
const page = await (engine.find as any)(...args);
464+
await engine.update(COMMENT, { id, thread_id: `${DOC}:${moved}` }, { context: SYS });
465+
return page;
466+
},
467+
update: (...args: any[]) => (engine.update as any)(...args),
468+
};
469+
await backfillCommentParentPair(racing as any, logger(), { apply: true, pageSize: 500 });
470+
expect(await engine.findOne(COMMENT, { where: { id }, context: SYS })).toMatchObject({
471+
thread_id: `${DOC}:${moved}`, object_name: DOC, record_id: moved,
472+
});
473+
});
474+
454475
it('a read the table refuses is a failure, never a clean report', async () => {
455476
const log = logger();
456477
const failing = { find: vi.fn(async () => { throw new Error('no such table'); }), update: vi.fn() };

‎packages/qa/dogfood/test/comment-thread-parent-pair.dogfood.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,29 @@ describe('[ADR-0139 E7] the comment-thread gate leafs over the parent pair: ever
277277
expect(gatedWheres.some(hasLeaf)).toBe(true);
278278
});
279279

280+
it('a member’s historical import (treatAsHistorical) naming a foreign pair stores its thread’s pair', async () => {
281+
const posted = await stack.apiAs(token.member, 'POST', `/data/${COMMENT}`, { thread_id: `${OBJ}:${id.mine}`, body: 'ctp import probe' });
282+
expect(posted.status, 'fixture comment').toBeLessThan(300);
283+
const j = (await posted.json()) as any;
284+
const commentId = String(j.id ?? j.record?.id ?? j.data?.id);
285+
const res = await stack.apiAs(token.member, 'POST', `/data/${COMMENT}/import`, {
286+
format: 'json',
287+
writeMode: 'update',
288+
matchFields: ['id'],
289+
treatAsHistorical: true,
290+
rows: [{ id: commentId, object_name: OBJ, record_id: id.other }],
291+
});
292+
expect(res.status).toBe(200);
293+
const report: any = await res.json();
294+
const atRest = await ql.findOne(COMMENT, { where: { id: commentId }, context: { ...SYS } });
295+
// The import updated the row, and the stored pair is still the thread's.
296+
expect({ updated: report.updated, ok: report.results?.[0]?.ok }).toEqual({ updated: 1, ok: true });
297+
expect({ object_name: atRest.object_name, record_id: atRest.record_id }).toEqual({ object_name: OBJ, record_id: id.mine });
298+
// ...so the writer, who reads `id.other` and not `id.mine`, is not served it.
299+
const writerScoped = await list('writer', `$filter=${filterOf({ id: commentId })}&$top=1`);
300+
expect(writerScoped.rows).toEqual([]);
301+
});
302+
280303
it('a row the backfill has not reached is probed by its thread, not leafed: still served when readable, still hidden when not', async () => {
281304
const cleared = [...bulk.mine.slice(0, UNSTAMPED), ...bulk.other.slice(0, UNSTAMPED)];
282305
// Planted below the engine, as a row from before the upgrade is: every

0 commit comments

Comments
 (0)