Files

170 lines
5.3 KiB
TypeScript
Raw Permalink Normal View History

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);
});
});