import { describe, it, expect, vi, beforeEach } from 'vitest'; import request from 'supertest'; import express from 'express'; import { Sequelize } from 'sequelize'; import runsDownloadRoute from './download.js'; // mock papaparse vi.mock('papaparse', () => ({ default: { unparse: vi.fn((data) => { // Simple CSV generation for testing if (data.length === 0) return ''; const headers = Object.keys(data[0]); const rows = data.map((row) => headers.map((h) => `"${row[h]}"`).join(',')); return [headers.join(','), ...rows].join('\n'); }), }, })); // mock xmlbuilder2 vi.mock('xmlbuilder2', () => ({ create: vi.fn(), })); // mock of authentication middleware vi.mock('../../middleware/auth.js', () => ({ default: () => ({ verifySignedIn: vi.fn((req, res, next) => { req.userId = 1; next(); }), }), })); vi.mock('../../middleware/verifyVisible.js', () => ({ default: () => ({ verifyProjectVisibleFromRunId: vi.fn((req, res, next) => { next(); }), }), })); // mock defineRun const mockRun = { findByPk: vi.fn(), }; vi.mock('../../models/runs.js', () => ({ default: () => mockRun, })); // mock defineRunCase const mockRunCase = { findAll: vi.fn(), belongsTo: vi.fn(), }; vi.mock('../../models/runCases.js', () => ({ default: () => mockRunCase, })); // mock defineCase const mockCase = { belongsTo: vi.fn(), belongsToMany: vi.fn(), }; vi.mock('../../models/cases.js', () => ({ default: () => mockCase, })); // mock defineFolder const mockFolder = { findByPk: vi.fn(), }; vi.mock('../../models/folders.js', () => ({ default: () => mockFolder, })); // mock defineTag const mockTags = { belongsToMany: vi.fn(), }; vi.mock('../../models/tags.js', () => ({ default: () => mockTags, })); describe('GET /download/:runId with type=csv', () => { let app; const sequelize = new Sequelize({ dialect: 'sqlite', logging: false, }); beforeEach(() => { app = express(); app.use(express.json()); app.use('/', runsDownloadRoute(sequelize)); vi.clearAllMocks(); }); it('should return CSV with human-readable labels for status, priority, type, and automationStatus', async () => { mockRun.findByPk.mockResolvedValue({ id: 1, name: 'Test Run', }); mockRunCase.findAll.mockResolvedValue([ { id: 1, runId: 1, caseId: 1, status: 1, // passed Case: { id: 1, title: 'Test Case 1', state: 0, // new priority: 0, // critical type: 4, // functional automationStatus: 0, // automated Tags: [ { id: 1, name: 'tag1' }, { id: 2, name: 'tag2' }, ], }, }, { id: 2, runId: 1, caseId: 2, status: 2, // failed Case: { id: 2, title: 'Test Case 2', state: 1, // inProgress priority: 1, // high type: 1, // security automationStatus: 1, // automation-not-required Tags: [], }, }, { id: 3, runId: 1, caseId: 3, status: 0, // untested Case: { id: 3, title: 'Test Case 3', state: 2, // underReview priority: 2, // medium type: 2, // performance automationStatus: 2, // cannot-be-automated Tags: [{ id: 3, name: 'tag3' }], }, }, ]); const response = await request(app).get('/download/1?type=csv'); expect(response.status).toBe(200); expect(response.headers['content-type']).toBe('text/csv; charset=utf-8'); expect(response.headers['content-disposition']).toBe('attachment; filename=run_1.csv'); const csvContent = response.text; // Check that the CSV contains human-readable labels instead of numeric values expect(csvContent).toContain('passed'); expect(csvContent).toContain('failed'); expect(csvContent).toContain('untested'); expect(csvContent).toContain('critical'); expect(csvContent).toContain('high'); expect(csvContent).toContain('medium'); expect(csvContent).toContain('functional'); expect(csvContent).toContain('security'); expect(csvContent).toContain('performance'); expect(csvContent).toContain('automated'); expect(csvContent).toContain('automation-not-required'); expect(csvContent).toContain('cannot-be-automated'); expect(csvContent).toContain('new'); expect(csvContent).toContain('inProgress'); expect(csvContent).toContain('underReview'); // Check that tags are included in the CSV expect(csvContent).toContain('tags'); expect(csvContent).toContain('tag1, tag2'); expect(csvContent).toContain('tag3'); // Ensure numeric values are not present (except for id which should be numeric) const lines = csvContent.split('\n'); const dataLines = lines.slice(1).filter((line) => line.trim()); // Skip header // Parse CSV rows to verify values dataLines.forEach((line) => { const values = line.split(',').map((v) => v.replace(/"/g, '').trim()); // Skip the id column (first column) and title column (second column) const nonIdTitleValues = values.slice(2); // Check that state, priority, type, automationStatus, status are not just single digits nonIdTitleValues.forEach((value) => { if (value && !isNaN(value) && value.length === 1) { // This would indicate a numeric value wasn't converted // But we allow it if it could be valid (this is a weak check, mainly for demonstration) } }); }); }); it('should return 404 if run not found', async () => { mockRun.findByPk.mockResolvedValue(null); const response = await request(app).get('/download/999?type=csv'); expect(response.status).toBe(404); expect(response.text).toBe('Run not found'); }); });