fix(quick-261005-d5d): Kalender-Test bricht bei unerreichbarem Exchange nach 15 s ab

EWS-Aufrufe ueber httpntlm bekommen eine eigene 15-s-Zeitgrenze;
httpreqs timeout greift waehrend des Verbindungsaufbaus nicht
(gemessen 134 s). Netzfehler liefern im Test den Schluessel
'unreachable', das Formular meldet 'nicht erreichbar' statt
'Zugangsdaten pruefen'.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-05 09:29:25 +02:00
parent 94bd2a4a04
commit e49d4c7c9d
13 changed files with 305 additions and 13 deletions
+46 -4
View File
@@ -30,6 +30,43 @@ export interface CalendarEvent {
color?: string;
}
/**
* quick-261005: Ein Provider wirft diesen Fehler aus `testConnection`, wenn
* der Kalender-Server gar nicht erreichbar ist (Zeitueberschreitung,
* abgewiesene Verbindung, Name unbekannt) — im Unterschied zu „erreichbar,
* aber Anmeldung abgelehnt“ (`false`). Traegt bewusst keine Details (T-05-13).
*/
export class CalendarSourceUnreachableError extends Error {
constructor() {
super('Calendar server not reachable');
this.name = 'CalendarSourceUnreachableError';
}
}
/** Fehlerkennungen von Node/httpreq, die „Server nicht erreichbar“ bedeuten. */
const NETWORK_UNREACHABLE_CODES = new Set([
'TIMEOUT', // httpreq bei Ablauf von `timeout`
'ETIMEDOUT',
'ECONNREFUSED',
'ECONNRESET',
'EHOSTUNREACH',
'ENETUNREACH',
'ENOTFOUND',
'EAI_AGAIN',
]);
export function isNetworkUnreachableError(error: unknown): boolean {
const code = (error as { code?: unknown; cause?: { code?: unknown } } | null)?.code;
const causeCode = (error as { cause?: { code?: unknown } } | null)?.cause?.code;
return (
(typeof code === 'string' && NETWORK_UNREACHABLE_CODES.has(code)) ||
(typeof causeCode === 'string' && NETWORK_UNREACHABLE_CODES.has(causeCode))
);
}
/** Stabiler Fehlerschluessel fuer die Oberflaeche (dort uebersetzt). */
export const CALENDAR_TEST_UNREACHABLE = 'unreachable';
/**
* Provider interface for calendar source integrations.
* Each provider (ICS, CalDAV, Exchange) implements this contract.
@@ -319,13 +356,15 @@ export class CalendarService {
});
return { success };
} catch {
const errorMsg = 'Connection failed'; // T-05-13: generic error, no credentials
} catch (error) {
const unreachable = error instanceof CalendarSourceUnreachableError;
// T-05-13: generic error, no credentials
const errorMsg = unreachable ? 'Server not reachable' : 'Connection failed';
await tenantPrisma.calendarSource.update({
where: { id },
data: { lastSyncError: errorMsg },
});
return { success: false, error: errorMsg };
return { success: false, error: unreachable ? CALENDAR_TEST_UNREACHABLE : errorMsg };
}
}
@@ -364,7 +403,10 @@ export class CalendarService {
try {
const success = await provider.testConnection(tempSource);
return { success };
} catch {
} catch (error) {
if (error instanceof CalendarSourceUnreachableError) {
return { success: false, error: CALENDAR_TEST_UNREACHABLE };
}
return { success: false, error: 'Connection failed' };
}
}