153 lines
5.0 KiB
TypeScript
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,
|
|
}),
|
|
});
|
|
});
|
|
});
|