mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
119 lines
4.2 KiB
TypeScript
119 lines
4.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import * as zlib from 'zlib';
|
|
import { createEmptyUnifiedConfig } from '../../../../src/config/unified-config-types';
|
|
import { saveUnifiedConfig } from '../../../../src/config/unified-config-loader';
|
|
import { clearRecentLogEntries } from '../../../../src/services/logging/log-buffer';
|
|
import { invalidateLoggingConfigCache } from '../../../../src/services/logging/log-config';
|
|
import { getCurrentLogPath, getLogArchiveDir } from '../../../../src/services/logging/log-paths';
|
|
import {
|
|
appendStructuredLogEntry,
|
|
pruneExpiredLogArchives,
|
|
} from '../../../../src/services/logging/log-storage';
|
|
import type { LogEntry } from '../../../../src/services/logging/log-types';
|
|
|
|
function createEntry(overrides: Partial<LogEntry>): LogEntry {
|
|
return {
|
|
id: overrides.id ?? 'entry-1',
|
|
timestamp: overrides.timestamp ?? new Date().toISOString(),
|
|
level: overrides.level ?? 'info',
|
|
source: overrides.source ?? 'unit:test',
|
|
event: overrides.event ?? 'test.event',
|
|
message: overrides.message ?? 'message',
|
|
processId: overrides.processId ?? 1234,
|
|
runId: overrides.runId ?? 'run-1',
|
|
context: overrides.context ?? {},
|
|
};
|
|
}
|
|
|
|
describe('log storage', () => {
|
|
let tempHome = '';
|
|
let originalCcsHome: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
originalCcsHome = process.env.CCS_HOME;
|
|
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-log-storage-'));
|
|
process.env.CCS_HOME = tempHome;
|
|
clearRecentLogEntries();
|
|
invalidateLoggingConfigCache();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalCcsHome !== undefined) {
|
|
process.env.CCS_HOME = originalCcsHome;
|
|
} else {
|
|
delete process.env.CCS_HOME;
|
|
}
|
|
|
|
clearRecentLogEntries();
|
|
invalidateLoggingConfigCache();
|
|
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
tempHome = '';
|
|
});
|
|
|
|
it('rotates the current log into the archive when the file exceeds the age threshold', () => {
|
|
const config = createEmptyUnifiedConfig();
|
|
config.logging.retain_days = 7;
|
|
config.logging.rotate_mb = 10;
|
|
saveUnifiedConfig(config);
|
|
invalidateLoggingConfigCache();
|
|
|
|
const currentLogPath = getCurrentLogPath();
|
|
fs.mkdirSync(path.dirname(currentLogPath), { recursive: true });
|
|
fs.writeFileSync(
|
|
currentLogPath,
|
|
`${JSON.stringify(createEntry({ id: 'old-entry' }))}\n`,
|
|
'utf8'
|
|
);
|
|
const staleTimestamp = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000);
|
|
fs.utimesSync(currentLogPath, staleTimestamp, staleTimestamp);
|
|
|
|
appendStructuredLogEntry(
|
|
createEntry({
|
|
id: 'new-entry',
|
|
message: 'new log entry after rotation',
|
|
})
|
|
);
|
|
|
|
const archiveDir = getLogArchiveDir();
|
|
const archives = fs.readdirSync(archiveDir);
|
|
expect(archives).toHaveLength(1);
|
|
|
|
const archivedContent = zlib
|
|
.gunzipSync(fs.readFileSync(path.join(archiveDir, archives[0])))
|
|
.toString('utf8');
|
|
expect(archivedContent).toContain('"id":"old-entry"');
|
|
|
|
const currentContent = fs.readFileSync(currentLogPath, 'utf8');
|
|
expect(currentContent).toContain('"id":"new-entry"');
|
|
expect(currentContent).not.toContain('"id":"old-entry"');
|
|
});
|
|
|
|
it('prunes expired archives according to retention settings', () => {
|
|
const config = createEmptyUnifiedConfig();
|
|
config.logging.retain_days = 1;
|
|
saveUnifiedConfig(config);
|
|
invalidateLoggingConfigCache();
|
|
|
|
const archiveDir = getLogArchiveDir();
|
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
|
|
const oldArchive = path.join(archiveDir, 'ccs-old.jsonl.gz');
|
|
const freshArchive = path.join(archiveDir, 'ccs-fresh.jsonl.gz');
|
|
fs.writeFileSync(oldArchive, zlib.gzipSync('old archive'), { mode: 0o600 });
|
|
fs.writeFileSync(freshArchive, zlib.gzipSync('fresh archive'), { mode: 0o600 });
|
|
|
|
const oldTimestamp = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
|
|
const freshTimestamp = new Date(Date.now() - 2 * 60 * 60 * 1000);
|
|
fs.utimesSync(oldArchive, oldTimestamp, oldTimestamp);
|
|
fs.utimesSync(freshArchive, freshTimestamp, freshTimestamp);
|
|
|
|
pruneExpiredLogArchives();
|
|
|
|
expect(fs.existsSync(oldArchive)).toBe(false);
|
|
expect(fs.existsSync(freshArchive)).toBe(true);
|
|
});
|
|
});
|