- Move test files to tests/ - Archive session notes to docs/archive/ - Remove temp/diagnostic files - Clean src/ to only contain production code
104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
#!/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');
|