- Box init + chownInBox exec explicitly as root: under podman keep-id the container's default user is the mapped uid, which cannot useradd/chown. - Init renames an existing uid-1000 passwd entry (podman keep-id injects one named after the HOST user) instead of adding a duplicate that would win the getpwuid lookup and make whoami report the wrong name. - Terminal connections tag their shell with a per-connection marker and reap the process group on WebSocket close: closing the attach socket never killed the exec'd shell, so every disconnect leaked an attached tmux client (session stuck at the smallest zombie's size; enough dead clients starve tmux rendering for live ones). - Job image: nvm 0.39.5 at /usr/local/nvm (user-writable) with /etc/profile.d/nvm.sh, matching what user dotfiles expect; docs for the box user + ownership model.
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
const load = async () => {
|
|
vi.resetModules();
|
|
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
|
|
process.env.GITHUB_APP_ID = '123';
|
|
process.env.GITHUB_APP_PRIVATE_KEY =
|
|
'-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----';
|
|
vi.doMock('../../src/worker', () => ({ getTerminalWorkspace: () => null }));
|
|
vi.doMock('../../src/user-container', () => ({
|
|
acquireUserBox: vi.fn(),
|
|
}));
|
|
return await import('../../src/terminal');
|
|
};
|
|
|
|
describe('parseResizeMessage', () => {
|
|
afterEach(() => vi.resetModules());
|
|
|
|
test('parses and clamps a resize control frame', async () => {
|
|
const { parseResizeMessage } = await load();
|
|
const msg = Buffer.from(
|
|
JSON.stringify({ type: 'resize', cols: 120, rows: 40 }),
|
|
);
|
|
expect(parseResizeMessage(msg, false)).toEqual({ cols: 120, rows: 40 });
|
|
});
|
|
|
|
test('returns null for binary input (raw keystrokes)', async () => {
|
|
const { parseResizeMessage } = await load();
|
|
expect(parseResizeMessage(Buffer.from([1, 2, 3]), true)).toBeNull();
|
|
});
|
|
|
|
test('returns null for non-resize JSON', async () => {
|
|
const { parseResizeMessage } = await load();
|
|
expect(
|
|
parseResizeMessage(Buffer.from('{"type":"other"}'), false),
|
|
).toBeNull();
|
|
});
|
|
|
|
test('clamps out-of-range dimensions into [1,1000]', async () => {
|
|
const { parseResizeMessage } = await load();
|
|
const msg = Buffer.from(
|
|
JSON.stringify({ type: 'resize', cols: 99999, rows: 0 }),
|
|
);
|
|
expect(parseResizeMessage(msg, false)).toEqual({ cols: 1000, rows: 1 });
|
|
});
|
|
});
|
|
|
|
describe('buildTerminalShellCommand', () => {
|
|
test('embeds the connection marker without exec-ing the shell away', async () => {
|
|
const { buildTerminalShellCommand } = await load();
|
|
const command = buildTerminalShellCommand('spoon-term-abc-123');
|
|
expect(command[0]).toBe('/bin/bash');
|
|
const script = command[2] ?? '';
|
|
// Marker must stay in the wrapper's argv for pgrep-based cleanup, so the
|
|
// inner shell/tmux must NOT be exec'd (exec would replace the argv).
|
|
expect(script).toContain('# spoon-term-abc-123');
|
|
expect(script).not.toContain('exec tmux');
|
|
expect(script).not.toContain('exec bash');
|
|
expect(script).toContain('tmux new-session -A -s spoon');
|
|
});
|
|
});
|