Files
goodbuddy/src/shared/remote-project-candidate-contracts.test.ts
T
mesalogo 9c88ca20f3 feat: layer runtime and model selection across conversation, project and global
- Store runtime selection as { provider?, model? } layers and resolve them per
  field (conversation -> project -> global); drop the legacy auto provider.
- Migrate stored selections (database v49) and clear deleted profile ids.
- Re-resolve selections in Main per request and at queue dispatch.
- Add ProjectRuntimeSelector and a two-column RuntimeModelPicker; manage
  link opens model connections; restore per-runtime default model tabs.
- Stop duplicating trusted instructions and history across OpenCode,
  Continue and DeepSeek Harness runtimes.
- Document the layered design, global provider removal plan and planned
  purpose-specific models.
2026-09-29 21:33:06 +08:00

125 lines
3.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
REMOTE_PROJECT_SAVE_LIMITS,
remoteProjectCreateDraftSchema,
remoteProjectSaveProgressSchema,
remoteProjectSaveRequestSchema,
remoteProjectUpdateDraftSchema
} from './remote-project-candidate-contracts'
const hostId = '00000000-0000-4000-8000-000000000010'
const projectId = '00000000-0000-4000-8000-000000000020'
const profileId = '00000000-0000-4000-8000-000000000040'
const draft = {
name: '远程项目',
description: 'Awaited save',
defaultWorkMode: 'ask' as const,
runtimeSelection: {
provider: 'opencode' as const,
model: { kind: 'profile' as const, profileId }
},
hostId,
remoteRootPath: '/srv/projects/goodbuddy'
}
describe('remote project save contracts', () => {
it('accepts closed create and update save requests', () => {
expect(
remoteProjectSaveRequestSchema.parse({
intent: 'create',
draft
})
).toEqual({ intent: 'create', draft })
expect(
remoteProjectSaveRequestSchema.parse({
intent: 'update',
draft: { projectId, ...draft }
})
).toEqual({ intent: 'update', draft: { projectId, ...draft } })
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
candidateId: projectId
})
).toThrow()
expect(() =>
remoteProjectUpdateDraftSchema.parse({
projectId,
...draft,
commitToken: 'token'
})
).toThrow()
})
it('accepts Execute without consent or trust fields', () => {
expect(
remoteProjectCreateDraftSchema.parse({
...draft,
defaultWorkMode: 'execute'
}).defaultWorkMode
).toBe('execute')
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
trust: {},
defaultWorkMode: 'execute'
})
).toThrow()
})
it('lets the Runtime follow global settings and bounds the absolute remote root', () => {
const withoutRuntime: Partial<typeof draft> = { ...draft }
delete withoutRuntime.runtimeSelection
expect(remoteProjectCreateDraftSchema.parse(withoutRuntime).runtimeSelection).toBeUndefined()
expect(() =>
remoteProjectCreateDraftSchema.parse({ ...draft, runtimeSelection: { provider: 'auto-ish' } })
).toThrow()
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
remoteRootPath: 'srv/project'
})
).toThrow()
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
remoteRootPath: '/srv/\nprivate'
})
).toThrow(/control characters/iu)
for (const remoteRootPath of [
'/srv//project',
'/srv/./project',
'/srv/../project'
]) {
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
remoteRootPath
})
).toThrow(/empty, dot, or parent segments/iu)
}
expect(() =>
remoteProjectCreateDraftSchema.parse({
...draft,
remoteRootPath: `/${'界'.repeat(
Math.floor(
REMOTE_PROJECT_SAVE_LIMITS.maximumAbsolutePathBytes / 3
) + 1
)}`
})
).toThrow(/4096 UTF-8 bytes/iu)
})
it('exposes only an ephemeral progress phase', () => {
expect(remoteProjectSaveProgressSchema.parse({ phase: 'agent' })).toEqual({
phase: 'agent'
})
expect(() =>
remoteProjectSaveProgressSchema.parse({
phase: 'agent',
candidateId: projectId
})
).toThrow()
})
})