Files
Cibello-app/packages/database/test/analytics-gdpr.test.ts
T
2026-08-05 19:51:37 +07:00

106 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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));
});
});