From 3fe09fe0f5e03ba6636c7127d93e8feb470a6725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 19:02:53 +0200 Subject: [PATCH] test(auth): pin invalid-timezone rejection on profile update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Covers the four shapes that should round-trip cleanly through the IANA refine: - Europe/Berlin → accepted, prisma.user.update fires. - Mars/Tharsis → 422 "Invalid IANA timezone", no prisma write. - empty string → 422 (min-length kicks first), no prisma write. - injection-shaped Europe/Berlin' OR 1=1 → 422, no prisma write. --- src/lib/auth/__tests__/profile-update.test.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/lib/auth/__tests__/profile-update.test.ts diff --git a/src/lib/auth/__tests__/profile-update.test.ts b/src/lib/auth/__tests__/profile-update.test.ts new file mode 100644 index 000000000..ccb0b7a9b --- /dev/null +++ b/src/lib/auth/__tests__/profile-update.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +vi.mock("@/lib/db", () => ({ + prisma: { + user: { + findUnique: vi.fn(), + update: vi.fn(), + }, + }, +})); + +vi.mock("@/lib/auth/audit", () => ({ + auditLog: vi.fn().mockResolvedValue(undefined), +})); + +import { applyProfileUpdate } from "../profile-update"; +import { prisma } from "@/lib/db"; + +const USER_ID = "user-1"; + +const STUB_USER = { + id: USER_ID, + username: "marc", + displayName: null, + email: null, + role: "USER", + heightCm: null, + dateOfBirth: null, + gender: null, + timezone: "Europe/Berlin", + locale: null, +}; + +beforeEach(() => { + vi.resetAllMocks(); + vi.mocked(prisma.user.update).mockResolvedValue(STUB_USER as never); +}); + +describe("applyProfileUpdate timezone validation", () => { + it("accepts a valid IANA zone", async () => { + const result = await applyProfileUpdate(USER_ID, { + timezone: "Europe/Berlin", + }); + expect(result.ok).toBe(true); + expect(prisma.user.update).toHaveBeenCalledTimes(1); + }); + + it("rejects an unknown zone with 422", async () => { + const result = await applyProfileUpdate(USER_ID, { + timezone: "Mars/Tharsis", + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.status).toBe(422); + expect(result.message).toMatch(/Invalid IANA timezone/i); + } + expect(prisma.user.update).not.toHaveBeenCalled(); + }); + + it("rejects an empty-string timezone", async () => { + const result = await applyProfileUpdate(USER_ID, { timezone: "" }); + expect(result.ok).toBe(false); + expect(prisma.user.update).not.toHaveBeenCalled(); + }); + + it("rejects an injection-shaped string", async () => { + const result = await applyProfileUpdate(USER_ID, { + timezone: "Europe/Berlin' OR 1=1", + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.status).toBe(422); + } + expect(prisma.user.update).not.toHaveBeenCalled(); + }); +});