UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

316 lines (257 loc) 9.13 kB
/** * Unit Tests: Configuration Validator * Story 1.6: Environment Configuration * * Tests for config-validator.js */ const { validateEnvFormat, validateApiKeyFormat, validateYamlSyntax, validateCoreConfigStructure, validatePath, sanitizeInput, } = require('../../src/config/validation/config-validator'); describe('Configuration Validator', () => { describe('validateEnvFormat', () => { it('should validate correct .env format', () => { const content = `# Comment NODE_ENV=development API_KEY=sk-12345 DATABASE_URL=postgresql://localhost`; const result = validateEnvFormat(content); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should reject spaces around =', () => { const content = 'NODE_ENV = development'; const result = validateEnvFormat(content); expect(result.valid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('spaces around ='); }); it('should reject invalid key names', () => { const content = 'invalid-key=value'; const result = validateEnvFormat(content); expect(result.valid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors[0]).toContain('Invalid format'); }); it('should allow empty values', () => { const content = 'API_KEY='; const result = validateEnvFormat(content); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should skip comments and empty lines', () => { const content = `# Comment NODE_ENV=development # Another comment `; const result = validateEnvFormat(content); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); }); describe('validateApiKeyFormat', () => { it('should allow empty keys (skip logic)', () => { const result = validateApiKeyFormat(''); expect(result.valid).toBe(true); expect(result.error).toBeNull(); }); it('should reject whitespace-only keys', () => { const result = validateApiKeyFormat(' '); expect(result.valid).toBe(false); expect(result.error).toContain('whitespace'); }); it('should validate OpenAI key format', () => { const validKey = 'sk-1234567890abcdefghij'; const result = validateApiKeyFormat(validKey, 'openai'); expect(result.valid).toBe(true); expect(result.error).toBeNull(); }); it('should reject invalid OpenAI key format', () => { const invalidKey = 'invalid-key'; const result = validateApiKeyFormat(invalidKey, 'openai'); expect(result.valid).toBe(false); expect(result.error).toContain('sk-'); }); it('should validate Anthropic key format', () => { const validKey = 'sk-ant-1234567890abcdefghijklmnopqrstuv'; const result = validateApiKeyFormat(validKey, 'anthropic'); expect(result.valid).toBe(true); expect(result.error).toBeNull(); }); it('should reject invalid Anthropic key format', () => { const invalidKey = 'sk-1234'; const result = validateApiKeyFormat(invalidKey, 'anthropic'); expect(result.valid).toBe(false); expect(result.error).toContain('sk-ant-'); }); it('should validate GitHub token format', () => { const validToken = 'ghp_1234567890abcdefghij'; const result = validateApiKeyFormat(validToken, 'github'); expect(result.valid).toBe(true); expect(result.error).toBeNull(); }); it('should reject keys with spaces', () => { const keyWithSpace = 'sk-12345 67890'; const result = validateApiKeyFormat(keyWithSpace); expect(result.valid).toBe(false); expect(result.error).toContain('spaces'); }); it('should reject too short keys', () => { const shortKey = 'sk-123'; const result = validateApiKeyFormat(shortKey); expect(result.valid).toBe(false); expect(result.error).toContain('too short'); }); }); describe('validateYamlSyntax', () => { it('should validate correct YAML', () => { const content = ` project: type: GREENFIELD version: 2.1.0 qa: qaLocation: docs/qa `; const result = validateYamlSyntax(content); expect(result.valid).toBe(true); expect(result.error).toBeNull(); expect(result.parsed).toBeDefined(); expect(result.parsed.project.type).toBe('GREENFIELD'); }); it('should reject invalid YAML', () => { // Use truly invalid YAML syntax that js-yaml will reject const content = ` project: type: GREENFIELD !!invalid %%% syntax `; const result = validateYamlSyntax(content); expect(result.valid).toBe(false); expect(result.error).toContain('YAML syntax error'); expect(result.parsed).toBeNull(); }); it('should handle empty YAML', () => { const content = ''; const result = validateYamlSyntax(content); expect(result.valid).toBe(true); expect(result.parsed).toBeUndefined(); }); }); describe('validateCoreConfigStructure', () => { it('should validate correct core config structure', () => { const config = { project: { type: 'GREENFIELD' }, qa: { qaLocation: 'docs/qa' }, prd: { prdFile: 'docs/prd.md' }, architecture: { architectureFile: 'docs/architecture.md' }, }; const result = validateCoreConfigStructure(config); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should reject missing required fields', () => { const config = { project: { type: 'GREENFIELD' }, // Missing qa, prd, architecture }; const result = validateCoreConfigStructure(config); expect(result.valid).toBe(false); expect(result.errors.length).toBeGreaterThan(0); expect(result.errors).toContain('Missing required field: qa'); }); it('should reject invalid project type', () => { const config = { project: { type: 'INVALID_TYPE' }, qa: {}, prd: {}, architecture: {}, }; const result = validateCoreConfigStructure(config); expect(result.valid).toBe(false); expect(result.errors).toContain('Invalid project.type: INVALID_TYPE. Expected greenfield, brownfield, or existing_aios'); }); it('should validate IDE section if present', () => { const config = { project: { type: 'GREENFIELD' }, qa: {}, prd: {}, architecture: {}, ide: { selected: 'not-an-array' }, // Should be array }; const result = validateCoreConfigStructure(config); expect(result.valid).toBe(false); expect(result.errors).toContain('ide.selected must be an array'); }); }); describe('validatePath', () => { it('should validate correct paths', () => { const paths = [ '/absolute/path/to/file', 'relative/path/to/file', 'C:\\Windows\\Path\\File.txt', './current/directory', ]; paths.forEach(p => { const result = validatePath(p); expect(result.valid).toBe(true); expect(result.error).toBeNull(); }); }); it('should reject empty paths', () => { const result = validatePath(''); expect(result.valid).toBe(false); expect(result.error).toContain('empty'); }); it('should reject path traversal', () => { const result = validatePath('../../../etc/passwd'); expect(result.valid).toBe(false); expect(result.error).toContain('Path traversal'); }); it('should reject invalid characters', () => { const invalidPaths = [ 'path/with<angle>brackets', 'path/with|pipe', 'path/with"quote', 'path/with?question', ]; invalidPaths.forEach(p => { const result = validatePath(p); expect(result.valid).toBe(false); expect(result.error).toContain('invalid characters'); }); }); }); describe('sanitizeInput', () => { it('should remove null bytes', () => { const input = 'text\0with\0nulls'; const result = sanitizeInput(input); expect(result).toBe('textwithnulls'); }); it('should trim whitespace', () => { const input = ' whitespace around '; const result = sanitizeInput(input); expect(result).toBe('whitespace around'); }); it('should limit length', () => { const input = 'a'.repeat(20000); // Exceeds max length const result = sanitizeInput(input); expect(result.length).toBeLessThanOrEqual(10000); }); it('should handle non-string input', () => { const inputs = [null, undefined, 123, {}, []]; inputs.forEach(input => { const result = sanitizeInput(input); expect(result).toBe(''); }); }); it('should preserve valid input', () => { const input = 'Valid API Key sk-1234567890'; const result = sanitizeInput(input); expect(result).toBe(input); }); }); });