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)
165 lines
5.6 KiB
JavaScript
165 lines
5.6 KiB
JavaScript
/**
|
|
* Auto-Restart Policy Routes
|
|
*
|
|
* CRUD endpoints for per-container auto-restart policies.
|
|
* Also provides a dry-run test endpoint.
|
|
*
|
|
* @module routes/auto-restart
|
|
*/
|
|
|
|
const express = require('express');
|
|
const { success } = require('../src/utils/responses');
|
|
const { ValidationError, NotFoundError } = require('../src/utilities/errors');
|
|
|
|
/**
|
|
* Auto-restart route factory
|
|
*
|
|
* @param {Object} deps - Explicit dependencies
|
|
* @param {Object} deps.autoRestartManager - AutoRestartManager instance
|
|
* @param {Function} deps.asyncHandler - Async route handler wrapper
|
|
* @param {Function} deps.logError - Error logging function
|
|
* @returns {express.Router}
|
|
*/
|
|
module.exports = function ({ autoRestartManager, asyncHandler, logError }) {
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* GET /auto-restart/policies
|
|
* List all configured auto-restart policies.
|
|
*/
|
|
router.get('/policies', asyncHandler(async (_req, res) => {
|
|
const policies = autoRestartManager.listPolicies();
|
|
success(res, { policies });
|
|
}, 'auto-restart-list'));
|
|
|
|
/**
|
|
* GET /auto-restart/policies/:serviceId
|
|
* Get the restart policy for a single service.
|
|
*/
|
|
router.get('/policies/:serviceId', asyncHandler(async (req, res) => {
|
|
const { serviceId } = req.params;
|
|
|
|
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
|
throw new ValidationError('Invalid service ID format');
|
|
}
|
|
|
|
const policy = autoRestartManager.getPolicy(serviceId);
|
|
if (!policy) {
|
|
throw new NotFoundError(`Auto-restart policy for "${serviceId}"`);
|
|
}
|
|
|
|
success(res, { policy });
|
|
}, 'auto-restart-get'));
|
|
|
|
/**
|
|
* POST /auto-restart/policies/:serviceId
|
|
* Create or update a restart policy.
|
|
*
|
|
* Body: { enabled, maxRetries, retryIntervalMs, windowMinutes }
|
|
*/
|
|
router.post('/policies/:serviceId', asyncHandler(async (req, res) => {
|
|
const { serviceId } = req.params;
|
|
|
|
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
|
throw new ValidationError('Invalid service ID format');
|
|
}
|
|
|
|
const { enabled, maxRetries, retryIntervalMs, windowMinutes } = req.body;
|
|
|
|
// Validate inputs
|
|
if (enabled !== undefined && typeof enabled !== 'boolean') {
|
|
throw new ValidationError('enabled must be a boolean');
|
|
}
|
|
if (maxRetries !== undefined) {
|
|
if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 100) {
|
|
throw new ValidationError('maxRetries must be an integer between 0 and 100');
|
|
}
|
|
}
|
|
if (retryIntervalMs !== undefined) {
|
|
if (!Number.isInteger(retryIntervalMs) || retryIntervalMs < 0 || retryIntervalMs > 3600000) {
|
|
throw new ValidationError('retryIntervalMs must be an integer between 0 and 3600000');
|
|
}
|
|
}
|
|
if (windowMinutes !== undefined) {
|
|
if (!Number.isInteger(windowMinutes) || windowMinutes < 0 || windowMinutes > 1440) {
|
|
throw new ValidationError('windowMinutes must be an integer between 0 and 1440');
|
|
}
|
|
}
|
|
|
|
const policy = await autoRestartManager.setPolicy(serviceId, {
|
|
...(enabled !== undefined && { enabled }),
|
|
...(maxRetries !== undefined && { maxRetries }),
|
|
...(retryIntervalMs !== undefined && { retryIntervalMs }),
|
|
...(windowMinutes !== undefined && { windowMinutes }),
|
|
});
|
|
|
|
success(res, { policy, message: `Policy ${serviceId} saved` });
|
|
}, 'auto-restart-set'));
|
|
|
|
/**
|
|
* DELETE /auto-restart/policies/:serviceId
|
|
* Remove a restart policy.
|
|
*/
|
|
router.delete('/policies/:serviceId', asyncHandler(async (req, res) => {
|
|
const { serviceId } = req.params;
|
|
|
|
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
|
throw new ValidationError('Invalid service ID format');
|
|
}
|
|
|
|
const removed = await autoRestartManager.removePolicy(serviceId);
|
|
if (!removed) {
|
|
throw new NotFoundError(`Auto-restart policy for "${serviceId}"`);
|
|
}
|
|
|
|
success(res, { message: `Policy for "${serviceId}" removed` });
|
|
}, 'auto-restart-delete'));
|
|
|
|
/**
|
|
* POST /auto-restart/policies/:serviceId/test
|
|
* Dry-run: simulate a restart attempt without actually restarting.
|
|
* Returns what *would* happen given the current policy state.
|
|
*/
|
|
router.post('/policies/:serviceId/test', asyncHandler(async (req, res) => {
|
|
const { serviceId } = req.params;
|
|
|
|
if (!serviceId || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,100}$/.test(serviceId)) {
|
|
throw new ValidationError('Invalid service ID format');
|
|
}
|
|
|
|
const policy = autoRestartManager.getPolicy(serviceId);
|
|
if (!policy) {
|
|
throw new NotFoundError(`Auto-restart policy for "${serviceId}"`);
|
|
}
|
|
|
|
const now = Date.now();
|
|
const inCooldown = policy.cooldownUntil && now < policy.cooldownUntil;
|
|
const wouldRetry = !inCooldown && policy.currentRetries < policy.maxRetries;
|
|
const nextAttempt = policy.currentRetries + 1;
|
|
|
|
success(res, {
|
|
dryRun: true,
|
|
serviceId,
|
|
policy: {
|
|
enabled: policy.enabled,
|
|
currentRetries: policy.currentRetries,
|
|
maxRetries: policy.maxRetries,
|
|
cooldownUntil: policy.cooldownUntil,
|
|
inCooldown,
|
|
},
|
|
wouldRestart: policy.enabled && wouldRetry,
|
|
wouldMaxOut: !wouldRetry && !inCooldown,
|
|
nextAttempt: wouldRetry ? nextAttempt : null,
|
|
message: !policy.enabled
|
|
? 'Policy is disabled — no restart would occur'
|
|
: inCooldown
|
|
? `In cooldown until ${new Date(policy.cooldownUntil).toISOString()} — would skip`
|
|
: wouldRetry
|
|
? `Would attempt restart ${nextAttempt}/${policy.maxRetries}`
|
|
: `Max retries (${policy.maxRetries}) already reached — would enter cooldown`,
|
|
});
|
|
}, 'auto-restart-test'));
|
|
|
|
return router;
|
|
};
|