Fas 3 steg 3a-fix: hermetisk test-setup + analytics-event regler för cooking sessions
This commit is contained in:
@@ -58,19 +58,22 @@ export async function cookingSessionRoutes(app: FastifyInstance) {
|
||||
})
|
||||
.returning();
|
||||
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionStarted({
|
||||
householdId,
|
||||
properties: {
|
||||
cookingSessionId: session!.id,
|
||||
recipeId: id,
|
||||
status,
|
||||
plannedPortions: input.portions ?? recipe.portions,
|
||||
},
|
||||
}),
|
||||
);
|
||||
// cooking_session_started skickas endast vid faktisk start, inte vid planned.
|
||||
if (status === "started") {
|
||||
await trackProductAnalytics(
|
||||
app.db,
|
||||
req.userId,
|
||||
cookingSessionStarted({
|
||||
householdId,
|
||||
properties: {
|
||||
cookingSessionId: session!.id,
|
||||
recipeId: id,
|
||||
status,
|
||||
plannedPortions: input.portions ?? recipe.portions,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return reply.status(201).send({ session: session });
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ describe("cooking sessions", () => {
|
||||
const config = loadConfig();
|
||||
let app: Awaited<ReturnType<typeof buildServer>>;
|
||||
let token: string;
|
||||
let userId: string;
|
||||
let householdId: string;
|
||||
let recipeId: string;
|
||||
const email = "cooking-session-test@example.invalid";
|
||||
@@ -46,6 +47,7 @@ describe("cooking sessions", () => {
|
||||
await testDb.db.delete(schema.households).where(eq(schema.households.id, m.householdId));
|
||||
}
|
||||
await testDb.db.delete(schema.userPreferences).where(eq(schema.userPreferences.userId, u.id));
|
||||
await testDb.db.delete(schema.productAnalyticsEvents).where(eq(schema.productAnalyticsEvents.userId, u.id));
|
||||
await testDb.db.delete(schema.users).where(eq(schema.users.id, u.id));
|
||||
}
|
||||
}
|
||||
@@ -61,6 +63,8 @@ describe("cooking sessions", () => {
|
||||
payload: { email, password: "Password123!", displayName: "Cooking Test" },
|
||||
});
|
||||
token = (JSON.parse(res.body) as { accessToken: string }).accessToken;
|
||||
const profile = await app.inject({ method: "GET", url: "/v1/me", headers: { authorization: `Bearer ${token}` } });
|
||||
userId = (JSON.parse(profile.body) as { id: string }).id;
|
||||
|
||||
const quick = await app.inject({
|
||||
method: "POST",
|
||||
@@ -85,7 +89,7 @@ describe("cooking sessions", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("creates a planned session", async () => {
|
||||
it("creates a planned session without firing cooking_session_started", async () => {
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
@@ -96,9 +100,15 @@ describe("cooking sessions", () => {
|
||||
const body = JSON.parse(res.body) as { session: { status: string; plannedPortions: number } };
|
||||
expect(body.session.status).toBe("planned");
|
||||
expect(body.session.plannedPortions).toBe(4);
|
||||
|
||||
const events = await testDb.db
|
||||
.select({ name: schema.productAnalyticsEvents.eventName })
|
||||
.from(schema.productAnalyticsEvents)
|
||||
.where(eq(schema.productAnalyticsEvents.userId, userId));
|
||||
expect(events.filter((e) => e.name === "cooking_session_started").length).toBe(0);
|
||||
});
|
||||
|
||||
it("starts a planned session", async () => {
|
||||
it("starts a planned session and fires cooking_session_started", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
@@ -116,6 +126,13 @@ describe("cooking sessions", () => {
|
||||
expect(res.statusCode).toBe(200);
|
||||
const body = JSON.parse(res.body) as { session: { status: string } };
|
||||
expect(body.session.status).toBe("started");
|
||||
|
||||
const started = await testDb.db
|
||||
.select()
|
||||
.from(schema.productAnalyticsEvents)
|
||||
.where(eq(schema.productAnalyticsEvents.userId, userId))
|
||||
.orderBy(schema.productAnalyticsEvents.occurredAt);
|
||||
expect(started.filter((e) => e.eventName === "cooking_session_started").length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("cancels a session without touching inventory", async () => {
|
||||
@@ -217,6 +234,47 @@ describe("cooking sessions", () => {
|
||||
expect(body.mealBoxId).toBeDefined();
|
||||
});
|
||||
|
||||
it("emits cooking_session_completed on complete and cooking_session_cancelled on cancel", async () => {
|
||||
// complete
|
||||
const startComplete = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { startNow: true },
|
||||
});
|
||||
const completeId = (JSON.parse(startComplete.body) as { session: { id: string } }).session.id;
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/cooking-sessions/${completeId}/complete`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { mealBoxPortions: 0, deductInventory: true },
|
||||
});
|
||||
|
||||
// cancel
|
||||
const startCancel = await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/recipes/${recipeId}/cook/start`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { startNow: true },
|
||||
});
|
||||
const cancelId = (JSON.parse(startCancel.body) as { session: { id: string } }).session.id;
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: `/v1/cooking-sessions/${cancelId}/cancel`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { reason: "test" },
|
||||
});
|
||||
|
||||
const names = (
|
||||
await testDb.db
|
||||
.select({ name: schema.productAnalyticsEvents.eventName })
|
||||
.from(schema.productAnalyticsEvents)
|
||||
.where(eq(schema.productAnalyticsEvents.userId, userId))
|
||||
).map((r) => r.name);
|
||||
expect(names).toContain("cooking_session_completed");
|
||||
expect(names).toContain("cooking_session_cancelled");
|
||||
});
|
||||
|
||||
it("times out started sessions older than 24 h", async () => {
|
||||
const start = await app.inject({
|
||||
method: "POST",
|
||||
|
||||
Reference in New Issue
Block a user