Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/ipAddress/services/IpAddressService.server.test.ts
T

153 lines
5.0 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("~/utils/db.server", () => ({
db: {
ipAddress: {
findUnique: vi.fn(),
upsert: vi.fn(),
},
ipAddressLog: {
create: vi.fn(),
},
},
}));
vi.mock("~/utils/cache.server", () => ({
cachified: vi.fn(({ getFreshValue }) => getFreshValue()),
clearCacheKey: vi.fn(),
}));
vi.mock("~/utils/server/IpUtils", () => ({ getClientIPAddress: 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: GeoIP/Hashing wird hermetisch gemockt — hashIp ist deterministisch, Land-Auflösung ein No-op.
vi.mock("./geoip.server", () => ({
hashIp: (ip: string) => `hash(${ip})`,
lookupCountry: vi.fn(async () => null),
}));
import IpAddressServiceServer from "./IpAddressService.server";
import { db } from "~/utils/db.server";
import { addBlacklistAttempt, findInBlacklist } from "~/utils/db/blacklist.db.server";
const RAW_IP = "203.0.113.10";
const HASHED_IP = `hash(${RAW_IP})`;
const dbMock = db as unknown as {
ipAddress: {
findUnique: ReturnType<typeof vi.fn>;
upsert: ReturnType<typeof vi.fn>;
};
ipAddressLog: {
create: ReturnType<typeof vi.fn>;
};
};
const findInBlacklistMock = vi.mocked(findInBlacklist);
const addBlacklistAttemptMock = vi.mocked(addBlacklistAttempt);
describe("IpAddressServiceServer", () => {
beforeEach(() => {
vi.clearAllMocks();
findInBlacklistMock.mockResolvedValue(null);
dbMock.ipAddress.findUnique.mockResolvedValue(null);
dbMock.ipAddress.upsert.mockResolvedValue({
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"),
});
dbMock.ipAddressLog.create.mockResolvedValue({ id: "log_1" });
});
it("speichert nur den IP-Hash, nie die Klartext-IP (AD-22)", async () => {
const request = new Request("https://admin.boiler.saas-ctrl.com/login");
await expect(
IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })
).resolves.toBeUndefined();
expect(dbMock.ipAddress.upsert).toHaveBeenCalledWith({
where: { ip: HASHED_IP },
update: {},
create: expect.objectContaining({ ip: HASHED_IP }),
});
const logArgs = dbMock.ipAddressLog.create.mock.calls[0][0];
expect(logArgs.data.ip).toBe(HASHED_IP);
expect(logArgs.data).toMatchObject({ ipAddressId: "ip_1", url: "/login", action: "login", success: true });
});
it("ersetzt die Request-IP nicht durch eine fixe Dev-Adresse", async () => {
const request = new Request("https://admin.boiler.saas-ctrl.com/login");
await expect(
IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })
).resolves.toBeUndefined();
expect(dbMock.ipAddress.upsert).toHaveBeenCalledWith(expect.objectContaining({ where: { ip: HASHED_IP } }));
expect(dbMock.ipAddress.upsert).not.toHaveBeenCalledWith(expect.objectContaining({ where: { ip: "hash(189.203.100.82)" } }));
});
it("speichert stabile blacklisted-Fehlercodes mit gehashter IP", async () => {
findInBlacklistMock.mockResolvedValue({ id: "blacklist_1" } as never);
const request = new Request("https://admin.boiler.saas-ctrl.com/login");
await expect(
IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })
).rejects.toThrow("blacklisted");
expect(addBlacklistAttemptMock).toHaveBeenCalledWith({ id: "blacklist_1" });
expect(dbMock.ipAddressLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
ip: HASHED_IP,
error: "blacklisted",
success: false,
}),
});
});
it("speichert stabile unauthorized-Fehlercodes für provider-abgelehnte IP-Log-Zeilen", async () => {
dbMock.ipAddress.findUnique.mockResolvedValue({
id: "ip_1",
ip: HASHED_IP,
provider: "ipapi.is",
type: "",
countryCode: "",
countryName: "",
regionCode: "",
regionName: "",
city: "",
zipCode: "",
latitude: null,
longitude: null,
metadata: JSON.stringify({ is_crawler: true }),
createdAt: new Date("2026-01-01T00:00:00.000Z"),
updatedAt: new Date("2026-01-01T00:00:00.000Z"),
});
const request = new Request("https://admin.boiler.saas-ctrl.com/login");
await expect(
IpAddressServiceServer.log(request, { action: "login", description: "admin@example.com" })
).rejects.toThrow("unauthorized");
expect(dbMock.ipAddressLog.create).toHaveBeenCalledWith({
data: expect.objectContaining({
ip: HASHED_IP,
error: "unauthorized",
success: false,
}),
});
});
});