test: build comprehensive test suite reaching 80%+ coverage threshold

Add 22 test files (~700 tests) covering security-critical modules, core
infrastructure, API routes, and error handling. Final coverage: 86.73%
statements / 80.57% branches / 85.57% functions / 87.42% lines, all above
the 80% threshold enforced by jest.config.js.

Highlights:
- Unit tests for crypto-utils, credential-manager, auth-manager, csrf,
  input-validator, state-manager, health-checker, backup-manager,
  update-manager, resource-monitor, app-templates, platform-paths,
  port-lock-manager, errors, error-handler, pagination, url-resolver
- Route tests for health, services, and containers (supertest + mocked deps)
- Shared test-utils helper for mock factories and Express app builder
- npm scripts for CI: test:ci, test:unit, test:routes, test:security,
  test:changed, test:debug
- jest.config.js: expand coverage targets, add 80% threshold gate
- routes/services.js: import ValidationError and NotFoundError from errors
- .gitignore: exclude coverage/, *.bak, *.log

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 21:36:46 -07:00
parent bdf3f247b1
commit ea5acfa9a2
26 changed files with 8010 additions and 3 deletions

View File

@@ -0,0 +1,122 @@
const { resolveServiceUrl } = require('../url-resolver');
describe('URL Resolver — DashCaddy service URL resolution', () => {
const buildServiceUrl = jest.fn(id => `https://${id}.sami`);
beforeEach(() => {
buildServiceUrl.mockClear();
});
describe('Internet connectivity check', () => {
it('always resolves "internet" to google.com regardless of config', () => {
expect(resolveServiceUrl('internet', null, null, buildServiceUrl))
.toBe('https://www.google.com');
expect(buildServiceUrl).not.toHaveBeenCalled();
});
it('ignores service object for internet ID', () => {
const service = { url: 'http://custom.test', isExternal: true, externalUrl: 'http://ext.test' };
expect(resolveServiceUrl('internet', service, {}, buildServiceUrl))
.toBe('https://www.google.com');
});
});
describe('External services (seedhost, cloud-hosted)', () => {
it('uses externalUrl for services marked isExternal', () => {
const service = { isExternal: true, externalUrl: 'https://usw123.seedhost.eu/sami/radarr' };
expect(resolveServiceUrl('radarr', service, {}, buildServiceUrl))
.toBe('https://usw123.seedhost.eu/sami/radarr');
});
it('ignores isExternal if externalUrl is missing', () => {
const service = { isExternal: true };
expect(resolveServiceUrl('plex', service, {}, buildServiceUrl))
.toBe('https://plex.sami');
});
});
describe('Custom URL override on service', () => {
it('uses service.url with http prefix as-is', () => {
const service = { url: 'http://192.168.1.100:32400' };
expect(resolveServiceUrl('plex', service, {}, buildServiceUrl))
.toBe('http://192.168.1.100:32400');
});
it('uses service.url with https prefix as-is', () => {
const service = { url: 'https://plex.mydomain.com' };
expect(resolveServiceUrl('plex', service, {}, buildServiceUrl))
.toBe('https://plex.mydomain.com');
});
it('prepends https:// to bare hostnames', () => {
const service = { url: 'plex.sami' };
expect(resolveServiceUrl('plex', service, {}, buildServiceUrl))
.toBe('https://plex.sami');
});
});
describe('DNS server resolution (Technitium, Pi-hole)', () => {
it('resolves DNS server by ID from siteConfig', () => {
const siteConfig = {
dnsServers: {
dns1: { ip: '192.168.254.204', port: 5380 },
dns2: { ip: '100.74.102.61', port: 5380 },
}
};
expect(resolveServiceUrl('dns1', null, siteConfig, buildServiceUrl))
.toBe('http://192.168.254.204:5380');
expect(resolveServiceUrl('dns2', null, siteConfig, buildServiceUrl))
.toBe('http://100.74.102.61:5380');
});
it('defaults to port 5380 when port is omitted', () => {
const siteConfig = { dnsServers: { dns1: { ip: '10.0.0.1' } } };
expect(resolveServiceUrl('dns1', null, siteConfig, buildServiceUrl))
.toBe('http://10.0.0.1:5380');
});
});
describe('Fallback to buildServiceUrl (Caddy subdomain/subdirectory)', () => {
it('falls back for local services with no special config', () => {
resolveServiceUrl('radarr', { name: 'Radarr' }, {}, buildServiceUrl);
expect(buildServiceUrl).toHaveBeenCalledWith('radarr');
});
it('works when service is null (top-card items)', () => {
expect(resolveServiceUrl('sonarr', null, {}, buildServiceUrl))
.toBe('https://sonarr.sami');
});
it('works when siteConfig is null', () => {
expect(resolveServiceUrl('jellyfin', null, null, buildServiceUrl))
.toBe('https://jellyfin.sami');
});
});
describe('Priority chain — higher priority wins', () => {
const fullService = {
isExternal: true,
externalUrl: 'https://external.test',
url: 'http://custom.test',
};
const siteConfig = {
dnsServers: { myservice: { ip: '10.0.0.1', port: 5380 } }
};
it('externalUrl wins over service.url and DNS', () => {
expect(resolveServiceUrl('myservice', fullService, siteConfig, buildServiceUrl))
.toBe('https://external.test');
});
it('service.url wins over DNS and fallback', () => {
const service = { url: 'http://custom.test' };
expect(resolveServiceUrl('myservice', service, siteConfig, buildServiceUrl))
.toBe('http://custom.test');
});
it('DNS wins over fallback', () => {
expect(resolveServiceUrl('myservice', null, siteConfig, buildServiceUrl))
.toBe('http://10.0.0.1:5380');
});
});
});