mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
300 lines
9.0 KiB
TypeScript
300 lines
9.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { ipcMain } from 'electron'
|
|
import type { Store } from '../persistence'
|
|
import type {
|
|
RemoteWorkspaceSession,
|
|
RemoteWorkspaceSnapshot
|
|
} from '../../shared/remote-workspace-types'
|
|
import type { SshTarget } from '../../shared/ssh-types'
|
|
import type { WorkspaceSessionState } from '../../shared/types'
|
|
|
|
const {
|
|
getActiveMultiplexerMock,
|
|
getSshConnectionStoreMock,
|
|
registerRemoteWorkspaceNotificationHandlerMock
|
|
} = vi.hoisted(() => ({
|
|
getActiveMultiplexerMock: vi.fn(),
|
|
getSshConnectionStoreMock: vi.fn(),
|
|
registerRemoteWorkspaceNotificationHandlerMock: vi.fn(() => vi.fn())
|
|
}))
|
|
|
|
vi.mock('electron', () => ({
|
|
ipcMain: {
|
|
handle: vi.fn(),
|
|
removeHandler: vi.fn()
|
|
}
|
|
}))
|
|
|
|
vi.mock('./ssh', () => ({
|
|
getActiveMultiplexer: getActiveMultiplexerMock,
|
|
getSshConnectionStore: getSshConnectionStoreMock
|
|
}))
|
|
|
|
vi.mock('./remote-workspace-events', () => ({
|
|
registerRemoteWorkspaceNotificationHandler: registerRemoteWorkspaceNotificationHandlerMock
|
|
}))
|
|
|
|
import {
|
|
_resetRemoteWorkspaceCachesForTests,
|
|
registerRemoteWorkspaceHandlers,
|
|
remoteWorkspaceSessionMatchesSnapshot
|
|
} from './remote-workspace'
|
|
|
|
function snapshot(session: RemoteWorkspaceSession, revision = 7): RemoteWorkspaceSnapshot {
|
|
return {
|
|
namespace: 'target',
|
|
revision,
|
|
updatedAt: 123,
|
|
schemaVersion: 1,
|
|
session
|
|
}
|
|
}
|
|
|
|
const baseSession = {
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeTabId: null,
|
|
tabsByWorktree: {},
|
|
terminalLayoutsByTabId: {}
|
|
} as WorkspaceSessionState
|
|
|
|
const targets: SshTarget[] = [
|
|
{
|
|
id: 'target-1',
|
|
label: 'Target 1',
|
|
host: 'one.example.com',
|
|
port: 22,
|
|
username: 'alice'
|
|
},
|
|
{
|
|
id: 'target-2',
|
|
label: 'Target 2',
|
|
host: 'two.example.com',
|
|
port: 22,
|
|
username: 'alice'
|
|
}
|
|
]
|
|
|
|
describe('remoteWorkspaceSessionMatchesSnapshot', () => {
|
|
it('matches normalized equivalent sessions', () => {
|
|
expect(
|
|
remoteWorkspaceSessionMatchesSnapshot(
|
|
snapshot({
|
|
activeWorktreePath: null,
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {}
|
|
}),
|
|
{
|
|
activeWorktreePath: null,
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {},
|
|
activeWorktreePathsOnShutdown: undefined,
|
|
activeTabIdByWorktreePath: undefined,
|
|
remoteSessionIdsByTabId: undefined,
|
|
lastVisitedAtByWorktreePath: undefined
|
|
}
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('treats empty optional projection fields as equivalent to absent fields', () => {
|
|
expect(
|
|
remoteWorkspaceSessionMatchesSnapshot(
|
|
snapshot({
|
|
activeWorktreePath: null,
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {},
|
|
activeWorktreePathsOnShutdown: [],
|
|
activeTabIdByWorktreePath: {},
|
|
remoteSessionIdsByTabId: {},
|
|
lastVisitedAtByWorktreePath: {}
|
|
}),
|
|
{
|
|
activeWorktreePath: null,
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {}
|
|
}
|
|
)
|
|
).toBe(true)
|
|
})
|
|
|
|
it('detects actual target session changes', () => {
|
|
expect(
|
|
remoteWorkspaceSessionMatchesSnapshot(
|
|
snapshot({
|
|
activeWorktreePath: '/repo',
|
|
activeTabId: 'tab-1',
|
|
tabsByWorktreePath: {
|
|
'/repo': [{ id: 'tab-1', type: 'terminal', title: 'Shell' } as never]
|
|
},
|
|
terminalLayoutsByTabId: {}
|
|
}),
|
|
{
|
|
activeWorktreePath: '/repo',
|
|
activeTabId: 'tab-2',
|
|
tabsByWorktreePath: {
|
|
'/repo': [{ id: 'tab-2', type: 'terminal', title: 'Shell 2' } as never]
|
|
},
|
|
terminalLayoutsByTabId: {}
|
|
}
|
|
)
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('remoteWorkspace:setForConnectedTargets', () => {
|
|
const handlers = new Map<string, (event: unknown, args: unknown) => unknown>()
|
|
const requestByTargetId = new Map<string, ReturnType<typeof vi.fn>>()
|
|
const muxByTargetId = new Map<string, { request: ReturnType<typeof vi.fn> }>()
|
|
const getRepoMock = vi.fn<Store['getRepo']>()
|
|
const getWorkspaceSessionMock = vi.fn<Store['getWorkspaceSession']>()
|
|
const store = {
|
|
getRepo: getRepoMock,
|
|
getWorkspaceSession: getWorkspaceSessionMock
|
|
} as unknown as Store
|
|
|
|
beforeEach(() => {
|
|
_resetRemoteWorkspaceCachesForTests()
|
|
handlers.clear()
|
|
requestByTargetId.clear()
|
|
muxByTargetId.clear()
|
|
vi.mocked(ipcMain.handle).mockReset()
|
|
vi.mocked(ipcMain.handle).mockImplementation((channel, handler) => {
|
|
handlers.set(channel, handler as (event: unknown, args: unknown) => unknown)
|
|
})
|
|
vi.mocked(ipcMain.removeHandler).mockReset()
|
|
getSshConnectionStoreMock.mockReset()
|
|
getSshConnectionStoreMock.mockReturnValue({
|
|
listTargets: () => targets
|
|
})
|
|
getRepoMock.mockReset()
|
|
getWorkspaceSessionMock.mockReset()
|
|
getWorkspaceSessionMock.mockReturnValue(baseSession)
|
|
getRepoMock.mockImplementation((repoId: string) =>
|
|
repoId === 'repo-target-1'
|
|
? ({
|
|
id: 'repo-target-1',
|
|
path: '/remote/repo',
|
|
displayName: 'Repo',
|
|
badgeColor: 'blue',
|
|
addedAt: 1,
|
|
connectionId: 'target-1'
|
|
} as never)
|
|
: undefined
|
|
)
|
|
getActiveMultiplexerMock.mockReset()
|
|
getActiveMultiplexerMock.mockImplementation((targetId: string) => {
|
|
let mux = muxByTargetId.get(targetId)
|
|
if (!mux) {
|
|
const request = vi.fn().mockImplementation((method: string) => {
|
|
if (method === 'workspace.get') {
|
|
return Promise.resolve(
|
|
snapshot({
|
|
activeWorktreePath: '/previous',
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {}
|
|
})
|
|
)
|
|
}
|
|
return Promise.resolve({
|
|
ok: true,
|
|
snapshot: snapshot({
|
|
activeWorktreePath: null,
|
|
activeTabId: null,
|
|
tabsByWorktreePath: {},
|
|
terminalLayoutsByTabId: {}
|
|
})
|
|
})
|
|
})
|
|
mux = { request }
|
|
muxByTargetId.set(targetId, mux)
|
|
requestByTargetId.set(targetId, request)
|
|
}
|
|
return mux
|
|
})
|
|
registerRemoteWorkspaceNotificationHandlerMock.mockClear()
|
|
|
|
registerRemoteWorkspaceHandlers(store, () => null)
|
|
})
|
|
|
|
async function callSetForConnectedTargets(args: {
|
|
session?: WorkspaceSessionState
|
|
hydratedTargetIds?: unknown
|
|
}): Promise<unknown> {
|
|
const handler = handlers.get('remoteWorkspace:setForConnectedTargets')
|
|
if (!handler) {
|
|
throw new Error('remoteWorkspace:setForConnectedTargets handler was never registered')
|
|
}
|
|
return handler(null, args)
|
|
}
|
|
|
|
it('does not write without an explicit non-empty hydrated target set', async () => {
|
|
await expect(callSetForConnectedTargets({ session: baseSession })).resolves.toEqual([])
|
|
await expect(
|
|
callSetForConnectedTargets({ session: baseSession, hydratedTargetIds: [] })
|
|
).resolves.toEqual([])
|
|
await expect(
|
|
callSetForConnectedTargets({ session: baseSession, hydratedTargetIds: ['target-1', 42] })
|
|
).resolves.toEqual([])
|
|
|
|
expect(getSshConnectionStoreMock).not.toHaveBeenCalled()
|
|
expect(getActiveMultiplexerMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('writes only to explicitly hydrated connected targets', async () => {
|
|
const result = await callSetForConnectedTargets({
|
|
session: baseSession,
|
|
hydratedTargetIds: ['target-1', 'missing-target']
|
|
})
|
|
|
|
expect(result).toMatchObject([{ targetId: 'target-1', result: { ok: true } }])
|
|
expect(getActiveMultiplexerMock).toHaveBeenCalledWith('target-1')
|
|
expect(getActiveMultiplexerMock).not.toHaveBeenCalledWith('target-2')
|
|
expect(requestByTargetId.get('target-1')).toHaveBeenCalledWith(
|
|
'workspace.patch',
|
|
expect.objectContaining({
|
|
patch: expect.objectContaining({ kind: 'replace-session' })
|
|
})
|
|
)
|
|
expect(requestByTargetId.get('target-2')).toBeUndefined()
|
|
})
|
|
|
|
it('can export from the persisted store session when no session argument is provided', async () => {
|
|
getWorkspaceSessionMock.mockReturnValue({
|
|
activeRepoId: 'repo-target-1',
|
|
activeWorktreeId: 'repo-target-1::/repo',
|
|
activeTabId: 'tab-store',
|
|
tabsByWorktree: {
|
|
'repo-target-1::/repo': [
|
|
{
|
|
id: 'tab-store',
|
|
title: 'Store shell',
|
|
ptyId: 'pty-store',
|
|
worktreeId: 'repo-target-1::/repo'
|
|
} as never
|
|
]
|
|
},
|
|
terminalLayoutsByTabId: {}
|
|
})
|
|
|
|
await callSetForConnectedTargets({ hydratedTargetIds: ['target-1'] })
|
|
|
|
expect(requestByTargetId.get('target-1')).toHaveBeenCalledWith(
|
|
'workspace.patch',
|
|
expect.objectContaining({
|
|
patch: expect.objectContaining({
|
|
session: expect.objectContaining({
|
|
activeWorktreePath: '/repo',
|
|
activeTabId: 'tab-store'
|
|
})
|
|
})
|
|
})
|
|
)
|
|
})
|
|
})
|