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,213 @@
jest.mock('proper-lockfile');
jest.mock('fs', () => ({
existsSync: jest.fn().mockReturnValue(true),
mkdirSync: jest.fn(),
writeFileSync: jest.fn(),
promises: {
readFile: jest.fn().mockResolvedValue('[]'),
writeFile: jest.fn().mockResolvedValue(),
},
}));
const lockfile = require('proper-lockfile');
const fs = require('fs');
const StateManager = require('../state-manager');
describe('StateManager', () => {
let sm;
const TEST_PATH = '/tmp/test-state.json';
beforeEach(() => {
jest.clearAllMocks();
fs.existsSync.mockReturnValue(true);
fs.promises.readFile.mockResolvedValue('[]');
fs.promises.writeFile.mockResolvedValue();
lockfile.lock.mockResolvedValue(jest.fn().mockResolvedValue());
lockfile.check.mockResolvedValue(false);
lockfile.unlock.mockResolvedValue();
sm = new StateManager(TEST_PATH);
});
describe('constructor', () => {
it('creates file with [] if it does not exist', () => {
fs.existsSync.mockReturnValue(false);
new StateManager('/tmp/new-state.json');
expect(fs.writeFileSync).toHaveBeenCalledWith('/tmp/new-state.json', '[]', 'utf8');
});
it('creates directory recursively if needed', () => {
fs.existsSync.mockReturnValue(false);
new StateManager('/tmp/deep/nested/state.json');
expect(fs.mkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true });
});
it('does not create file if it exists', () => {
fs.existsSync.mockReturnValue(true);
fs.writeFileSync.mockClear();
new StateManager(TEST_PATH);
expect(fs.writeFileSync).not.toHaveBeenCalled();
});
});
describe('read', () => {
it('returns parsed JSON from file', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: 'svc1' }]));
const data = await sm.read();
expect(data).toEqual([{ id: 'svc1' }]);
});
it('returns [] and recreates file on ENOENT', async () => {
const err = new Error('ENOENT');
err.code = 'ENOENT';
fs.promises.readFile.mockRejectedValue(err);
fs.existsSync.mockReturnValue(false);
const data = await sm.read();
expect(data).toEqual([]);
});
it('throws on invalid JSON', async () => {
fs.promises.readFile.mockResolvedValue('{bad json}');
await expect(sm.read()).rejects.toThrow('Failed to read state file');
});
});
describe('write', () => {
it('acquires lock, writes JSON, releases lock', async () => {
const releaseFn = jest.fn().mockResolvedValue();
lockfile.lock.mockResolvedValue(releaseFn);
await sm.write([{ id: 'new' }]);
expect(lockfile.lock).toHaveBeenCalledWith(TEST_PATH, expect.any(Object));
expect(fs.promises.writeFile).toHaveBeenCalledWith(
TEST_PATH,
JSON.stringify([{ id: 'new' }], null, 2),
'utf8'
);
expect(releaseFn).toHaveBeenCalled();
});
it('throws on ELOCKED', async () => {
const err = new Error('locked');
err.code = 'ELOCKED';
lockfile.lock.mockRejectedValue(err);
await expect(sm.write([])).rejects.toThrow('locked by another process');
});
it('releases lock even on write error', async () => {
const releaseFn = jest.fn().mockResolvedValue();
lockfile.lock.mockResolvedValue(releaseFn);
fs.promises.writeFile.mockRejectedValue(new Error('disk full'));
await expect(sm.write([])).rejects.toThrow();
expect(releaseFn).toHaveBeenCalled();
});
});
describe('update', () => {
it('atomic read-modify-write cycle', async () => {
const releaseFn = jest.fn().mockResolvedValue();
lockfile.lock.mockResolvedValue(releaseFn);
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: '1' }]));
const result = await sm.update(items => {
items.push({ id: '2' });
return items;
});
expect(result).toEqual([{ id: '1' }, { id: '2' }]);
expect(fs.promises.writeFile).toHaveBeenCalled();
expect(releaseFn).toHaveBeenCalled();
});
it('passes current data to updateFn', async () => {
const releaseFn = jest.fn().mockResolvedValue();
lockfile.lock.mockResolvedValue(releaseFn);
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: 'existing' }]));
const updateFn = jest.fn(data => data);
await sm.update(updateFn);
expect(updateFn).toHaveBeenCalledWith([{ id: 'existing' }]);
});
it('throws on ELOCKED', async () => {
const err = new Error('locked');
err.code = 'ELOCKED';
lockfile.lock.mockRejectedValue(err);
await expect(sm.update(d => d)).rejects.toThrow('locked by another process');
});
});
describe('convenience methods', () => {
beforeEach(() => {
const releaseFn = jest.fn().mockResolvedValue();
lockfile.lock.mockResolvedValue(releaseFn);
});
it('addItem appends to array', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: '1' }]));
const result = await sm.addItem({ id: '2', name: 'New' });
expect(result).toEqual([{ id: '1' }, { id: '2', name: 'New' }]);
});
it('removeItem filters by id', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: '1' }, { id: '2' }]));
const result = await sm.removeItem('1');
expect(result).toEqual([{ id: '2' }]);
});
it('updateItem merges updates for matching id', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: '1', name: 'Old' }]));
const result = await sm.updateItem('1', { name: 'New', port: 8080 });
expect(result).toEqual([{ id: '1', name: 'New', port: 8080 }]);
});
it('findItem returns matching item or null', async () => {
fs.promises.readFile.mockResolvedValue(JSON.stringify([{ id: '1', name: 'Found' }]));
const found = await sm.findItem('1');
expect(found).toEqual({ id: '1', name: 'Found' });
const missing = await sm.findItem('999');
expect(missing).toBeNull();
});
});
describe('isLocked', () => {
it('returns lockfile.check result', async () => {
lockfile.check.mockResolvedValue(true);
expect(await sm.isLocked()).toBe(true);
lockfile.check.mockResolvedValue(false);
expect(await sm.isLocked()).toBe(false);
});
it('returns false on error', async () => {
lockfile.check.mockRejectedValue(new Error('fail'));
expect(await sm.isLocked()).toBe(false);
});
});
describe('forceUnlock', () => {
it('calls lockfile.unlock', async () => {
await sm.forceUnlock();
expect(lockfile.unlock).toHaveBeenCalledWith(TEST_PATH);
});
it('ignores ENOTACQUIRED error', async () => {
const err = new Error('not locked');
err.code = 'ENOTACQUIRED';
lockfile.unlock.mockRejectedValue(err);
await expect(sm.forceUnlock()).resolves.toBeUndefined();
});
it('throws other errors', async () => {
lockfile.unlock.mockRejectedValue(new Error('other'));
await expect(sm.forceUnlock()).rejects.toThrow('other');
});
});
});