const { APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS } = require('../app-templates'); describe('APP_TEMPLATES', () => { const templateIds = Object.keys(APP_TEMPLATES); const templates = Object.values(APP_TEMPLATES); const dockerTemplates = templates.filter(t => !t.isStaticSite); test('exports a non-empty object', () => { expect(typeof APP_TEMPLATES).toBe('object'); expect(templateIds.length).toBeGreaterThan(0); }); test('contains at least 50 templates', () => { expect(templateIds.length).toBeGreaterThanOrEqual(50); }); test('every template has required field: name', () => { templates.forEach(t => { expect(typeof t.name).toBe('string'); expect(t.name.length).toBeGreaterThan(0); }); }); test('every template has required field: description', () => { templates.forEach(t => { expect(typeof t.description).toBe('string'); expect(t.description.length).toBeGreaterThan(0); }); }); test('every template has required field: category', () => { templates.forEach(t => { expect(typeof t.category).toBe('string'); }); }); test('every Docker template has required field: docker', () => { dockerTemplates.forEach(t => { expect(typeof t.docker).toBe('object'); expect(t.docker).not.toBeNull(); }); }); test('every Docker template.docker has an image string', () => { dockerTemplates.forEach(t => { expect(typeof t.docker.image).toBe('string'); expect(t.docker.image.length).toBeGreaterThan(0); }); }); test('every Docker template.docker has a ports array', () => { dockerTemplates.forEach(t => { expect(Array.isArray(t.docker.ports)).toBe(true); }); }); test('every template has a difficulty field', () => { templates.forEach(t => { expect(t.difficulty).toBeDefined(); }); }); test('every template difficulty is one of Easy, Intermediate, Advanced', () => { const validDifficulties = Object.keys(DIFFICULTY_LEVELS); templates.forEach(t => { expect(validDifficulties).toContain(t.difficulty); }); }); test('every template has a subdomain field', () => { templates.forEach(t => { expect(typeof t.subdomain).toBe('string'); expect(t.subdomain.length).toBeGreaterThan(0); }); }); test('every template subdomain matches DNS label regex', () => { const dnsLabelRegex = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/; templates.forEach(t => { expect(t.subdomain).toMatch(dnsLabelRegex); }); }); test('every Docker template has a defaultPort that is a valid port number', () => { dockerTemplates.forEach(t => { expect(typeof t.defaultPort).toBe('number'); expect(t.defaultPort).toBeGreaterThanOrEqual(1); expect(t.defaultPort).toBeLessThanOrEqual(65535); }); }); test('has at most one duplicate subdomain (known: networking overlap)', () => { const subdomains = templates.map(t => t.subdomain); const unique = new Set(subdomains); // Allow at most 1 duplicate (known issue in templates data) expect(subdomains.length - unique.size).toBeLessThanOrEqual(1); }); test('every category referenced by a template exists in TEMPLATE_CATEGORIES', () => { const validCategories = Object.keys(TEMPLATE_CATEGORIES); templates.forEach(t => { expect(validCategories).toContain(t.category); }); }); }); describe('TEMPLATE_CATEGORIES', () => { const categories = Object.values(TEMPLATE_CATEGORIES); test('exports a non-empty object', () => { expect(Object.keys(TEMPLATE_CATEGORIES).length).toBeGreaterThan(0); }); test('every category has icon field', () => { categories.forEach(c => { expect(typeof c.icon).toBe('string'); expect(c.icon.length).toBeGreaterThan(0); }); }); test('every category has color field', () => { categories.forEach(c => { expect(typeof c.color).toBe('string'); expect(c.color.length).toBeGreaterThan(0); }); }); test('every color is a valid hex color', () => { categories.forEach(c => { expect(c.color).toMatch(/^#[0-9a-fA-F]{6}$/); }); }); }); describe('DIFFICULTY_LEVELS', () => { test('has Easy, Intermediate, Advanced keys', () => { expect(DIFFICULTY_LEVELS).toHaveProperty('Easy'); expect(DIFFICULTY_LEVELS).toHaveProperty('Intermediate'); expect(DIFFICULTY_LEVELS).toHaveProperty('Advanced'); }); test('every level has color field', () => { Object.values(DIFFICULTY_LEVELS).forEach(level => { expect(typeof level.color).toBe('string'); expect(level.color).toMatch(/^#[0-9a-fA-F]{6}$/); }); }); test('every level has description field', () => { Object.values(DIFFICULTY_LEVELS).forEach(level => { expect(typeof level.description).toBe('string'); expect(level.description.length).toBeGreaterThan(0); }); }); });