mirror of https://github.com/garrytan/gstack.git
91 lines
3.8 KiB
TypeScript
91 lines
3.8 KiB
TypeScript
import { EventEmitter } from 'node:events';
|
|
import { describe, it, expect } from 'bun:test';
|
|
|
|
// ─── BrowserManager basic unit tests ─────────────────────────────
|
|
|
|
describe('BrowserManager defaults', () => {
|
|
it('getConnectionMode defaults to launched', async () => {
|
|
const { BrowserManager } = await import('../src/browser-manager');
|
|
const bm = new BrowserManager();
|
|
expect(bm.getConnectionMode()).toBe('launched');
|
|
});
|
|
|
|
it('getRefMap returns empty array initially', async () => {
|
|
const { BrowserManager } = await import('../src/browser-manager');
|
|
const bm = new BrowserManager();
|
|
expect(bm.getRefMap()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
// ─── resolveDisconnectCause ──────────────────────────────────────
|
|
//
|
|
// Pinning the clean-vs-crash distinction matters because gbd's
|
|
// HealthMonitor consumes our exit code (0 = don't restart, !=0 =
|
|
// restart). A regression here brings back the "Cmd+Q makes the browser
|
|
// keep coming back" UX bug.
|
|
|
|
function makeFakeBrowser(opts: {
|
|
exitCode: number | null;
|
|
signalCode: NodeJS.Signals | null;
|
|
/** ms before emitting 'exit'; default = already exited at construction */
|
|
exitDelay?: number;
|
|
}): { process(): { exitCode: number | null; signalCode: NodeJS.Signals | null; once: EventEmitter['once'] } } {
|
|
const ee = new EventEmitter();
|
|
const state = {
|
|
exitCode: opts.exitDelay != null ? null : opts.exitCode,
|
|
signalCode: opts.exitDelay != null ? null : opts.signalCode,
|
|
once: ee.once.bind(ee),
|
|
};
|
|
if (opts.exitDelay != null) {
|
|
setTimeout(() => {
|
|
state.exitCode = opts.exitCode;
|
|
state.signalCode = opts.signalCode;
|
|
ee.emit('exit', opts.exitCode, opts.signalCode);
|
|
}, opts.exitDelay);
|
|
}
|
|
return { process: () => state };
|
|
}
|
|
|
|
describe('resolveDisconnectCause', () => {
|
|
it('clean: process already exited with code 0', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: 0, signalCode: null });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('clean');
|
|
});
|
|
|
|
it('crash: non-zero exit code', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: 1, signalCode: null });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('crash');
|
|
});
|
|
|
|
it('crash: SIGSEGV', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: null, signalCode: 'SIGSEGV' });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('crash');
|
|
});
|
|
|
|
it('crash: SIGKILL', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: null, signalCode: 'SIGKILL' });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('crash');
|
|
});
|
|
|
|
it('clean: process exits asynchronously with code 0 within timeout', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: 0, signalCode: null, exitDelay: 50 });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('clean');
|
|
});
|
|
|
|
it('crash: process exits asynchronously with non-zero code', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
const fake = makeFakeBrowser({ exitCode: 137, signalCode: null, exitDelay: 50 });
|
|
expect(await resolveDisconnectCause(fake as never)).toBe('crash');
|
|
});
|
|
|
|
it('crash: null browser returns crash (defensive default)', async () => {
|
|
const { resolveDisconnectCause } = await import('../src/browser-manager');
|
|
expect(await resolveDisconnectCause(null)).toBe('crash');
|
|
});
|
|
});
|