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:
140
dashcaddy-api/__tests__/helpers/test-utils.js
Normal file
140
dashcaddy-api/__tests__/helpers/test-utils.js
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
Reference in New Issue
Block a user