fix(domains): HTTP 5xx nie als Ablehnung werten, Verwerfen mit Neupruefung, Entwurfsstand im Anspruch (CR-01, WR-01, WR-02, WR-03, IN-03)
CR-01: 5xx, 408, 425 (auch mit AutoDNS-Huelle) sind kein "abgelehnt" mehr, Bestellung endet in UNKNOWN und der Abgleich wertet sie nicht als "Domain gibt es nicht". 429 gilt bei der Bestellung ebenfalls als unklar, weil nicht belegt ist, dass AutoDNS vor der Verarbeitung abgelehnt hat. WR-01: Verwerfen eines unklaren Auftrags fragt im Augenblick des Verwerfens erneut bei AutoDNS nach; nur ein ausdrueckliches "nicht gefunden" verwirft. WR-02: Die Bestaetigung traegt den Entwurfsstand (updatedAt); der Anspruch greift nur bei gleichem Stand, sonst 409 orderChanged. WR-03: Auftraege ohne lesbares Datum/Objekt oder eine volle Trefferliste machen "nichts gefunden" unklar statt verwerfbar. IN-03: Scheitert das Speichern nach POST /domain, wird die Auftragsnummer protokolliert und der Auftrag auf UNKNOWN gebracht (nie DRAFT), ohne 500. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,6 +102,8 @@ export function OrdersTab({ canManage }: { canManage: boolean }) {
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setDiscardError(e instanceof DomainsRequestError ? e.message : tc('requestFailed'));
|
||||
// Beim Verwerfen hat Tessera frisch bei AutoDNS nachgesehen; der Zustand kann sich geaendert haben.
|
||||
void refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ function summary(over: Partial<OrderSummary> = {}): OrderSummary {
|
||||
},
|
||||
nameServers: ['ns1.example.com', 'ns2.example.com'],
|
||||
periodYears: 1,
|
||||
version: '2026-10-08T10:00:00.000Z',
|
||||
...over,
|
||||
};
|
||||
}
|
||||
@@ -324,7 +325,8 @@ describe('RegisterTab — Zusammenfassung und verbindliche Bestaetigung', () =>
|
||||
fireEvent.click(button);
|
||||
fireEvent.click(button);
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith('o1');
|
||||
// Der Stand aus der Zusammenfassung geht mit: bestellt wird nur, was der Benutzer gesehen hat.
|
||||
expect(mockSubmit).toHaveBeenCalledWith('o1', '2026-10-08T10:00:00.000Z');
|
||||
expect(screen.getByRole('button', { name: 'Wird übermittelt …' })).toBeTruthy();
|
||||
resolveSubmit(order({ status: 'SUBMITTED', jobStatus: 'RUNNING' }));
|
||||
await screen.findByText(/wird bearbeitet/);
|
||||
@@ -383,6 +385,18 @@ describe('RegisterTab — Zusammenfassung und verbindliche Bestaetigung', () =>
|
||||
expect(screen.queryByRole('button', { name: 'Jetzt verbindlich registrieren' })).toBeNull();
|
||||
});
|
||||
|
||||
it('hat sich der Entwurf geaendert, zeigt der Dialog die Meldung des Servers und bestellt nichts', async () => {
|
||||
const message =
|
||||
'Der Auftrag wurde inzwischen geändert. Bitte prüfen Sie die Zusammenfassung erneut, bevor Sie bestellen.';
|
||||
mockSubmit.mockRejectedValue(new DomainsRequestError(409, 'orderChanged', message));
|
||||
renderTab();
|
||||
await reachSummary();
|
||||
fireEvent.click(screen.getByRole('checkbox'));
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Jetzt verbindlich registrieren' }));
|
||||
expect(await screen.findByText(message)).toBeTruthy();
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('"Abbrechen" ruft cancelOrder auf und beginnt neu', async () => {
|
||||
renderTab();
|
||||
await reachSummary();
|
||||
|
||||
@@ -194,7 +194,7 @@ export function RegisterTab({
|
||||
setSubmitting(true);
|
||||
setSubmitError(null);
|
||||
try {
|
||||
setResult(await submitOrder(summary.order.id));
|
||||
setResult(await submitOrder(summary.order.id, summary.version));
|
||||
} catch (e) {
|
||||
// Die Sperre bleibt: der Auftrag koennte angekommen sein. Nur "Zurueck" hebt sie auf.
|
||||
setSubmitError(e instanceof DomainsRequestError ? e.message : t('submitFailed'));
|
||||
|
||||
@@ -304,6 +304,8 @@ export interface OrderSummary {
|
||||
};
|
||||
nameServers: string[];
|
||||
periodYears: 1;
|
||||
/** Stand des Entwurfs; geht beim verbindlichen Bestellen unveraendert zurueck. */
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface CreateOrderInput {
|
||||
@@ -323,9 +325,16 @@ export function createOrder(input: CreateOrderInput): Promise<OrderSummary> {
|
||||
return request<OrderSummary>('/orders', { method: 'POST', json: input });
|
||||
}
|
||||
|
||||
/** Verbindlich: schickt die Registrierung an AutoDNS (kostet im Live-System Geld). */
|
||||
export function submitOrder(id: string): Promise<DomainsOrder> {
|
||||
return request<DomainsOrder>(`/orders/${encodeURIComponent(id)}/submit`, { method: 'POST' });
|
||||
/**
|
||||
* Verbindlich: schickt die Registrierung an AutoDNS (kostet im Live-System Geld).
|
||||
* `version` ist der Stand aus der Zusammenfassung; hat sich der Entwurf seither
|
||||
* geaendert, lehnt der Server mit 409 ab.
|
||||
*/
|
||||
export function submitOrder(id: string, version: string): Promise<DomainsOrder> {
|
||||
return request<DomainsOrder>(`/orders/${encodeURIComponent(id)}/submit`, {
|
||||
method: 'POST',
|
||||
json: { version },
|
||||
});
|
||||
}
|
||||
|
||||
export function cancelOrder(id: string): Promise<DomainsOrder> {
|
||||
|
||||
Reference in New Issue
Block a user