import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import type { AddressInfo } from 'node:net'; import type { Database } from '@atlas/database'; import type { NestExpressApplication } from '@nestjs/platform-express'; import { afterAll, beforeAll, describe, it } from 'vitest'; import { DATABASE } from '../src/database/database.module.js'; import { createApplication } from '../src/main.js'; type SuccessResponse = { data: T }; type ErrorResponse = { error: { code: string; message: string; request_id: string; details?: Array<{ field: string; code: string; message: string }>; }; }; type SessionPayload = { access_token: string; access_token_expires_in: number; user: { id: string; email: string; display_name: string | null; avatar_asset_id: string | null; revision: number; }; workspace: { id: string; name: string; role: string }; }; const password = 'correct-horse-battery-staple-123'; const testUsers: Array<{ userId: string; workspaceId: string }> = []; let app: NestExpressApplication; let database: Database; let baseUrl: string; function email(label: string): string { return `${label}-${randomUUID()}@example.test`; } async function request( path: string, init: RequestInit = {}, ): Promise<{ response: Response; body: unknown }> { const response = await fetch(`${baseUrl}${path}`, { ...init, headers: { 'Content-Type': 'application/json', ...init.headers, }, }); return { response, body: response.status === 204 ? undefined : await response.json(), }; } function expectSuccess(body: unknown): SuccessResponse { assert.ok(body && typeof body === 'object' && 'data' in body); return body as SuccessResponse; } function expectError(body: unknown): ErrorResponse { assert.ok(body && typeof body === 'object' && 'error' in body); return body as ErrorResponse; } function refreshCookie(response: Response): string { const cookie = response.headers.get('set-cookie'); assert.ok(cookie, '认证成功响应必须写入 refresh cookie'); assert.match(cookie, /HttpOnly/i); const match = cookie.match(/atlas_refresh=[^;]+/); assert.ok(match, '响应必须包含 atlas_refresh cookie'); return match[0]; } describe('认证 HTTP 集成测试', () => { beforeAll(async () => { Object.assign(process.env, { NODE_ENV: 'test', SWAGGER_ENABLED: 'false', AUTH_LOGIN_IP_LIMIT: '100', AUTH_LOGIN_EMAIL_LIMIT: '2', AUTH_REGISTER_IP_LIMIT: '100', AUTH_REGISTER_EMAIL_LIMIT: '100', AUTH_REFRESH_IP_LIMIT: '100', }); const application = await createApplication(); app = application.app; database = app.get(DATABASE); await app.listen(0, '127.0.0.1'); const address = app.getHttpServer().address() as AddressInfo; baseUrl = `http://127.0.0.1:${address.port}`; }); afterAll(async () => { for (const user of testUsers) { await database.workspaceTransaction( user.workspaceId, async (tx) => { await tx.query( 'DELETE FROM atlas.idempotency_requests WHERE workspace_id = $1', [user.workspaceId], ); }, ); await database.query( 'DELETE FROM atlas.workspace_memberships WHERE user_id = $1', [user.userId], ); await database.query('DELETE FROM atlas.workspaces WHERE id = $1', [ user.workspaceId, ]); await database.query('DELETE FROM atlas.users WHERE id = $1', [ user.userId, ]); } await app.close(); }); it('健康与 readiness 端点保持公开', async () => { const health = await request('/health'); assert.equal(health.response.status, 200); assert.deepEqual(health.body, { status: 'ok', service: 'api' }); const readiness = await request('/ready'); assert.equal(readiness.response.status, 200); assert.deepEqual(readiness.body, { status: 'ready', service: 'api' }); }); it('注册、重复邮箱和参数校验遵守响应契约', async () => { const registrationEmail = email('register'); const registration = await request('/api/auth/register', { method: 'POST', body: JSON.stringify({ email: registrationEmail, password, password_confirmation: password, display_name: '集成测试用户', }), }); assert.equal(registration.response.status, 201); const payload = expectSuccess(registration.body).data; assert.equal(payload.user.email, registrationEmail); assert.equal(payload.workspace.role, 'owner'); assert.ok(payload.access_token); assert.ok(payload.access_token_expires_in > 0); refreshCookie(registration.response); testUsers.push({ userId: payload.user.id, workspaceId: payload.workspace.id, }); const duplicate = await request('/api/auth/register', { method: 'POST', body: JSON.stringify({ email: registrationEmail, password, password_confirmation: password, }), }); assert.equal(duplicate.response.status, 409); assert.equal( expectError(duplicate.body).error.code, 'EMAIL_ALREADY_REGISTERED', ); const invalid = await request('/api/auth/register', { method: 'POST', body: JSON.stringify({ email: 'not-an-email', password, password_confirmation: password, }), }); assert.equal(invalid.response.status, 422); assert.equal(expectError(invalid.body).error.code, 'VALIDATION_FAILED'); }); it('登录、刷新、退出和当前用户读取可形成完整会话闭环', async () => { const accountEmail = email('session'); const registration = await request('/api/auth/register', { method: 'POST', body: JSON.stringify({ email: accountEmail, password, password_confirmation: password, }), }); const registered = expectSuccess( registration.body, ).data; testUsers.push({ userId: registered.user.id, workspaceId: registered.workspace.id, }); const login = await request('/api/auth/login', { method: 'POST', body: JSON.stringify({ email: accountEmail, password }), }); assert.equal(login.response.status, 200); const loggedIn = expectSuccess(login.body).data; const loginRefreshCookie = refreshCookie(login.response); const currentUser = await request('/api/auth/me', { headers: { Authorization: `Bearer ${loggedIn.access_token}` }, }); assert.equal(currentUser.response.status, 200); const me = expectSuccess<{ user: { id: string }; workspace: { id: string }; }>(currentUser.body).data; assert.equal(me.user.id, registered.user.id); assert.equal(me.workspace.id, registered.workspace.id); const refresh = await request('/api/auth/refresh', { method: 'POST', headers: { Cookie: loginRefreshCookie }, }); assert.equal(refresh.response.status, 200); const refreshed = expectSuccess<{ access_token: string; access_token_expires_in: number; }>(refresh.body).data; assert.ok(refreshed.access_token); const refreshedCookie = refreshCookie(refresh.response); const reusedRefresh = await request('/api/auth/refresh', { method: 'POST', headers: { Cookie: loginRefreshCookie }, }); assert.equal(reusedRefresh.response.status, 401); assert.equal( expectError(reusedRefresh.body).error.code, 'SESSION_EXPIRED', ); const logout = await request('/api/auth/logout', { method: 'POST', headers: { Authorization: `Bearer ${refreshed.access_token}`, Cookie: refreshedCookie, }, }); assert.equal(logout.response.status, 204); assert.equal(logout.body, undefined); const revoked = await request('/api/auth/me', { headers: { Authorization: `Bearer ${refreshed.access_token}` }, }); assert.equal(revoked.response.status, 401); }); it('拒绝错误密码、无效访问令牌和超过邮箱限额的登录请求', async () => { const accountEmail = email('rejected'); const wrongPassword = await request('/api/auth/login', { method: 'POST', body: JSON.stringify({ email: accountEmail, password }), }); assert.equal(wrongPassword.response.status, 401); const wrongPasswordError = expectError(wrongPassword.body).error; assert.equal(wrongPasswordError.code, 'INVALID_CREDENTIALS'); assert.equal( wrongPasswordError.request_id, wrongPassword.response.headers.get('x-request-id'), ); const invalidToken = await request('/api/auth/me', { headers: { Authorization: 'Bearer invalid-access-token' }, }); assert.equal(invalidToken.response.status, 401); assert.equal(expectError(invalidToken.body).error.code, 'HTTP_ERROR'); const rateLimitedEmail = email('rate-limit'); for (const expectedStatus of [401, 401, 429]) { const response = await request('/api/auth/login', { method: 'POST', body: JSON.stringify({ email: rateLimitedEmail, password }), }); assert.equal(response.response.status, expectedStatus); if (expectedStatus === 429) { assert.equal( expectError(response.body).error.code, 'AUTH_RATE_LIMITED', ); } } }); it('更新资料遵守版本与幂等约束,改密会撤销旧会话并轮换当前会话', async () => { const accountEmail = email('profile-password'); const registration = await request('/api/auth/register', { method: 'POST', body: JSON.stringify({ email: accountEmail, password, password_confirmation: password, display_name: '原始名称', }), }); const registered = expectSuccess( registration.body, ).data; testUsers.push({ userId: registered.user.id, workspaceId: registered.workspace.id, }); const profileKey = randomUUID(); const update = await request('/api/auth/me', { method: 'PATCH', headers: { Authorization: `Bearer ${registered.access_token}`, 'If-Match': `"${registered.user.revision}"`, 'Idempotency-Key': profileKey, }, body: JSON.stringify({ display_name: '更新后的名称' }), }); assert.equal(update.response.status, 200); const updatedUser = expectSuccess( update.body, ).data; assert.equal(updatedUser.display_name, '更新后的名称'); assert.equal(updatedUser.avatar_asset_id, null); assert.equal(updatedUser.revision, registered.user.revision + 1); const replay = await request('/api/auth/me', { method: 'PATCH', headers: { Authorization: `Bearer ${registered.access_token}`, 'If-Match': `"${registered.user.revision}"`, 'Idempotency-Key': profileKey, }, body: JSON.stringify({ display_name: '更新后的名称' }), }); assert.equal(replay.response.status, 200); assert.deepEqual(replay.body, update.body); const stale = await request('/api/auth/me', { method: 'PATCH', headers: { Authorization: `Bearer ${registered.access_token}`, 'If-Match': `"${registered.user.revision}"`, 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ display_name: '过期修改' }), }); assert.equal(stale.response.status, 409); assert.equal(expectError(stale.body).error.code, 'VERSION_CONFLICT'); const invalidAvatar = await request('/api/auth/me', { method: 'PATCH', headers: { Authorization: `Bearer ${registered.access_token}`, 'If-Match': `"${updatedUser.revision}"`, 'Idempotency-Key': randomUUID(), }, body: JSON.stringify({ avatar_asset_id: randomUUID() }), }); assert.equal(invalidAvatar.response.status, 422); assert.equal( expectError(invalidAvatar.body).error.code, 'AVATAR_ASSET_UNAVAILABLE', ); const secondLogin = await request('/api/auth/login', { method: 'POST', body: JSON.stringify({ email: accountEmail, password }), }); const otherSession = expectSuccess( secondLogin.body, ).data; const changed = await request('/api/auth/change-password', { method: 'POST', headers: { Authorization: `Bearer ${registered.access_token}` }, body: JSON.stringify({ old_password: password, password: 'new-correct-horse-battery-staple-456', password_confirmation: 'new-correct-horse-battery-staple-456', }), }); assert.equal(changed.response.status, 200); const rotated = expectSuccess<{ access_token: string; access_token_expires_in: number; }>(changed.body).data; assert.ok(rotated.access_token); refreshCookie(changed.response); const formerCurrent = await request('/api/auth/me', { headers: { Authorization: `Bearer ${registered.access_token}` }, }); assert.equal(formerCurrent.response.status, 401); const revokedOther = await request('/api/auth/me', { headers: { Authorization: `Bearer ${otherSession.access_token}` }, }); assert.equal(revokedOther.response.status, 401); const current = await request('/api/auth/me', { headers: { Authorization: `Bearer ${rotated.access_token}` }, }); assert.equal(current.response.status, 200); const oldPasswordChange = await request('/api/auth/change-password', { method: 'POST', headers: { Authorization: `Bearer ${rotated.access_token}` }, body: JSON.stringify({ old_password: password, password: 'another-correct-horse-battery-staple-789', password_confirmation: 'another-correct-horse-battery-staple-789', }), }); assert.equal(oldPasswordChange.response.status, 422); assert.equal( expectError(oldPasswordChange.body).error.code, 'VALIDATION_FAILED', ); const newPasswordLogin = await request('/api/auth/login', { method: 'POST', body: JSON.stringify({ email: accountEmail, password: 'new-correct-horse-battery-staple-456', }), }); assert.equal(newPasswordLogin.response.status, 200); }); });