test(auth): Vitest coverage for login action
Six tests covering: load() exposes ?registered and ?reason; action returns 400 on missing email; 401 with INVALID_CREDENTIALS on backend reject; success re-emits fa_session and deletes legacy auth_token; 500 when backend omits fa_session in Set-Cookie. Closes the frontend coverage gap on the credential- handling logic that moved out of the Java side. Addresses PR #612 / Sara S1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
117
frontend/src/routes/login/page.server.test.ts
Normal file
117
frontend/src/routes/login/page.server.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('$env/dynamic/private', () => ({
|
||||
env: { API_INTERNAL_URL: 'http://backend:8080' }
|
||||
}));
|
||||
|
||||
import { actions, load } from './+page.server';
|
||||
|
||||
type ActionsRecord = Record<string, (e: never) => unknown>;
|
||||
|
||||
function makeRequest(form: Record<string, string>): Request {
|
||||
const fd = new FormData();
|
||||
for (const [k, v] of Object.entries(form)) fd.set(k, v);
|
||||
return new Request('http://localhost/login?/login', { method: 'POST', body: fd });
|
||||
}
|
||||
|
||||
function makeCookies() {
|
||||
return {
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
get: vi.fn()
|
||||
};
|
||||
}
|
||||
|
||||
function loadEvent(search: string) {
|
||||
return {
|
||||
url: new URL(`http://localhost/login${search}`),
|
||||
request: new Request('http://localhost/login', { method: 'GET' }),
|
||||
route: { id: '/login' }
|
||||
} as never;
|
||||
}
|
||||
|
||||
describe('login load', () => {
|
||||
it('exposes registered=true when ?registered=1 is present', async () => {
|
||||
const result = await load(loadEvent('?registered=1'));
|
||||
expect(result).toEqual({ registered: true, reason: null });
|
||||
});
|
||||
|
||||
it('exposes reason=expired when ?reason=expired is present', async () => {
|
||||
const result = await load(loadEvent('?reason=expired'));
|
||||
expect(result).toEqual({ registered: false, reason: 'expired' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('login action', () => {
|
||||
beforeEach(() => vi.restoreAllMocks());
|
||||
|
||||
it('returns 400 when email is missing', async () => {
|
||||
const result = await (actions as ActionsRecord).login({
|
||||
request: makeRequest({ password: 'pw' }),
|
||||
cookies: makeCookies(),
|
||||
fetch: vi.fn(),
|
||||
url: new URL('http://localhost/login')
|
||||
} as never);
|
||||
expect((result as { status: number }).status).toBe(400);
|
||||
});
|
||||
|
||||
it('returns 401 with INVALID_CREDENTIALS when the backend rejects credentials', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ code: 'INVALID_CREDENTIALS' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
);
|
||||
|
||||
const result = await (actions as ActionsRecord).login({
|
||||
request: makeRequest({ email: 'a@b.de', password: 'wrong' }),
|
||||
cookies: makeCookies(),
|
||||
fetch: mockFetch,
|
||||
url: new URL('http://localhost/login')
|
||||
} as never);
|
||||
|
||||
expect((result as { status: number }).status).toBe(401);
|
||||
});
|
||||
|
||||
it('re-emits fa_session and deletes legacy auth_token on success', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(
|
||||
new Response('{}', {
|
||||
status: 200,
|
||||
headers: { 'Set-Cookie': 'fa_session=opaque-id; Path=/; HttpOnly; SameSite=Strict' }
|
||||
})
|
||||
);
|
||||
const cookies = makeCookies();
|
||||
|
||||
// redirect() throws a Redirect instance — assert via rejects.
|
||||
const redirected = (actions as ActionsRecord).login({
|
||||
request: makeRequest({ email: 'a@b.de', password: 'pw' }),
|
||||
cookies,
|
||||
fetch: mockFetch,
|
||||
url: new URL('http://localhost/login')
|
||||
} as never);
|
||||
|
||||
await expect(redirected).rejects.toMatchObject({ status: 303, location: '/' });
|
||||
|
||||
expect(cookies.set).toHaveBeenCalledWith(
|
||||
'fa_session',
|
||||
'opaque-id',
|
||||
expect.objectContaining({ httpOnly: true, sameSite: 'strict', maxAge: 60 * 60 * 8 })
|
||||
);
|
||||
expect(cookies.delete).toHaveBeenCalledWith('auth_token', { path: '/' });
|
||||
});
|
||||
|
||||
it('returns 500 when backend response omits fa_session cookie', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }));
|
||||
const cookies = makeCookies();
|
||||
|
||||
const result = await (actions as ActionsRecord).login({
|
||||
request: makeRequest({ email: 'a@b.de', password: 'pw' }),
|
||||
cookies,
|
||||
fetch: mockFetch,
|
||||
url: new URL('http://localhost/login')
|
||||
} as never);
|
||||
|
||||
expect((result as { status: number }).status).toBe(500);
|
||||
expect(cookies.set).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user