lightweight-browser-load-tester
Version:
A lightweight load testing tool using real browsers for streaming applications with DRM support
382 lines (321 loc) • 13.4 kB
text/typescript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { RequestInterceptor, VariableContext, InterceptedRequest } from './request-interceptor';
import { ParameterTemplate } from '../types';
import { writeFileSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
describe('Randomization Functional Tests', () => {
let testDataDir: string;
beforeEach(() => {
// Create test data directory
testDataDir = join(process.cwd(), 'test-randomization-functional');
try {
mkdirSync(testDataDir, { recursive: true });
} catch (error) {
// Directory might already exist
}
});
afterEach(() => {
// Clean up test data directory
try {
rmSync(testDataDir, { recursive: true, force: true });
} catch (error) {
// Directory might not exist
}
});
it('should apply all randomization methods to request parameters', () => {
// Create test data files
const userAgentsFile = join(testDataDir, 'user-agents.txt');
const authTokensFile = join(testDataDir, 'auth-tokens.txt');
writeFileSync(userAgentsFile, [
'TestAgent/1.0',
'TestAgent/2.0',
'TestAgent/3.0'
].join('\n'));
writeFileSync(authTokensFile, [
'token-alpha',
'token-beta',
'token-gamma'
].join('\n'));
// Define parameter templates using all randomization methods
const templates: ParameterTemplate[] = [
{
target: 'header',
name: 'X-Request-ID',
valueTemplate: '{{random:uuid}}',
scope: 'per-session'
},
{
target: 'header',
name: 'X-Random-Number',
valueTemplate: '{{random:1-1000}}',
scope: 'per-session'
},
{
target: 'header',
name: 'X-Device-Type',
valueTemplate: '{{randomFrom:deviceTypes}}',
scope: 'per-session'
},
{
target: 'header',
name: 'User-Agent',
valueTemplate: `{{randomFromFile:${userAgentsFile}}}`,
scope: 'per-session'
},
{
target: 'header',
name: 'Authorization',
valueTemplate: `Bearer {{randomFromFile:${authTokensFile}}}`,
scope: 'per-session'
},
{
target: 'query',
name: 'sessionId',
valueTemplate: '{{random:alphanumeric}}',
scope: 'per-session'
},
{
target: 'header',
name: 'X-Complex',
valueTemplate: `{{randomFrom:environments}}_{{random:hex}}_{{randomFromFile:${authTokensFile}}}`,
scope: 'per-session'
}
];
// Define variable context for randomFrom
const variableContext: Partial<VariableContext> = {
deviceTypes: ['mobile', 'tablet', 'desktop'],
environments: ['prod', 'staging', 'dev']
};
// Create mock page
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
// Create interceptor
const interceptor = new RequestInterceptor(mockPage, templates, variableContext);
// Create test request
const testRequest: InterceptedRequest = {
url: 'https://example.com/test',
method: 'GET',
headers: {},
postData: undefined
};
// Test request modification
const modifyRequest = (interceptor as any).modifyRequest.bind(interceptor);
const result = modifyRequest(testRequest);
// Verify modification occurred
expect(result.modified).toBe(true);
expect(result.modifiedHeaders).toBeDefined();
expect(result.modifiedUrl).toBeDefined();
// Check that randomization was applied
const headers = result.modifiedHeaders!;
// Check UUID format
if (headers['X-Request-ID']) {
expect(headers['X-Request-ID']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
}
// Check random number range
if (headers['X-Random-Number']) {
const num = parseInt(headers['X-Random-Number'], 10);
expect(num).toBeGreaterThanOrEqual(1);
expect(num).toBeLessThanOrEqual(1000);
}
// Check randomFrom array selection
if (headers['X-Device-Type']) {
expect(['mobile', 'tablet', 'desktop']).toContain(headers['X-Device-Type']);
}
// Check randomFromFile selection
if (headers['User-Agent']) {
expect(['TestAgent/1.0', 'TestAgent/2.0', 'TestAgent/3.0']).toContain(headers['User-Agent']);
}
if (headers['Authorization']) {
expect(headers['Authorization']).toMatch(/^Bearer (token-alpha|token-beta|token-gamma)$/);
}
// Check query parameter randomization
if (result.modifiedUrl && result.modifiedUrl.includes('sessionId=')) {
const sessionIdMatch = result.modifiedUrl.match(/sessionId=([^&]+)/);
if (sessionIdMatch) {
expect(sessionIdMatch[1]).toMatch(/^[A-Za-z0-9]{8}$/);
}
}
// Check complex template
if (headers['X-Complex']) {
expect(headers['X-Complex']).toMatch(/^(prod|staging|dev)_[0-9a-f]+_(token-alpha|token-beta|token-gamma)$/);
}
});
it('should handle file caching correctly', () => {
const testFile = join(testDataDir, 'cached-values.txt');
writeFileSync(testFile, ['value1', 'value2', 'value3'].join('\n'));
const templates: ParameterTemplate[] = [{
target: 'header',
name: 'X-Cached-Value',
valueTemplate: `{{randomFromFile:${testFile}}}`,
scope: 'per-session'
}];
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
const interceptor = new RequestInterceptor(mockPage, templates);
// Test multiple substitutions to verify caching
const substituteVariables = (interceptor as any).substituteVariables.bind(interceptor);
const validValues = ['value1', 'value2', 'value3'];
for (let i = 0; i < 5; i++) {
const result = substituteVariables(`{{randomFromFile:${testFile}}}`);
expect(validValues).toContain(result);
}
});
it('should handle errors gracefully', () => {
const templates: ParameterTemplate[] = [
{
target: 'header',
name: 'X-Valid',
valueTemplate: '{{random:uuid}}',
scope: 'per-session'
},
{
target: 'header',
name: 'X-Invalid-File',
valueTemplate: '{{randomFromFile:/nonexistent/file.txt}}',
scope: 'per-session'
},
{
target: 'header',
name: 'X-Invalid-Array',
valueTemplate: '{{randomFrom:nonexistentArray}}',
scope: 'per-session'
}
];
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
const interceptor = new RequestInterceptor(mockPage, templates);
const testRequest: InterceptedRequest = {
url: 'https://example.com/test',
method: 'GET',
headers: {},
postData: undefined
};
const modifyRequest = (interceptor as any).modifyRequest.bind(interceptor);
const result = modifyRequest(testRequest);
// Verify request was processed despite errors
expect(result.modified).toBe(true);
expect(result.modifiedHeaders).toBeDefined();
const headers = result.modifiedHeaders!;
// Valid template should work
expect(headers['X-Valid']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
// Invalid templates should return original expressions (fallback behavior)
expect(headers['X-Invalid-File']).toBe('randomFromFile:/nonexistent/file.txt');
expect(headers['X-Invalid-Array']).toBe('randomFrom:nonexistentArray');
});
it('should work with different parameter targets', () => {
const templates: ParameterTemplate[] = [
{
target: 'header',
name: 'X-Header-Random',
valueTemplate: '{{random:uuid}}',
scope: 'per-session'
},
{
target: 'query',
name: 'queryRandom',
valueTemplate: '{{random:number}}',
scope: 'per-session'
},
{
target: 'body',
name: 'bodyRandom',
valueTemplate: '{{random:alphanumeric}}',
scope: 'per-session'
}
];
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
const interceptor = new RequestInterceptor(mockPage, templates);
const testRequest: InterceptedRequest = {
url: 'https://example.com/test',
method: 'POST',
headers: {},
postData: '{"existing": "data"}'
};
const modifyRequest = (interceptor as any).modifyRequest.bind(interceptor);
const result = modifyRequest(testRequest);
expect(result.modified).toBe(true);
// Check header randomization
expect(result.modifiedHeaders!['X-Header-Random']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
// Check query parameter randomization
expect(result.modifiedUrl).toMatch(/queryRandom=\d+/);
// Check body parameter randomization
if (result.modifiedPostData) {
const bodyData = JSON.parse(result.modifiedPostData);
expect(bodyData.bodyRandom).toMatch(/^[A-Za-z0-9]{8}$/);
expect(bodyData.existing).toBe('data'); // Original data should be preserved
}
});
it('should generate different values on multiple calls', () => {
const templates: ParameterTemplate[] = [{
target: 'header',
name: 'X-Random-UUID',
valueTemplate: '{{random:uuid}}',
scope: 'per-session'
}];
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
const interceptor = new RequestInterceptor(mockPage, templates);
const testRequest: InterceptedRequest = {
url: 'https://example.com/test',
method: 'GET',
headers: {},
postData: undefined
};
const modifyRequest = (interceptor as any).modifyRequest.bind(interceptor);
// Generate multiple values
const values = new Set<string>();
for (let i = 0; i < 10; i++) {
const result = modifyRequest(testRequest);
const uuid = result.modifiedHeaders!['X-Random-UUID'];
values.add(uuid);
}
// Should generate different UUIDs each time
expect(values.size).toBe(10);
});
it('should handle combined randomization methods in single template', () => {
const testFile = join(testDataDir, 'test-values.txt');
writeFileSync(testFile, ['file-value-1', 'file-value-2'].join('\n'));
const templates: ParameterTemplate[] = [{
target: 'header',
name: 'X-Combined',
valueTemplate: `{{randomFrom:types}}_{{random:number}}_{{randomFromFile:${testFile}}}_{{random:uuid}}`,
scope: 'per-session'
}];
const variableContext: Partial<VariableContext> = {
types: ['type-a', 'type-b']
};
const mockPage = {
route: () => Promise.resolve(),
on: () => {},
unroute: () => Promise.resolve()
} as any;
const interceptor = new RequestInterceptor(mockPage, templates, variableContext);
const testRequest: InterceptedRequest = {
url: 'https://example.com/test',
method: 'GET',
headers: {},
postData: undefined
};
const modifyRequest = (interceptor as any).modifyRequest.bind(interceptor);
const result = modifyRequest(testRequest);
expect(result.modified).toBe(true);
const combinedValue = result.modifiedHeaders!['X-Combined'];
expect(combinedValue).toMatch(/^(type-a|type-b)_\d+_(file-value-1|file-value-2)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
});
});