Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
216 lines
6.7 KiB
TypeScript
216 lines
6.7 KiB
TypeScript
/**
|
|
* LogManager Tests
|
|
*
|
|
* Tests for the log directory and file path management.
|
|
* Uses temporary directories to avoid polluting the real log directory.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { mkdir, rm, writeFile, utimes } from 'node:fs/promises';
|
|
import { existsSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { LogManager } from './manager.js';
|
|
|
|
describe('LogManager', () => {
|
|
let testDir: string;
|
|
let manager: LogManager;
|
|
|
|
beforeEach(async () => {
|
|
// Create a unique temp directory for each test
|
|
testDir = join(tmpdir(), `cw-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
manager = new LogManager({ baseDir: testDir });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
// Clean up temp directory after each test
|
|
try {
|
|
await rm(testDir, { recursive: true, force: true });
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
});
|
|
|
|
describe('getBaseDir', () => {
|
|
it('should return the configured base directory', () => {
|
|
expect(manager.getBaseDir()).toBe(testDir);
|
|
});
|
|
|
|
it('should use default directory when not configured', () => {
|
|
const defaultManager = new LogManager();
|
|
expect(defaultManager.getBaseDir()).toContain('.cw');
|
|
expect(defaultManager.getBaseDir()).toContain('logs');
|
|
});
|
|
});
|
|
|
|
describe('ensureLogDir', () => {
|
|
it('should create the base log directory', async () => {
|
|
expect(existsSync(testDir)).toBe(false);
|
|
|
|
await manager.ensureLogDir();
|
|
|
|
expect(existsSync(testDir)).toBe(true);
|
|
});
|
|
|
|
it('should not error if directory already exists', async () => {
|
|
await mkdir(testDir, { recursive: true });
|
|
expect(existsSync(testDir)).toBe(true);
|
|
|
|
// Should not throw
|
|
await manager.ensureLogDir();
|
|
|
|
expect(existsSync(testDir)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('ensureProcessDir', () => {
|
|
it('should create the process-specific log directory', async () => {
|
|
const processId = 'test-process-123';
|
|
const expectedDir = join(testDir, processId);
|
|
|
|
expect(existsSync(expectedDir)).toBe(false);
|
|
|
|
await manager.ensureProcessDir(processId);
|
|
|
|
expect(existsSync(expectedDir)).toBe(true);
|
|
});
|
|
|
|
it('should create nested directories if base does not exist', async () => {
|
|
const processId = 'nested-process';
|
|
const expectedDir = join(testDir, processId);
|
|
|
|
expect(existsSync(testDir)).toBe(false);
|
|
|
|
await manager.ensureProcessDir(processId);
|
|
|
|
expect(existsSync(testDir)).toBe(true);
|
|
expect(existsSync(expectedDir)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('getProcessDir', () => {
|
|
it('should return the correct path for a process', () => {
|
|
const processId = 'my-process';
|
|
const expected = join(testDir, processId);
|
|
|
|
expect(manager.getProcessDir(processId)).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('getLogPath', () => {
|
|
it('should return correct path for stdout log', () => {
|
|
const processId = 'proc-1';
|
|
const expected = join(testDir, processId, 'stdout.log');
|
|
|
|
expect(manager.getLogPath(processId, 'stdout')).toBe(expected);
|
|
});
|
|
|
|
it('should return correct path for stderr log', () => {
|
|
const processId = 'proc-2';
|
|
const expected = join(testDir, processId, 'stderr.log');
|
|
|
|
expect(manager.getLogPath(processId, 'stderr')).toBe(expected);
|
|
});
|
|
});
|
|
|
|
describe('listLogs', () => {
|
|
it('should return empty array if base directory does not exist', async () => {
|
|
expect(existsSync(testDir)).toBe(false);
|
|
|
|
const logs = await manager.listLogs();
|
|
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should return empty array if no log directories exist', async () => {
|
|
await mkdir(testDir, { recursive: true });
|
|
|
|
const logs = await manager.listLogs();
|
|
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should return process IDs for existing log directories', async () => {
|
|
// Create some process directories
|
|
await mkdir(join(testDir, 'process-a'), { recursive: true });
|
|
await mkdir(join(testDir, 'process-b'), { recursive: true });
|
|
await mkdir(join(testDir, 'process-c'), { recursive: true });
|
|
|
|
const logs = await manager.listLogs();
|
|
|
|
expect(logs).toHaveLength(3);
|
|
expect(logs).toContain('process-a');
|
|
expect(logs).toContain('process-b');
|
|
expect(logs).toContain('process-c');
|
|
});
|
|
|
|
it('should only return directories, not files', async () => {
|
|
await mkdir(testDir, { recursive: true });
|
|
await mkdir(join(testDir, 'valid-process'), { recursive: true });
|
|
await writeFile(join(testDir, 'some-file.txt'), 'not a directory');
|
|
|
|
const logs = await manager.listLogs();
|
|
|
|
expect(logs).toEqual(['valid-process']);
|
|
});
|
|
});
|
|
|
|
describe('cleanOldLogs', () => {
|
|
it('should return 0 when retainDays is not configured', async () => {
|
|
const managerNoRetain = new LogManager({ baseDir: testDir });
|
|
|
|
const removed = await managerNoRetain.cleanOldLogs();
|
|
|
|
expect(removed).toBe(0);
|
|
});
|
|
|
|
it('should return 0 when no directories exist', async () => {
|
|
const managerWithRetain = new LogManager({ baseDir: testDir, retainDays: 7 });
|
|
|
|
const removed = await managerWithRetain.cleanOldLogs();
|
|
|
|
expect(removed).toBe(0);
|
|
});
|
|
|
|
it('should remove directories older than retainDays', async () => {
|
|
const managerWithRetain = new LogManager({ baseDir: testDir, retainDays: 7 });
|
|
|
|
// Create an "old" directory
|
|
const oldDir = join(testDir, 'old-process');
|
|
await mkdir(oldDir, { recursive: true });
|
|
|
|
// Set mtime to 10 days ago
|
|
const tenDaysAgo = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000);
|
|
await utimes(oldDir, tenDaysAgo, tenDaysAgo);
|
|
|
|
// Create a "new" directory
|
|
const newDir = join(testDir, 'new-process');
|
|
await mkdir(newDir, { recursive: true });
|
|
|
|
const removed = await managerWithRetain.cleanOldLogs();
|
|
|
|
expect(removed).toBe(1);
|
|
expect(existsSync(oldDir)).toBe(false);
|
|
expect(existsSync(newDir)).toBe(true);
|
|
});
|
|
|
|
it('should use provided retainDays over config value', async () => {
|
|
const managerWithRetain = new LogManager({ baseDir: testDir, retainDays: 30 });
|
|
|
|
// Create directory that is 10 days old
|
|
const oldDir = join(testDir, 'process');
|
|
await mkdir(oldDir, { recursive: true });
|
|
const tenDaysAgo = new Date(Date.now() - 10 * 24 * 60 * 60 * 1000);
|
|
await utimes(oldDir, tenDaysAgo, tenDaysAgo);
|
|
|
|
// With config (30 days), should NOT remove
|
|
expect(await managerWithRetain.cleanOldLogs()).toBe(0);
|
|
expect(existsSync(oldDir)).toBe(true);
|
|
|
|
// With explicit 5 days, SHOULD remove
|
|
expect(await managerWithRetain.cleanOldLogs(5)).toBe(1);
|
|
expect(existsSync(oldDir)).toBe(false);
|
|
});
|
|
});
|
|
});
|