import { beforeEach, describe, expect, it, vi } from "vitest"; const storeMock = vi.hoisted(() => ({ createUserSession: vi.fn(), revokeSessionByToken: vi.fn(), })); vi.mock("./sessionStore.server", () => ({ createUserSession: (...args: unknown[]) => storeMock.createUserSession(...args), revokeSessionByToken: (...args: unknown[]) => storeMock.revokeSessionByToken(...args), })); const geoMock = vi.hoisted(() => ({ lookupCountry: vi.fn() })); vi.mock("~/modules/ipAddress/services/geoip.server", () => ({ // Realistischer Mock: der Hash enthaelt die Roh-IP NICHT (wie der echte hashIp). hashIp: () => "HASHED", lookupCountry: (...args: unknown[]) => geoMock.lookupCountry(...args), })); import { buildSessionDeviceContext, endServerSession, startServerSession } from "./sessionLifecycle.server"; function requestWith(headers: Record) { return new Request("https://example.test/login", { headers }); } beforeEach(() => { vi.clearAllMocks(); geoMock.lookupCountry.mockResolvedValue({ countryCode: "DE", countryName: "Germany" }); }); describe("buildSessionDeviceContext (AD-22)", () => { it("hashes the IP and resolves the country, never exposing the raw IP", async () => { const ctx = await buildSessionDeviceContext(requestWith({ "user-agent": "Safari", "x-forwarded-for": "9.9.9.9" })); expect(ctx.ipHash).toBe("HASHED"); expect(ctx.userAgent).toBe("Safari"); expect(ctx.approxLocation).toBe("Germany"); expect(JSON.stringify(ctx)).not.toContain("9.9.9.9"); }); it("yields nulls and skips the geo lookup when no IP is present", async () => { const ctx = await buildSessionDeviceContext(requestWith({})); expect(ctx.ipHash).toBeNull(); expect(ctx.userAgent).toBeNull(); expect(ctx.approxLocation).toBeNull(); expect(geoMock.lookupCountry).not.toHaveBeenCalled(); }); }); describe("startServerSession", () => { it("creates a session row with the pseudonymized device context and returns the raw token", async () => { storeMock.createUserSession.mockResolvedValue({ session: { id: "s1" }, rawToken: "tok-123" }); const token = await startServerSession({ request: requestWith({ "user-agent": "Safari", "x-forwarded-for": "1.2.3.4" }), userId: "u1" }); expect(token).toBe("tok-123"); expect(storeMock.createUserSession).toHaveBeenCalledWith({ userId: "u1", ipHash: "HASHED", userAgent: "Safari", approxLocation: "Germany" }); }); }); describe("endServerSession", () => { it("delegates revocation to the store", async () => { storeMock.revokeSessionByToken.mockResolvedValue(true); await endServerSession("tok-123"); expect(storeMock.revokeSessionByToken).toHaveBeenCalledWith("tok-123"); }); });