UNPKG

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
/** * 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 };