import { describe, expect, it, beforeEach, afterAll } from "vitest"; import { eq, sql } from "drizzle-orm"; import { createDatabase, closeDatabase } from "../src/client.js"; import { schema, seedReleaseGates, evaluateReleaseGates, summarizeReleaseGates } from "../src/index.js"; import { BUILT_IN_RELEASE_GATES } from "@app/shared-types"; const { db, pool } = createDatabase(); async function reset() { await db.delete(schema.productAnalyticsEvents); await db.delete(schema.releaseGates); await db .delete(schema.users) .where(sql`${schema.users.email} LIKE 'release-gates-test-%@example.invalid'`); } async function createTestUser(id: string) { const [user] = await db .insert(schema.users) .values({ id, email: `release-gates-test-${id.slice(0, 8)}@example.invalid`, displayName: "Release Gate Test", locale: "sv-SE", }) .returning({ id: schema.users.id }); return user!.id; } beforeEach(async () => { await reset(); }); afterAll(async () => { await reset(); await closeDatabase(); }); describe("release gates", () => { it("seeds built-in gates idempotently", async () => { await seedReleaseGates(db); const first = await db.select().from(schema.releaseGates); expect(first.length).toBe(BUILT_IN_RELEASE_GATES.length); await seedReleaseGates(db); const second = await db.select().from(schema.releaseGates); expect(second.length).toBe(BUILT_IN_RELEASE_GATES.length); }); it("evaluates first_scan_rate from product_analytics_events", async () => { await seedReleaseGates(db); const userA = await createTestUser("11111111-1111-1111-1111-111111111111"); const userB = await createTestUser("22222222-2222-2222-2222-222222222222"); const now = new Date(); await db.insert(schema.productAnalyticsEvents).values([ { eventName: "account_created", userId: userA, occurredAt: now, receivedAt: now, properties: {} }, { eventName: "account_created", userId: userB, occurredAt: now, receivedAt: now, properties: {} }, { eventName: "scan_completed", userId: userA, occurredAt: now, receivedAt: now, properties: {} }, ]); const results = await evaluateReleaseGates(db); const firstScan = results.find((g) => g.gateKey === "first_scan_rate"); expect(firstScan).toBeDefined(); expect(firstScan?.value).toBeCloseTo(0.5); expect(firstScan?.status).toBe("failed"); // target 70% }); it("summarizes overall go/no-go verdict", async () => { await seedReleaseGates(db); const results = await evaluateReleaseGates(db); const summary = summarizeReleaseGates(results); expect(summary.overall).toBeOneOf(["go", "no_go", "pending"]); expect(summary.passed + summary.failed + summary.pending + summary.notMeasurable).toBe( BUILT_IN_RELEASE_GATES.length, ); }); it("persists evaluation results", async () => { await seedReleaseGates(db); const user = await createTestUser("33333333-3333-3333-3333-333333333333"); const now = new Date(); await db.insert(schema.productAnalyticsEvents).values([ { eventName: "account_created", userId: user, occurredAt: now, receivedAt: now, properties: {} }, { eventName: "scan_completed", userId: user, occurredAt: now, receivedAt: now, properties: {} }, ]); await evaluateReleaseGates(db); const [gate] = await db.select().from(schema.releaseGates).where(eq(schema.releaseGates.gateKey, "first_scan_rate")); expect(gate?.lastValue).toBeCloseTo(1); expect(gate?.status).toBe("passed"); expect(gate?.lastEvaluatedAt).not.toBeNull(); }); });