Fix DC-005 depth-2 route path bugs: 67 broken requires across 21 files
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

The DC-005 src/ refactor left depth-2 route files (routes/auth/*,
routes/recipes/*, routes/apps/*, routes/arr/*, routes/config/*) with
broken require() paths. A filesystem-resolving scanner found 67 broken
requires across 21 files — three distinct bug classes:

  A) '../../../src/...' (3 levels up, above package root) — Bug 7, ~49 occurrences
  B) '../src/utils/...' (1 level up, resolves to nonexistent routes/src/) — ~15 occurrences
  C) routes/apps/restore.js:5 used utilities/responses (wrong dir) — should be utils/responses

All fixed to '../../src/...' (or '../../src/utils/responses' for class C).
routes/auth/totp.js was already fixed in the DC-006 commit.

Post-fix: 922/922 tests pass, zero new ESLint warnings. No logic changes —
purely mechanical require() path corrections.
This commit is contained in:
Hermes
2026-06-25 16:55:06 -07:00
parent 57a6a22f89
commit c39c80b3ad
22 changed files with 68 additions and 68 deletions
+1 -1
View File
@@ -48,7 +48,7 @@
- **owner:** krystie - **owner:** krystie
- **details:** 40+ JS files at `dashcaddy-api/` root level (auth-manager.js, credential-manager.js, etc.). Move into organized subdirs under `src/` (e.g., `src/managers/`, `src/security/`, `src/docker/`). Update all require() paths. This is a big refactor — run tests after. - **details:** 40+ JS files at `dashcaddy-api/` root level (auth-manager.js, credential-manager.js, etc.). Move into organized subdirs under `src/` (e.g., `src/managers/`, `src/security/`, `src/docker/`). Update all require() paths. This is a big refactor — run tests after.
- **result:** Refactor complete on `krystie-improvements` branch (879/879 tests passing on branch). Merged into main via commit `283121e` after resolving 24 conflicts. Post-merge regression check surfaced one additional latent path bug from DC-005: `src/monitoring/health-checker.js` still had `require('./platform-paths')` (relative to `src/monitoring/`), but `platform-paths.js` lives at top level — fixed in commit `9688e64` to `require('../../platform-paths')`. Without that fix, 59 cascading test failures in `health-checker.test.js`. Final post-merge state: 921/922 tests passing. - **result:** Refactor complete on `krystie-improvements` branch (879/879 tests passing on branch). Merged into main via commit `283121e` after resolving 24 conflicts. Post-merge regression check surfaced one additional latent path bug from DC-005: `src/monitoring/health-checker.js` still had `require('./platform-paths')` (relative to `src/monitoring/`), but `platform-paths.js` lives at top level — fixed in commit `9688e64` to `require('../../platform-paths')`. Without that fix, 59 cascading test failures in `health-checker.test.js`. Final post-merge state: 921/922 tests passing.
- **remaining latent bugs (tracked, NOT yet fixed):** The DC-005 path-rewrite script left depth-2 route files (`routes/auth/*.js`, `routes/recipes/*.js`, `routes/apps/*.js`, `routes/arr/*.js`, `routes/config/*.js`) with `'../../../src/...'`**3 levels up instead of 2**, which goes above `dashcaddy-api/` entirely. Required path should be `'../../src/...'` for depth-2 routes. Tests didn't catch this because no test previously imported any depth-2 route (only depth-1 routes like `routes/services.js` were tested). Confirmed-broken imports (with file → offending line): `routes/auth/totp.js:2` (FIXED in DC-006 commit), `routes/auth/keys.js:2`, `routes/auth/sso-gate.js:2-3`, `routes/auth/session-handlers.js:2-3`, `routes/recipes/manage.js:2-3`, `routes/recipes/deploy.js:2-3`, `routes/recipes/index.js:2-3`, `routes/config/assets.js:2-4`, `routes/config/settings.js:2-4`, `routes/config/backup.js:2-4`, `routes/apps/restore.js:2`, `routes/apps/compose.js:2-3`, `routes/apps/deploy.js:2-5`, `routes/apps/helpers.js:2-3`, `routes/apps/templates.js:2-3`, `routes/apps/removal.js:2-3`, `routes/arr/detect.js:2`, `routes/arr/smart-connect.js:2`, `routes/arr/credentials.js:2-3`, `routes/arr/helpers.js:2`, `routes/arr/config.js:2-5`, `routes/arr/plex.js:2`. The fix is mechanical (3 → 2 levels) but touches ~22 files — should be its own PR/commit for clean review. - **remaining latent bugs (FIXED):** The DC-005 path-rewrite script left depth-2 route files (`routes/auth/*.js`, `routes/recipes/*.js`, `routes/apps/*.js`, `routes/arr/*.js`, `routes/config/*.js`) with broken require() paths. A filesystem-resolving scanner found **67 broken requires across 21 files** — three distinct bug classes: (A) `'../../../src/...'` (3 levels up, goes above package root) — the documented Bug 7, ~49 occurrences; (B) `'../src/utils/...'` (only 1 level up, resolves to nonexistent `routes/src/`) — undocumented, ~15 occurrences for `responses` and `logging`; (C) `routes/apps/restore.js:5` imported `utilities/responses` when the module lives at `utils/responses` (wrong directory + wrong depth). All 67 fixed to `'../../src/...'` (or `'../../src/utils/responses'` for the class-C case). `routes/auth/totp.js` was already fixed in the DC-006 commit. Tests didn't catch any of these previously because no test imported any depth-2 route. Post-fix: 922/922 tests pass, zero new ESLint warnings.
### DC-006: Add integration test for TOTP auth flow ### DC-006: Add integration test for TOTP auth flow
- **status:** done - **status:** done
+3 -3
View File
@@ -1,9 +1,9 @@
const express = require('express'); const express = require('express');
const yaml = require('js-yaml'); const yaml = require('js-yaml');
const { DOCKER, REGEX } = require('../../../src/utilities/constants'); const { DOCKER, REGEX } = require('../../src/utilities/constants');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Docker Compose import routes * Docker Compose import routes
+6 -6
View File
@@ -2,13 +2,13 @@ const express = require('express');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const path = require('path'); const path = require('path');
const validatorLib = require('validator'); const validatorLib = require('validator');
const { REGEX, DOCKER } = require('../../../src/utilities/constants'); const { REGEX, DOCKER } = require('../../src/utilities/constants');
const { isValidPort } = require('../../../src/security/input-validator'); const { isValidPort } = require('../../src/security/input-validator');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const { logError } = require('../src/utils/logging'); const { logError } = require('../../src/utils/logging');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Apps deployment routes factory * Apps deployment routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies
+2 -2
View File
@@ -2,8 +2,8 @@ const fs = require('fs');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const path = require('path'); const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const { REGEX, DOCKER } = require('../../../src/utilities/constants'); const { REGEX, DOCKER } = require('../../src/utilities/constants');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
/** /**
+3 -3
View File
@@ -1,7 +1,7 @@
const express = require('express'); const express = require('express');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const { logError } = require('../src/utils/logging'); const { logError } = require('../../src/utils/logging');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
module.exports = function({ module.exports = function({
docker, caddy, servicesStateManager, asyncHandler, log, helpers, docker, caddy, servicesStateManager, asyncHandler, log, helpers,
+2 -2
View File
@@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { DOCKER } = require('../../../src/utilities/constants'); const { DOCKER } = require('../../src/utilities/constants');
const { ok, validationError, notFound, errorResponse } = require('../../../src/utilities/responses'); const { ok, validationError, notFound, errorResponse } = require('../../src/utils/responses');
const DEFAULT_BACKUP_DIR = process.env.BACKUP_DIR || path.join(__dirname, '..', 'backups'); const DEFAULT_BACKUP_DIR = process.env.BACKUP_DIR || path.join(__dirname, '..', 'backups');
+5 -5
View File
@@ -1,5 +1,5 @@
const express = require('express'); const express = require('express');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
/** /**
* Apps templates routes factory * Apps templates routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies
@@ -19,8 +19,8 @@ const { exists } = require('../../../src/utilities/fs-helpers');
* @param {string} deps.SERVICES_FILE - Services file path * @param {string} deps.SERVICES_FILE - Services file path
* @returns {express.Router} * @returns {express.Router}
*/ */
const { REGEX } = require('../../../src/utilities/constants'); const { REGEX } = require('../../src/utilities/constants');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
module.exports = function({ module.exports = function({
servicesStateManager, asyncHandler, helpers, servicesStateManager, asyncHandler, helpers,
@@ -55,7 +55,7 @@ module.exports = function({
const { appId } = req.params; const { appId } = req.params;
const template = ctx.APP_TEMPLATES[appId]; const template = ctx.APP_TEMPLATES[appId];
if (!template) { if (!template) {
const { NotFoundError } = require('../../../src/utilities/errors'); const { NotFoundError } = require('../../src/utilities/errors');
throw new NotFoundError('App template'); throw new NotFoundError('App template');
} }
ok(res, { template }); ok(res, { template });
@@ -90,7 +90,7 @@ module.exports = function({
// Update subdomain for deployed app // Update subdomain for deployed app
router.post('/update-subdomain', asyncHandler(async (req, res) => { router.post('/update-subdomain', asyncHandler(async (req, res) => {
const { serviceId, oldSubdomain, newSubdomain, containerId, ip } = req.body; const { serviceId, oldSubdomain, newSubdomain, containerId, ip } = req.body;
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
if (!oldSubdomain || typeof oldSubdomain !== 'string') { if (!oldSubdomain || typeof oldSubdomain !== 'string') {
throw new ValidationError('oldSubdomain is required'); throw new ValidationError('oldSubdomain is required');
+5 -5
View File
@@ -1,9 +1,9 @@
const express = require('express'); const express = require('express');
const { APP_PORTS, ARR_SERVICES } = require('../../../src/utilities/constants'); const { APP_PORTS, ARR_SERVICES } = require('../../src/utilities/constants');
const { validateURL, validateToken } = require('../../../src/security/input-validator'); const { validateURL, validateToken } = require('../../src/security/input-validator');
const { ValidationError, AuthenticationError, NotFoundError } = require('../../../src/utilities/errors'); const { ValidationError, AuthenticationError, NotFoundError } = require('../../src/utilities/errors');
const { logError } = require('../src/utils/logging'); const { logError } = require('../../src/utils/logging');
const { ok, successMessage } = require('../src/utils/responses'); const { ok, successMessage } = require('../../src/utils/responses');
/** /**
* Arr configuration routes factory * Arr configuration routes factory
+3 -3
View File
@@ -1,7 +1,7 @@
const express = require('express'); const express = require('express');
const { validateURL, validateToken } = require('../../../src/security/input-validator'); const { validateURL, validateToken } = require('../../src/security/input-validator');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const { ok, successMessage } = require('../src/utils/responses'); const { ok, successMessage } = require('../../src/utils/responses');
/** /**
* Arr credentials routes factory * Arr credentials routes factory
+2 -2
View File
@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const { APP_PORTS, ARR_SERVICES } = require('../../../src/utilities/constants'); const { APP_PORTS, ARR_SERVICES } = require('../../src/utilities/constants');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Arr service detection routes factory * Arr service detection routes factory
+1 -1
View File
@@ -1,4 +1,4 @@
const { APP_PORTS } = require('../../../src/utilities/constants'); const { APP_PORTS } = require('../../src/utilities/constants');
/** /**
* Arr helpers factory * Arr helpers factory
+2 -2
View File
@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const { APP_PORTS } = require('../../../src/utilities/constants'); const { APP_PORTS } = require('../../src/utilities/constants');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Plex routes factory * Plex routes factory
+1 -1
View File
@@ -1,5 +1,5 @@
const express = require('express'); const express = require('express');
const { APP_PORTS } = require('../../../src/utilities/constants'); const { APP_PORTS } = require('../../src/utilities/constants');
/** /**
* Arr smart-connect routes factory * Arr smart-connect routes factory
+2 -2
View File
@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const { ValidationError, ForbiddenError, NotFoundError } = require('../../../src/utilities/errors'); const { ValidationError, ForbiddenError, NotFoundError } = require('../../src/utilities/errors');
const { ok, successMessage } = require('../src/utils/responses'); const { ok, successMessage } = require('../../src/utils/responses');
/** /**
* Auth API keys routes factory * Auth API keys routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies
@@ -1,5 +1,5 @@
const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../../src/utilities/constants'); const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../src/utilities/constants');
const { createCache, CACHE_CONFIGS } = require('../../../src/utilities/cache-config'); const { createCache, CACHE_CONFIGS } = require('../../src/utilities/cache-config');
/** /**
* Auth session handlers routes factory * Auth session handlers routes factory
+2 -2
View File
@@ -1,6 +1,6 @@
const express = require('express'); const express = require('express');
const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../../src/utilities/constants'); const { SESSION_TTL, APP, PLEX, TIMEOUTS, buildMediaAuth } = require('../../src/utilities/constants');
const { AuthenticationError, NotFoundError } = require('../../../src/utilities/errors'); const { AuthenticationError, NotFoundError } = require('../../src/utilities/errors');
/** /**
* Auth SSO gate routes factory * Auth SSO gate routes factory
+4 -4
View File
@@ -1,11 +1,11 @@
const express = require('express'); const express = require('express');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const path = require('path'); const path = require('path');
const { LIMITS } = require('../../../src/utilities/constants'); const { LIMITS } = require('../../src/utilities/constants');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const { ok, successMessage } = require('../src/utils/responses'); const { ok, successMessage } = require('../../src/utils/responses');
/** /**
* Config assets routes factory * Config assets routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies
+5 -5
View File
@@ -1,11 +1,11 @@
const fsp = require('fs').promises; const fsp = require('fs').promises;
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { CADDY } = require('../../../src/utilities/constants'); const { CADDY } = require('../../src/utilities/constants');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const { ValidationError, AuthenticationError } = require('../../../src/utilities/errors'); const { ValidationError, AuthenticationError } = require('../../src/utilities/errors');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Config backup routes factory * Config backup routes factory
@@ -380,7 +380,7 @@ module.exports = function(deps) {
if (results.restored.includes('encryptionKey')) { if (results.restored.includes('encryptionKey')) {
try { try {
// Clear the cached key so crypto-utils reloads from the new file on next use // Clear the cached key so crypto-utils reloads from the new file on next use
const cryptoUtils = require('../../../src/security/crypto-utils'); const cryptoUtils = require('../../src/security/crypto-utils');
if (typeof cryptoUtils.clearCachedKey === 'function') { if (typeof cryptoUtils.clearCachedKey === 'function') {
cryptoUtils.clearCachedKey(); cryptoUtils.clearCachedKey();
} }
+4 -4
View File
@@ -1,8 +1,8 @@
const fsp = require('fs').promises; const fsp = require('fs').promises;
const { validateConfig } = require('../../../src/utilities/config-schema'); const { validateConfig } = require('../../src/utilities/config-schema');
const { exists } = require('../../../src/utilities/fs-helpers'); const { exists } = require('../../src/utilities/fs-helpers');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const { ok, successMessage } = require('../src/utils/responses'); const { ok, successMessage } = require('../../src/utils/responses');
/** /**
* Config settings routes factory * Config settings routes factory
+4 -4
View File
@@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const { ValidationError } = require('../../../src/utilities/errors'); const { ValidationError } = require('../../src/utilities/errors');
const crypto = require('crypto'); const crypto = require('crypto');
const { DOCKER } = require('../../../src/utilities/constants'); const { DOCKER } = require('../../src/utilities/constants');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Recipes deployment routes factory * Recipes deployment routes factory
@@ -28,7 +28,7 @@ module.exports = function({ docker, credentialManager: _credentialManager, servi
// eslint-disable-next-line complexity // eslint-disable-next-line complexity
router.post('/deploy', asyncHandler(async (req, res) => { router.post('/deploy', asyncHandler(async (req, res) => {
const { recipeId, config } = req.body; const { recipeId, config } = req.body;
const { RECIPE_TEMPLATES } = require('../../../src/recipes/recipe-templates'); const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
const recipe = RECIPE_TEMPLATES[recipeId]; const recipe = RECIPE_TEMPLATES[recipeId];
if (!recipe) throw new ValidationError('Invalid recipe template', 'recipeId'); if (!recipe) throw new ValidationError('Invalid recipe template', 'recipeId');
+4 -4
View File
@@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const deployRoutes = require('./deploy'); const deployRoutes = require('./deploy');
const manageRoutes = require('./manage'); const manageRoutes = require('./manage');
const { NotFoundError } = require('../../../src/utilities/errors'); const { NotFoundError } = require('../../src/utilities/errors');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
/** /**
* Recipes routes aggregator * Recipes routes aggregator
@@ -32,7 +32,7 @@ module.exports = function(ctx) {
// GET /api/recipes/templates — list all recipe templates // GET /api/recipes/templates — list all recipe templates
router.get('/templates', deps.asyncHandler(async (req, res) => { router.get('/templates', deps.asyncHandler(async (req, res) => {
const { RECIPE_TEMPLATES, RECIPE_CATEGORIES } = require('../../../src/recipes/recipe-templates'); const { RECIPE_TEMPLATES, RECIPE_CATEGORIES } = require('../../src/recipes/recipe-templates');
const templates = Object.entries(RECIPE_TEMPLATES).map(([id, recipe]) => ({ const templates = Object.entries(RECIPE_TEMPLATES).map(([id, recipe]) => ({
id, id,
name: recipe.name, name: recipe.name,
@@ -61,7 +61,7 @@ module.exports = function(ctx) {
// GET /api/recipes/templates/:recipeId — get single recipe template detail // GET /api/recipes/templates/:recipeId — get single recipe template detail
router.get('/templates/:recipeId', deps.asyncHandler(async (req, res) => { router.get('/templates/:recipeId', deps.asyncHandler(async (req, res) => {
const { RECIPE_TEMPLATES } = require('../../../src/recipes/recipe-templates'); const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
const recipe = RECIPE_TEMPLATES[req.params.recipeId]; const recipe = RECIPE_TEMPLATES[req.params.recipeId];
if (!recipe) throw new NotFoundError(`Recipe template ${req.params.recipeId}`); if (!recipe) throw new NotFoundError(`Recipe template ${req.params.recipeId}`);
+5 -5
View File
@@ -1,7 +1,7 @@
const express = require('express'); const express = require('express');
const { DOCKER } = require('../../../src/utilities/constants'); const { DOCKER } = require('../../src/utilities/constants');
const { NotFoundError } = require('../../../src/utilities/errors'); const { NotFoundError } = require('../../src/utilities/errors');
const { ok } = require('../src/utils/responses'); const { ok } = require('../../src/utils/responses');
module.exports = function({ servicesStateManager, asyncHandler, log, docker, notification, buildDomain, caddy }) { module.exports = function({ servicesStateManager, asyncHandler, log, docker, notification, buildDomain, caddy }) {
const router = express.Router(); const router = express.Router();
@@ -269,7 +269,7 @@ module.exports = function({ servicesStateManager, asyncHandler, log, docker, not
* Find all Docker containers belonging to a recipe by label * Find all Docker containers belonging to a recipe by label
*/ */
async function findRecipeContainers(recipeId) { async function findRecipeContainers(recipeId) {
const { RECIPE_TEMPLATES } = require('../../../src/recipes/recipe-templates'); const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
const recipe = RECIPE_TEMPLATES[recipeId]; const recipe = RECIPE_TEMPLATES[recipeId];
const recipeLabel = recipe const recipeLabel = recipe
? recipe.name.toLowerCase().replace(/\s+/g, '-') ? recipe.name.toLowerCase().replace(/\s+/g, '-')
@@ -293,7 +293,7 @@ module.exports = function({ servicesStateManager, asyncHandler, log, docker, not
* Find recipe ID by its label (name slug) * Find recipe ID by its label (name slug)
*/ */
function findRecipeIdByLabel(label) { function findRecipeIdByLabel(label) {
const { RECIPE_TEMPLATES } = require('../../../src/recipes/recipe-templates'); const { RECIPE_TEMPLATES } = require('../../src/recipes/recipe-templates');
for (const [id, recipe] of Object.entries(RECIPE_TEMPLATES)) { for (const [id, recipe] of Object.entries(RECIPE_TEMPLATES)) {
if (recipe.name.toLowerCase().replace(/\s+/g, '-') === label) { if (recipe.name.toLowerCase().replace(/\s+/g, '-') === label) {
return id; return id;