import { randomUUID } from 'node:crypto'; import argon2 from 'argon2'; import ExcelJS from 'exceljs'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { createServer } from '../src/app.js'; import { db } from '../src/db/knex.js'; import { seedSurveyDefinition } from '../src/survey/seed.js'; import { truncateTables } from './truncate.js'; const email = '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('survey export', () => { /** @type {import('@hapi/hapi').Server | undefined} */ let server; beforeEach(async () => { await seedSurveyDefinition(); }); afterEach(async () => { await server?.stop(); await truncateTables(); }); it('writes response headers, question ids, and choice counts', 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 definitionId = await seedSurveyDefinition(); const [applicationId] = await db('applications').insert({ public_id: randomUUID(), email: 'physician@example.com', status: 'APPROVED', full_name: 'Asha Rao', registration_no: 'MMC-100', version: 1, }); await db('survey_responses').insert({ application_id: applicationId, survey_definition_id: definitionId, answers: JSON.stringify({ name: 'Dr Asha Rao', adherence_comparison: { choice: 'much_better' }, practice_setting: { choices: ['private_clinic', 'govt_hospital'] }, barriers: { choices: ['cost', 'other'], other: 'Lack of time' }, benefit: { choices: ['elderly'] }, pack_split: { regular_pmdi: { asthma: 25, copd: 25 }, regular_rotacap: { asthma: 25, copd: 25 }, extended_pmdi: { asthma: 25, copd: 25 }, extended_rotacap: { asthma: 25, copd: 25 }, }, }), status: 'COMPLETED', started_at: new Date(), completed_at: new Date(), }); const response = await server.inject({ method: 'GET', url: '/api/admin/survey/export?status=COMPLETED', headers: { cookie: sessionCookie(login) }, }); expect(response.statusCode).toBe(200); expect(response.headers['content-disposition']).toContain('survey-responses.xlsx'); const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(response.rawPayload); const responses = workbook.getWorksheet('Responses'); const headings = responses.getRow(1).values.slice(1); const optionLabels = responses.getRow(2).values; const data = responses.getRow(3); expect(headings).toContain( 'Prescribing % - Extended-Dose Pack — Rotacap (pack of 60) - COPD', ); const column = headings.indexOf('Prescribing % - Extended-Dose Pack — Rotacap (pack of 60) - COPD') + 1; expect(data.getCell(column).value).toBe(25); expect(data.getCell(headings.indexOf('Physician name') + 1).value).toBe('Asha Rao'); const barriersHeading = 'Reasons for Non-adherence: In your opinion, what are the key barriers to adherence to inhaled maintenance therapy of formoterol/budesonide in your asthma and COPD patients?'; const barriersStart = headings.indexOf(barriersHeading) + 1; expect(barriersStart).toBeGreaterThan(0); expect(responses.getCell(1, barriersStart + 1).isMerged).toBe(true); expect(optionLabels[barriersStart]).toBe('Side effects'); const barrierCell = (/** @type {string} */ label) => data.getCell(optionLabels.indexOf(label, barriersStart)).value; expect(barrierCell('Cost')).toBe('Yes'); expect(barrierCell('Side effects')).toBeNull(); expect(barrierCell('Other (specify)')).toBe('Lack of time'); const elderly = optionLabels.indexOf('Elderly patients'); expect(responses.getCell(1, elderly).value).toBe( 'Patient Characteristics Associated with Adherence: Which patients, in your opinion, are most likely to benefit from an Extended-dose pack?', ); expect(data.getCell(elderly).value).toBe('Yes'); const settingStart = headings.indexOf('Practice Setting') + 1; expect(settingStart).toBeGreaterThan(0); expect(responses.getCell(1, settingStart + 2).isMerged).toBe(true); expect(optionLabels.slice(settingStart, settingStart + 3)).toEqual([ 'Private Clinic', 'Private Hospital', 'Govt. Hospital', ]); expect(data.getCell(settingStart).value).toBe('Yes'); expect(data.getCell(settingStart + 1).value).toBeNull(); expect(data.getCell(settingStart + 2).value).toBe('Yes'); const questions = workbook.getWorksheet('Questions'); const questionIds = questions.getColumn(1).values; expect(questionIds).toContain('pack_split'); const summary = workbook.getWorksheet('Summary'); const summaryText = summary.getSheetValues().flat().join(' '); expect(summaryText).toContain('Much better'); expect(summaryText).toContain('1'); expect(await db('audit_log').where({ action: 'SURVEY_EXPORTED' }).first()).toBeTruthy(); }); });