PPPAIMT-Crest/backend/test/userAuth.test.js

121 lines
3.2 KiB
JavaScript

import { afterEach, describe, expect, it } from 'vitest';
import { createServer } from '../src/app.js';
import { db } from '../src/db/knex.js';
import { clearOutbox, outbox } from '../src/services/mail.js';
import { truncateTables } from './truncate.js';
const email = 'doctor@example.com';
/**
* @param {import('@hapi/hapi').ServerInjectResponse} response
* @param {string} name
*/
function cookieNamed(response, name) {
const raw = response.headers['set-cookie'];
const headers = Array.isArray(raw) ? raw : raw ? [raw] : [];
const match = headers.find((header) => header.startsWith(`${name}=`));
return match ? match.split(';')[0] : null;
}
describe('user OTP auth', () => {
/** @type {import('@hapi/hapi').Server | undefined} */
let server;
afterEach(async () => {
clearOutbox();
await server?.stop();
await truncateTables();
});
async function start() {
server = await createServer();
return server;
}
it('requests an OTP, verifies it, returns /api/me, and logs out', async () => {
const api = await start();
const requested = await api.inject({
method: 'POST',
url: '/api/auth/otp/request',
payload: { email },
});
expect(requested.statusCode).toBe(200);
expect(requested.result).toMatchObject({ ok: true });
const user = await db('users').where({ email }).first();
expect(user).toBeTruthy();
const application = await db('applications').where({ user_id: user.id }).first();
expect(application).toMatchObject({ email, status: 'DRAFT' });
expect(outbox).toHaveLength(1);
expect(outbox[0]?.template).toBe('login_otp');
expect(outbox[0]?.otp).toMatch(/^\d{6}$/);
const verified = await api.inject({
method: 'POST',
url: '/api/auth/otp/verify',
payload: { email, otp: outbox[0].otp },
});
expect(verified.statusCode).toBe(200);
expect(verified.result).toMatchObject({
user: { email },
me: { nextStep: 'consent' },
});
const session = cookieNamed(verified, 'user_token');
expect(session).toBeTruthy();
const me = await api.inject({
method: 'GET',
url: '/api/me',
headers: { cookie: session },
});
expect(me.statusCode).toBe(200);
expect(me.result).toMatchObject({
user: { email },
steps: {
consent: { done: false },
agreement: { done: false },
survey: { done: false },
},
nextStep: 'consent',
});
const logout = await api.inject({
method: 'POST',
url: '/api/auth/logout',
headers: { cookie: session },
});
expect(logout.statusCode).toBe(200);
expect(cookieNamed(logout, 'user_token')).toMatch(/user_token=/);
expect(String(logout.headers['set-cookie'])).toMatch(/Max-Age=0|Expires=/i);
const missing = await api.inject({ method: 'GET', url: '/api/me' });
expect(missing.statusCode).toBe(401);
});
it('rejects an invalid OTP', async () => {
const api = await start();
await api.inject({
method: 'POST',
url: '/api/auth/otp/request',
payload: { email },
});
const verified = await api.inject({
method: 'POST',
url: '/api/auth/otp/verify',
payload: { email, otp: '000000' },
});
expect(verified.statusCode).toBe(401);
});
});