chore: export csv with human-readable labels (#325)

This commit is contained in:
kimatata
2025-11-08 23:09:34 +09:00
committed by GitHub
parent 12b5a1babe
commit b25bcfdaf3
5 changed files with 397 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ import Papa from 'papaparse';
import defineCase from '../../models/cases.js';
import authMiddleware from '../../middleware/auth.js';
import visibilityMiddleware from '../../middleware/verifyVisible.js';
import { testRunStatus, priorities, testTypes, automationStatus, templates } from '../../config/enums.js';
export default function (sequelize) {
const Case = defineCase(sequelize, DataTypes);
@@ -35,7 +36,17 @@ export default function (sequelize) {
if (type === 'json') {
return res.json(cases);
} else if (type === 'csv') {
const csv = Papa.unparse(cases, {
// Convert numeric values to human-readable labels
const casesWithLabels = cases.map((c) => ({
...c,
state: testRunStatus[c.state] || c.state,
priority: priorities[c.priority] || c.priority,
type: testTypes[c.type] || c.type,
automationStatus: automationStatus[c.automationStatus] || c.automationStatus,
template: templates[c.template] || c.template,
}));
const csv = Papa.unparse(casesWithLabels, {
quotes: true,
skipEmptyLines: true,
});

View File

@@ -0,0 +1,149 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import request from 'supertest';
import express from 'express';
import { Sequelize } from 'sequelize';
import casesDownloadRoute 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 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: () => ({
verifyProjectVisibleFromFolderId: vi.fn((req, res, next) => {
next();
}),
}),
}));
// mock defineCase
const mockCase = {
findAll: vi.fn(),
};
vi.mock('../../models/cases.js', () => ({
default: () => mockCase,
}));
describe('GET /download with type=csv', () => {
let app;
const sequelize = new Sequelize({
dialect: 'sqlite',
logging: false,
});
beforeEach(() => {
app = express();
app.use(express.json());
app.use('/', casesDownloadRoute(sequelize));
vi.clearAllMocks();
});
it('should return CSV with human-readable labels for state, priority, type, automationStatus, and template', async () => {
mockCase.findAll.mockResolvedValue([
{
id: 1,
title: 'Test Case 1',
state: 0, // new
priority: 0, // critical
type: 4, // functional
automationStatus: 0, // automated
template: 1, // step
description: 'Test description',
preConditions: 'Test preconditions',
expectedResults: 'Test expected results',
folderId: 1,
},
{
id: 2,
title: 'Test Case 2',
state: 1, // inProgress
priority: 1, // high
type: 1, // security
automationStatus: 1, // automation-not-required
template: 0, // text
description: 'Test description 2',
preConditions: 'Test preconditions 2',
expectedResults: 'Test expected results 2',
folderId: 1,
},
{
id: 3,
title: 'Test Case 3',
state: 2, // underReview
priority: 2, // medium
type: 2, // performance
automationStatus: 2, // cannot-be-automated
template: 1, // step
description: 'Test description 3',
preConditions: 'Test preconditions 3',
expectedResults: 'Test expected results 3',
folderId: 1,
},
]);
const response = await request(app).get('/download?folderId=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=cases_folder_1.csv');
const csvContent = response.text;
// Check that the CSV contains human-readable labels instead of numeric values
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');
expect(csvContent).toContain('text');
expect(csvContent).toContain('step');
});
it('should return 404 if no cases found', async () => {
mockCase.findAll.mockResolvedValue([]);
const response = await request(app).get('/download?folderId=999&type=csv');
expect(response.status).toBe(404);
expect(response.text).toBe('No cases found');
});
it('should return 400 if folderId is missing', async () => {
const response = await request(app).get('/download?type=csv');
expect(response.status).toBe(400);
expect(response.body.error).toBe('folderId is required');
});
it('should return 400 if type is missing', async () => {
const response = await request(app).get('/download?folderId=1');
expect(response.status).toBe(400);
expect(response.body.error).toBe('download type is required');
});
});