tressi
Version:
A lightweight, declarative stress testing CLI for modern developers.
254 lines (220 loc) • 7.33 kB
text/typescript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
// Mock dependencies
vi.mock('fs/promises', () => ({
access: vi.fn(),
writeFile: vi.fn(),
}));
vi.mock('../src/index', () => ({
runLoadTest: vi.fn(),
}));
const runCli = async (args: string[]): Promise<void> => {
process.argv = ['node', 'tressi', ...args];
await import('../src/cli');
};
/**
* Test suite for the main CLI functionality.
*/
describe('CLI', () => {
beforeEach(() => {
vi.resetModules(); // Reset module cache before each test
vi.clearAllMocks(); // Clear mocks
});
afterEach(() => {
// Restore original process.argv
process.argv = process.env.npm_config_user_agent
? ['node', 'vitest', 'test']
: ['node'];
});
/**
* Test suite for the main command of the CLI.
*/
describe('main command', () => {
/**
* It should correctly parse all the command-line arguments
* and call the main `runLoadTest` function with the expected options object.
*/
it('should call runLoadTest with the correct options', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--workers',
'20',
'--duration',
'60',
'--rps',
'500',
'--autoscale',
'--export',
'my-report',
]);
expect(runLoadTest).toHaveBeenCalledWith({
config: './tressi.config.ts',
workers: 20,
durationSec: 60,
rps: 500,
autoscale: true,
exportPath: 'my-report',
rampUpTimeSec: undefined,
useUI: true,
});
});
/**
* It should pass `exportPath: true` when the --export flag is used without a value.
*/
it('should handle the --export flag without a path', async () => {
const { runLoadTest } = await import('../src/index');
await runCli(['--config', './tressi.config.ts', '--export']);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
exportPath: true,
}),
);
});
/**
* It should validate that when --autoscale is used, --rps is also provided.
* If not, it should print an error and exit with a non-zero status code.
*/
it('should exit if --autoscale is used without --rps', async () => {
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation(() => undefined as never);
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
await runCli(['--config', 'tressi.config.ts', '--autoscale']);
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error: --rps is required when --autoscale is enabled.',
);
processExitSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
describe('early exit options', () => {
it('should parse early exit options correctly', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-rate-threshold',
'0.05',
'--error-count-threshold',
'100',
'--error-status-codes',
'500,503,404',
]);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
earlyExitOnError: true,
errorRateThreshold: 0.05,
errorCountThreshold: 100,
errorStatusCodes: [500, 503, 404],
}),
);
});
it('should parse single error status code', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-status-codes',
'500',
]);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
earlyExitOnError: true,
errorStatusCodes: [500],
}),
);
});
it('should exit with error when early exit is enabled but no thresholds provided', async () => {
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation(() => undefined as never);
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
]);
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error: When --early-exit-on-error is enabled, at least one of --error-rate-threshold, --error-count-threshold, or --error-status-codes must be provided.',
);
processExitSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
it('should exit with error when error status codes are invalid', async () => {
const processExitSpy = vi
.spyOn(process, 'exit')
.mockImplementation(() => undefined as never);
const consoleErrorSpy = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-status-codes',
'invalid,500',
]);
expect(processExitSpy).toHaveBeenCalledWith(1);
expect(consoleErrorSpy).toHaveBeenCalled();
processExitSpy.mockRestore();
consoleErrorSpy.mockRestore();
});
it('should exit with error when error rate threshold is invalid', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-rate-threshold',
'invalid',
]);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
earlyExitOnError: true,
errorRateThreshold: NaN,
}),
);
});
it('should exit with error when error count threshold is invalid', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-count-threshold',
'invalid',
]);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
earlyExitOnError: true,
errorCountThreshold: NaN,
}),
);
});
it('should handle edge case status codes correctly', async () => {
const { runLoadTest } = await import('../src/index');
await runCli([
'--config',
'./tressi.config.ts',
'--early-exit-on-error',
'--error-status-codes',
'100,200,300,400,500',
]);
expect(runLoadTest).toHaveBeenCalledWith(
expect.objectContaining({
earlyExitOnError: true,
errorStatusCodes: [100, 200, 300, 400, 500],
}),
);
});
});
});
});