/** * GDPR helpers for product analytics – integration tests. * Assumes DATABASE_URL points to a writable database. */ import { describe, expect, it, beforeAll, afterAll } from "vitest"; import { eq } from "drizzle-orm"; import { createDatabase, closeDatabase, schema, isAnalyticsOptedIn, deleteUserAnalyticsEvents, } from "@app/database"; const TEST_USER_EMAIL = "analytics-gdpr-test@example.invalid"; describe("analytics GDPR helpers", () => { const { db, pool } = createDatabase(); let userId: string; beforeAll(async () => { // Clean up any stale test user. await db.delete(schema.users).where(eq(schema.users.email, TEST_USER_EMAIL)); const [user] = await db .insert(schema.users) .values({ email: TEST_USER_EMAIL, displayName: "Analytics Test", locale: "sv-SE", }) .returning({ id: schema.users.id }); userId = user!.id; }); afterAll(async () => { await db.delete(schema.userConsents).where(eq(schema.userConsents.userId, userId)); await db.delete(schema.users).where(eq(schema.users.id, userId)); await closeDatabase(); }); it("missing consent row → opted in (legitimate interest, opt-out)", async () => { await db.delete(schema.userConsents).where(eq(schema.userConsents.userId, userId)); expect(await isAnalyticsOptedIn(db, userId)).toBe(true); }); it("explicit granted → opted in", async () => { await db .insert(schema.userConsents) .values({ userId, kind: "product_analytics", status: "granted", grantedAt: new Date(), updatedAt: new Date(), }) .onConflictDoUpdate({ target: [schema.userConsents.userId, schema.userConsents.kind], set: { status: "granted", revokedAt: null, updatedAt: new Date() }, }); expect(await isAnalyticsOptedIn(db, userId)).toBe(true); }); it("explicit revoked → opted out", async () => { const now = new Date(); await db .insert(schema.userConsents) .values({ userId, kind: "product_analytics", status: "revoked", revokedAt: now, updatedAt: now, }) .onConflictDoUpdate({ target: [schema.userConsents.userId, schema.userConsents.kind], set: { status: "revoked", revokedAt: now, updatedAt: now }, }); expect(await isAnalyticsOptedIn(db, userId)).toBe(false); }); it("deleteUserAnalyticsEvents removes only the target user's events", async () => { // Insert a dummy event for the test user and another user. const [otherUser] = await db .insert(schema.users) .values({ email: "other-analytics-test@example.invalid", displayName: "Other", locale: "sv-SE", }) .returning({ id: schema.users.id }); await db.insert(schema.productAnalyticsEvents).values({ userId, eventName: "app_first_open", anonymousId: "anon-test", }); await db.insert(schema.productAnalyticsEvents).values({ userId: otherUser!.id, eventName: "app_first_open", anonymousId: "anon-other", }); const deleted = await deleteUserAnalyticsEvents(db, userId); expect(deleted).toBe(1); const remaining = await db .select({ id: schema.productAnalyticsEvents.id }) .from(schema.productAnalyticsEvents) .where(eq(schema.productAnalyticsEvents.userId, otherUser!.id)); expect(remaining).toHaveLength(1); await db .delete(schema.productAnalyticsEvents) .where(eq(schema.productAnalyticsEvents.userId, otherUser!.id)); await db.delete(schema.users).where(eq(schema.users.id, otherUser!.id)); }); });