Retry bounded company prefix collisions in chat fixtures

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-09-29 11:28:48 -05:00
1 parent 3ec07cc8fa
commit ad472a7755
1 file changed
+61 -8
@@ -1068,17 +1068,30 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => {
.where(and(inArray(chatConversations.companyId, companyIds), inArray(chatConversations.state, ["active", "waiting"])));
}
async function seedCompany() {
const companyPrefixAttempts = 8;
const freshCompanyPrefix = () => `C${randomUUID().replaceAll("-", "").slice(0, 7).toUpperCase()}`;
async function seedCompany(nextIssuePrefix = freshCompanyPrefix) {
const companyId = randomUUID();
fixtureCompanies.add(companyId);
const assignedAgentId = randomUUID();
const replacementAgentId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: `Chat Test ${companyId.slice(0, 8)}`,
issuePrefix: `C${companyId.replaceAll("-", "").slice(0, 7).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
let inserted = false;
// Retired fixtures retain company rows. Retry only the short-prefix unique
// conflict; every other database error must still fail the test immediately.
for (let attempt = 0; attempt < companyPrefixAttempts; attempt += 1) {
const [company] = await db.insert(companies).values({
id: companyId,
name: `Chat Test ${companyId.slice(0, 8)}`,
issuePrefix: nextIssuePrefix(),
requireBoardApprovalForNewAgents: false,
}).onConflictDoNothing({ target: companies.issuePrefix }).returning({ id: companies.id });
if (company) {
inserted = true;
fixtureCompanies.add(companyId);
break;
}
}
if (!inserted) throw new Error(`Could not allocate a chat fixture company prefix after ${companyPrefixAttempts} attempts`);
const now = new Date();
await db
.insert(authUsers)
@@ -1133,6 +1146,46 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => {
return { companyId, assignedAgentId, replacementAgentId };
}
it("retries a colliding company fixture prefix and creates a distinct complete fixture", async () => {
const existing = await seedCompany();
const [original] = await db.select().from(companies).where(eq(companies.id, existing.companyId));
const candidates: string[] = [];
const nextPrefix = vi.fn(() => {
const prefix = candidates.length === 0 ? original!.issuePrefix : freshCompanyPrefix();
candidates.push(prefix);
return prefix;
});
const fixture = await seedCompany(nextPrefix);
const [allocated] = await db.select().from(companies).where(eq(companies.id, fixture.companyId));
expect(nextPrefix.mock.calls.length).toBeGreaterThanOrEqual(2);
expect(allocated!.issuePrefix).toBe(candidates.at(-1));
expect(allocated!.issuePrefix).toMatch(/^C[A-F0-9]{7}$/);
expect(allocated!.issuePrefix).not.toBe(original!.issuePrefix);
expect(fixture.companyId).not.toBe(existing.companyId);
expect(await db.select().from(companies).where(eq(companies.id, existing.companyId))).toEqual([original]);
expect((await db.select().from(agents).where(eq(agents.companyId, fixture.companyId))).map(agent => agent.id).sort())
.toEqual([fixture.assignedAgentId, fixture.replacementAgentId].sort());
expect(await db.select().from(companyMemberships).where(eq(companyMemberships.companyId, fixture.companyId)))
.toEqual([expect.objectContaining({ principalId: "owner-user", membershipRole: "operator" })]);
});
it("bounds company fixture prefix collision retries without creating a partial fixture", async () => {
const fixture = await seedCompany();
const [company] = await db.select().from(companies).where(eq(companies.id, fixture.companyId));
const before = await db.select({ id: companies.id }).from(companies);
const nextPrefix = vi.fn(() => company!.issuePrefix);
await expect(seedCompany(nextPrefix)).rejects.toThrow(`after ${companyPrefixAttempts} attempts`);
expect(nextPrefix).toHaveBeenCalledTimes(companyPrefixAttempts);
expect((await db.select({ id: companies.id }).from(companies)).map(row => row.id).sort())
.toEqual(before.map(row => row.id).sort());
});
it("does not retry unrelated company fixture database errors", async () => {
const nextPrefix = vi.fn(() => "C\0INVALID");
await expect(seedCompany(nextPrefix)).rejects.toMatchObject({ cause: { code: "22021" } });
expect(nextPrefix).toHaveBeenCalledOnce();
});
// A truthy return is not a durable scheduler receipt. These transport tests
// record the same exact receipt identity; real scheduling/coalescing is
// separately exercised by durable-chat-wakeup.test.ts against heartbeat.