Files
Wabenhain/src/lib/dhl-locations.test.ts
T
Nick SzaunigandClaude Opus 5 9dd71ffb9a
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m8s
feat: Packstation ueber DHL-Standortsuche als Lieferziel waehlbar
Im Checkout laesst sich unter "Versand" jetzt zwischen der eigenen
Anschrift und einer DHL-Packstation waehlen. Postleitzahl eingeben,
suchen, Packstation aus der Trefferliste anklicken, DHL-Postnummer
eintragen — die Auswahl wird an der Bestellung gespeichert.

Aufbau:

- lib/dhl-locations.ts: Client fuer die DHL Location Finder Unified API
  (GET /find-by-address). Bewusst getrennt von lib/dhl.ts — das ist ein
  anderer DHL-Dienst mit anderer Authentifizierung: nur der API-Key im
  Header, kein OAuth und kein Geschaeftskundenvertrag. Die
  Packstation-Auswahl funktioniert damit auch, solange die
  Versandmarken-Erstellung noch nicht eingerichtet ist.
- api/shop/packstationen: serverseitiger Zwischenschritt, damit der
  DHL-API-Key nicht ins Browser-Bundle geraet. Streng rate-limitiert
  (15/Minute statt 60 wie die Produktsuche) und mit 15-Minuten-Cache:
  dahinter steht ein FREMDES Kontingent von 500 Aufrufen pro Tag, das ein
  ungebremstes Suchfeld an einem Nachmittag aufbrauchen koennte.
- Order/Address: packstationNumber + postNumber (Migration
  20260915222938_add_packstation_delivery, beide nullable).
- checkoutSchema: Postnummer als 6-10 Ziffern, plus zwei refine-Regeln —
  ohne Postnummer keine Packstation (DHL verlangt sie zwingend), und eine
  Packstation nur bei Versand, nicht bei Abholung.

Zwei Entscheidungen, die Folgearbeit ersparen:

- Bei Packstation-Auswahl tragen street/zip/city zusaetzlich die lesbare
  Anschrift ("Packstation 145", PLZ, Ort). Dadurch zeigen Rechnung,
  Bestaetigungsmail, Sendungsverfolgung und Admin-Ansicht ohne jede
  Aenderung etwas Sinnvolles; packstationNumber/postNumber tragen daneben
  das, was DHL technisch braucht.
- lib/dhl.ts baut daraus einen Locker-Empfaenger statt einer
  Hausanschrift. DHL kennt fuer den Empfaenger zwei sich ausschliessende
  Formen; eine Packstation als Strassentext zu schicken ergaebe eine Marke
  auf eine Anschrift, die es nicht gibt.

Die Option erscheint nur, wenn DHL_API_KEY gesetzt ist — sonst wuerde
Kundschaft in eine Suche laufen, die immer fehlschlaegt.

Geprueft: sechs Tests fuer das Umsetzen der DHL-Antwort (die
Packstation-Nummer steht in location.keywordId, NICHT in
location.ids[0].locationId — letzteres ist ein Systemcode, mit dem weder
Kundschaft noch DHL etwas anfangen kann). Dazu am laufenden
Produktions-Build: ohne Key liefert der Endpunkt 503 und der Checkout
zeigt die Option gar nicht; mit Key 400 bei ungueltiger PLZ und ein echter
Aufruf an api.dhl.com, der mit 401 quittiert wird — URL, Header und
Anfrageformat stimmen also. Im Browser durchgeklickt: Umschalten blendet
das Strassenfeld aus und Postnummer samt Suche ein, die Suche ruft die
Route und zeigt die Fehlermeldung an.

Feldnamen beider DHL-APIs vorab gegen developer.dhl.com geprueft. Ein
Lauf gegen einen echten Schluessel steht noch aus.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-16 00:37:00 +02:00

90 lines
3.0 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { parseLocations } from "./dhl-locations";
/**
* Ein Treffer im Format der DHL Location Finder API. Die Packstation-Nummer
* steht in location.keywordId — location.ids[0].locationId ist ein
* zusammengesetzter Systemcode und ausdruecklich NICHT die Nummer, die
* Kundschaft am Geraet eingibt.
*/
function dhlLocation(overrides: Record<string, unknown> = {}) {
return {
url: "https://api.dhl.com/location-finder/v1/locations/8003-453113145",
location: {
ids: [{ locationId: "8003-453113145", provider: "parcel-de" }],
keyword: "DHL Packstation",
keywordId: "145",
type: "locker",
},
name: "Packstation 145",
distance: 820,
place: {
address: {
countryCode: "DE",
postalCode: "86450",
addressLocality: "Altenmünster",
streetAddress: "Bahnhofstr. 1",
},
},
...overrides,
};
}
describe("parseLocations", () => {
it("nimmt die Packstation-Nummer aus keywordId, nicht aus locationId", () => {
const [packstation] = parseLocations([dhlLocation()]);
// locationId waere "8003-453113145" — damit koennte niemand etwas anfangen,
// und DHL wuerde die Versandmarke ablehnen.
expect(packstation.number).toBe("145");
expect(packstation.zip).toBe("86450");
expect(packstation.city).toBe("Altenmünster");
expect(packstation.street).toBe("Bahnhofstr. 1");
expect(packstation.distanceMeters).toBe(820);
});
it("sortiert nach Entfernung aufsteigend", () => {
const parsed = parseLocations([
dhlLocation({ distance: 3000, location: { keywordId: "300", keyword: "DHL Packstation" } }),
dhlLocation({ distance: 120, location: { keywordId: "120", keyword: "DHL Packstation" } }),
]);
expect(parsed.map((p) => p.number)).toEqual(["120", "300"]);
});
it("ueberspringt einen Eintrag ohne Packstation-Nummer, statt die ganze Suche scheitern zu lassen", () => {
const parsed = parseLocations([
{ location: { keyword: "DHL Packstation" }, place: { address: {} } },
dhlLocation(),
]);
// Ein Standort ohne Nummer ist unbrauchbar — die uebrigen bleiben gueltig.
expect(parsed).toHaveLength(1);
expect(parsed[0].number).toBe("145");
});
it("faellt auf einen zusammengesetzten Namen zurueck, wenn DHL keinen liefert", () => {
const [packstation] = parseLocations([dhlLocation({ name: undefined })]);
expect(packstation.name).toBe("Packstation 145");
});
it("kommt ohne Strasse und ohne Entfernung zurecht", () => {
const [packstation] = parseLocations([
dhlLocation({
distance: undefined,
place: { address: { postalCode: "10115", addressLocality: "Berlin" } },
}),
]);
expect(packstation.street).toBe("");
expect(packstation.distanceMeters).toBeNull();
// Ohne Entfernung darf der Eintrag nicht verschwinden, nur ans Ende rutschen.
expect(packstation.city).toBe("Berlin");
});
it("liefert fuer eine leere Trefferliste ein leeres Ergebnis", () => {
expect(parseLocations([])).toEqual([]);
});
});