Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
155
dashcaddy-api/__tests__/app-templates.test.js
Normal file
155
dashcaddy-api/__tests__/app-templates.test.js
Normal file
@@ -0,0 +1,155 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user