qapinterface
Version:
Comprehensive API utilities for Node.js applications including authentication, security, request processing, and response handling with zero external dependencies
531 lines (425 loc) • 17.4 kB
JavaScript
/**
* Comprehensive unit tests for utility modules
* Tests ID generation, timing, validation, and core utilities with mocked dependencies
*/
const { expect } = require('chai');
const sinon = require('sinon');
// Import modules to test
const { generateId } = require('../id/generator');
const { generateApiKey } = require('../id/api-key-generator');
const { generateSecureNonce } = require('../id/nonce-generator');
const { generateSyncId } = require('../id/sync-generator');
const { generateUniqueId } = require('../id/unique-generator');
const { startTiming, calculateProcessingTime } = require('../timing/timer');
const { sanitizeApiData } = require('../data/sanitizer');
const { formatApiVersion } = require('../versioning/formatter');
const { validateApiPath } = require('../validation/api-path-validator');
const { createStringValidator } = require('../validation/string-validator-factory');
describe('Utilities Module Tests', () => {
describe('ID Generation', () => {
it('should generate unique IDs', () => {
const id1 = generateId();
const id2 = generateId();
expect(typeof id1).to.equal('string');
expect(typeof id2).to.equal('string');
expect(id1 !== id2).to.equal(true);
expect(id1.length > 0).to.equal(true);
expect(id2.length > 0).to.equal(true);
});
it('should generate IDs with sufficient entropy', () => {
const ids = new Set();
for (let i = 0; i < 1000; i++) {
ids.add(generateId());
}
// All IDs should be unique
expect(ids.size).to.equal(1000);
});
it('should generate IDs with consistent format', () => {
const id = generateId();
// Should be alphanumeric with possible hyphens/underscores
expect(/^[a-zA-Z0-9_-]+$/.test(id)).to.equal(true);
expect(id.length >= 16).to.equal(true); // Minimum secure length
});
it('should generate sync IDs consistently', () => {
const syncId1 = generateSyncId('test-seed');
const syncId2 = generateSyncId('test-seed');
const syncId3 = generateSyncId('different-seed');
expect(syncId1).to.equal(syncId2); // Same seed = same ID
expect(syncId1 !== syncId3).to.equal(true); // Different seed = different ID
});
it('should generate unique IDs with timestamp', () => {
const uniqueId1 = generateUniqueId();
// Small delay to ensure different timestamp
const start = Date.now();
while (Date.now() - start < 2) { /* wait */ }
const uniqueId2 = generateUniqueId();
expect(typeof uniqueId1).to.equal('string');
expect(typeof uniqueId2).to.equal('string');
expect(uniqueId1 !== uniqueId2).to.equal(true);
});
});
describe('API Key Generation', () => {
it('should generate secure API keys', () => {
const apiKey = generateApiKey();
expect(typeof apiKey).to.equal('string');
expect(apiKey.length >= 32).to.equal(true);
expect(/^[a-zA-Z0-9_.-]+$/.test(apiKey)).to.equal(true);
});
it('should generate different keys each time', () => {
const keys = [];
for (let i = 0; i < 10; i++) {
keys.push(generateApiKey());
}
const uniqueKeys = new Set(keys);
expect(uniqueKeys.size).to.equal(keys.length);
});
it('should generate keys with custom length', () => {
const shortKey = generateApiKey({ length: 16 });
const longKey = generateApiKey({ length: 64 });
expect(shortKey.length).to.equal(16);
expect(longKey.length).to.equal(64);
});
});
describe('Nonce Generation', () => {
it('should generate secure nonces', () => {
const nonce = generateSecureNonce();
expect(typeof nonce).to.equal('string');
expect(nonce.length >= 16).to.equal(true);
});
it('should generate unique nonces', () => {
const nonce1 = generateSecureNonce();
const nonce2 = generateSecureNonce();
expect(nonce1 !== nonce2).to.equal(true);
});
it('should generate base64-safe nonces', () => {
const nonce = generateSecureNonce();
// Should be URL-safe base64 characters
expect(/^[a-zA-Z0-9_-]+$/.test(nonce)).to.equal(true);
});
});
describe('Timing Utilities', () => {
it('should start timing measurement', () => {
const timing = startTiming();
expect(typeof timing).to.equal('object');
expect(typeof timing.startTime).to.equal('number');
expect(timing.startTime > 0).to.equal(true);
});
it('should calculate processing time', () => {
const timing = startTiming();
// Simulate some processing time
const start = Date.now();
while (Date.now() - start < 5) { /* wait 5ms */ }
const processingTime = calculateProcessingTime(timing);
expect(typeof processingTime).to.equal('number');
expect(processingTime >= 0).to.equal(true);
expect(processingTime < 1000).to.equal(true); // Should be under 1 second
});
it('should handle high-precision timing', () => {
const timing = startTiming();
const processingTime = calculateProcessingTime(timing);
// Should support sub-millisecond precision
expect(processingTime >= 0).to.equal(true);
expect(typeof processingTime).to.equal('number');
});
it('should handle multiple timing measurements', () => {
const timing1 = startTiming();
const timing2 = startTiming();
const time1 = calculateProcessingTime(timing1);
const time2 = calculateProcessingTime(timing2);
expect(typeof time1).to.equal('number');
expect(typeof time2).to.equal('number');
expect(time1 >= 0).to.equal(true);
expect(time2 >= 0).to.equal(true);
});
});
describe('Data Sanitization', () => {
it('should sanitize API data safely', () => {
const unsafeData = {
username: 'john<script>alert("xss")</script>',
email: 'john@example.com',
description: 'User description with <img src=x onerror=alert(1)>'
};
const sanitized = sanitizeApiData(unsafeData);
expect(typeof sanitized).to.equal('object');
expect(sanitized.email).to.equal('john@example.com'); // Safe data unchanged
expect(!sanitized.username.includes('<script>')).to.equal(true);
expect(!sanitized.description.includes('onerror')).to.equal(true);
});
it('should preserve safe data', () => {
const safeData = {
name: 'John Doe',
age: 30,
active: true,
metadata: {
created: '2025-01-01T00:00:00Z',
tags: ['user', 'verified']
}
};
const sanitized = sanitizeApiData(safeData);
expect(sanitized.name).to.equal('John Doe');
expect(sanitized.age).to.equal(30);
expect(sanitized.active).to.equal(true);
expect(sanitized.metadata.created).to.equal('2025-01-01T00:00:00Z');
expect(Array.isArray(sanitized.metadata.tags)).to.equal(true);
});
it('should handle nested objects', () => {
const nestedData = {
user: {
profile: {
bio: 'Safe bio content',
website: 'https://example.com'
}
}
};
const sanitized = sanitizeApiData(nestedData);
expect(sanitized.user.profile.bio).to.equal('Safe bio content');
expect(sanitized.user.profile.website).to.equal('https://example.com');
});
it('should handle arrays', () => {
const arrayData = {
items: [
{ name: 'Item 1', safe: true },
{ name: 'Item<script>', safe: false }
]
};
const sanitized = sanitizeApiData(arrayData);
expect(Array.isArray(sanitized.items)).to.equal(true);
expect(sanitized.items[0].name).to.equal('Item 1');
expect(!sanitized.items[1].name.includes('<script>')).to.equal(true);
});
it('should handle null and undefined values', () => {
const dataWithNulls = {
validField: 'value',
nullField: null,
undefinedField: undefined,
emptyString: ''
};
const sanitized = sanitizeApiData(dataWithNulls);
expect(sanitized.validField).to.equal('value');
expect(sanitized.nullField).to.equal(null);
expect(sanitized.emptyString).to.equal('');
});
});
describe('Version Formatting', () => {
it('should format API version with default prefix', () => {
const formatted = formatApiVersion('1.2.3');
expect(typeof formatted).to.equal('string');
expect(formatted.includes('1.2.3')).to.equal(true);
expect(formatted.startsWith('v')).to.equal(true);
});
it('should format version with custom prefix', () => {
const formatted = formatApiVersion('2.0.0', 'api-');
expect(formatted.startsWith('api-')).to.equal(true);
expect(formatted.includes('2.0.0')).to.equal(true);
});
it('should handle numeric versions', () => {
const formatted = formatApiVersion(1.5);
expect(typeof formatted).to.equal('string');
expect(formatted.includes('1.5')).to.equal(true);
});
it('should preserve existing prefix', () => {
const formatted = formatApiVersion('v3.1.4');
expect(formatted).to.equal('v3.1.4');
expect(formatted.indexOf('v')).to.equal(0); // Only one 'v' at start
});
it('should handle beta and pre-release versions', () => {
const betaFormatted = formatApiVersion('2.0.0-beta.1');
const alphaFormatted = formatApiVersion('1.0.0-alpha');
expect(betaFormatted.includes('beta')).to.equal(true);
expect(alphaFormatted.includes('alpha')).to.equal(true);
});
});
describe('API Path Validation', () => {
it('should validate correct API paths', () => {
const validPaths = [
'/api/users',
'/api/v1/users/123',
'/api/v2/posts/456/comments',
'/health',
'/status'
];
validPaths.forEach(path => {
const result = validateApiPath(path);
expect(result.valid).to.equal(true);
});
});
it('should reject invalid API paths', () => {
const invalidPaths = [
'',
'no-leading-slash',
'/api/../../../etc/passwd',
'/api/users/<script>',
'/api/users?query=<script>alert(1)</script>'
];
invalidPaths.forEach(path => {
const result = validateApiPath(path);
expect(result.valid).to.equal(false);
expect(typeof result.reason).to.equal('string');
});
});
it('should provide detailed validation errors', () => {
const result = validateApiPath('invalid-path');
expect(result.valid).to.equal(false);
expect(typeof result.reason).to.equal('string');
expect(result.reason.length > 0).to.equal(true);
});
it('should validate path parameters', () => {
const pathWithParams = '/api/users/:id/posts/:postId';
const result = validateApiPath(pathWithParams);
expect(result.valid).to.equal(true);
});
it('should detect path traversal attempts', () => {
const traversalPaths = [
'/api/../admin',
'/api/users/../../secrets',
'/api/files/%2e%2e%2fadmin'
];
traversalPaths.forEach(path => {
const result = validateApiPath(path);
expect(result.valid).to.equal(false);
expect(result.reason.includes('traversal')).to.equal(true);
});
});
});
describe('String Validator Factory', () => {
it('should create length validator', () => {
const validator = createStringValidator({
minLength: 5,
maxLength: 20
});
expect(typeof validator).to.equal('function');
const validResult = validator('hello world');
expect(validResult.valid).to.equal(true);
const shortResult = validator('hi');
expect(shortResult.valid).to.equal(false);
const longResult = validator('this is a very long string that exceeds the limit');
expect(longResult.valid).to.equal(false);
});
it('should create pattern validator', () => {
const emailValidator = createStringValidator({
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
patternDescription: 'valid email format'
});
const validEmail = emailValidator('user@example.com');
expect(validEmail.valid).to.equal(true);
const invalidEmail = emailValidator('invalid-email');
expect(invalidEmail.valid).to.equal(false);
expect(invalidEmail.errors[0].includes('email format')).to.equal(true);
});
it('should create composite validator', () => {
const usernameValidator = createStringValidator({
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/,
patternDescription: 'alphanumeric characters and underscores only'
});
const validUsername = usernameValidator('john_doe_123');
expect(validUsername.valid).to.equal(true);
const shortUsername = usernameValidator('ab');
expect(shortUsername.valid).to.equal(false);
const invalidCharUsername = usernameValidator('john-doe!');
expect(invalidCharUsername.valid).to.equal(false);
});
it('should handle required vs optional strings', () => {
const requiredValidator = createStringValidator({
required: true,
minLength: 1
});
const optionalValidator = createStringValidator({
required: false,
minLength: 5
});
expect(requiredValidator('').valid).to.equal(false);
expect(requiredValidator(null).valid).to.equal(false);
expect(optionalValidator('').valid).to.equal(true);
expect(optionalValidator(null).valid).to.equal(true);
expect(optionalValidator('hello').valid).to.equal(true);
expect(optionalValidator('hi').valid).to.equal(false); // Too short when provided
});
it('should provide detailed error messages', () => {
const validator = createStringValidator({
minLength: 5,
maxLength: 10,
pattern: /^[A-Z]+$/,
patternDescription: 'uppercase letters only'
});
const result = validator('abc');
expect(result.valid).to.equal(false);
expect(result.errors.length > 0).to.equal(true);
expect(result.errors.some(e => e.includes('length'))).to.equal(true);
expect(result.errors.some(e => e.includes('uppercase'))).to.equal(true);
});
});
describe('Utility Integration', () => {
it('should work together in data processing pipeline', () => {
// Generate request ID
const requestId = generateId();
// Start timing
const timing = startTiming();
// Process data
const rawData = {
id: requestId,
content: 'User content with <script>alert("xss")</script>',
version: '1.0.0'
};
const sanitizedData = sanitizeApiData(rawData);
const formattedVersion = formatApiVersion(sanitizedData.version);
// Calculate processing time
const processingTime = calculateProcessingTime(timing);
expect(sanitizedData.id).to.equal(requestId);
expect(!sanitizedData.content.includes('<script>')).to.equal(true);
expect(formattedVersion.startsWith('v')).to.equal(true);
expect(typeof processingTime).to.equal('number');
});
it('should handle error cases gracefully', () => {
// Test with null inputs
const nullSanitized = sanitizeApiData(null);
expect(nullSanitized).to.equal(null);
// Test with undefined timing
try {
calculateProcessingTime(null);
expect(false).to.equal(true); // Should throw
} catch (error) {
expect(error instanceof Error).to.equal(true);
}
// Test with invalid version
const invalidVersion = formatApiVersion('');
expect(typeof invalidVersion).to.equal('string');
});
it('should maintain performance under load', () => {
const startTime = Date.now();
// Generate many IDs quickly
const ids = [];
for (let i = 0; i < 1000; i++) {
ids.push(generateId());
}
const endTime = Date.now();
const duration = endTime - startTime;
expect(ids.length).to.equal(1000);
expect(duration < 1000).to.equal(true); // Should complete in under 1 second
expect(new Set(ids).size).to.equal(1000); // All unique
});
it('should validate complex API structures', () => {
const apiStructure = {
endpoint: '/api/v1/users/123',
version: '1.2.3',
data: {
name: 'John Doe',
email: 'john@example.com',
bio: 'Safe bio content'
}
};
// Validate path
const pathValidation = validateApiPath(apiStructure.endpoint);
expect(pathValidation.valid).to.equal(true);
// Format version
const formattedVersion = formatApiVersion(apiStructure.version);
expect(formattedVersion).to.equal('v1.2.3');
// Sanitize data
const sanitizedData = sanitizeApiData(apiStructure.data);
expect(sanitizedData.name).to.equal('John Doe');
expect(sanitizedData.email).to.equal('john@example.com');
});
});
});
module.exports = { runUtilitiesTests: () => expect().to.be.undefined };