Fas 1b punkt 6: First Scan Coach + aktiveringsmätning

This commit is contained in:
Sven (AAMOS AI)
2026-08-06 04:14:49 +07:00
parent 2f3cdf8016
commit bd8df59138
29 changed files with 9378 additions and 29 deletions
+85
View File
@@ -0,0 +1,85 @@
import { describe, expect, it, beforeAll, afterAll } from "vitest";
import { eq, inArray } from "drizzle-orm";
import { createDatabase, closeDatabase, markMilestone, getActivationState, schema } from "@app/database";
describe("activation", () => {
const { db } = createDatabase();
const emails = ["act-test-1@example.invalid", "act-test-2@example.invalid"];
const inviteCodes = ["ACT123", "ACT223"];
async function cleanup() {
await db.delete(schema.householdMembers).where(
inArray(
schema.householdMembers.householdId,
db.select({ id: schema.households.id }).from(schema.households).where(inArray(schema.households.inviteCode, inviteCodes)),
),
);
await db.delete(schema.households).where(inArray(schema.households.inviteCode, inviteCodes));
await db.delete(schema.users).where(inArray(schema.users.email, emails));
}
beforeAll(cleanup);
afterAll(async () => {
await cleanup();
await closeDatabase();
});
it("marks first scan and activates household", async () => {
const [user] = await db
.insert(schema.users)
.values({ email: emails[0]!, displayName: "Act" })
.returning();
const [household] = await db
.insert(schema.households)
.values({ name: "Act family", inviteCode: inviteCodes[0]! })
.returning();
await db.insert(schema.householdMembers).values({
householdId: household.id,
userId: user.id,
role: "owner",
});
const r1 = await markMilestone(db, household.id, "firstScanCompletedAt");
expect(r1.wasNew).toBe(true);
expect(r1.activatedNow).toBe(false);
await markMilestone(db, household.id, "fifthItemConfirmedAt");
await markMilestone(db, household.id, "firstRecipeRecommendationViewedAt");
const r2 = await markMilestone(db, household.id, "firstRecipeSavedOrStartedAt");
expect(r2.activatedNow).toBe(true);
const state = await getActivationState(db, household.id);
expect(state.activatedAt).not.toBeNull();
expect(state.valueConfirmedAt).toBeNull();
await markMilestone(db, household.id, "firstCookingSessionCompletedAt");
const r3 = await markMilestone(db, household.id, "inventoryUpdatedAfterCookingAt");
expect(r3.valueConfirmedNow).toBe(true);
const state2 = await getActivationState(db, household.id);
expect(state2.valueConfirmedAt).not.toBeNull();
});
it("idempotent: does not move timestamps backward", async () => {
const [user] = await db
.insert(schema.users)
.values({ email: emails[1]!, displayName: "Act2" })
.returning();
const [household] = await db
.insert(schema.households)
.values({ name: "Act2 family", inviteCode: inviteCodes[1]! })
.returning();
await db.insert(schema.householdMembers).values({
householdId: household.id,
userId: user.id,
role: "owner",
});
await markMilestone(db, household.id, "firstScanCompletedAt");
const state1 = await getActivationState(db, household.id);
await new Promise((r) => setTimeout(r, 20));
await markMilestone(db, household.id, "firstScanCompletedAt");
const state2 = await getActivationState(db, household.id);
expect(state2.firstScanCompletedAt?.getTime()).toBe(state1.firstScanCompletedAt?.getTime());
});
});