From 0c788bf975544ab4c43b4dfefb7c5d89e4504f9b Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:54:32 -0700 Subject: [PATCH] test(runtime): make the SSH guard test observe the corruption it names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test asserted `gitSpawn` was never called and no repo was registered, but neither assertion could fail. `/home/brennan` is unwritable on macOS, so the pre-guard clone died at `mkdir` before reaching `gitSpawn`, and `/home/brennan/orca` failed `isGitRepo` before reaching `addRepo` — the exact side effects under test were unreachable either way. `rejects.toThrow` also aborted the test before those lines ran. Use a real temp destination and a real temp git repo, await both calls via `.catch`, and assert the side effects before the wording. With the guard disabled the test now fails on `gitSpawn` being called once with a real `git clone`, and on a repo registered stamped `executionHostId: 'ssh:openclaw'` — the silent local-clone-recorded-as-remote defect itself. `gitSpawn` is stubbed so a regression records the call instead of hitting the network. Also document the SSH restriction on `project setup-existing-folder`, which the guard now rejects. `setup-clone` already carried that note; its sibling did not. --- src/cli/specs/project.ts | 5 ++- src/main/runtime/orca-runtime.test.ts | 44 ++++++++++++++++++++------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/cli/specs/project.ts b/src/cli/specs/project.ts index 989a83c9f77..caeabda3de7 100644 --- a/src/cli/specs/project.ts +++ b/src/cli/specs/project.ts @@ -27,7 +27,10 @@ export const PROJECT_COMMAND_SPECS: CommandSpec[] = [ usage: 'orca project setup-existing-folder --project --host --path [--kind git|folder] [--display-name ] [--json]', allowedFlags: [...GLOBAL_FLAGS, 'project', 'host', 'path', 'kind', 'display-name'], - notes: ['For remote runtimes, --path must be an absolute path on the remote server.'], + notes: [ + 'For remote runtimes, --path must be an absolute path on the remote server.', + 'SSH targets are set up through the desktop UI because the desktop client owns SSH connections.' + ], examples: [ 'orca project setup-existing-folder --project github:stablyai/orca --host local --path ~/orca', 'orca project setup-existing-folder --project github:stablyai/orca --host runtime:gpu --path /home/me/orca --kind git --json' diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index c1cfd6f47a6..0f38554ec38 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -7160,7 +7160,21 @@ describe('OrcaRuntimeService', () => { }) it('refuses SSH hosts instead of setting the project up on the local machine', async () => { - const spawnSpy = vi.spyOn(gitRunner, 'gitSpawn') + // Why: both inputs must be paths the pre-guard code would have accepted. An unwritable + // destination fails at mkdir and a non-repo path fails at isGitRepo, which would leave the + // side-effect assertions below unable to observe the local clone/probe they exist to catch. + const destination = await mkdtemp(join(tmpdir(), 'orca-runtime-ssh-guard-')) + const existingFolder = join(destination, 'orca') + mkdirSync(existingFolder, { recursive: true }) + execFileSync('git', ['init'], { cwd: existingFolder, stdio: 'ignore' }) + const spawnSpy = vi.spyOn(gitRunner, 'gitSpawn').mockImplementation(() => { + // Why: unreachable while the guard holds; stubbed so a regression records the call + // instead of shelling out to a real network clone. + const proc = new EventEmitter() as EventEmitter & { stderr: EventEmitter } + proc.stderr = new EventEmitter() + queueMicrotask(() => proc.emit('close', 1, null)) + return proc as never + }) const repos: Record[] = [] const runtimeStore = { ...store, @@ -7172,29 +7186,37 @@ describe('OrcaRuntimeService', () => { const runtime = new OrcaRuntimeService(runtimeStore as never) try { - await expect( - runtime.setupProjectClone({ + const cloneError = await runtime + .setupProjectClone({ projectId: 'github:stablyai/orca', hostId: 'ssh:openclaw', url: 'https://example.com/orca.git', - destination: '/home/brennan' + destination }) - ).rejects.toThrow(/SSH hosts are not supported/) - - await expect( - runtime.setupProjectExistingFolder({ + .catch((error: unknown) => error) + const existingFolderError = await runtime + .setupProjectExistingFolder({ projectId: 'github:stablyai/orca', hostId: 'ssh:openclaw', - path: '/home/brennan/orca', + path: existingFolder, kind: 'git' }) - ).rejects.toThrow(/SSH hosts are not supported/) + .catch((error: unknown) => error) - // Why: the defect was a silent local clone/probe recorded as remote, not a bad message. + // Why: the defect was a silent local clone/probe recorded as remote, not a bad message, + // so the absent side effects are asserted before the wording. Both calls are awaited + // first so a regression reports the corruption rather than stopping at the first throw. expect(spawnSpy).not.toHaveBeenCalled() expect(repos).toHaveLength(0) + expect(cloneError).toMatchObject({ + message: expect.stringMatching(/SSH hosts are not supported/) + }) + expect(existingFolderError).toMatchObject({ + message: expect.stringMatching(/SSH hosts are not supported/) + }) } finally { spawnSpy.mockRestore() + await rm(destination, { recursive: true, force: true }) } })