import { beforeEach, describe, expect, it, vi } from "vitest"; vi.mock("@hivegrid/db/pretenant-security.server", () => ({ appendIpAddressLogCapability: vi.fn(), findIpAddressCapability: vi.fn(), upsertIpAddressCapability: vi.fn(), })); vi.mock("~/utils/cache.server", () => ({ cachified: vi.fn(({ getFreshValue }) => getFreshValue()), clearCacheKey: vi.fn(), })); vi.mock("~/utils/server/IpUtils", () => ({ getTrustedClientIpOrNull: vi.fn(() => "203.0.113.10") })); vi.mock("~/utils/db/blacklist.db.server", () => ({ addBlacklistAttempt: vi.fn(), addToBlacklist: vi.fn(), findInBlacklist: vi.fn(() => null), })); // AD-22: Keep GeoIP and hashing hermetic while preserving deterministic hashes. vi.mock("./geoip.server", () => ({ hashIp: (ip: string) => `hash-${ip.length}`, lookupCountry: vi.fn(async () => null), })); import { appendIpAddressLogCapability, findIpAddressCapability, upsertIpAddressCapability } from "@hivegrid/db/pretenant-security.server"; import { addBlacklistAttempt, findInBlacklist } from "~/utils/db/blacklist.db.server"; import IpAddressServiceServer from "./IpAddressService.server"; const RAW_IP = "203.0.113.10"; const HASHED_IP = `hash-${RAW_IP.length}`; const ipRecord = { id: "ip_1", ip: HASHED_IP, provider: "", type: "", countryCode: "", countryName: "", regionCode: "", regionName: "", city: "", zipCode: "", latitude: null, longitude: null, metadata: "null", createdAt: new Date("2026-01-01T00:00:00.000Z"), updatedAt: new Date("2026-01-01T00:00:00.000Z"), } as Awaited>; const appendLogMock = vi.mocked(appendIpAddressLogCapability); const findIpAddressMock = vi.mocked(findIpAddressCapability); const upsertIpAddressMock = vi.mocked(upsertIpAddressCapability); const findInBlacklistMock = vi.mocked(findInBlacklist); const addBlacklistAttemptMock = vi.mocked(addBlacklistAttempt); describe("IpAddressServiceServer", () => { beforeEach(() => { vi.clearAllMocks(); findInBlacklistMock.mockResolvedValue(null); findIpAddressMock.mockResolvedValue(null); upsertIpAddressMock.mockResolvedValue(ipRecord); appendLogMock.mockResolvedValue({ id: "log_1" } as Awaited>); }); it("persists only the IP hash through pre-tenant capabilities (AD-22)", async () => { const request = new Request("https://admin.hivegrid.de/login"); await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).resolves.toBeUndefined(); expect(findIpAddressMock).toHaveBeenCalledWith(HASHED_IP); expect(upsertIpAddressMock).toHaveBeenCalledWith( expect.objectContaining({ ip: HASHED_IP, metadata: "null", }) ); expect(appendLogMock).toHaveBeenCalledWith({ ipAddressId: "ip_1", ip: HASHED_IP, url: "/login", action: "login", description: "admin@example.com", metadata: null, error: null, success: true, }); expect(JSON.stringify(upsertIpAddressMock.mock.calls)).not.toContain(RAW_IP); expect(JSON.stringify(appendLogMock.mock.calls)).not.toContain(RAW_IP); }); it("does not replace the request IP with a fixed development address", async () => { const request = new Request("https://admin.hivegrid.de/login"); await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).resolves.toBeUndefined(); expect(upsertIpAddressMock).toHaveBeenCalledWith(expect.objectContaining({ ip: HASHED_IP })); expect(upsertIpAddressMock).not.toHaveBeenCalledWith(expect.objectContaining({ ip: "hash-14" })); }); it("persists stable blacklisted error codes with a hashed IP", async () => { findInBlacklistMock.mockResolvedValue({ id: "blacklist_1" } as never); const request = new Request("https://admin.hivegrid.de/login"); await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).rejects.toThrow("blacklisted"); expect(addBlacklistAttemptMock).toHaveBeenCalledWith({ id: "blacklist_1" }); expect(appendLogMock).toHaveBeenCalledWith( expect.objectContaining({ ip: HASHED_IP, error: "blacklisted", success: false, }) ); }); it("persists stable unauthorized codes for provider-rejected IPs", async () => { findIpAddressMock.mockResolvedValue({ ...ipRecord, provider: "ipapi.is", metadata: JSON.stringify({ is_crawler: true }), }); const request = new Request("https://admin.hivegrid.de/login"); await expect(IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })).rejects.toThrow("unauthorized"); expect(upsertIpAddressMock).not.toHaveBeenCalled(); expect(appendLogMock).toHaveBeenCalledWith( expect.objectContaining({ ip: HASHED_IP, error: "unauthorized", success: false, }) ); }); });