fix(auth): skip auth:expired dispatch for 401 on /auth/login (#69)
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>
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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');
|
||||
});
|
||||
Reference in New Issue
Block a user