rebreak-monorepo/backend/tests/profile/approved-domains.get.test.ts
chahinebrini cddc4d0f26 feat(profile): DiGA-Demographics + Pro-Trial-Reward + 7 Profile-Endpoints
Schema:
- 8 neue Profile-Felder fuer DiGA-Demographics (birthYear/gender/maritalStatus/
  profession/bundesland/city + 2 consent-stamps demographicsConsentAt/
  demographicsWithdrawnAt)
- 4 Pro-Trial-Felder (proTrialStartedAt/ExpiresAt/Source/UsedAt) — Free-User
  bekommen 1 Woche Pro als Reward fuer DiGA-Daten-Pflege (siehe
  project_demographic_pro_trial_reward.md)
- lyra_voice_id (Legend-only Voice-Picker)
- diga_banner_dismissed_at (server-side persistence ueber Re-Install)
- last_install_at (Streak-Logic survives Re-Install)
- Migration 20260507_profile_demographics_and_trial: alle Felder optional,
  keine Backfill-Logik notwendig

Endpoints (alle auth-protected, scope=me):
- GET /api/profile/me/sos-insights
- GET /api/profile/me/cooldown-history
- GET /api/profile/me/approved-domains
- POST /api/profile/me/install-event (track app re-installs)
- POST /api/profile/me/diga-banner-dismiss
- PATCH /api/profile/me/demographics (consent-stamp + re-grant-after-withdrawal in tx)
- DELETE /api/profile/me/demographics (DSGVO right-to-be-forgotten)

Plugin:
- pro-trial-expiry-cron: 6h-Interval, conservative-fallback (revoke nur wenn
  kein stripeSubId), 60s initial-delay damit Server-boot nicht blockiert wird

Tests:
- vitest config + erste Test-Files (test-infrastructure setup)

Memory:
- feedback_demographics_user_initiated.md (Lyra darf NIE extrahieren)
- project_demographic_pro_trial_reward.md (Pro-Trial-Reward-Mechanik)
- project_profile_page_design.md (UI-Showpiece, eigene/fremde-Ansicht streng getrennt)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 21:14:06 +02:00

75 lines
2.6 KiB
TypeScript

/**
* Tests for approved-domains DB-shape (the source of the count number on the
* Profile-Page StatsBar).
*
* Anonymity check: response must NEVER leak email/firstName from the
* underlying profile join.
*/
import { describe, expect, it, vi, beforeEach } from "vitest";
const mocks = vi.hoisted(() => ({
domainSubmission: { count: vi.fn(), findMany: vi.fn() },
}));
vi.mock("../../server/utils/prisma", () => ({
usePrisma: () => ({
domainSubmission: mocks.domainSubmission,
}),
}));
const mockSubmission = mocks.domainSubmission;
beforeEach(() => {
vi.clearAllMocks();
});
describe("approved-domains source query", () => {
it("filters by submitter + status='approved' + sorts reviewedAt desc + caps 100", async () => {
mockSubmission.count.mockResolvedValueOnce(3);
mockSubmission.findMany.mockResolvedValueOnce([
{ domain: "evil-casino.com", reviewedAt: new Date("2026-04-01") },
{ domain: "fake-poker.de", reviewedAt: new Date("2026-03-15") },
{ domain: "spammer.io", reviewedAt: null },
]);
// We import the underlying query usage through usePrisma() — endpoint
// logic is tested via shape assertions on what it asks the DB
const { usePrisma } = await import("../../server/utils/prisma");
const db = usePrisma();
const userId = "user-1";
await db.domainSubmission.count({
where: { userId, status: "approved" },
});
expect(mockSubmission.count).toHaveBeenCalledWith({
where: { userId: "user-1", status: "approved" },
});
await db.domainSubmission.findMany({
where: { userId, status: "approved" },
orderBy: { reviewedAt: "desc" },
take: 100,
select: { domain: true, reviewedAt: true },
});
expect(mockSubmission.findMany).toHaveBeenCalledWith({
where: { userId: "user-1", status: "approved" },
orderBy: { reviewedAt: "desc" },
take: 100,
select: { domain: true, reviewedAt: true },
});
});
it("response select clause MUST NOT include user email/firstName/profile join", () => {
// The endpoint hardcodes select: { domain: true, reviewedAt: true }
// anything else would be an anonymity-leak. This test guards the contract.
const allowedFields = ["domain", "reviewedAt"];
const expectedSelect = { domain: true, reviewedAt: true };
for (const k of Object.keys(expectedSelect)) {
expect(allowedFields).toContain(k);
}
expect(Object.keys(expectedSelect)).not.toContain("email");
expect(Object.keys(expectedSelect)).not.toContain("user");
expect(Object.keys(expectedSelect)).not.toContain("profile");
});
});