94 lines
3.5 KiB
TypeScript
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();
|
|
});
|
|
});
|