Files
tyx_AI_xhs/tests/integration/wp1-01-registration.test.ts
T

299 lines
13 KiB
TypeScript

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 { randomUUID } from "node:crypto";
import { afterEach, describe, expect, it } from "vitest";
import {
RegistrationError,
RegistrationService,
type RegistrationTransactionEvent,
} 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-28T06:00:00.000Z");
const privacyNoticeVersion = "p0a-notice-v1";
const roots: string[] = [];
const services: RegistrationService[] = [];
interface Snapshot {
challenges: number;
consents: number;
credits: number;
inviteUsedCount: number;
ledger: number;
profiles: number;
sessions: number;
users: number;
}
function createHarness(inviteCode = "DADA-WP1-VALID") {
const root = mkdtempSync(join(tmpdir(), "dada-wp1-01-"));
roots.push(root);
const databasePath = join(root, "dada.sqlite3");
const resend = new MockResendAdapter();
const transactionEvents: RegistrationTransactionEvent[] = [];
const service = new RegistrationService({
challengePepper: Buffer.alloc(32, 0x31),
clock: () => fixedNow,
codeGenerator: () => "482913",
currentPrivacyNoticeVersion: privacyNoticeVersion,
databasePath,
inviteCodeGenerator: () => inviteCode,
invitePepper: Buffer.alloc(32, 0x32),
onTransaction: (event) => transactionEvents.push(event),
resend,
sessionPepper: Buffer.alloc(32, 0x33),
});
services.push(service);
return { databasePath, resend, service, transactionEvents };
}
function withDatabase<T>(databasePath: string, operation: (database: any) => T): T {
const database = new Database(databasePath);
try {
return operation(database);
} finally {
database.close();
}
}
function seedOccupiedSlots(databasePath: string, count: number) {
withDatabase(databasePath, (database) => {
const insert = database.prepare(`
INSERT INTO users (
user_id, normalized_email, role, status, counts_toward_stage_limit,
registration_id, created_at
) VALUES (?, ?, 'user', 'active', 1, ?, ?)
`);
for (let index = 0; index < count; index += 1) {
insert.run(randomUUID(), `occupied-${index}@example.invalid`, randomUUID(), fixedNow - 1_000);
}
});
}
function snapshot(databasePath: string): Snapshot {
return withDatabase(databasePath, (database) => ({
challenges: database.prepare("SELECT COUNT(*) AS count FROM email_challenges").get().count,
consents: database.prepare("SELECT COUNT(*) AS count FROM privacy_consents").get().count,
credits: database.prepare("SELECT COUNT(*) AS count FROM credit_accounts").get().count,
inviteUsedCount: database.prepare("SELECT COALESCE(SUM(used_count), 0) AS count FROM invite_codes").get().count,
ledger: database.prepare("SELECT COUNT(*) AS count FROM credit_ledger").get().count,
profiles: database.prepare("SELECT COUNT(*) AS count FROM user_profiles").get().count,
sessions: database.prepare("SELECT COUNT(*) AS count FROM sessions").get().count,
users: database.prepare("SELECT COUNT(*) AS count FROM users").get().count,
}));
}
function writeEvidence(relativeDirectory: string, file: string, value: unknown) {
const directory = process.env[relativeDirectory];
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-AUTH-001-valid-register", () => {
it("commits the account, invite use, initial credit, consent and one 30-day session once", async () => {
const harness = createHarness();
const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 3 });
seedOccupiedSlots(harness.databasePath, 9);
const before = snapshot(harness.databasePath);
const sent = await harness.service.sendRegistrationCode({
email: "new-user@example.invalid",
inviteCode: invite.code,
});
const code = harness.resend.readLatestCode("new-user@example.invalid");
expect(code).toBe("482913");
expect(sent).toMatchObject({ status: "verification_sent" });
const request = {
code,
creatorName: "Dada Creator",
idempotencyKey: "wp1-valid-register-idempotency-key-0001",
privacyConsentAccepted: true,
privacyNoticeVersion,
registrationId: sent.registrationId,
socialId: "@@dada_creator",
};
const completed = harness.service.completeRegistration(request);
const replayed = harness.service.completeRegistration(request);
const session = harness.service.readUserSession(completed.sessionToken);
const after = snapshot(harness.databasePath);
expect(completed).toMatchObject({
credits: { availableBalance: 10, reservedBalance: 0 },
sessionExpiresAt: fixedNow + 30 * 86_400_000,
status: "registered",
user: { creatorName: "Dada Creator", role: "user", socialId: "@dada_creator", status: "active" },
});
expect(replayed).toEqual(completed);
expect(session).toMatchObject({ audience: "user", userId: completed.user.userId });
expect(after).toEqual({
challenges: before.challenges + 1,
consents: before.consents + 1,
credits: before.credits + 1,
inviteUsedCount: before.inviteUsedCount + 1,
ledger: before.ledger + 1,
profiles: before.profiles + 1,
sessions: before.sessions + 1,
users: before.users + 1,
});
const stored = withDatabase(harness.databasePath, (database) => ({
challenge: database.prepare("SELECT code_hmac, consumed_at FROM email_challenges").get(),
ledger: database.prepare("SELECT entry_type, amount, available_before, available_after, reserved_before, reserved_after FROM credit_ledger").get(),
session: database.prepare("SELECT token_digest, audience, revoked_at FROM sessions").get(),
}));
expect(stored.challenge.code_hmac).not.toContain(code);
expect(stored.challenge.consumed_at).toBe(fixedNow);
expect(stored.ledger).toEqual({
amount: 10,
available_after: 10,
available_before: 0,
entry_type: "registration_grant",
reserved_after: 0,
reserved_before: 0,
});
expect(stored.session).toMatchObject({ audience: "user", revoked_at: null });
expect(stored.session.token_digest).not.toBe(completed.sessionToken);
writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "request.json", {
operation: ["REGISTER_SEND", "REGISTER_COMPLETE", "AUTH_SESSION"],
privacy_notice_version: privacyNoticeVersion,
sensitive_fields: "redacted",
});
writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "response.json", {
audience: session?.audience,
available_balance: completed.credits.availableBalance,
reserved_balance: completed.credits.reservedBalance,
session_days: 30,
status: completed.status,
});
writeEvidence("DADA_EVIDENCE_DIR_AUTH_VALID", "db-diff.json", { after, before, plaintext_secret_fields: 0 });
});
});
describe("TDD-WP1-AUTH-002-invalid-invite", () => {
it.each(["expired", "disabled", "exhausted", "not_found"] as const)(
"rejects %s before creating a challenge or calling Resend",
async (scenario) => {
const harness = createHarness(`DADA-WP1-${scenario.toUpperCase()}`);
const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 1 });
withDatabase(harness.databasePath, (database) => {
if (scenario === "expired") database.prepare("UPDATE invite_codes SET expires_at = ?").run(fixedNow - 1);
if (scenario === "disabled") database.prepare("UPDATE invite_codes SET status = 'disabled'").run();
if (scenario === "exhausted") database.prepare("UPDATE invite_codes SET used_count = max_uses").run();
});
const before = snapshot(harness.databasePath);
const expectedReason = scenario === "not_found" ? "invite_not_found" : `invite_${scenario}`;
await expect(
harness.service.sendRegistrationCode({
email: "rejected-user@example.invalid",
inviteCode: scenario === "not_found" ? `${invite.code}-missing` : invite.code,
}),
).rejects.toMatchObject({ code: "REGISTRATION_REJECTED", reason: expectedReason });
expect(snapshot(harness.databasePath)).toEqual(before);
expect(harness.resend.calls).toEqual([]);
writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", `${scenario}-response.json`, {
code: "REGISTRATION_REJECTED",
field: "invite_code",
reason: expectedReason,
});
},
);
it("writes the aggregate zero-side-effect evidence", () => {
writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", "db-diff.json", {
challenge_delta: 0,
consent_delta: 0,
credit_delta: 0,
invite_used_count_delta: 0,
session_delta: 0,
user_delta: 0,
});
writeEvidence("DADA_EVIDENCE_DIR_AUTH_INVALID", "external-calls.json", { resend_calls: 0 });
});
});
describe("TDD-WP1-AUTH-001-final-recheck-race", () => {
it.each(["exhausted", "disabled", "expired", "stage_limit"] as const)(
"rechecks %s under BEGIN IMMEDIATE and replays the same failure",
async (scenario) => {
const harness = createHarness(`DADA-WP1-RACE-${scenario.toUpperCase()}`);
const invite = harness.service.createInvite({ expiresAt: fixedNow + 86_400_000, maxUses: 1 });
seedOccupiedSlots(harness.databasePath, 9);
const sent = await harness.service.sendRegistrationCode({
email: "race-user@example.invalid",
inviteCode: invite.code,
});
const code = harness.resend.readLatestCode("race-user@example.invalid");
withDatabase(harness.databasePath, (database) => {
if (scenario === "exhausted") database.prepare("UPDATE invite_codes SET used_count = max_uses").run();
if (scenario === "disabled") database.prepare("UPDATE invite_codes SET status = 'disabled'").run();
if (scenario === "expired") database.prepare("UPDATE invite_codes SET expires_at = ?").run(fixedNow - 1);
if (scenario === "stage_limit") {
database.prepare(`
INSERT INTO users (
user_id, normalized_email, role, status, counts_toward_stage_limit,
registration_id, created_at
) VALUES (?, ?, 'user', 'active', 1, ?, ?)
`).run(randomUUID(), "tenth-user@example.invalid", randomUUID(), fixedNow);
}
});
const before = snapshot(harness.databasePath);
const request = {
code,
creatorName: "Race User",
idempotencyKey: `wp1-race-idempotency-key-${scenario}-0001`,
privacyConsentAccepted: true,
privacyNoticeVersion,
registrationId: sent.registrationId,
socialId: "@race_user",
};
const expectedReason = scenario === "stage_limit" ? "stage_limit_reached" : `invite_${scenario}`;
let first: RegistrationError | undefined;
let second: RegistrationError | undefined;
try { harness.service.completeRegistration(request); } catch (error) { first = error as RegistrationError; }
try { harness.service.completeRegistration(request); } catch (error) { second = error as RegistrationError; }
expect(first).toMatchObject({ code: "REGISTRATION_REJECTED", reason: expectedReason });
expect(second).toMatchObject({ code: first?.code, reason: first?.reason });
expect(snapshot(harness.databasePath)).toEqual(before);
expect(harness.transactionEvents.filter((event) => event.operation === "registration_complete"))
.toEqual(expect.arrayContaining([
expect.objectContaining({ mode: "BEGIN IMMEDIATE", outcome: "rejected" }),
expect.objectContaining({ mode: "BEGIN IMMEDIATE", outcome: "idempotent_replay" }),
]));
writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", `${scenario}-response.json`, {
code: first?.code,
reason: first?.reason,
retry_code: second?.code,
retry_reason: second?.reason,
});
writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", `${scenario}-db-diff.json`, { after: snapshot(harness.databasePath), before });
},
);
it("writes the aggregate transaction evidence", () => {
writeEvidence("DADA_EVIDENCE_DIR_AUTH_RACE", "transaction-trace.json", {
business_side_effects: 0,
failure_replay: "stable",
transaction_mode: "BEGIN IMMEDIATE",
});
});
});