// SPDX-License-Identifier: AGPL-3.0-or-later import {afterAll, beforeAll, beforeEach, describe, expect, it} from 'vitest'; import type {ApiTestHarness} from '../../test/ApiTestHarness'; import {createBuilderWithoutAuth} from '../../test/TestRequestBuilder'; import { createAuthHarness, createUniqueEmail, createUniqueUsername, fetchMe, type LoginSuccessResponse, loginUser, registerUser, } from './AuthTestUtils'; describe('Auth case-insensitive email', () => { let harness: ApiTestHarness; beforeAll(async () => { harness = await createAuthHarness(); }); beforeEach(async () => { await harness.reset(); }); afterAll(async () => { await harness?.shutdown(); }); describe('login with different case variations succeeds', () => { const baseEmail = createUniqueEmail('login-case'); const password = 'Xk9#mP2$vL5@nQ8'; beforeEach(async () => { await registerUser(harness, { email: baseEmail, username: createUniqueUsername('login'), global_name: 'Login Test User', password, date_of_birth: '2000-01-01', consent: true, }); }); it('allows login with lowercase email', async () => { const login = await loginUser(harness, { email: baseEmail.toLowerCase(), password, }); expect('mfa' in login).toBe(false); expect((login as LoginSuccessResponse).token).toBeTruthy(); }); it('allows login with uppercase email', async () => { const login = await loginUser(harness, { email: baseEmail.toUpperCase(), password, }); expect('mfa' in login).toBe(false); expect((login as LoginSuccessResponse).token).toBeTruthy(); }); it('allows login with mixed case email', async () => { const mixedCaseEmail = baseEmail .split('') .map((char, index) => (index % 2 === 0 ? char.toUpperCase() : char.toLowerCase())) .join(''); const login = await loginUser(harness, { email: mixedCaseEmail, password, }); expect('mfa' in login).toBe(false); expect((login as LoginSuccessResponse).token).toBeTruthy(); }); it('allows login with title case email', async () => { const titleCaseEmail = baseEmail .toLowerCase() .replace(/(^|[.@])([a-z])/g, (_match, prefix, char) => `${prefix}${char.toUpperCase()}`); const login = await loginUser(harness, { email: titleCaseEmail, password, }); expect('mfa' in login).toBe(false); expect((login as LoginSuccessResponse).token).toBeTruthy(); }); }); it('rejects registration with different case as duplicate', async () => { const baseEmail = createUniqueEmail('duplicate-case'); const password = 'Rt7&kW3!qL9@mP2'; await registerUser(harness, { email: baseEmail, username: createUniqueUsername('duplicate1'), global_name: 'Duplicate Test User 1', password, date_of_birth: '2000-01-01', consent: true, }); await createBuilderWithoutAuth(harness) .post('/auth/register') .body({ email: baseEmail.toUpperCase(), username: createUniqueUsername('duplicate2'), global_name: 'Duplicate Test User 2', password: 'different-password-456', date_of_birth: '2000-01-01', consent: true, }) .expect(400) .execute(); }); it('allows forgot password with different case', async () => { const baseEmail = createUniqueEmail('forgot-case'); const password = 'Mn8$jX4&vB6@pL1'; await registerUser(harness, { email: baseEmail, username: createUniqueUsername('forgot'), global_name: 'Forgot Test User', password, date_of_birth: '2000-01-01', consent: true, }); await createBuilderWithoutAuth(harness) .post('/auth/forgot') .body({ email: baseEmail.toUpperCase(), }) .expect(204) .execute(); }); it('preserves original email case in user record', async () => { const mixedEmail = `${createUniqueEmail('normalized').split('@')[0]}@Example.COM`; const password = 'Df5&gH9@kW3!qL2'; const reg = await registerUser(harness, { email: mixedEmail, username: createUniqueUsername('normalize'), global_name: 'Normalize Test User', password, date_of_birth: '2000-01-01', consent: true, }); const {response, json} = await fetchMe(harness, reg.token); expect(response.status).toBe(200); const user = json as { email: string | null; username: string; global_name: string | null; }; expect(user.email).toBe(mixedEmail); const login = await loginUser(harness, { email: mixedEmail.toLowerCase(), password, }); expect('mfa' in login).toBe(false); const nonMfaLogin = login as { user_id: string; token: string; }; expect(nonMfaLogin.token).toBeTruthy(); }); });