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>
141 lines
3.7 KiB
JavaScript
141 lines
3.7 KiB
JavaScript
/**
|
|
* Shared test utilities for DashCaddy test suite
|
|
*/
|
|
const express = require('express');
|
|
|
|
/**
|
|
* Create a mock credential manager
|
|
*/
|
|
function createMockCredentialManager() {
|
|
return {
|
|
store: jest.fn().mockResolvedValue(true),
|
|
retrieve: jest.fn().mockResolvedValue(null),
|
|
delete: jest.fn().mockResolvedValue(true),
|
|
list: jest.fn().mockResolvedValue([]),
|
|
getMetadata: jest.fn().mockResolvedValue(null),
|
|
rotateEncryptionKey: jest.fn().mockResolvedValue(true),
|
|
exportBackup: jest.fn().mockResolvedValue('encrypted-backup'),
|
|
importBackup: jest.fn().mockResolvedValue(true),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a mock crypto utils module
|
|
*/
|
|
function createMockCryptoUtils() {
|
|
const fixedKey = Buffer.alloc(32, 'a');
|
|
return {
|
|
encrypt: jest.fn(data => `mock-iv:mock-tag:${Buffer.from(String(data)).toString('base64')}`),
|
|
decrypt: jest.fn(data => {
|
|
const parts = data.split(':');
|
|
return Buffer.from(parts[2], 'base64').toString('utf8');
|
|
}),
|
|
isEncrypted: jest.fn(data => typeof data === 'string' && data.split(':').length === 3),
|
|
encryptFields: jest.fn((obj, fields) => ({ ...obj, _encrypted: true, _encryptedFields: fields })),
|
|
decryptFields: jest.fn(obj => {
|
|
const result = { ...obj };
|
|
delete result._encrypted;
|
|
delete result._encryptedFields;
|
|
return result;
|
|
}),
|
|
loadOrCreateKey: jest.fn(() => fixedKey),
|
|
clearCachedKey: jest.fn(),
|
|
rotateKey: jest.fn(() => ({ oldKey: fixedKey, newKey: Buffer.alloc(32, 'b') })),
|
|
deriveKey: jest.fn().mockResolvedValue(fixedKey),
|
|
decryptWithKey: jest.fn(data => {
|
|
const parts = data.split(':');
|
|
return Buffer.from(parts[2], 'base64').toString('utf8');
|
|
}),
|
|
readEncryptedFile: jest.fn().mockReturnValue(null),
|
|
writeEncryptedFile: jest.fn(),
|
|
migrateToEncrypted: jest.fn(obj => obj),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a mock state manager
|
|
*/
|
|
function createMockStateManager() {
|
|
let data = [];
|
|
return {
|
|
read: jest.fn().mockResolvedValue(data),
|
|
write: jest.fn().mockResolvedValue(),
|
|
update: jest.fn(async fn => { data = fn(data); return data; }),
|
|
addItem: jest.fn().mockResolvedValue(),
|
|
removeItem: jest.fn().mockResolvedValue(),
|
|
updateItem: jest.fn().mockResolvedValue(),
|
|
findItem: jest.fn().mockResolvedValue(null),
|
|
_setData: (newData) => { data = newData; },
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a mock logger
|
|
*/
|
|
function createMockLogger() {
|
|
return {
|
|
info: jest.fn(),
|
|
warn: jest.fn(),
|
|
error: jest.fn(),
|
|
debug: jest.fn(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Build a minimal Express app for route testing with supertest
|
|
*/
|
|
function buildTestApp(routeFactory, deps, prefix = '/api') {
|
|
const app = express();
|
|
app.use(express.json());
|
|
const router = routeFactory(deps);
|
|
app.use(prefix, router);
|
|
// Error handler
|
|
const { errorMiddleware } = require('../../error-handler');
|
|
app.use(errorMiddleware);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Create mock Express req/res/next for middleware testing
|
|
*/
|
|
function createMockReqRes(overrides = {}) {
|
|
const req = {
|
|
method: 'GET',
|
|
path: '/test',
|
|
headers: {},
|
|
cookies: {},
|
|
ip: '127.0.0.1',
|
|
protocol: 'https',
|
|
secure: true,
|
|
body: {},
|
|
params: {},
|
|
query: {},
|
|
get: jest.fn(header => req.headers[header.toLowerCase()]),
|
|
...overrides,
|
|
};
|
|
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn().mockReturnThis(),
|
|
send: jest.fn().mockReturnThis(),
|
|
set: jest.fn().mockReturnThis(),
|
|
cookie: jest.fn().mockReturnThis(),
|
|
setHeader: jest.fn().mockReturnThis(),
|
|
getHeader: jest.fn(),
|
|
end: jest.fn(),
|
|
};
|
|
|
|
const next = jest.fn();
|
|
|
|
return { req, res, next };
|
|
}
|
|
|
|
module.exports = {
|
|
createMockCredentialManager,
|
|
createMockCryptoUtils,
|
|
createMockStateManager,
|
|
createMockLogger,
|
|
buildTestApp,
|
|
createMockReqRes,
|
|
};
|