Files
mealprep/frontend/src/routes/(public)/login/page.server.test.ts
Marcel Raddatz 61249af086 feat(auth): add secure flag to JSESSIONID cookie and test JSESSIONID cookie setting
- Add secure: true to cookies.set() in login and signup actions
- Add tests verifying JSESSIONID is forwarded to browser on successful
  login and signup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 18:50:34 +02:00

176 lines
4.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('$env/dynamic/private', () => ({
env: { BACKEND_URL: 'http://localhost:8080' }
}));
const mockPost = vi.fn();
vi.mock('$lib/server/api', () => ({
apiClient: () => ({ POST: mockPost })
}));
describe('login form action', () => {
let actions: any;
beforeEach(async () => {
mockPost.mockReset();
const mod = await import('./+page.server');
actions = mod.actions;
});
function createEvent(formData: Record<string, string>, searchParams = '') {
const fd = new FormData();
for (const [key, value] of Object.entries(formData)) {
fd.append(key, value);
}
return {
request: { formData: () => Promise.resolve(fd) },
url: new URL(`http://localhost/login${searchParams}`),
fetch: vi.fn(),
cookies: { get: vi.fn(), set: vi.fn() }
} as any;
}
function mockSuccess() {
return {
data: { data: { id: '123' } },
error: undefined,
response: { headers: { get: vi.fn().mockReturnValue(null) } }
};
}
it('calls POST /v1/auth/login with form data', async () => {
mockPost.mockResolvedValue(mockSuccess());
try {
await actions.default(createEvent({
email: 'sarah@example.com',
password: 'password123'
}));
} catch {
// redirect throws
}
expect(mockPost).toHaveBeenCalledWith('/v1/auth/login', {
body: {
email: 'sarah@example.com',
password: 'password123'
}
});
});
it('redirects to /planner on success by default', async () => {
mockPost.mockResolvedValue(mockSuccess());
try {
await actions.default(createEvent({
email: 'sarah@example.com',
password: 'password123'
}));
expect.unreachable();
} catch (e: any) {
expect(e.status).toBe(303);
expect(e.location).toBe('/planner');
}
});
it('redirects to ?redirect param when present', async () => {
mockPost.mockResolvedValue(mockSuccess());
try {
await actions.default(createEvent(
{ email: 'sarah@example.com', password: 'password123' },
'?redirect=%2Frecipes%2Fabc'
));
expect.unreachable();
} catch (e: any) {
expect(e.status).toBe(303);
expect(e.location).toBe('/recipes/abc');
}
});
it('falls back to /planner when ?redirect= is an absolute URL', async () => {
mockPost.mockResolvedValue(mockSuccess());
try {
await actions.default(createEvent(
{ email: 'sarah@example.com', password: 'password123' },
'?redirect=https%3A%2F%2Fevil.com'
));
expect.unreachable();
} catch (e: any) {
expect(e.status).toBe(303);
expect(e.location).toBe('/planner');
}
});
it('falls back to /planner when ?redirect= is a protocol-relative URL', async () => {
mockPost.mockResolvedValue(mockSuccess());
try {
await actions.default(createEvent(
{ email: 'sarah@example.com', password: 'password123' },
'?redirect=%2F%2Fevil.com'
));
expect.unreachable();
} catch (e: any) {
expect(e.status).toBe(303);
expect(e.location).toBe('/planner');
}
});
it('sets JSESSIONID cookie on successful login', async () => {
mockPost.mockResolvedValue({
data: { data: { id: '123' } },
error: undefined,
response: { headers: { get: vi.fn().mockReturnValue('JSESSIONID=abc123; Path=/; HttpOnly') } }
});
const event = createEvent({ email: 'sarah@example.com', password: 'password123' });
try {
await actions.default(event);
} catch {
// redirect throws
}
expect(event.cookies.set).toHaveBeenCalledWith('JSESSIONID', 'abc123', expect.objectContaining({ path: '/', secure: true }));
});
it('rejects empty email with validation error', async () => {
const result = await actions.default(createEvent({
email: '',
password: 'password123'
}));
expect(result.status).toBe(400);
expect(result.data.errors.email).toBe('Ungültige E-Mail-Adresse');
expect(mockPost).not.toHaveBeenCalled();
});
it('rejects empty password with validation error', async () => {
const result = await actions.default(createEvent({
email: 'sarah@example.com',
password: ''
}));
expect(result.status).toBe(400);
expect(result.data.errors.password).toBe('Passwort ist erforderlich');
expect(mockPost).not.toHaveBeenCalled();
});
it('returns fail with form error on API error', async () => {
mockPost.mockResolvedValue({
data: undefined,
error: { status: 401, message: 'Invalid credentials' }
});
const result = await actions.default(createEvent({
email: 'sarah@example.com',
password: 'password123'
}));
expect(result.status).toBe(400);
expect(result.data.errors.form).toBe('E-Mail oder Passwort ist falsch.');
});
});