9ad1165d48
On Safari/iOS PWA cold start or after cookie clear, logging in with wrong credentials triggered auth:expired, re-rendering the login page and losing the error message. The login endpoint returns 401 for invalid credentials, not for session expiry, so apiFetch must not fire auth:expired in that path. Resolves #68 Co-authored-by: Ulas Kalayci <ulas.kalayci@googlemail.com>
113 lines
3.7 KiB
JavaScript
113 lines
3.7 KiB
JavaScript
/**
|
|
* Tests: API-Client (public/api.js)
|
|
* Fokus: CSRF-Token-Handling, auth:expired-Dispatch-Verhalten
|
|
*/
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
// Browser-Globals für Node-Kontext simulieren
|
|
global.CustomEvent = class CustomEvent {
|
|
constructor(type, init) { this.type = type; this.detail = init?.detail; }
|
|
};
|
|
|
|
let dispatchedEvents = [];
|
|
global.window = {
|
|
dispatchEvent(e) { dispatchedEvents.push(e); },
|
|
addEventListener() {},
|
|
};
|
|
global.document = { cookie: '' };
|
|
|
|
// fetch-Mock: wird pro Test überschrieben
|
|
let _mockFetch = null;
|
|
global.fetch = (...args) => _mockFetch(...args);
|
|
|
|
function mockResponse(status, body = {}, headers = {}) {
|
|
return Promise.resolve({
|
|
status,
|
|
ok: status >= 200 && status < 300,
|
|
headers: {
|
|
get(name) { return headers[name] ?? null; },
|
|
},
|
|
json: () => Promise.resolve(body),
|
|
});
|
|
}
|
|
|
|
const { api, auth, ApiError } = await import('./public/api.js');
|
|
|
|
function setup() {
|
|
dispatchedEvents = [];
|
|
document.cookie = '';
|
|
}
|
|
|
|
// ─── 401 auf Login-Endpunkt ──────────────────────────────────────────────────
|
|
|
|
test('auth.login: 401 feuert kein auth:expired', async () => {
|
|
setup();
|
|
_mockFetch = () => mockResponse(401, { error: 'Ungültige Anmeldedaten.', code: 401 });
|
|
|
|
await assert.rejects(
|
|
() => auth.login('user', 'wrong'),
|
|
(err) => {
|
|
assert.equal(err.constructor.name, 'ApiError');
|
|
assert.equal(err.status, 401);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
const expired = dispatchedEvents.filter((e) => e.type === 'auth:expired');
|
|
assert.equal(expired.length, 0, 'auth:expired darf bei Login-401 nicht gefeuert werden');
|
|
});
|
|
|
|
test('auth.login: 401 wirft ApiError mit status 401', async () => {
|
|
setup();
|
|
_mockFetch = () => mockResponse(401, { error: 'Ungültige Anmeldedaten.', code: 401 });
|
|
|
|
let thrownErr;
|
|
try {
|
|
await auth.login('user', 'wrong');
|
|
} catch (e) {
|
|
thrownErr = e;
|
|
}
|
|
|
|
assert.ok(thrownErr instanceof ApiError, 'Muss ApiError sein');
|
|
assert.equal(thrownErr.status, 401);
|
|
});
|
|
|
|
// ─── 401 auf anderen Endpunkten ─────────────────────────────────────────────
|
|
|
|
test('api.get: 401 auf geschütztem Endpunkt feuert auth:expired', async () => {
|
|
setup();
|
|
_mockFetch = () => mockResponse(401, { error: 'Nicht authentifiziert.', code: 401 });
|
|
|
|
await assert.rejects(() => api.get('/tasks'));
|
|
|
|
const expired = dispatchedEvents.filter((e) => e.type === 'auth:expired');
|
|
assert.equal(expired.length, 1, 'auth:expired muss bei 401 auf geschütztem Endpunkt gefeuert werden');
|
|
});
|
|
|
|
test('api.post: 401 auf Logout-Endpunkt feuert auth:expired', async () => {
|
|
setup();
|
|
_mockFetch = () => mockResponse(401, { error: 'Nicht authentifiziert.', code: 401 });
|
|
|
|
await assert.rejects(() => api.post('/auth/logout', {}));
|
|
|
|
const expired = dispatchedEvents.filter((e) => e.type === 'auth:expired');
|
|
assert.equal(expired.length, 1, 'auth:expired muss bei 401 auf /auth/logout gefeuert werden');
|
|
});
|
|
|
|
// ─── Erfolgreicher Login ─────────────────────────────────────────────────────
|
|
|
|
test('auth.login: Erfolg speichert csrfToken aus Body', async () => {
|
|
setup();
|
|
const token = 'abc123def456';
|
|
_mockFetch = () => mockResponse(200, {
|
|
user: { id: 1, username: 'admin' },
|
|
csrfToken: token,
|
|
});
|
|
|
|
const result = await auth.login('admin', 'password');
|
|
assert.equal(result.user.username, 'admin');
|
|
assert.equal(result.csrfToken, token);
|
|
assert.equal(dispatchedEvents.length, 0, 'Kein Event bei erfolgreichem Login');
|
|
});
|