157 lines
5.3 KiB
JavaScript
157 lines
5.3 KiB
JavaScript
import { randomUUID } from 'node:crypto';
|
|
import argon2 from 'argon2';
|
|
import ExcelJS from 'exceljs';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { CONSENT_QUESTIONS } from '../../frontend/src/consentQuestions.js';
|
|
import { createServer } from '../src/app.js';
|
|
import { db } from '../src/db/knex.js';
|
|
import { CONSENT_STATEMENTS } from '../src/schemas/consent.js';
|
|
import { encryptField } from '../src/services/fieldCrypto.js';
|
|
import { seedSurveyDefinition } from '../src/survey/seed.js';
|
|
import { truncateTables } from './truncate.js';
|
|
|
|
const email = 'applications-export-admin@example.com';
|
|
const password = 'correct-password';
|
|
|
|
/**
|
|
* @param {import('@hapi/hapi').ServerInjectResponse} response
|
|
*/
|
|
function sessionCookie(response) {
|
|
const raw = response.headers['set-cookie'];
|
|
const header = Array.isArray(raw) ? raw[0] : raw;
|
|
return header.split(';')[0];
|
|
}
|
|
|
|
describe('consent statements', () => {
|
|
it('match the statements shown on the consent page', () => {
|
|
expect(CONSENT_STATEMENTS).toEqual(
|
|
CONSENT_QUESTIONS.map(({ id, label }) => ({ id, label })),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('applications export', () => {
|
|
/** @type {import('@hapi/hapi').Server | undefined} */
|
|
let server;
|
|
|
|
afterEach(async () => {
|
|
await server?.stop();
|
|
await truncateTables();
|
|
});
|
|
|
|
it('writes application, consent and survey columns for the created-date range', async () => {
|
|
server = await createServer();
|
|
await db('admin_users').insert({
|
|
name: 'Administrator',
|
|
email,
|
|
password_hash: await argon2.hash(password, { type: argon2.argon2id }),
|
|
is_active: true,
|
|
});
|
|
const login = await server.inject({
|
|
method: 'POST',
|
|
url: '/api/admin/auth/login',
|
|
payload: { email, password },
|
|
});
|
|
|
|
const [userId] = await db('users').insert({ email: 'asha@example.com', name: 'Asha Rao' });
|
|
const [applicationId] = await db('applications').insert({
|
|
public_id: randomUUID(),
|
|
user_id: userId,
|
|
email: 'asha@example.com',
|
|
status: 'APPROVED',
|
|
full_name: 'Asha Rao',
|
|
registration_no: 'MMC-100',
|
|
pan: encryptField('ABCDE1234F'),
|
|
account_number: encryptField('001234567890'),
|
|
ifsc: 'HDFC0001234',
|
|
cancelled_cheque_key: 'cheques/1.png',
|
|
final_pdf_key: 'final/1.pdf',
|
|
version: 1,
|
|
created_at: '2026-10-10 09:00:00',
|
|
});
|
|
await db('applications').insert({
|
|
public_id: randomUUID(),
|
|
email: 'outside@example.com',
|
|
status: 'DRAFT',
|
|
version: 1,
|
|
created_at: '2026-09-01 09:00:00',
|
|
});
|
|
await db('signatures').insert({
|
|
application_id: applicationId,
|
|
version: 1,
|
|
type: 'TYPED',
|
|
typed_text: 'Asha Rao',
|
|
signed_at: new Date(),
|
|
});
|
|
await db('consents').insert({
|
|
user_id: userId,
|
|
application_id: applicationId,
|
|
participant_name: 'Asha Rao',
|
|
consent_date: '2026-10-10',
|
|
answers: JSON.stringify(
|
|
Object.fromEntries(CONSENT_STATEMENTS.map((statement) => [statement.id, true])),
|
|
),
|
|
submitted_at: '2026-10-10 09:05:00',
|
|
});
|
|
const definitionId = await seedSurveyDefinition();
|
|
await db('survey_responses').insert({
|
|
application_id: applicationId,
|
|
survey_definition_id: definitionId,
|
|
answers: JSON.stringify({}),
|
|
status: 'IN_PROGRESS',
|
|
started_at: new Date(),
|
|
});
|
|
|
|
const response = await server.inject({
|
|
method: 'GET',
|
|
url: '/api/admin/applications/export?from=2026-10-01&to=2026-10-31',
|
|
headers: { cookie: sessionCookie(login) },
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.headers['content-disposition']).toContain('applications-consent.xlsx');
|
|
const workbook = new ExcelJS.Workbook();
|
|
await workbook.xlsx.load(response.rawPayload);
|
|
const sheet = workbook.getWorksheet('Applications');
|
|
const headers = sheet.getRow(1).values;
|
|
const cell = (/** @type {string} */ header) => sheet.getRow(2).getCell(headers.indexOf(header)).value;
|
|
|
|
expect(sheet.rowCount).toBe(2);
|
|
expect(sheet.getRow(1).getCell(1).font?.bold).toBe(true);
|
|
expect(cell('Status')).toBe('Approved');
|
|
expect(cell('PAN')).toBe('ABCDE1234F');
|
|
expect(cell('Account number')).toBe('001234567890');
|
|
expect(cell('Cheque uploaded')).toBe('Yes');
|
|
expect(cell('Signed')).toBe('Yes');
|
|
expect(cell('Final PDF available')).toBe('Yes');
|
|
expect(cell('Consent date')).toBe('2026-10-10');
|
|
expect(cell(CONSENT_STATEMENTS[0].label)).toBe('Yes');
|
|
expect(cell('I agree and consent to participate in the survey.')).toBe('Yes');
|
|
expect(cell('Survey status')).toBe('In progress');
|
|
expect(await db('audit_log').where({ action: 'APPLICATIONS_EXPORTED' }).first()).toBeTruthy();
|
|
});
|
|
|
|
it('rejects malformed dates', async () => {
|
|
server = await createServer();
|
|
await db('admin_users').insert({
|
|
name: 'Administrator',
|
|
email,
|
|
password_hash: await argon2.hash(password, { type: argon2.argon2id }),
|
|
is_active: true,
|
|
});
|
|
const login = await server.inject({
|
|
method: 'POST',
|
|
url: '/api/admin/auth/login',
|
|
payload: { email, password },
|
|
});
|
|
|
|
const response = await server.inject({
|
|
method: 'GET',
|
|
url: '/api/admin/applications/export?from=10-10-2026',
|
|
headers: { cookie: sessionCookie(login) },
|
|
});
|
|
|
|
expect(response.statusCode).toBe(400);
|
|
});
|
|
});
|