Skip to content

Commit 082f31f

Browse files
committed
test(webapp,run-store): tighten batches-list cursor assertions, add dedup-overlap regression, fix a routing comment
1 parent 47fdd54 commit 082f31f

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

apps/webapp/test/batchListPresenter.readroute.test.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,10 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
349349
// union DESC of all 5: e, d, c, b, a -> first 4.
350350
expect(pageB.batches.map((b) => b.id)).toEqual(["batch_e", "batch_d", "batch_c", "batch_b"]);
351351
expect(legacySpyB.counts.findMany).toBeGreaterThan(0);
352-
// cursor parity: next is the composite (createdAt, id) cursor of the 4th row, previous undefined.
353-
expect(pageB.pagination.next?.endsWith("_batch_b")).toBe(true);
352+
// cursor parity: next is the FULL composite (createdAt, id) cursor of the 4th row (batch_b),
353+
// previous undefined. Assert the complete value so a bad timestamp prefix can't pass.
354+
const bRow = pageB.batches.find((b) => b.id === "batch_b")!;
355+
expect(pageB.pagination.next).toBe(`${new Date(bRow.createdAt).getTime()}_batch_b`);
354356
expect(pageB.pagination.previous).toBeUndefined();
355357
expect(pageB.hasAnyBatches).toBe(true);
356358
}
@@ -438,7 +440,9 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
438440
const expectedPage = direct.slice(0, 2);
439441
expect(page.batches.map((b) => b.id)).toEqual(expectedPage.map((r) => r.id));
440442
expect(
441-
hasMore ? page.pagination.next?.endsWith(`_${expectedPage[1].id}`) : !page.pagination.next
443+
hasMore
444+
? page.pagination.next === `${expectedPage[1].createdAt.getTime()}_${expectedPage[1].id}`
445+
: !page.pagination.next
442446
).toBe(true);
443447
expect(page.pagination.previous).toBeUndefined();
444448
expect(page.hasAnyBatches).toBe(true);
@@ -537,6 +541,61 @@ describe("BatchListPresenter run-ops read routing (PG14 control-plane/legacy + P
537541
}
538542
);
539543

544+
// Overlap regression: batches duplicated on BOTH stores (mid-migration copies) must de-dupe to one
545+
// each without dropping rows or underfilling pages. Proves the merge needs no post-dedup refill:
546+
// the union of each store's top-(pageSize+1) always contains the global top, and the next page
547+
// re-queries both stores from the cursor.
548+
heteroPostgresTest(
549+
"flipped org: batches duplicated across both stores de-dupe without dropping rows across pagination",
550+
async ({ prisma14, prisma17 }) => {
551+
const ctx = await seedParents(prisma14, "ovl");
552+
await mirrorEnvParents(prisma17, ctx, "ovl");
553+
554+
const at = (secondsAgo: number) => new Date(Date.now() - secondsAgo * 1000);
555+
// a..e newest->oldest. a,b,c live on BOTH DBs (dup); d,e only on legacy.
556+
const rows = [
557+
{ id: "batch_ov_a", both: true, s: 1 },
558+
{ id: "batch_ov_b", both: true, s: 2 },
559+
{ id: "batch_ov_c", both: true, s: 3 },
560+
{ id: "batch_ov_d", both: false, s: 4 },
561+
{ id: "batch_ov_e", both: false, s: 5 },
562+
];
563+
for (const r of rows) {
564+
await createBatch(prisma14, ctx, {
565+
id: r.id,
566+
friendlyId: `fr_${r.id}`,
567+
createdAt: at(r.s),
568+
});
569+
if (r.both) {
570+
await createBatch(prisma17, ctx, {
571+
id: r.id,
572+
friendlyId: `fr_${r.id}`,
573+
createdAt: at(r.s),
574+
});
575+
}
576+
}
577+
578+
const seen: string[] = [];
579+
let cursor: string | undefined = undefined;
580+
for (let i = 0; i < 10; i++) {
581+
const presenter = new BatchListPresenter(prisma17, prisma17, {
582+
runOpsNew: prisma17,
583+
runOpsLegacyReplica: prisma14,
584+
controlPlaneReplica: prisma14,
585+
splitEnabled: true,
586+
});
587+
const page = await presenter.call(
588+
baseCall(ctx, { pageSize: 2, cursor, direction: "forward" })
589+
);
590+
seen.push(...page.batches.map((b) => b.id));
591+
if (!page.pagination.next) break;
592+
cursor = page.pagination.next;
593+
}
594+
// Every batch exactly once, newest-first; the three dups collapsed to one each.
595+
expect(seen).toEqual(["batch_ov_a", "batch_ov_b", "batch_ov_c", "batch_ov_d", "batch_ov_e"]);
596+
}
597+
);
598+
540599
heteroRunOpsPostgresTest(
541600
"scan against dedicated RunOpsPrismaClient (splitEnabled): returns batches from new DB",
542601
async ({ prisma14, prisma17 }) => {

internal-packages/run-store/src/runOpsStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1888,7 +1888,7 @@ export class RoutingRunStore implements RunStore {
18881888
tx?: PrismaClientOrTransaction,
18891889
residency?: Residency
18901890
): Promise<WaitpointTag> {
1891-
// No owning run; route by a minted id-shape when present, else the env's residency hint, else
1891+
// No owning run; route by the env's residency hint when present, else a minted id-shape, else
18921892
// fall back to LEGACY (same precedence as a standalone waitpoint). Caller tx is never forwarded.
18931893
const store = this.#waitpointWriteStore(undefined, residency, data.id);
18941894
return store.upsertWaitpointTag(data, undefined);

0 commit comments

Comments
 (0)