mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 22:49:41 +02:00
Merge pull request #110 from aakhter/cod-31-tmux-session-reliability
fix: harden tmux launch cwd
This commit is contained in:
@@ -20,6 +20,17 @@ vi.mock('node:child_process', async () => {
|
||||
const actual = await vi.importActual('node:child_process');
|
||||
return {
|
||||
...actual,
|
||||
exec: vi.fn((_cmd: string, optionsOrCallback?: unknown, maybeCallback?: unknown) => {
|
||||
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : maybeCallback;
|
||||
if (typeof callback === 'function') {
|
||||
setImmediate(() => callback(null, '', ''));
|
||||
}
|
||||
return {
|
||||
on: vi.fn(),
|
||||
kill: vi.fn(),
|
||||
pid: 12345,
|
||||
};
|
||||
}),
|
||||
execSync: vi.fn(),
|
||||
spawn: vi.fn(() => ({
|
||||
unref: vi.fn(),
|
||||
@@ -41,6 +52,15 @@ vi.mock('node:fs', async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('node:fs/promises', async () => {
|
||||
const actual = await vi.importActual('node:fs/promises');
|
||||
return {
|
||||
...actual,
|
||||
writeFile: vi.fn(() => Promise.resolve()),
|
||||
rename: vi.fn(() => Promise.resolve()),
|
||||
};
|
||||
});
|
||||
|
||||
describe('TmuxManager (unit)', () => {
|
||||
let manager: TmuxManager;
|
||||
const mockedExecSync = vi.mocked(execSync);
|
||||
@@ -369,6 +389,95 @@ describe('TmuxManager (unit)', () => {
|
||||
// No error thrown
|
||||
});
|
||||
});
|
||||
|
||||
describe('tmux launch cwd hardening', () => {
|
||||
async function importWithTmuxCommandsEnabled(): Promise<typeof TmuxManager> {
|
||||
const originalVitest = process.env.VITEST;
|
||||
vi.resetModules();
|
||||
delete process.env.VITEST;
|
||||
const module = await import('../src/tmux-manager.js');
|
||||
if (originalVitest === undefined) {
|
||||
delete process.env.VITEST;
|
||||
} else {
|
||||
process.env.VITEST = originalVitest;
|
||||
}
|
||||
return module.TmuxManager;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockedExecSync.mockImplementation((cmd: string) => {
|
||||
if (typeof cmd === 'string' && cmd.includes('which tmux')) {
|
||||
return '/usr/bin/tmux\n';
|
||||
}
|
||||
if (typeof cmd === 'string' && cmd.includes('display-message') && cmd.includes('#{pane_pid}')) {
|
||||
return '4242\n';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
});
|
||||
|
||||
it('starts new tmux sessions from /tmp and cd-bounces into the requested workspace', async () => {
|
||||
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
|
||||
const nonTestManager = new NonTestTmuxManager();
|
||||
|
||||
try {
|
||||
const session = await nonTestManager.createSession({
|
||||
sessionId: 'abc12345-1234-5678-90ab-cdef12345678',
|
||||
workingDir: '/mnt/gdrive/project with spaces',
|
||||
mode: 'shell',
|
||||
});
|
||||
|
||||
expect(session.workingDir).toBe('/mnt/gdrive/project with spaces');
|
||||
expect(session.pid).toBe(4242);
|
||||
|
||||
const newSessionCall = mockedExecSync.mock.calls.find(
|
||||
([cmd]) => typeof cmd === 'string' && cmd.includes(' new-session ')
|
||||
);
|
||||
expect(newSessionCall?.[0]).toBe(`tmux -L 'codeman' new-session -ds "codeman-abc12345" -c /tmp`);
|
||||
expect(newSessionCall?.[1]).toEqual(expect.objectContaining({ cwd: '/tmp' }));
|
||||
|
||||
const respawnCall = mockedExecSync.mock.calls.find(
|
||||
([cmd]) => typeof cmd === 'string' && cmd.includes(' respawn-pane ')
|
||||
);
|
||||
expect(respawnCall?.[0]).toContain(`tmux -L 'codeman' respawn-pane -k -c /tmp -t "codeman-abc12345"`);
|
||||
expect(respawnCall?.[0]).toContain('cd \\"/mnt/gdrive/project with spaces\\" &&');
|
||||
} finally {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
it('respawns existing panes from /tmp and cd-bounces into the requested workspace', async () => {
|
||||
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
|
||||
const nonTestManager = new NonTestTmuxManager();
|
||||
nonTestManager.registerSession({
|
||||
sessionId: 'respawn1234',
|
||||
muxName: 'codeman-abcd1234',
|
||||
pid: 1000,
|
||||
createdAt: Date.now(),
|
||||
workingDir: '/tmp',
|
||||
mode: 'shell',
|
||||
attached: false,
|
||||
});
|
||||
|
||||
try {
|
||||
const pid = await nonTestManager.respawnPane({
|
||||
sessionId: 'respawn1234',
|
||||
workingDir: '/mnt/gdrive/project',
|
||||
mode: 'shell',
|
||||
});
|
||||
|
||||
expect(pid).toBe(4242);
|
||||
const { exec: currentExec } = await import('node:child_process');
|
||||
const respawnCall = vi
|
||||
.mocked(currentExec)
|
||||
.mock.calls.find(([cmd]) => typeof cmd === 'string' && cmd.includes(' respawn-pane '));
|
||||
expect(respawnCall?.[0]).toContain(`tmux -L 'codeman' respawn-pane -k -c /tmp -t "codeman-abcd1234"`);
|
||||
expect(respawnCall?.[0]).toContain('cd \\"/mnt/gdrive/project\\" &&');
|
||||
} finally {
|
||||
nonTestManager.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user