Files
shiro-neko/test/diagnostics.test.ts
T

106 lines
3.8 KiB
TypeScript
Raw Normal View History

import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import { diagStart, diagStop, diagStatus, bootDiagnostics, defaultDiagnosticsCommand, shutdownDiagnostics } from '../src/diagnostics';
// Diagnostics spawns real processes, so each test gets a fresh state (the module
// is a singleton — one diagnostics command at a time).
describe('diagnostics module', () => {
afterEach(() => {
shutdownDiagnostics();
});
it('starts, shows running status, and stops a command', async () => {
const started = diagStart('node -e "setInterval(()=>{}, 1000)"');
expect(started.started).toBe(true);
const status = diagStatus();
expect(status.running).toBe(true);
expect(status.command).toContain('setInterval');
const stopped = diagStop();
expect(stopped.stopped).toBe(true);
expect(diagStatus().running).toBe(false);
});
it('reports exit code once the command finishes', async () => {
diagStart('node -e "process.exit(3)"');
// Wait for the process to actually exit.
await Bun.sleep(300);
const status = diagStatus();
expect(status.running).toBe(false);
expect(status.exit).toBe(3);
});
it('captures output into the tail', async () => {
diagStart('node -e "console.log(\'hello-diag\')"');
await Bun.sleep(300);
const status = diagStatus();
expect(status.tail).toContain('hello-diag');
expect(status.exit).toBe(0);
});
it('diagStop with nothing running is a no-op', () => {
expect(diagStop().stopped).toBe(false);
expect(diagStatus().running).toBe(false);
});
it('starting replaces a running command and kills the old one', async () => {
const oldHandle = diagStart('node -e "setInterval(()=>{}, 1000)"');
expect(oldHandle.started).toBe(true);
const second = diagStart('node -e "console.log(\'second\')"');
expect(second.started).toBe(true);
const status = diagStatus();
expect(status.command).toContain('second');
// old process is gone; status.command reflects the newest start
shutdownDiagnostics();
});
it('bootDiagnostics ignores empty config and starts a real one', async () => {
bootDiagnostics(undefined);
expect(diagStatus().running).toBe(false);
bootDiagnostics('node -e "setInterval(()=>{}, 1000)"');
expect(diagStatus().running).toBe(true);
});
it('bootDiagnostics never throws on a bad command — keeps boot clean', async () => {
expect(() => bootDiagnostics('')).not.toThrow();
// A command that cannot spawn (bad binary) still must not throw synchronously.
expect(() => bootDiagnostics('/nonexistent/binary')).not.toThrow();
await Bun.sleep(50);
shutdownDiagnostics();
});
});
describe('defaultDiagnosticsCommand — auto-detection', () => {
const tmp = `${Bun.env['TMPDIR'] ?? '/tmp'}/diag-default-${process.pid}-${Math.random().toString(36).slice(2)}`;
let savedCwd: string;
const { mkdirSync, rmSync, writeFileSync } = require('node:fs') as typeof import('node:fs');
beforeEach(() => {
savedCwd = process.cwd();
rmSync(tmp, { recursive: true, force: true });
mkdirSync(tmp, { recursive: true });
process.chdir(tmp);
});
afterEach(() => {
process.chdir(savedCwd);
diagStop();
});
it('picks tsc --watch for a bun+ts project', async () => {
writeFileSync('bun.lock', '');
writeFileSync('package.json', '{}');
writeFileSync('tsconfig.json', '{}');
expect(await defaultDiagnosticsCommand(process.cwd())).toBe('bun run typecheck --watch');
});
it('falls back to bun test --watch without tsconfig', async () => {
writeFileSync('bun.lock', '');
writeFileSync('package.json', '{}');
expect(await defaultDiagnosticsCommand(process.cwd())).toBe('bun test --watch');
});
it('returns undefined for an unknown project', async () => {
expect(await defaultDiagnosticsCommand(process.cwd())).toBeUndefined();
});
});