import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; import { prisma } from '../src/lib/prisma.js'; import { assertNoSecrets, authHeader, createTestApp, isDatabaseAvailable, requestJson, } from './helpers.js'; const app = createTestApp(); const createdUserIds: bigint[] = []; describe('profile API', () => { let dbAvailable = false; before(async () => { dbAvailable = await isDatabaseAvailable(); }); after(async () => { if (dbAvailable && createdUserIds.length > 0) { await prisma.user.deleteMany({ where: { id: { in: createdUserIds } } }); } }); async function createAuthenticatedUser() { const email = `profile-${Date.now()}-${Math.random().toString(16).slice(2)}@example.com`; const registered = await requestJson(app, '/auth/register', { method: 'POST', body: JSON.stringify({ email, password: 'password12' }), }); const id = (registered.body.data?.user as { id: string }).id; const user = await prisma.user.findUnique({ where: { externalId: id } }); if (user) { createdUserIds.push(user.id); } return { token: registered.body.data?.sessionToken as string, id, }; } it('rejects unauthenticated profile access', async () => { const missing = await requestJson(app, '/users/me'); assert.equal(missing.status, 401); }); it('lets an authenticated user read and update their own profile', async (t) => { if (!dbAvailable) { t.skip('MySQL is not running'); return; } const { token } = await createAuthenticatedUser(); const before = await requestJson(app, '/users/me', { headers: authHeader(token) }); assert.equal(before.status, 200); assert.equal((before.body.data as { profile: unknown }).profile, null); assert.equal((before.body.data as { profileComplete: boolean }).profileComplete, false); const updated = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: ' علی رضایی ', age: 29 }), }); assert.equal(updated.status, 200); assert.equal((updated.body.data as { profileComplete: boolean }).profileComplete, true); assert.equal((updated.body.data as { profile: { name: string; age: number } }).profile.name, 'علی رضایی'); assert.equal((updated.body.data as { profile: { name: string; age: number } }).profile.age, 29); assertNoSecrets(updated.raw); }); it('validates name and requires an integer age', async (t) => { if (!dbAvailable) { t.skip('MySQL is not running'); return; } const { token } = await createAuthenticatedUser(); const emptyName = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: ' ', age: 20 }), }); assert.equal(emptyName.status, 422); const decimalAge = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Sara', age: 17.5 }), }); assert.equal(decimalAge.status, 422); const stringAge = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Sara', age: '17' }), }); assert.equal(stringAge.status, 422); const underage = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Sara', age: 11 }), }); assert.equal(underage.status, 422); const teenOk = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Sara', age: 12 }), }); assert.equal(teenOk.status, 200); const seniorOk = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Sara', age: 101 }), }); assert.equal(seniorOk.status, 200); }); it('does not expose a userId parameter for modifying another user', async (t) => { if (!dbAvailable) { t.skip('MySQL is not running'); return; } const first = await createAuthenticatedUser(); const second = await createAuthenticatedUser(); const missing = await requestJson(app, `/users/${second.id}/profile`, { method: 'PATCH', headers: authHeader(first.token), body: JSON.stringify({ name: 'Hacker', age: 40 }), }); assert.equal(missing.status, 404); const other = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(first.token), body: JSON.stringify({ name: 'Only Me', age: 22 }), }); assert.equal(other.status, 200); const secondRead = await requestJson(app, '/users/me', { headers: authHeader(second.token) }); assert.equal((secondRead.body.data as { profile: unknown }).profile, null); }); it('requires both name and age when creating a profile', async (t) => { if (!dbAvailable) { t.skip('MySQL is not running'); return; } const { token } = await createAuthenticatedUser(); const partial = await requestJson(app, '/users/me/profile', { method: 'PATCH', headers: authHeader(token), body: JSON.stringify({ name: 'Only Name' }), }); assert.equal(partial.status, 422); }); });