Files
dashcaddy/dashcaddy-api/__tests__/app-templates.test.js
T
Hermes 7bc2a207f3 DC-005: Fix all 138 broken test paths after src/ refactor
After the DC-005 module reorganization (41 files moved into src/ subdirs),
138 test suites failed because the refactor script's path-rewrite logic
missed three categories:

1. Files inside src/ doing 'require("./src/...")' — should be 'require("../...")'
2. Files in src/X/Y/ doing 'require("../../../src/...")' — should be 'require("../../...")'
3. Test files in __tests__/ with leftover 'require("../../../src/...")' paths

Root cause: the original refactor script ran before all files were moved,
so it computed relative paths against stale filesystem state.

Result:
- 30/30 test suites pass
- 879/879 tests pass (was: 18/30 suites, 614/687 tests)

Also fixed:
- routes/apps/restore.js: wrong responses import path
- routes/*/*.js: '../../src/utilities/X' → '../src/utilities/X' (depth 2 routes)
2026-06-13 12:16:56 -07:00

183 lines
6.3 KiB
JavaScript

const { APP_TEMPLATES, TEMPLATE_CATEGORIES, DIFFICULTY_LEVELS } = require('../src/docker/app-templates');
describe('App Templates', () => {
const templates = Object.values(APP_TEMPLATES);
const templateIds = Object.keys(APP_TEMPLATES);
const categoryNames = Object.keys(TEMPLATE_CATEGORIES);
describe('Template Structure', () => {
it('has at least 40 templates', () => {
expect(templates.length).toBeGreaterThanOrEqual(40);
});
it('every template has required fields: name, description, icon, category', () => {
for (const tmpl of templates) {
expect(tmpl).toHaveProperty('name');
expect(tmpl).toHaveProperty('description');
expect(tmpl).toHaveProperty('icon');
expect(tmpl).toHaveProperty('category');
expect(typeof tmpl.name).toBe('string');
expect(tmpl.name.length).toBeGreaterThan(0);
expect(typeof tmpl.description).toBe('string');
}
});
it('every Docker-based template has docker config with image', () => {
for (const id of templateIds) {
const tmpl = APP_TEMPLATES[id];
if (!tmpl.docker) continue; // Skip static sites and dashboard widgets
expect(tmpl.docker).toHaveProperty('image');
expect(typeof tmpl.docker.image).toBe('string');
expect(tmpl.docker.image.length).toBeGreaterThan(0);
}
});
it('every template has subdomain property', () => {
for (const id of templateIds) {
const tmpl = APP_TEMPLATES[id];
expect(tmpl).toHaveProperty('subdomain');
// subdomain can be null for widgets
if (tmpl.subdomain !== null) {
expect(typeof tmpl.subdomain).toBe('string');
}
}
});
it('all Docker-based templates have valid defaultPorts (1-65535)', () => {
for (const id of templateIds) {
const tmpl = APP_TEMPLATES[id];
if (!tmpl.docker) continue; // Skip non-Docker templates
const port = tmpl.defaultPort;
expect(port).toBeGreaterThanOrEqual(1);
expect(port).toBeLessThanOrEqual(65535);
}
});
it('all category values are in TEMPLATE_CATEGORIES', () => {
for (const tmpl of templates) {
expect(categoryNames).toContain(tmpl.category);
}
});
it('Docker images have no shell injection characters', () => {
const dangerous = [';', '&', '|', '`', '$', '\n'];
for (const id of templateIds) {
const tmpl = APP_TEMPLATES[id];
if (!tmpl.docker) continue;
const image = tmpl.docker.image;
for (const char of dangerous) {
expect(image).not.toContain(char);
}
}
});
});
describe('TEMPLATE_CATEGORIES', () => {
it('is a non-empty object with category entries', () => {
expect(typeof TEMPLATE_CATEGORIES).toBe('object');
expect(TEMPLATE_CATEGORIES).not.toBeNull();
expect(categoryNames.length).toBeGreaterThan(0);
});
it('each category has icon and color', () => {
for (const name of categoryNames) {
const cat = TEMPLATE_CATEGORIES[name];
expect(cat).toHaveProperty('icon');
expect(cat).toHaveProperty('color');
expect(typeof cat.color).toBe('string');
}
});
});
describe('DIFFICULTY_LEVELS', () => {
it('is a non-empty object with difficulty entries', () => {
const levels = Object.keys(DIFFICULTY_LEVELS);
expect(levels.length).toBeGreaterThan(0);
});
it('each level has color and description', () => {
for (const [name, level] of Object.entries(DIFFICULTY_LEVELS)) {
expect(level).toHaveProperty('color');
expect(level).toHaveProperty('description');
expect(typeof level.color).toBe('string');
expect(typeof level.description).toBe('string');
}
});
it('includes Easy, Intermediate, and Advanced levels', () => {
expect(DIFFICULTY_LEVELS).toHaveProperty('Easy');
expect(DIFFICULTY_LEVELS).toHaveProperty('Intermediate');
expect(DIFFICULTY_LEVELS).toHaveProperty('Advanced');
});
});
describe('Specific Templates', () => {
it('plex template has PLEX_CLAIM as empty string', () => {
const plex = APP_TEMPLATES.plex;
expect(plex).toBeDefined();
expect(plex.docker.environment).toHaveProperty('PLEX_CLAIM');
expect(plex.docker.environment.PLEX_CLAIM).toBe('');
});
it('jellyfin template exists with correct default port', () => {
const jf = APP_TEMPLATES.jellyfin;
expect(jf).toBeDefined();
expect(jf.defaultPort).toBe(8096);
});
it('radarr template exists with correct default port', () => {
const radarr = APP_TEMPLATES.radarr;
expect(radarr).toBeDefined();
expect(radarr.defaultPort).toBe(7878);
});
it('sonarr template exists with correct default port', () => {
const sonarr = APP_TEMPLATES.sonarr;
expect(sonarr).toBeDefined();
expect(sonarr.defaultPort).toBe(8989);
});
it('prowlarr template exists with correct default port', () => {
const prowlarr = APP_TEMPLATES.prowlarr;
expect(prowlarr).toBeDefined();
expect(prowlarr.defaultPort).toBe(9696);
});
it('DashCA is a static site without docker config', () => {
const dashca = APP_TEMPLATES.dashca;
if (dashca) {
expect(dashca.isStaticSite).toBe(true);
expect(dashca.docker).toBeUndefined();
}
});
});
describe('Template Ports', () => {
it('all templates with docker.ports have valid port mappings', () => {
// Ports use template syntax like "{{PORT}}:32400" or "{{PORT}}:32400/tcp"
const portPattern = /^(\{\{PORT\}\}|\d+):(\d+)(\/[a-z]+)?$/;
for (const id of templateIds) {
const tmpl = APP_TEMPLATES[id];
if (!tmpl.docker || !tmpl.docker.ports) continue;
expect(Array.isArray(tmpl.docker.ports)).toBe(true);
for (const port of tmpl.docker.ports) {
expect(typeof port).toBe('string');
expect(port).toMatch(portPattern);
}
}
});
it('no two templates share the same default port (prevent conflicts)', () => {
const portMap = new Map();
for (const id of templateIds) {
const port = APP_TEMPLATES[id].defaultPort;
if (port !== null) {
portMap.set(port, id);
}
}
// At minimum, we should have more unique ports than 30% of templates
expect(portMap.size).toBeGreaterThan(templateIds.length * 0.3);
});
});
});