Files
tyx_AI_xhs/tests/integration/wp1-03-slot-limit.test.ts
T

156 lines
6.5 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { createRequire } from "node:module";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { RegistrationService } from "../../apps/api/src/registration.js";
import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js";
const requireFromApi = createRequire(new URL("../../apps/api/package.json", import.meta.url));
const Database = requireFromApi("better-sqlite3");
const fixedNow = Date.parse("2026-07-28T09:00:00.000Z");
const roots: string[] = [];
const services: RegistrationService[] = [];
function createHarness() {
const root = mkdtempSync(join(tmpdir(), "dada-wp1-03-slot-"));
roots.push(root);
const databasePath = join(root, "dada.sqlite3");
const resend = new MockResendAdapter();
const service = new RegistrationService({
challengePepper: Buffer.alloc(32, 0x71),
clock: () => fixedNow,
codeGenerator: () => "731905",
currentPrivacyNoticeVersion: "p0a-registration-notice-v1",
databasePath,
inviteCodeGenerator: () => `DADA-SLOT-${randomUUID()}`,
invitePepper: Buffer.alloc(32, 0x72),
resend,
sessionPepper: Buffer.alloc(32, 0x73),
});
services.push(service);
return { databasePath, resend, service };
}
function withDatabase<T>(databasePath: string, operation: (database: any) => T): T {
const database = new Database(databasePath);
try { return operation(database); } finally { database.close(); }
}
function seedSubject(
databasePath: string,
index: number,
role: "user" | "super_admin",
status: "active" | "suspended" | "deleted",
) {
withDatabase(databasePath, (database) => {
database.prepare(`
INSERT INTO users (
user_id, normalized_email, role, status, counts_toward_stage_limit,
registration_id, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?)
`).run(
randomUUID(),
`slot-fixture-${role}-${status}-${index}@example.invalid`,
role,
status,
role === "user" ? 1 : 0,
randomUUID(),
fixedNow - 1_000,
);
});
}
function writeEvidence(file: string, value: unknown) {
const directory = process.env.DADA_EVIDENCE_DIR_SLOT;
if (!directory) return;
mkdirSync(directory, { recursive: true });
writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`);
}
afterEach(() => {
for (const service of services.splice(0)) service.close();
for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true });
});
describe("TDD-WP1-SLOT-001-stage-limit", () => {
it("counts active and suspended users while excluding deleted users and multiple super_admins", async () => {
const harness = createHarness();
for (let index = 0; index < 8; index += 1) seedSubject(harness.databasePath, index, "user", "active");
seedSubject(harness.databasePath, 8, "user", "suspended");
seedSubject(harness.databasePath, 9, "user", "deleted");
seedSubject(harness.databasePath, 10, "super_admin", "active");
seedSubject(harness.databasePath, 11, "super_admin", "active");
const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 3 });
const tenth = await harness.service.sendRegistrationCode({
email: "stage-tenth@example.invalid",
inviteCode: invite.code,
});
const completed = harness.service.completeRegistration({
code: harness.resend.readLatestCode("stage-tenth@example.invalid"),
creatorName: "Tenth User",
idempotencyKey: "wp1-03-stage-tenth-complete-00000001",
privacyConsentAccepted: true,
privacyNoticeVersion: "p0a-registration-notice-v1",
registrationId: tenth.registrationId,
socialId: "@stage_tenth",
});
expect(completed.status).toBe("registered");
await expect(harness.service.sendRegistrationCode({
email: "stage-eleventh@example.invalid",
inviteCode: invite.code,
})).rejects.toMatchObject({ reason: "stage_limit_reached" });
const counts = withDatabase(harness.databasePath, (database) => ({
active_and_suspended_users: database.prepare(`
SELECT COUNT(*) AS count FROM users
WHERE role = 'user' AND status IN ('active', 'suspended')
`).get().count,
deleted_users: database.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'user' AND status = 'deleted'").get().count,
super_admins: database.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'super_admin'").get().count,
}));
expect(counts).toEqual({ active_and_suspended_users: 10, deleted_users: 1, super_admins: 2 });
writeEvidence("response.json", { eleventh: "stage_limit_reached", tenth: completed.status });
writeEvidence("db-diff.json", { ...counts, failed_invite_use_delta: 0, failed_user_delta: 0 });
});
it("rechecks capacity after challenge issuance under BEGIN IMMEDIATE", async () => {
const harness = createHarness();
for (let index = 0; index < 9; index += 1) seedSubject(harness.databasePath, index, "user", "active");
const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 2 });
const sent = await harness.service.sendRegistrationCode({
email: "stage-race@example.invalid",
inviteCode: invite.code,
});
seedSubject(harness.databasePath, 9, "user", "suspended");
expect(() => harness.service.completeRegistration({
code: harness.resend.readLatestCode("stage-race@example.invalid"),
creatorName: "Race User",
idempotencyKey: "wp1-03-stage-race-complete-000000001",
privacyConsentAccepted: true,
privacyNoticeVersion: "p0a-registration-notice-v1",
registrationId: sent.registrationId,
socialId: "@stage_race",
})).toThrowError(expect.objectContaining({ reason: "stage_limit_reached" }));
const state = withDatabase(harness.databasePath, (database) => ({
consents: database.prepare("SELECT COUNT(*) AS count FROM privacy_consents").get().count,
invite_used: database.prepare("SELECT used_count AS count FROM invite_codes WHERE invite_id = ?").get(invite.inviteId).count,
sessions: database.prepare("SELECT COUNT(*) AS count FROM sessions").get().count,
users: database.prepare("SELECT COUNT(*) AS count FROM users WHERE role = 'user'").get().count,
}));
expect(state).toEqual({ consents: 0, invite_used: 0, sessions: 0, users: 10 });
writeEvidence("concurrency-trace.json", {
final_recheck: "stage_limit_reached",
mode: "BEGIN IMMEDIATE",
side_effects: state,
});
});
});