UNPKG

tressi

Version:

A lightweight, declarative stress testing CLI for modern developers.

182 lines (163 loc) 5.13 kB
/// <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 }, ]); }); });