DC-007: Add smoke tests for 7 untested modules
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

This commit is contained in:
Hermes
2026-06-13 11:38:51 -07:00
parent 5b1d631870
commit f96e903710
8 changed files with 1624 additions and 1 deletions
+187
View File
@@ -0,0 +1,187 @@
/**
* Smoke tests for log-digest.js
* Verifies the singleton LogDigest exposes the expected interface, parses
* Docker multiplexed log streams, formats digests, and supports on-demand
* daily digest generation with mocked Docker.
*/
const fsReal = require('fs');
const os = require('os');
const path = require('path');
jest.mock('dockerode', () => {
const listContainers = jest.fn().mockResolvedValue([]);
const getContainer = jest.fn(() => ({
logs: jest.fn().mockResolvedValue(Buffer.from([])),
}));
function Docker() {}
Docker.prototype.listContainers = listContainers;
Docker.prototype.getContainer = getContainer;
return Docker;
});
jest.mock('fs', () => {
const actual = jest.requireActual('fs');
return {
...actual,
existsSync: jest.fn().mockReturnValue(true),
mkdirSync: jest.fn(),
};
});
jest.mock('../docker-maintenance', () => ({
getDiskUsage: jest.fn().mockResolvedValue(null),
}));
const Docker = require('dockerode');
const fs = require('fs');
const logDigest = require('../log-digest');
describe('LogDigest (singleton)', () => {
let dockerInstance;
let tempDir;
beforeEach(() => {
// Each test gets a fresh Docker() mock instance
jest.clearAllMocks();
fs.existsSync.mockReturnValue(true);
// Use a real, writable temp directory so writeFile inside generateDailyDigest
// does not blow up. Each test gets a fresh dir to avoid cross-test pollution.
tempDir = fsReal.mkdtempSync(path.join(os.tmpdir(), 'dc-digest-test-'));
logDigest.hourlySummaries = [];
logDigest.lastCollect = null;
logDigest.running = false;
logDigest.digestDir = null;
if (logDigest.collectInterval) {
clearInterval(logDigest.collectInterval);
logDigest.collectInterval = null;
}
if (logDigest.digestTimeout) {
clearTimeout(logDigest.digestTimeout);
logDigest.digestTimeout = null;
}
dockerInstance = new Docker();
});
afterEach(() => {
logDigest.stop();
if (tempDir && fsReal.existsSync(tempDir)) {
fsReal.rmSync(tempDir, { recursive: true, force: true });
}
});
test('is an EventEmitter and exposes the documented API', () => {
expect(typeof logDigest.on).toBe('function');
expect(typeof logDigest.emit).toBe('function');
expect(typeof logDigest.start).toBe('function');
expect(typeof logDigest.stop).toBe('function');
expect(typeof logDigest.generateDailyDigest).toBe('function');
expect(typeof logDigest.getLatestDigest).toBe('function');
expect(typeof logDigest.getDigestByDate).toBe('function');
expect(typeof logDigest.getDigestText).toBe('function');
expect(typeof logDigest.listDigests).toBe('function');
expect(typeof logDigest.getLiveData).toBe('function');
expect(typeof logDigest.getStatus).toBe('function');
});
test('getStatus returns current state', () => {
const status = logDigest.getStatus();
expect(status).toEqual({
running: false,
lastCollect: null,
hourlySummaries: 0,
digestDir: null,
});
});
test('start sets running and digestDir', () => {
logDigest.start(tempDir);
expect(logDigest.running).toBe(true);
expect(logDigest.digestDir).toBe(tempDir);
});
test('start is idempotent — second call does nothing new', () => {
logDigest.start(tempDir);
const firstInterval = logDigest.collectInterval;
logDigest.start(tempDir);
expect(logDigest.collectInterval).toBe(firstInterval);
});
test('_parseDockerLogs decodes multiplexed log frames into lines', () => {
// Stream type byte: 0=stdin, 1=stdout, 2=stderr
// Header: [type, 0, 0, 0, size-BE-uint32]
function frame(streamType, text) {
const buf = Buffer.from(text, 'utf8');
const header = Buffer.alloc(8);
header[0] = streamType;
header.writeUInt32BE(buf.length, 4);
return Buffer.concat([header, buf]);
}
const multiplexed = Buffer.concat([
frame(1, 'hello world\n'),
frame(2, '2026-03-13T12:00:00.000Z an error happened\n'),
]);
const lines = logDigest._parseDockerLogs(multiplexed);
expect(lines).toHaveLength(2);
expect(lines[0]).toEqual({
stream: 'stdout',
text: 'hello world',
timestamp: null,
});
expect(lines[1].stream).toBe('stderr');
expect(lines[1].text).toBe('an error happened');
expect(lines[1].timestamp).toBe('2026-03-13T12:00:00');
});
test('generateDailyDigest with empty summaries produces minimal digest', async () => {
logDigest.start(tempDir);
const digest = await logDigest.generateDailyDigest('2099-01-01');
expect(digest.date).toBe('2099-01-01');
expect(digest.services).toEqual({});
expect(digest.summary.totalServices).toBe(0);
expect(digest.summary.totalErrors).toBe(0);
expect(Array.isArray(digest.notableEvents)).toBe(true);
// Confirm the file was actually written
const writtenPath = path.join(tempDir, 'digest-2099-01-01.log');
expect(fsReal.existsSync(writtenPath)).toBe(true);
const jsonPath = path.join(tempDir, 'digest-2099-01-01.json');
expect(fsReal.existsSync(jsonPath)).toBe(true);
});
test('getLiveData returns shape with date, hoursCollected, services', () => {
const data = logDigest.getLiveData();
expect(data).toHaveProperty('date');
expect(data).toHaveProperty('hoursCollected');
expect(data).toHaveProperty('services');
expect(data).toHaveProperty('lastCollect');
});
test('getLatestDigest returns null when digestDir is null', async () => {
logDigest.digestDir = null;
const result = await logDigest.getLatestDigest();
expect(result).toBeNull();
});
test('getDigestByDate returns null when no file exists', async () => {
logDigest.digestDir = '/nonexistent/path';
const result = await logDigest.getDigestByDate('2020-01-01');
expect(result).toBeNull();
});
test('listDigests returns empty array when digestDir is null', async () => {
logDigest.digestDir = null;
const result = await logDigest.listDigests();
expect(result).toEqual([]);
});
test('stop clears intervals and timeouts', () => {
logDigest.start(tempDir);
logDigest.stop();
expect(logDigest.running).toBe(false);
expect(logDigest.collectInterval).toBeNull();
expect(logDigest.digestTimeout).toBeNull();
});
});