tressi
Version:
A lightweight, declarative stress testing CLI for modern developers.
182 lines (163 loc) • 5.13 kB
text/typescript
/// <reference types="vitest/globals" />
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import * as xlsx from 'xlsx';
import { exportDataFiles } from '../src/exporter';
import { RequestResult } from '../src/stats';
import { EndpointSummary, GlobalSummary, TestSummary } from '../src/summarizer';
// Mock file system and external libraries
vi.mock('fs/promises', () => ({
writeFile: vi.fn(),
}));
vi.mock('xlsx', async () => {
const actualXlsx = await vi.importActual<typeof import('xlsx')>('xlsx');
return {
...actualXlsx,
writeFile: vi.fn(),
utils: {
...actualXlsx.utils,
book_new: vi.fn(() => ({ SheetNames: [], Sheets: {} })),
book_append_sheet: vi.fn(),
json_to_sheet: vi.fn(),
},
};
});
vi.mock('ora', () => {
const mOra = {
start: vi.fn().mockReturnThis(),
succeed: vi.fn().mockReturnThis(),
fail: vi.fn().mockReturnThis(),
};
return { default: vi.fn(() => mOra) };
});
vi.mock('chalk', () => ({
default: {
red: vi.fn((str) => str),
},
}));
const mockResults: RequestResult[] = [
{
method: 'GET',
timestamp: 1,
url: 'http://a.com',
status: 200,
latencyMs: 100.45, // Use a float to test rounding
success: true,
body: '{"ok":true}',
},
];
const mockGlobalSummary: GlobalSummary = {
totalRequests: 1,
successfulRequests: 1,
failedRequests: 0,
avgLatencyMs: 100.5, // Use a float
minLatencyMs: 100.5,
maxLatencyMs: 100.5,
p95LatencyMs: 100.5,
p99LatencyMs: 100.5,
actualRps: 10.8,
theoreticalMaxRps: 20.2,
achievedPercentage: 50.9,
duration: 10.1,
};
const mockEndpointSummary: EndpointSummary[] = [
{
method: 'GET',
url: 'http://a.com',
totalRequests: 1,
successfulRequests: 1,
failedRequests: 0,
avgLatencyMs: 100.5,
minLatencyMs: 100.5,
maxLatencyMs: 100.5,
p95LatencyMs: 100.5,
p99LatencyMs: 100.5,
},
];
const mockSummary: TestSummary = {
tressiVersion: 'test',
global: mockGlobalSummary,
endpoints: mockEndpointSummary,
};
/**
* Test suite for the multi-file data exporter functionality.
*/
describe('exporter', () => {
interface GlobalSheetRow {
Stat: string;
Value: number | string;
}
let writeFileMock: Mock;
let xlsxWriteFileMock: Mock;
let jsonToSheetMock: Mock;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let mockRunner: any;
beforeEach(async () => {
// Dynamically import the mocked module to get the mock function
const fs = await import('fs/promises');
writeFileMock = fs.writeFile as Mock;
xlsxWriteFileMock = xlsx.writeFile as Mock;
jsonToSheetMock = xlsx.utils.json_to_sheet as Mock;
writeFileMock.mockClear();
xlsxWriteFileMock.mockClear();
mockRunner = {
getStatusCodeMap: vi.fn(() => ({ 200: 3, 500: 1 })),
};
});
/**
* It should call the respective file writers for all CSV and XLSX files
* with the correctly formatted data and file paths.
*/
it('should export all data files and round numbers for reports', async () => {
await exportDataFiles(
mockSummary,
mockResults,
'./test-output',
mockRunner,
);
// Should be called 1 time for the raw CSV
expect(writeFileMock).toHaveBeenCalledTimes(1);
// Should be called 1 time for XLSX
expect(xlsxWriteFileMock).toHaveBeenCalledTimes(1);
const bookAppendSheetMock = xlsx.utils.book_append_sheet as Mock;
const sheetNames = bookAppendSheetMock.mock.calls.map((call) => call[2]);
expect(sheetNames).toContain('Sampled Responses');
expect(sheetNames).toContain('Status Code Distribution');
// Check XLSX call
expect(xlsxWriteFileMock.mock.calls[0][1]).toBe(
'./test-output/report.xlsx',
);
// Check raw CSV log file
const rawCsvCall = writeFileMock.mock.calls.find((c) =>
c[0].endsWith('results.csv'),
);
expect(rawCsvCall).toBeDefined();
expect(rawCsvCall![1]).toContain(
'timestamp,url,status,latencyMs,success,error',
);
// Check that 100.45 was rounded to 100
expect(rawCsvCall![1]).toContain('1,"http://a.com",200,100,true,""');
// Check that data passed to XLSX generator is rounded
// 1. Global Summary
const globalSheetData = jsonToSheetMock.mock.calls[0][0];
expect(
globalSheetData.find((r: GlobalSheetRow) => r.Stat === 'avgLatencyMs')!
.Value,
).toBe(101); // 100.5 -> 101
expect(
globalSheetData.find((r: GlobalSheetRow) => r.Stat === 'actualRps')!
.Value,
).toBe(11); // 10.8 -> 11
// 2. Endpoint Summary
const endpointSheetData = jsonToSheetMock.mock.calls[1][0];
expect(endpointSheetData[0].avgLatencyMs).toBe(101);
// 3. Status Code Distribution
const statusCodeSheetData = jsonToSheetMock.mock.calls[2][0];
expect(statusCodeSheetData).toEqual([
{ 'Status Code Category': '2xx', Count: 3 },
{ 'Status Code Category': '3xx', Count: 0 },
{ 'Status Code Category': '4xx', Count: 0 },
{ 'Status Code Category': '5xx', Count: 1 },
{ 'Status Code Category': 'other', Count: 0 },
]);
});
});