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:
@@ -0,0 +1,110 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CalendarSourceUnreachableError, isNetworkUnreachableError } from '../calendar.service';
|
||||
import type { ExchangeProvider as ExchangeProviderType } from './exchange.provider';
|
||||
|
||||
/**
|
||||
* ExchangeProvider.testConnection (quick-261005): Zeitgrenze fuer EWS und
|
||||
* getrennte Meldung „nicht erreichbar“.
|
||||
*
|
||||
* httpntlm wird per CommonJS-`require` geladen — `vi.mock` greift dort nicht.
|
||||
* Wie in `inbox/exchange-inbox.provider.spec.ts` wird deshalb Nodes
|
||||
* `require.cache` vor dem ersten Laden des Providers mit einem Stub belegt.
|
||||
*/
|
||||
|
||||
const httpntlmPath = require.resolve('httpntlm');
|
||||
|
||||
const httpntlmPost = vi.fn((_opts: any, cb: (err: Error | null, res: any) => void) => {
|
||||
cb(new Error('httpntlmPost not configured for this test'), null);
|
||||
});
|
||||
|
||||
require.cache[httpntlmPath] = {
|
||||
id: httpntlmPath,
|
||||
filename: httpntlmPath,
|
||||
loaded: true,
|
||||
exports: { post: httpntlmPost },
|
||||
} as any;
|
||||
|
||||
let ExchangeProvider: typeof ExchangeProviderType;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ ExchangeProvider } = await import('./exchange.provider'));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
httpntlmPost.mockReset();
|
||||
});
|
||||
|
||||
const SOURCE = {
|
||||
id: 'src-1',
|
||||
url: 'https://owa.example.invalid/EWS/Exchange.asmx',
|
||||
username: 'kalender',
|
||||
password: 'geheim',
|
||||
domain: 'CONTOSO',
|
||||
exchangeMode: 'ews',
|
||||
};
|
||||
|
||||
function failWith(code: string) {
|
||||
httpntlmPost.mockImplementation((_opts, cb) => {
|
||||
const err = Object.assign(new Error(`fail ${code}`), { code });
|
||||
cb(err, null);
|
||||
});
|
||||
}
|
||||
|
||||
describe('ExchangeProvider.testConnection (EWS)', () => {
|
||||
it('uebergibt httpntlm eine Zeitgrenze von 15 Sekunden', async () => {
|
||||
httpntlmPost.mockImplementation((_opts, cb) => cb(null, { statusCode: 200, body: '' }));
|
||||
|
||||
await new ExchangeProvider().testConnection(SOURCE);
|
||||
|
||||
expect(httpntlmPost).toHaveBeenCalled();
|
||||
expect(httpntlmPost.mock.calls[0][0].timeout).toBe(15_000);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'TIMEOUT',
|
||||
'ETIMEDOUT',
|
||||
'ECONNREFUSED',
|
||||
'ENOTFOUND',
|
||||
'EHOSTUNREACH',
|
||||
])('Netzfehler %s -> CalendarSourceUnreachableError', async (code) => {
|
||||
failWith(code);
|
||||
await expect(new ExchangeProvider().testConnection(SOURCE)).rejects.toBeInstanceOf(
|
||||
CalendarSourceUnreachableError,
|
||||
);
|
||||
});
|
||||
|
||||
it('keine Rueckmeldung von httpntlm (Verbindungsaufbau haengt) -> nach 15 s nicht erreichbar', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
httpntlmPost.mockImplementation(() => {
|
||||
/* ruft nie zurueck — wie ein unbeantworteter Verbindungsaufbau */
|
||||
});
|
||||
const pending = new ExchangeProvider().testConnection(SOURCE);
|
||||
const assertion = expect(pending).rejects.toBeInstanceOf(CalendarSourceUnreachableError);
|
||||
await vi.advanceTimersByTimeAsync(14_999);
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
await assertion;
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('erreichbar, aber Anmeldung abgelehnt (401) -> false, kein Wurf', async () => {
|
||||
httpntlmPost.mockImplementation((_opts, cb) => cb(null, { statusCode: 401, body: '' }));
|
||||
await expect(new ExchangeProvider().testConnection(SOURCE)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('sonstiger Fehler ohne Netzkennung -> false', async () => {
|
||||
httpntlmPost.mockImplementation((_opts, cb) => cb(new Error('kaputt'), null));
|
||||
await expect(new ExchangeProvider().testConnection(SOURCE)).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNetworkUnreachableError', () => {
|
||||
it('erkennt Kennung direkt und in cause, sonst nicht', () => {
|
||||
expect(isNetworkUnreachableError({ code: 'ETIMEDOUT' })).toBe(true);
|
||||
expect(isNetworkUnreachableError({ cause: { code: 'ECONNREFUSED' } })).toBe(true);
|
||||
expect(isNetworkUnreachableError(new Error('x'))).toBe(false);
|
||||
expect(isNetworkUnreachableError(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user