mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
* refactor(protocol): each area owns its published runtime capability list RUNTIME_CAPABILITIES now spreads one array per area instead of a flat hand-appended list, and runtime-capability-constants.ts is split into the areas that own each constant. The published set is unchanged and pinned by an exact sorted test. * test: preserve capability baseline while allowing additions
857 lines
26 KiB
TypeScript
857 lines
26 KiB
TypeScript
import path from 'node:path'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
|
|
const {
|
|
callMock,
|
|
runtimeClientConstructorMock,
|
|
serveOrcaAppMock,
|
|
getDefaultUserDataPathMock,
|
|
addEnvironmentFromPairingCodeMock,
|
|
listEnvironmentsMock,
|
|
spawnMock
|
|
} = vi.hoisted(() => ({
|
|
callMock: vi.fn(),
|
|
runtimeClientConstructorMock: vi.fn(),
|
|
serveOrcaAppMock: vi.fn(),
|
|
getDefaultUserDataPathMock: vi.fn(() => '/tmp/orca-user-data'),
|
|
addEnvironmentFromPairingCodeMock: vi.fn(),
|
|
listEnvironmentsMock: vi.fn(),
|
|
spawnMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./runtime-client', async () => {
|
|
const { createRuntimeClientModuleMock } = await import('./index-test-harness.js')
|
|
return createRuntimeClientModuleMock({
|
|
callMock,
|
|
runtimeClientConstructorMock,
|
|
serveOrcaAppMock,
|
|
getDefaultUserDataPathMock
|
|
})
|
|
})
|
|
|
|
vi.mock('./runtime/environments', () => ({
|
|
addEnvironmentFromPairingCode: addEnvironmentFromPairingCodeMock,
|
|
listEnvironments: listEnvironmentsMock,
|
|
removeEnvironment: vi.fn(),
|
|
resolveEnvironment: vi.fn()
|
|
}))
|
|
|
|
vi.mock('child_process', async () => {
|
|
const { createChildProcessModuleMock } = await import('./index-test-harness.js')
|
|
return createChildProcessModuleMock(spawnMock)
|
|
})
|
|
|
|
import { main } from './index'
|
|
import { okFixture, queueFixtures } from './test-fixtures'
|
|
import { PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY } from '../shared/worktree-runtime-capabilities'
|
|
import { pairRuntimeEnvironment, useWorktreeAwarenessEnvironment } from './index-test-harness'
|
|
|
|
const ORCA_PROJECT = {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: ['repo-1'],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
}
|
|
const PROJECT_LIST_FIXTURE = okFixture('req_project_list', { projects: [ORCA_PROJECT] })
|
|
|
|
describe('orca cli worktree awareness', () => {
|
|
useWorktreeAwarenessEnvironment({
|
|
callMock,
|
|
serveOrcaAppMock,
|
|
getDefaultUserDataPathMock,
|
|
addEnvironmentFromPairingCodeMock,
|
|
listEnvironmentsMock,
|
|
spawnMock
|
|
})
|
|
|
|
it('resolves repo.add paths against the invoking cli cwd', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_repo_add', {
|
|
repo: {
|
|
id: 'repo-1',
|
|
path: path.resolve('/tmp/repo/apps/web'),
|
|
displayName: 'web'
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(['repo', 'add', '--path', './apps/web', '--json'], '/tmp/repo')
|
|
|
|
expect(callMock).toHaveBeenCalledWith('repo.add', {
|
|
path: path.resolve('/tmp/repo/apps/web')
|
|
})
|
|
})
|
|
|
|
it('lists projects through the project-first runtime API', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_project_list', {
|
|
projects: [
|
|
{
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
providerIdentity: {
|
|
provider: 'github',
|
|
owner: 'stablyai',
|
|
repo: 'orca'
|
|
},
|
|
sourceRepoIds: ['repo-1'],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(['project', 'list', '--json'], '/tmp/repo')
|
|
|
|
expect(callMock).toHaveBeenCalledWith('project.list')
|
|
})
|
|
|
|
it('routes a runtime host filter to that paired server and keeps its own local rows', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'gpu')
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_project_setups', {
|
|
setups: [
|
|
{
|
|
id: 'setup-local',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
repoId: 'repo-local',
|
|
path: '/tmp/orca',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'legacy-repo',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
{
|
|
id: 'setup-remote',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:gpu',
|
|
repoId: 'repo-remote',
|
|
path: '/srv/orca',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'legacy-repo',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
)
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
['project', 'setups', '--project', 'github:stablyai/orca', '--host', 'runtime:gpu'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(runtimeClientConstructorMock).toHaveBeenCalledWith(null, 'gpu')
|
|
expect(callMock).toHaveBeenCalledWith('projectHostSetup.list')
|
|
expect(logSpy.mock.calls[0]?.[0]).toContain('setup-remote')
|
|
expect(logSpy.mock.calls[0]?.[0]).toContain('setup-local')
|
|
})
|
|
|
|
it('keeps --host local a filter on the selected environment rather than a second selector', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'prod')
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_project_setups', {
|
|
setups: [
|
|
{
|
|
id: 'setup-on-box',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
repoId: 'repo-on-box',
|
|
path: '/srv/orca',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'legacy-repo',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
{
|
|
id: 'setup-by-client',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:prod',
|
|
repoId: 'repo-by-client',
|
|
path: '/srv/orca-2',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'legacy-repo',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
)
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(['project', 'setups', '--environment', 'prod', '--host', 'local'], '/tmp/repo')
|
|
|
|
expect(runtimeClientConstructorMock).toHaveBeenCalledWith(undefined, 'prod')
|
|
expect(logSpy.mock.calls[0]?.[0]).toContain('setup-on-box')
|
|
expect(logSpy.mock.calls[0]?.[0]).not.toContain('setup-by-client')
|
|
})
|
|
|
|
// Why: --host runtime:<id> routes to a paired server, so an older one is reachable without the
|
|
// caller meaning to. A raw method_not_found reads as an Orca bug rather than a version gap.
|
|
it('names the version gap when the server predates project host setup', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'old-server')
|
|
const { RuntimeClientError } = await import('./runtime/types.js')
|
|
callMock.mockRejectedValueOnce(
|
|
new RuntimeClientError('method_not_found', 'Unknown method: projectHostSetup.list')
|
|
)
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(['project', 'setups', '--host', 'runtime:old-server', '--json'], '/tmp/repo')
|
|
|
|
const printed = [...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')
|
|
expect(printed).toContain('does not support project host setup yet')
|
|
expect(printed).not.toContain('Unknown method')
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects a runtime host id that no paired server owns instead of answering empty', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(['project', 'setups', '--host', 'runtime:not-a-real-env', '--json'], '/tmp/repo')
|
|
|
|
// The command itself never reached a runtime; only the suggestion lookup did.
|
|
expect(callMock).not.toHaveBeenCalledWith('projectHostSetup.list')
|
|
const printed = [...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')
|
|
expect(printed).toContain('no paired Orca server is named or has id not-a-real-env')
|
|
// An agent reads the code and the retry candidates, not the prose.
|
|
expect(JSON.parse(printed).error.code).toBe('invalid_argument')
|
|
expect(JSON.parse(printed).error.data.knownEnvironments).toEqual([])
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
// Why: ssh: was never validated, so an unknown target answered ok:true with an empty list —
|
|
// the same silent wrong-machine answer unknown runtime ids used to give.
|
|
it('rejects an unknown ssh host instead of answering empty', async () => {
|
|
queueFixtures(callMock, okFixture('req_ssh_targets', { targets: [] }))
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(['project', 'setups', '--host', 'ssh:openclaw', '--json'], '/tmp/repo')
|
|
|
|
expect(callMock).not.toHaveBeenCalledWith('projectHostSetup.list')
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'no SSH target named or with id openclaw'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('tells a caller reaching for a paired server by ssh that it is an environment', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'awin')
|
|
queueFixtures(callMock, okFixture('req_ssh_targets', { targets: [] }))
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(['project', 'setups', '--host', 'ssh:awin', '--json'], '/tmp/repo')
|
|
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'--environment awin'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('resolves an ssh label to its target id before filtering', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_ssh_targets', { targets: [{ id: 'ssh-123-abc', label: 'openclaw' }] }),
|
|
okFixture('req_project_setups', {
|
|
setups: [
|
|
{
|
|
id: 'setup-openclaw',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'ssh:ssh-123-abc',
|
|
repoId: 'repo-openclaw',
|
|
path: '/home/me/orca',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'legacy-repo',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
}
|
|
]
|
|
})
|
|
)
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(['project', 'setups', '--host', 'ssh:openclaw'], '/tmp/repo')
|
|
|
|
expect(logSpy.mock.calls[0]?.[0]).toContain('setup-openclaw')
|
|
})
|
|
|
|
// Why: `runtime:<id>` is a persisted token — it lands in ProjectHostSetup.hostId and is
|
|
// embedded in generated setup ids. Accepting a name is only safe because it is canonicalized
|
|
// to the id before anything downstream sees it; this pins that.
|
|
it('never lets an environment name reach a persisted host id', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'env-uuid-1', 'awin')
|
|
queueFixtures(
|
|
callMock,
|
|
PROJECT_LIST_FIXTURE,
|
|
okFixture('req_project_setup_create', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: [],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-awin',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
repoId: '',
|
|
path: '',
|
|
displayName: 'awin',
|
|
setupState: 'setting-up',
|
|
setupMethod: 'provisioned',
|
|
createdAt: 1,
|
|
updatedAt: 2
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-create',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'runtime:awin',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(runtimeClientConstructorMock).toHaveBeenCalledWith(null, 'env-uuid-1')
|
|
expect(callMock).toHaveBeenCalledWith(
|
|
'projectHostSetup.create',
|
|
expect.objectContaining({ hostId: 'runtime:env-uuid-1' })
|
|
)
|
|
})
|
|
|
|
it('rejects a malformed --host value before contacting any runtime', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(['project', 'setups', '--host', 'runtime:', '--json'], '/tmp/repo')
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Invalid --host value: runtime:'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('refuses a runtime host id alongside an unrelated --pairing-code connection', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'gpu')
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
['project', 'setups', '--host', 'runtime:gpu', '--pairing-code', 'remote-runtime', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'use either --host runtime:<id> or --pairing-code, not both'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('sets up an existing project folder with a path resolved against the local cli cwd', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
PROJECT_LIST_FIXTURE,
|
|
okFixture('req_project_setup', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: ['repo-1'],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-local',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
repoId: 'repo-1',
|
|
path: path.resolve('/tmp/orca'),
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'imported-existing-folder',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
repo: {
|
|
id: 'repo-1',
|
|
path: path.resolve('/tmp/orca'),
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
addedAt: 1
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-existing-folder',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'local',
|
|
'--path',
|
|
'..',
|
|
'--kind',
|
|
'git',
|
|
'--display-name',
|
|
'Orca',
|
|
'--json'
|
|
],
|
|
'/tmp/orca/worktrees/feature'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenCalledWith('projectHostSetup.setupExistingFolder', {
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
path: path.resolve('/tmp/orca/worktrees'),
|
|
kind: 'git',
|
|
displayName: 'Orca'
|
|
})
|
|
})
|
|
|
|
it('rejects remote project setup relative paths instead of resolving against client cwd', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'gpu')
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-existing-folder',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'runtime:gpu',
|
|
'--path',
|
|
'./orca',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Remote project setup requires --path to be an absolute path on the remote server.'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects SSH project setup relative paths, which name the client filesystem', async () => {
|
|
// A local CLI reaching an `ssh:*` host is still off-client: resolving `./orca` against the
|
|
// CLI cwd would register a path that exists on the wrong machine.
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-existing-folder',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'ssh:openclaw',
|
|
'--path',
|
|
'./orca',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Remote project setup requires --path to be an absolute path on the remote server.'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('rejects remote repo.add relative paths instead of resolving against client cwd', async () => {
|
|
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
const priorExitCode = process.exitCode
|
|
|
|
await main(
|
|
['repo', 'add', '--path', './apps/web', '--pairing-code', 'remote-runtime', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalled()
|
|
expect([...logSpy.mock.calls, ...errSpy.mock.calls].flat().join('\n')).toContain(
|
|
'Remote repo add requires --path to be an absolute path on the remote server.'
|
|
)
|
|
expect(process.exitCode).toBe(1)
|
|
|
|
process.exitCode = priorExitCode
|
|
})
|
|
|
|
it('sends remote repo.add absolute paths unchanged', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_repo_add', {
|
|
repo: {
|
|
id: 'repo-1',
|
|
path: '/srv/orca/web',
|
|
displayName: 'web'
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
['repo', 'add', '--path', '/srv/orca/web', '--pairing-code', 'remote-runtime', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenCalledWith('repo.add', {
|
|
path: '/srv/orca/web'
|
|
})
|
|
})
|
|
|
|
it.each(['C:\\repo', 'C:/repo', '\\\\server\\share\\repo', '//server/share/repo'])(
|
|
'sends remote repo.add server absolute path %s unchanged',
|
|
async (serverPath) => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_repo_add', {
|
|
repo: {
|
|
id: 'repo-1',
|
|
path: serverPath,
|
|
displayName: 'web'
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
['repo', 'add', '--path', serverPath, '--pairing-code', 'remote-runtime', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenCalledWith('repo.add', {
|
|
path: serverPath
|
|
})
|
|
}
|
|
)
|
|
|
|
// Why: STA-4792 defect 2. `--host runtime:<id>` used to leave the client local, so a Windows
|
|
// destination fell into resolve(cwd, ...) and became a literal directory next to the caller.
|
|
// Routing makes the client remote, which is what sends the path through untouched.
|
|
it('sends a windows destination to the routed host instead of joining it to the local cwd', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'awin')
|
|
queueFixtures(
|
|
callMock,
|
|
PROJECT_LIST_FIXTURE,
|
|
okFixture('req_project_setup_clone', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: [],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-awin',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'local',
|
|
repoId: 'repo-awin',
|
|
path: 'C:\\orca-probe\\orca',
|
|
displayName: 'Orca',
|
|
setupState: 'ready',
|
|
setupMethod: 'cloned',
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
repo: {
|
|
id: 'repo-awin',
|
|
path: 'C:\\orca-probe\\orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
addedAt: 1
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-clone',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'runtime:awin',
|
|
'--url',
|
|
'https://github.com/stablyai/orca.git',
|
|
'--destination',
|
|
'C:\\orca-probe',
|
|
'--json'
|
|
],
|
|
'/Users/nwparker/orca/workspaces/orca/IME-koko'
|
|
)
|
|
|
|
expect(runtimeClientConstructorMock).toHaveBeenCalledWith(null, 'awin')
|
|
expect(callMock).toHaveBeenCalledWith(
|
|
'projectHostSetup.clone',
|
|
expect.objectContaining({ destination: 'C:\\orca-probe' })
|
|
)
|
|
})
|
|
|
|
it('updates project host setup metadata through the project-first runtime API', async () => {
|
|
queueFixtures(
|
|
callMock,
|
|
okFixture('req_project_setup_update', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: [],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-gpu',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:gpu',
|
|
repoId: '',
|
|
path: '/srv/orca',
|
|
displayName: 'GPU VM',
|
|
setupState: 'ready',
|
|
setupMethod: 'imported-existing-folder',
|
|
createdAt: 1,
|
|
updatedAt: 2
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-update',
|
|
'--setup',
|
|
'setup-gpu',
|
|
'--display-name',
|
|
'GPU VM',
|
|
'--path',
|
|
'/srv/orca',
|
|
'--worktree-base-path',
|
|
'../worktrees',
|
|
'--state',
|
|
'ready',
|
|
'--method',
|
|
'imported-existing-folder',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenCalledWith('projectHostSetup.update', {
|
|
setupId: 'setup-gpu',
|
|
updates: {
|
|
displayName: 'GPU VM',
|
|
path: path.resolve('/tmp/repo', '/srv/orca'),
|
|
worktreeBasePath: '../worktrees',
|
|
gitUsername: undefined,
|
|
kind: undefined,
|
|
setupState: 'ready',
|
|
setupMethod: 'imported-existing-folder'
|
|
}
|
|
})
|
|
})
|
|
|
|
it('creates independent project host setup metadata through the project-first runtime API', async () => {
|
|
pairRuntimeEnvironment(listEnvironmentsMock, 'gpu')
|
|
queueFixtures(
|
|
callMock,
|
|
PROJECT_LIST_FIXTURE,
|
|
okFixture('req_project_setup_create', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: [],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-gpu',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:gpu',
|
|
repoId: '',
|
|
path: '',
|
|
displayName: 'GPU VM',
|
|
setupState: 'setting-up',
|
|
setupMethod: 'provisioned',
|
|
createdAt: 1,
|
|
updatedAt: 2
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(
|
|
[
|
|
'project',
|
|
'setup-create',
|
|
'--project',
|
|
'github:stablyai/orca',
|
|
'--host',
|
|
'runtime:gpu',
|
|
'--setup-id',
|
|
'setup-gpu',
|
|
'--display-name',
|
|
'GPU VM',
|
|
'--state',
|
|
'setting-up',
|
|
'--method',
|
|
'provisioned',
|
|
'--json'
|
|
],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).toHaveBeenCalledWith('projectHostSetup.create', {
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:gpu',
|
|
setupId: 'setup-gpu',
|
|
path: undefined,
|
|
kind: undefined,
|
|
displayName: 'GPU VM',
|
|
worktreeBasePath: undefined,
|
|
gitUsername: undefined,
|
|
setupState: 'setting-up',
|
|
setupMethod: 'provisioned'
|
|
})
|
|
})
|
|
|
|
it.each([
|
|
{ flags: [], force: false, hostId: undefined },
|
|
{ flags: ['--force'], force: true, hostId: undefined },
|
|
// Why: a project on two hosts has two setups with one id; --host names the twin.
|
|
{ flags: ['--host', 'local'], force: false, hostId: 'local' }
|
|
])('deletes project host setup metadata with $flags', async ({ flags, force, hostId }) => {
|
|
queueFixtures(
|
|
callMock,
|
|
...(hostId ? [hostTargetStatusFixture(true)] : []),
|
|
okFixture('req_project_setup_delete', {
|
|
result: {
|
|
project: {
|
|
id: 'github:stablyai/orca',
|
|
displayName: 'Orca',
|
|
badgeColor: '#7c3aed',
|
|
sourceRepoIds: [],
|
|
createdAt: 1,
|
|
updatedAt: 1
|
|
},
|
|
setup: {
|
|
id: 'setup-gpu',
|
|
projectId: 'github:stablyai/orca',
|
|
hostId: 'runtime:gpu',
|
|
repoId: '',
|
|
path: '/srv/orca',
|
|
displayName: 'GPU VM',
|
|
setupState: 'ready',
|
|
setupMethod: 'imported-existing-folder',
|
|
createdAt: 1,
|
|
updatedAt: 2
|
|
}
|
|
}
|
|
})
|
|
)
|
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
|
|
await main(['project', 'setup-delete', '--setup', 'setup-gpu', ...flags, '--json'], '/tmp/repo')
|
|
|
|
expect(callMock).toHaveBeenCalledWith('projectHostSetup.delete', {
|
|
setupId: 'setup-gpu',
|
|
hostId,
|
|
force
|
|
})
|
|
})
|
|
|
|
it('refuses --host on a server that would ignore it and act on the first same-id twin', async () => {
|
|
queueFixtures(callMock, hostTargetStatusFixture(false))
|
|
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
|
|
await main(
|
|
['project', 'setup-delete', '--setup', 'dup', '--host', 'local', '--force', '--json'],
|
|
'/tmp/repo'
|
|
)
|
|
|
|
expect(callMock).not.toHaveBeenCalledWith('projectHostSetup.delete', expect.anything())
|
|
})
|
|
})
|
|
|
|
function hostTargetStatusFixture(supported: boolean) {
|
|
return okFixture('req_status', {
|
|
capabilities: supported ? [PROJECT_HOST_SETUP_HOST_TARGET_RUNTIME_CAPABILITY] : []
|
|
})
|
|
}
|