test: Testabdeckung fuer CSRF-Schutz, Entra-ID-SSO und DHL-Client

Keine der in dieser Session neu hinzugefuegten sicherheitskritischen
Module (lib/csrf.ts, lib/entra.ts, lib/dhl.ts) hatte bisher Tests.

- csrf.test.ts: Same-Origin-Erkennung, Ablehnung gefaelschter
  Cross-Origin-Requests, tolerantes Verhalten bei fehlendem/kaputtem
  Origin-Header.
- entra.test.ts: PKCE-Paar-Erzeugung (RFC 7636), Authorize-URL-Aufbau,
  Extraktion des verifizierten E-Mail-Claims (nie ein unplausibles
  Feld raten).
- dhl.test.ts: Adress-Mapping (Strasse/Hausnummer-Trennung,
  ISO2->ISO3-Laendercode), Fehlerbehandlung bei nicht unterstuetztem
  Land, DHL-Validierungsfehlern und HTTP-Fehlern, statt einen falschen
  Erfolg vorzutaeuschen.

Alle 118 Tests (13 Dateien) gruen, tsc --noEmit clean.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-04 08:48:08 +02:00
1 parent c59913b75b
commit 154b75e0d8
3 files changed
+322

No files matched your search

+177
View File
@@ -0,0 +1,177 @@
import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
const ENV_KEYS = ["DHL_API_KEY", "DHL_API_SECRET", "DHL_GKP_USERNAME", "DHL_GKP_PASSWORD", "DHL_ACCOUNT_NUMBER"] as const;
const originalEnv: Record<string, string | undefined> = {};
function configureDhl(): void {
process.env.DHL_API_KEY = "api-key";
process.env.DHL_API_SECRET = "api-secret";
process.env.DHL_GKP_USERNAME = "gkp-user";
process.env.DHL_GKP_PASSWORD = "gkp-pass";
process.env.DHL_ACCOUNT_NUMBER = "33333333330101";
}
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
}
const validAddress = {
firstName: "Erika",
lastName: "Musterfrau",
street: "Musterstraße 12a",
zip: "12345",
city: "Musterstadt",
country: "DE",
};
const validShipper = { ...validAddress, companyName: "Wabenhain" };
// dhl.ts haelt das OAuth2-Token in einem modulweiten In-Memory-Cache (siehe
// Kommentar dort) — vi.resetModules() + dynamischer Import pro Test stellt
// sicher, dass kein Token zwischen Tests "durchsickert" und einen erwarteten
// Token-Request-Mock stillschweigend ueberspringt.
beforeEach(() => {
vi.resetModules();
for (const key of ENV_KEYS) {
originalEnv[key] = process.env[key];
delete process.env[key];
}
delete process.env.DHL_ENVIRONMENT;
});
afterEach(() => {
for (const key of ENV_KEYS) {
if (originalEnv[key] === undefined) delete process.env[key];
else process.env[key] = originalEnv[key];
}
vi.unstubAllGlobals();
});
describe("isDhlConfigured", () => {
test("is false when env vars are missing", async () => {
const { isDhlConfigured } = await import("./dhl");
expect(isDhlConfigured()).toBe(false);
});
test("is true once all required env vars are set", async () => {
configureDhl();
const { isDhlConfigured } = await import("./dhl");
expect(isDhlConfigured()).toBe(true);
});
});
describe("createDhlShipmentLabel", () => {
test("throws DhlNotConfiguredError instead of calling the network when unconfigured", async () => {
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);
const { createDhlShipmentLabel, DhlNotConfiguredError } = await import("./dhl");
await expect(
createDhlShipmentLabel({
reference: "WH-2026-0001",
weightGrams: 500,
shipper: validShipper,
receiver: validAddress,
})
).rejects.toThrow(DhlNotConfiguredError);
expect(fetchMock).not.toHaveBeenCalled();
});
test("splits the street/house-number, maps the ISO2 country to ISO3, and returns the tracking number + label on success", async () => {
configureDhl();
const fetchMock = vi
.fn()
// 1. Token-Aufruf
.mockResolvedValueOnce(jsonResponse({ access_token: "token-abc", expires_in: 3600 }))
// 2. Sendungserstellung
.mockResolvedValueOnce(
jsonResponse({
items: [{ shipmentNo: "00340434161094123456", label: { b64: "cGRmLWJ5dGVz" } }],
})
);
vi.stubGlobal("fetch", fetchMock);
const { createDhlShipmentLabel } = await import("./dhl");
const result = await createDhlShipmentLabel({
reference: "WH-2026-0001",
weightGrams: 500,
shipper: validShipper,
receiver: validAddress,
});
expect(result).toEqual({ trackingNumber: "00340434161094123456", labelPdfBase64: "cGRmLWJ5dGVz" });
// Zweiter fetch-Aufruf ist die eigentliche Sendungserstellung — Body
// pruefen, dass Adress-Mapping korrekt durchgereicht wurde.
const shipmentCall = fetchMock.mock.calls[1];
const requestBody = JSON.parse(shipmentCall[1].body as string);
const consignee = requestBody.shipments[0].consignee;
expect(consignee.address.addressStreet).toBe("Musterstraße");
expect(consignee.address.addressHouse).toBe("12a");
expect(consignee.address.country).toBe("DEU");
expect(shipmentCall[1].headers["dhl-api-key"]).toBe("api-key");
expect(shipmentCall[1].headers["Authorization"]).toBe("Bearer token-abc");
});
test("rejects an unsupported destination country instead of guessing a code", async () => {
configureDhl();
const fetchMock = vi.fn().mockResolvedValueOnce(jsonResponse({ access_token: "token-abc", expires_in: 3600 }));
vi.stubGlobal("fetch", fetchMock);
const { createDhlShipmentLabel, DhlApiError } = await import("./dhl");
await expect(
createDhlShipmentLabel({
reference: "WH-2026-0002",
weightGrams: 500,
shipper: validShipper,
receiver: { ...validAddress, country: "US" },
})
).rejects.toThrow(DhlApiError);
});
test("surfaces a DHL-side validation error instead of returning a fake success", async () => {
configureDhl();
const fetchMock = vi
.fn()
.mockResolvedValueOnce(jsonResponse({ access_token: "token-abc", expires_in: 3600 }))
.mockResolvedValueOnce(
jsonResponse({
items: [
{
validationMessages: [{ validationState: "Error", validationMessage: "postalCode ungültig" }],
},
],
})
);
vi.stubGlobal("fetch", fetchMock);
const { createDhlShipmentLabel, DhlApiError } = await import("./dhl");
await expect(
createDhlShipmentLabel({
reference: "WH-2026-0003",
weightGrams: 500,
shipper: validShipper,
receiver: validAddress,
})
).rejects.toThrow(DhlApiError);
});
test("surfaces an HTTP error from the DHL API with its detail message", async () => {
configureDhl();
const fetchMock = vi
.fn()
.mockResolvedValueOnce(jsonResponse({ access_token: "token-abc", expires_in: 3600 }))
.mockResolvedValueOnce(jsonResponse({ detail: "Ungültige Anfrage" }, 400));
vi.stubGlobal("fetch", fetchMock);
const { createDhlShipmentLabel, DhlApiError } = await import("./dhl");
await expect(
createDhlShipmentLabel({
reference: "WH-2026-0004",
weightGrams: 500,
shipper: validShipper,
receiver: validAddress,
})
).rejects.toThrow(/Ungültige Anfrage/);
});
});