Files
goodbuddy/src/agent-daemon/workspace-registry.test.ts
T
mesalogo c1848a9dc4
GoodBuddy Agents / Validate Agent source (push) Canceled after 0s
GoodBuddy Agents / Build darwin arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux arm64 Agent (push) Canceled after 0s
GoodBuddy Agents / Build linux x64 Agent (push) Canceled after 0s
Cross-platform packages / Validate source (push) Canceled after 0s
Cross-platform packages / Authorize desktop packaging (push) Canceled after 0s
Cross-platform packages / linux arm64 (push) Canceled after 0s
Cross-platform packages / macos arm64 (push) Canceled after 0s
Cross-platform packages / windows arm64 (push) Canceled after 0s
Cross-platform packages / linux x64 (push) Canceled after 0s
Cross-platform packages / macos x64 (push) Canceled after 0s
Cross-platform packages / windows x64 (push) Canceled after 0s
Cross-platform packages / Publish GitHub and OSS release (push) Canceled after 0s
test: align runtime fixtures across platforms
Account for Electron-as-Node when probing the current executable and update the Linux workspace registry scenario to validate the writable handle behavior shipped with remote file management.
2026-09-10 07:01:48 +08:00

419 lines
13 KiB
TypeScript

import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
symlinkSync,
writeFileSync
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { ControllerRegistry } from './controller-registry'
import { WorkspaceRegistry } from './workspace-registry'
const temporaryPaths: string[] = []
const linuxIt = process.platform === 'linux' ? it : it.skip
afterEach(() => {
vi.restoreAllMocks()
for (const path of temporaryPaths.splice(0)) {
rmSync(path, { recursive: true, force: true })
}
})
describe('WorkspaceRegistry', () => {
linuxIt('binds ephemeral handles to owner and connection generation', async () => {
const root = temporaryDirectory()
const controllers = new ControllerRegistry()
const owner = controllers.attach('controller-owner')
const other = controllers.attach('controller-other')
const registry = workspaceRegistry(controllers)
const validated = await validate(registry, root, owner)
await expect(
registry.get(
validated.handle.workspaceId,
validated.handle.generation,
other
)
).rejects.toMatchObject({ code: 'not-owner' })
controllers.disconnect(owner.controllerId, owner.generation)
const replacement = controllers.attach(owner.controllerId)
await expect(
registry.resume(
{
workspaceId: validated.handle.workspaceId,
generation: validated.handle.generation,
workspaceIdentity: validated.handle.workspaceIdentity
},
replacement
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
await expect(
registry.open(
{
workspaceIdentity: validated.handle.workspaceIdentity,
requestedAccess: 'read-only'
},
replacement
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
})
linuxIt('resolves only a current Workspace lease owned by the controller', async () => {
const root = temporaryDirectory()
const controllers = new ControllerRegistry()
const owner = controllers.attach('controller-owner')
const other = controllers.attach('controller-other')
const registry = workspaceRegistry(controllers)
const validated = await validate(registry, root, owner)
await expect(
registry.getCurrentByIdentity(
validated.handle.workspaceIdentity,
owner
)
).resolves.toMatchObject({
controllerId: owner.controllerId,
controllerGeneration: owner.generation,
handle: {
workspaceIdentity: validated.handle.workspaceIdentity
}
})
await expect(
registry.getCurrentByIdentity(
validated.handle.workspaceIdentity,
other
)
).rejects.toMatchObject({ code: 'not-owner' })
controllers.disconnect(owner.controllerId, owner.generation)
const replacement = controllers.attach(owner.controllerId)
await expect(
registry.getCurrentByIdentity(
validated.handle.workspaceIdentity,
replacement
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
})
linuxIt('revokes a validation grant and all handles opened from it', async () => {
const root = temporaryDirectory()
const controllers = new ControllerRegistry()
const controller = controllers.attach('controller-owner')
const registry = workspaceRegistry(controllers)
const validated = await validate(registry, root, controller)
const opened = await registry.open(
{
workspaceIdentity: validated.handle.workspaceIdentity,
requestedAccess: 'read-only'
},
controller
)
await expect(
registry.close(
validated.handle.workspaceId,
validated.handle.generation,
controller
)
).resolves.toMatchObject({ closed: true })
expect(registry.activeHandleCount()).toBe(0)
await expect(
registry.get(opened.workspaceId, opened.generation, controller)
).rejects.toMatchObject({ code: 'workspace-not-found' })
await expect(
registry.open(
{
workspaceIdentity: validated.handle.workspaceIdentity,
requestedAccess: 'read-only'
},
controller
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
})
linuxIt('supports writable handles while enforcing caps and symlink-root rejection', async () => {
const root = temporaryDirectory()
const linked = join(tmpdir(), `goodbuddy-linked-${Date.now()}`)
symlinkSync(root, linked, 'dir')
temporaryPaths.push(linked)
const controllers = new ControllerRegistry()
const controller = controllers.attach('controller-owner')
const registry = new WorkspaceRegistry({
controllers,
maximumControllerHandles: 1,
inspectGit: async () => 'not-a-repository'
})
const writable = await registry.validate(
{
remoteRootPath: root,
requestedAccess: 'read-write',
requiredCapabilities: []
},
controller
)
expect(writable.handle.access).toBe('read-write')
await registry.close(
writable.handle.workspaceId,
writable.handle.generation,
controller
)
await expect(
registry.validate(
{
remoteRootPath: linked,
requestedAccess: 'read-only',
requiredCapabilities: []
},
controller
)
).rejects.toMatchObject({ code: 'symlink-rejected' })
const validated = await validate(registry, root, controller)
await expect(
registry.open(
{
workspaceIdentity: validated.handle.workspaceIdentity,
requestedAccess: 'read-only'
},
controller
)
).rejects.toMatchObject({ code: 'capacity-exceeded' })
})
linuxIt('does not mutate workspace or registry storage during validation', async () => {
const state = temporaryDirectory()
const root = join(state, 'workspace')
mkdirSync(root)
writeFileSync(join(root, 'existing.txt'), 'unchanged')
const before = readdirSync(root)
const storagePath = join(state, 'workspaces.json')
const controllers = new ControllerRegistry()
const controller = controllers.attach('controller-owner')
const registry = new WorkspaceRegistry({
controllers,
storagePath,
inspectGit: async () => 'not-a-repository'
})
const validated = await validate(registry, root, controller)
expect(existsSync(storagePath)).toBe(false)
expect(readdirSync(root)).toEqual(before)
expect(readFileSync(join(root, 'existing.txt'), 'utf8')).toBe(
'unchanged'
)
await registry.close(
validated.handle.workspaceId,
validated.handle.generation,
controller
)
expect(existsSync(storagePath)).toBe(false)
})
linuxIt('leaves no grant when validation is cancelled', async () => {
const state = temporaryDirectory()
const root = join(state, 'workspace')
mkdirSync(root)
const storagePath = join(state, 'workspaces.json')
const controllers = new ControllerRegistry()
const controller = controllers.attach('controller-owner')
const abort = new AbortController()
let releaseInspection: ((value: 'not-a-repository') => void) | undefined
let markInspectionStarted: (() => void) | undefined
const inspectionStarted = new Promise<void>((resolve) => {
markInspectionStarted = resolve
})
const registry = new WorkspaceRegistry({
controllers,
storagePath,
inspectGit: async () => {
markInspectionStarted?.()
return await new Promise<'not-a-repository'>((resolve) => {
releaseInspection = resolve
})
}
})
const pending = validate(registry, root, controller, {
signal: abort.signal
})
await inspectionStarted
abort.abort(new Error('cancelled'))
releaseInspection?.('not-a-repository')
await expect(pending).rejects.toMatchObject({
code: 'aborted',
message: 'Workspace operation was aborted'
})
expect(registry.activeHandleCount()).toBe(0)
expect(existsSync(storagePath)).toBe(false)
})
linuxIt('requires path revalidation after daemon restart', async () => {
const state = temporaryDirectory()
const root = join(state, 'workspace')
mkdirSync(root)
const storagePath = join(state, 'workspaces.json')
const firstControllers = new ControllerRegistry()
const firstController = firstControllers.attach('controller-owner')
const firstRegistry = new WorkspaceRegistry({
controllers: firstControllers,
storagePath,
inspectGit: async () => 'not-a-repository'
})
const first = await validate(firstRegistry, root, firstController)
firstRegistry.closeAll()
const replacementControllers = new ControllerRegistry()
const replacement = replacementControllers.attach('controller-owner')
const replacementRegistry = new WorkspaceRegistry({
controllers: replacementControllers,
storagePath,
inspectGit: async () => 'not-a-repository'
})
await expect(
replacementRegistry.resume(
{
workspaceId: first.handle.workspaceId,
generation: first.handle.generation,
workspaceIdentity: first.handle.workspaceIdentity
},
replacement
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
await expect(
replacementRegistry.open(
{
workspaceIdentity: first.handle.workspaceIdentity,
requestedAccess: 'read-only'
},
replacement
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
const revalidated = await validate(
replacementRegistry,
root,
replacement
)
expect(revalidated.handle.workspaceIdentity).toBe(
first.handle.workspaceIdentity
)
expect(revalidated.handle.workspaceId).not.toBe(
first.handle.workspaceId
)
})
it('clears legacy persisted roots and never authorizes them', async () => {
const state = temporaryDirectory()
const root = join(state, 'workspace')
mkdirSync(root)
const storagePath = join(state, 'workspaces.json')
writeFileSync(
storagePath,
JSON.stringify({
version: 1,
roots: [
{
canonicalPath: root,
device: '1',
inode: '2',
workspaceIdentity: 'workspace-legacy',
git: 'not-a-repository',
trustAttestationRevision: 1,
authorizedControllers: ['controller-owner']
}
],
leases: [
{
handle: {
workspaceId: 'workspace-legacy-handle',
workspaceIdentity: 'workspace-legacy',
canonicalDisplayPath: root,
access: 'read-only',
git: 'not-a-repository',
capabilities: ['list', 'stat', 'read-text', 'search'],
generation: 1
},
controllerId: 'controller-owner',
controllerGeneration: controllerGenerationForLegacyFixture
}
]
}),
{ mode: 0o600 }
)
const controllers = new ControllerRegistry()
const controller = controllers.attach('controller-owner')
const registry = new WorkspaceRegistry({
controllers,
storagePath,
inspectGit: async () => 'not-a-repository'
})
expect(JSON.parse(readFileSync(storagePath, 'utf8'))).toEqual({
version: 2,
authorization: 'ephemeral'
})
await expect(
registry.open(
{
workspaceIdentity: 'workspace-legacy',
requestedAccess: 'read-only'
},
controller
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
await expect(
registry.resume(
{
workspaceId: 'workspace-legacy-handle',
workspaceIdentity: 'workspace-legacy',
generation: 1
},
controller
)
).rejects.toMatchObject({ code: 'workspace-not-found' })
expect(registry.activeHandleCount()).toBe(0)
})
})
function workspaceRegistry(
controllers: ControllerRegistry
): WorkspaceRegistry {
return new WorkspaceRegistry({
controllers,
inspectGit: async () => 'not-a-repository'
})
}
const controllerGenerationForLegacyFixture = 1
async function validate(
registry: WorkspaceRegistry,
root: string,
controller: ReturnType<ControllerRegistry['attach']>,
options: { signal?: AbortSignal } = {}
) {
return await registry.validate(
{
remoteRootPath: root,
requestedAccess: 'read-only',
requiredCapabilities: []
},
controller,
options
)
}
function temporaryDirectory(): string {
const path = mkdtempSync(join(tmpdir(), 'goodbuddy-registry-'))
temporaryPaths.push(path)
return path
}