#!/usr/bin/env bun /** * Unit tests for validation logic * Tests name validation without external dependencies */ import { expect, test, describe } from 'bun:test'; // Extracted validation function for testing function validateStackName(name: string, reservedNames: string[] = []): { valid: boolean; error?: string } { if (!name || typeof name !== 'string') { return { valid: false, error: 'Name is required' }; } const trimmedName = name.trim().toLowerCase(); if (trimmedName.length < 3 || trimmedName.length > 20) { return { valid: false, error: 'Name must be between 3 and 20 characters' }; } if (!/^[a-z0-9-]+$/.test(trimmedName)) { return { valid: false, error: 'Name can only contain lowercase letters, numbers, and hyphens' }; } if (trimmedName.startsWith('-') || trimmedName.endsWith('-')) { return { valid: false, error: 'Name cannot start or end with a hyphen' }; } if (reservedNames.includes(trimmedName)) { return { valid: false, error: `Name "${trimmedName}" is reserved` }; } return { valid: true }; } describe('validateStackName', () => { const reservedNames = ['admin', 'api', 'www', 'root', 'system', 'test', 'demo', 'portal']; test('accepts valid names', () => { expect(validateStackName('john', reservedNames).valid).toBe(true); expect(validateStackName('alice-123', reservedNames).valid).toBe(true); expect(validateStackName('my-stack', reservedNames).valid).toBe(true); expect(validateStackName('abc', reservedNames).valid).toBe(true); expect(validateStackName('12345678901234567890', reservedNames).valid).toBe(true); // exactly 20 chars }); test('rejects empty or null names', () => { expect(validateStackName('', reservedNames).valid).toBe(false); expect(validateStackName(' ', reservedNames).valid).toBe(false); }); test('rejects names too short or too long', () => { expect(validateStackName('ab', reservedNames).error).toContain('between 3 and 20'); expect(validateStackName('a'.repeat(21), reservedNames).error).toContain('between 3 and 20'); }); test('rejects invalid characters', () => { const result1 = validateStackName('john_doe', reservedNames); expect(result1.valid).toBe(false); expect(result1.error).toBe('Name can only contain lowercase letters, numbers, and hyphens'); const result2 = validateStackName('john.doe', reservedNames); expect(result2.valid).toBe(false); expect(result2.error).toBe('Name can only contain lowercase letters, numbers, and hyphens'); const result3 = validateStackName('john doe', reservedNames); expect(result3.valid).toBe(false); expect(result3.error).toBe('Name can only contain lowercase letters, numbers, and hyphens'); const result4 = validateStackName('john@example', reservedNames); expect(result4.valid).toBe(false); expect(result4.error).toBe('Name can only contain lowercase letters, numbers, and hyphens'); }); test('rejects names with leading or trailing hyphens', () => { expect(validateStackName('-john', reservedNames).error).toContain('cannot start or end'); expect(validateStackName('john-', reservedNames).error).toContain('cannot start or end'); expect(validateStackName('-john-', reservedNames).error).toContain('cannot start or end'); }); test('rejects reserved names', () => { expect(validateStackName('admin', reservedNames).error).toContain('reserved'); expect(validateStackName('api', reservedNames).error).toContain('reserved'); expect(validateStackName('www', reservedNames).error).toContain('reserved'); expect(validateStackName('root', reservedNames).error).toContain('reserved'); }); test('normalizes names (lowercase, trim)', () => { // Uppercase input gets normalized to lowercase const result1 = validateStackName(' John ', reservedNames); expect(result1.valid).toBe(true); // passes after trim and lowercase // Already lowercase with spaces - should pass const result2 = validateStackName(' john ', reservedNames); expect(result2.valid).toBe(true); // All uppercase - should pass after normalization const result3 = validateStackName('MYSTACK', reservedNames); expect(result3.valid).toBe(true); }); }); console.log('\n✅ Running unit tests...\n');