Files
Cibello-app/packages/database/test/release-gates.test.ts
T
2026-08-05 23:10:09 +07:00

94 lines
3.5 KiB
TypeScript

import { describe, expect, it, beforeEach, afterAll } from "vitest";
import { eq } 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(eq(schema.users.email, "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();
});
});