mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: remove schema inventories and redundant capability checks (#36040)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
// Why: the zod schemas in src/shared/plugins are the only definition; this writes the published
|
||||
// JSON Schemas and author references from them. The drift test in plugin-json-schema.test.ts
|
||||
// fails when these files fall behind.
|
||||
// Generate published JSON Schemas and author references from the Zod schemas.
|
||||
import { mkdirSync, writeFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { loadPluginSchemaModule, REPO_ROOT } from './plugin-schema-module-loader.mjs'
|
||||
|
||||
@@ -99,55 +99,6 @@ describe('task promotion query allocation', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['ready', 'failed', 'blocked'] as const)(
|
||||
'leaves a dependent pending when another dependency is %s',
|
||||
(status) => {
|
||||
const db = createDb()
|
||||
const target = db.createTask({ runId: 'run_legacy_local', spec: 'Completing task' })
|
||||
const other = db.createTask({ runId: 'run_legacy_local', spec: 'Other dependency' })
|
||||
if (status !== 'ready') {
|
||||
db.updateTaskStatus(other.id, status)
|
||||
}
|
||||
const child = db.createTask({
|
||||
runId: 'run_legacy_local',
|
||||
spec: 'Dependent task',
|
||||
deps: [target.id, other.id]
|
||||
})
|
||||
|
||||
db.updateTaskStatus(target.id, 'completed')
|
||||
|
||||
expect(db.getTask(child.id)?.status).toBe('pending')
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['not-json', 'null', '{}', '2'])(
|
||||
'rolls back completion and earlier promotions on persisted malformed deps %s',
|
||||
(deps) => {
|
||||
const db = createDb()
|
||||
const target = db.createTask({ runId: 'run_legacy_local', spec: 'Completing task' })
|
||||
const child = db.createTask({
|
||||
runId: 'run_legacy_local',
|
||||
spec: 'Promoted before malformed row',
|
||||
deps: [target.id]
|
||||
})
|
||||
const run = db.createRun({
|
||||
objective: 'Other run',
|
||||
coordinatorHandle: 'term_other',
|
||||
coordinatorPaneKey: 'tab_other:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
|
||||
})
|
||||
const open = db.createTask({ runId: run.id, spec: 'Other dependency' })
|
||||
const malformed = db.createTask({ runId: run.id, spec: 'Malformed task', deps: [open.id] })
|
||||
db.db.prepare('UPDATE tasks SET deps = ? WHERE id = ?').run(deps, malformed.id)
|
||||
|
||||
expect(() => db.updateTaskStatus(target.id, 'completed', 'Must roll back')).toThrow()
|
||||
|
||||
expect(db.getTask(target.id)).toEqual(target)
|
||||
expect(db.getTask(child.id)).toEqual(child)
|
||||
expect(db.getTask(malformed.id)).toMatchObject({ status: 'pending', deps })
|
||||
expect(db.db.isTransaction).toBe(false)
|
||||
}
|
||||
)
|
||||
|
||||
it('leaves promotion inside the caller-owned transaction', () => {
|
||||
const db = createDb()
|
||||
const target = db.createTask({ runId: 'run_legacy_local', spec: 'Completing task' })
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { clearNativeChatSessionOptionCacheForTests } from './native-chat-session-option-cache'
|
||||
import { createNativeChatPtySessionOptions } from '../../../../shared/native-chat-pty-session-options'
|
||||
import {
|
||||
normalizeAgentStatusPayload,
|
||||
pickParsedAgentStatusPayload
|
||||
} from '../../../../shared/agent-status-types'
|
||||
|
||||
describe('OMP model command capability', () => {
|
||||
beforeEach(() => clearNativeChatSessionOptionCacheForTests())
|
||||
@@ -39,20 +35,4 @@ describe('OMP model command capability', () => {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it('preserves only the recognized capability through normalization and remote projection', () => {
|
||||
for (const command of ['orca-model', 'arbitrary-command', undefined]) {
|
||||
const payload = normalizeAgentStatusPayload({
|
||||
state: 'done',
|
||||
agentType: 'omp',
|
||||
modelSwitchCommand: command
|
||||
})
|
||||
if (!payload) {
|
||||
throw new Error('expected status')
|
||||
}
|
||||
expect(pickParsedAgentStatusPayload(payload).modelSwitchCommand).toBe(
|
||||
command === 'orca-model' ? command : undefined
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -40,17 +40,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('peekRuntimeEnvironmentCapability', () => {
|
||||
it('answers undefined without issuing a request when no status is cached', () => {
|
||||
expect(peekRuntimeEnvironmentCapability('env-cold', CAPABILITY)).toBeUndefined()
|
||||
expect(runtimeEnvironmentCall).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('answers a cached positive verdict synchronously', async () => {
|
||||
runtimeEnvironmentCall.mockResolvedValue(statusReply([CAPABILITY]))
|
||||
await runtimeEnvironmentSupportsCapability('env-new', CAPABILITY)
|
||||
expect(peekRuntimeEnvironmentCapability('env-new', CAPABILITY)).toBe(true)
|
||||
})
|
||||
|
||||
it('caches an old host answer for the TTL while async callers still re-probe it', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(new Date(0))
|
||||
|
||||
@@ -1,37 +1,12 @@
|
||||
import { readdirSync, readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import {
|
||||
catalogEntryFixture,
|
||||
catalogFixture,
|
||||
compilePluginJsonSchema as compile
|
||||
} from './plugin-json-schema.test-fixture'
|
||||
import {
|
||||
pluginCatalogJsonSchema,
|
||||
pluginManifestJsonSchema,
|
||||
pluginSchemaGeneratedFiles,
|
||||
type PluginJsonSchema
|
||||
} from './plugin-json-schema'
|
||||
import { parsePluginManifest, pluginManifestV2Object } from './plugin-manifest'
|
||||
import { pluginCatalogDocumentSchema, pluginCatalogSchema } from '../catalog/plugin-catalog'
|
||||
import { compilePluginJsonSchema as compile } from './plugin-json-schema.test-fixture'
|
||||
import { pluginManifestJsonSchema } from './plugin-json-schema'
|
||||
import { parsePluginManifest } from './plugin-manifest'
|
||||
|
||||
const REPO_ROOT = path.resolve(import.meta.dirname, '..', '..', '..', '..')
|
||||
|
||||
/** Every property in the schema with its JSON pointer, including fragments and nested objects. */
|
||||
function properties(schema: PluginJsonSchema, pointer = ''): [string, PluginJsonSchema][] {
|
||||
const own = Object.entries(schema.properties ?? {}).flatMap(([key, node]) => {
|
||||
const field = typeof node === 'object' ? node : {}
|
||||
const at = `${pointer}/properties/${key}`
|
||||
const entry: [string, PluginJsonSchema] = [at, field]
|
||||
return [entry, ...properties(field, at)]
|
||||
})
|
||||
const defs = Object.entries(schema.$defs ?? {}).flatMap(([name, def]) =>
|
||||
properties(def, `${pointer}/$defs/${name}`)
|
||||
)
|
||||
return [...own, ...defs]
|
||||
}
|
||||
|
||||
function manifestFixtures(): { file: string; raw: unknown }[] {
|
||||
return ['examples/plugins', 'resources/plugins/launch'].flatMap((dir) =>
|
||||
readdirSync(path.join(REPO_ROOT, dir), { withFileTypes: true })
|
||||
@@ -41,98 +16,7 @@ function manifestFixtures(): { file: string; raw: unknown }[] {
|
||||
)
|
||||
}
|
||||
|
||||
type ZodField = [path: string, field: z.core.$ZodType]
|
||||
|
||||
/** Unwraps optional/default/catch/pipe layers to the object or array that holds child fields. */
|
||||
function container(schema: z.core.$ZodType): z.core.$ZodType {
|
||||
if (
|
||||
schema instanceof z.ZodOptional ||
|
||||
schema instanceof z.ZodDefault ||
|
||||
schema instanceof z.ZodPrefault ||
|
||||
schema instanceof z.ZodCatch
|
||||
) {
|
||||
return container(schema.def.innerType)
|
||||
}
|
||||
return schema instanceof z.ZodPipe ? container(schema.def.in) : schema
|
||||
}
|
||||
|
||||
/** Every object field under `schema` with its dotted path, as declared (so it carries its meta). */
|
||||
function zodFields(schema: z.core.$ZodType, at = ''): ZodField[] {
|
||||
const inner = container(schema)
|
||||
if (inner instanceof z.ZodArray) {
|
||||
return zodFields(inner.def.element, `${at}[]`)
|
||||
}
|
||||
if (!(inner instanceof z.ZodObject)) {
|
||||
return []
|
||||
}
|
||||
return Object.entries(inner.def.shape).flatMap(([key, field]) => [
|
||||
[`${at}.${key}`, field] satisfies ZodField,
|
||||
...zodFields(field, `${at}.${key}`)
|
||||
])
|
||||
}
|
||||
|
||||
/** A catch layer accepts anything; examples must satisfy the shape it guards. */
|
||||
function strict(schema: z.core.$ZodType): z.core.$ZodType {
|
||||
return schema instanceof z.ZodCatch ? strict(schema.def.innerType) : schema
|
||||
}
|
||||
|
||||
function examplesOf(field: z.core.$ZodType): unknown[] {
|
||||
const examples: unknown = z.globalRegistry.get(field)?.examples
|
||||
return Array.isArray(examples) ? examples : []
|
||||
}
|
||||
|
||||
describe('published plugin schemas', () => {
|
||||
it('match the committed files (run `pnpm generate:plugin-schemas` when this fails)', () => {
|
||||
for (const [file, content] of Object.entries(pluginSchemaGeneratedFiles())) {
|
||||
const committed = readFileSync(path.join(REPO_ROOT, file), 'utf8')
|
||||
expect(committed, `${file} is stale: run pnpm generate:plugin-schemas`).toBe(content)
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['manifest', pluginManifestJsonSchema()],
|
||||
['catalog', pluginCatalogJsonSchema()]
|
||||
])('documents every %s field with a description and a since-version', (_name, schema) => {
|
||||
// Why skip $ref fields: they point at a fragment, which is checked below.
|
||||
for (const [pointer, field] of properties(schema).filter(([, field]) => !field.$ref)) {
|
||||
expect(field.description, pointer).toBeTruthy()
|
||||
expect(field['x-orca-since'], pointer).toBeTruthy()
|
||||
}
|
||||
for (const [name, def] of Object.entries(schema.$defs ?? {})) {
|
||||
expect(def.description, name).toBeTruthy()
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['manifest', pluginManifestJsonSchema()],
|
||||
['catalog', pluginCatalogJsonSchema()]
|
||||
])('accepts every documented %s example', (_name, schema) => {
|
||||
const { ajv } = compile(schema)
|
||||
for (const [pointer, field] of properties(schema)) {
|
||||
for (const example of field.examples ?? []) {
|
||||
const validate = ajv.getSchema(`${schema.$id}#${pointer}`)
|
||||
expect(validate?.(example), `${pointer}: ${JSON.stringify(example)}`).toBe(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it.each([
|
||||
['manifest', pluginManifestV2Object],
|
||||
['catalog', pluginCatalogDocumentSchema]
|
||||
])('accepts every documented %s example at runtime too', (_name, root) => {
|
||||
const fields = zodFields(root)
|
||||
expect(fields.length).toBeGreaterThan(10)
|
||||
const examples = fields.flatMap(([at, field]) =>
|
||||
examplesOf(field).map((example) => ({ at, field, example }))
|
||||
)
|
||||
expect(examples.length).toBeGreaterThan(5)
|
||||
for (const { at, field, example } of examples) {
|
||||
expect(z.safeParse(strict(field), example).success, `${at}: ${JSON.stringify(example)}`).toBe(
|
||||
true
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('agrees with the runtime on every bundled and example v2 manifest', () => {
|
||||
const { validate } = compile(pluginManifestJsonSchema())
|
||||
const v2 = manifestFixtures().filter(({ raw }) =>
|
||||
@@ -144,27 +28,4 @@ describe('published plugin schemas', () => {
|
||||
expect(parsePluginManifest(raw), file).toMatchObject({ ok: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('accepts a release-bearing catalog Orca accepts', () => {
|
||||
const { validate } = compile(pluginCatalogJsonSchema())
|
||||
const catalog = {
|
||||
$schema: pluginCatalogJsonSchema().$id,
|
||||
...catalogFixture([
|
||||
catalogEntryFixture(
|
||||
{},
|
||||
{
|
||||
name: 'Gleam',
|
||||
fileTypes: ['gleam'],
|
||||
license: 'Apache-2.0',
|
||||
repository: 'https://github.com/acme/gleam',
|
||||
homepage: 'https://gleam.example.com',
|
||||
updatedAt: '2026-10-20T09:30:00Z',
|
||||
changelog: 'Highlights use expressions.'
|
||||
}
|
||||
)
|
||||
])
|
||||
}
|
||||
expect(validate(catalog), JSON.stringify(validate.errors)).toBe(true)
|
||||
expect(pluginCatalogSchema.parse(catalog).plugins).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,9 +14,7 @@ import {
|
||||
export const PLUGIN_SCHEMA_DIRECTORY = 'docs/schemas/plugins'
|
||||
export const PLUGIN_MANIFEST_SCHEMA_FILE = 'orca-plugin.v2.schema.json'
|
||||
export const PLUGIN_CATALOG_SCHEMA_FILE = 'orca-catalog.v2.schema.json'
|
||||
/** Git ref the published `$id` points at. Why `main` for now: no release ships v2 yet; switch to
|
||||
* the release tag that does, so editors only offer fields a shipped Orca reads. A test enforces
|
||||
* the switch once that tag exists. */
|
||||
/** Git ref for published schemas; switch to the first v2 release tag when it ships. */
|
||||
export const PLUGIN_SCHEMA_GIT_REF = 'main'
|
||||
export const PLUGIN_SCHEMA_BASE_URL = `https://raw.githubusercontent.com/stablyai/orca/${PLUGIN_SCHEMA_GIT_REF}/${PLUGIN_SCHEMA_DIRECTORY}/`
|
||||
/** The starter plugin authors copy; it carries the schema so completion works offline. */
|
||||
@@ -58,8 +56,7 @@ export function pluginCatalogJsonSchema(): PluginJsonSchema {
|
||||
)
|
||||
}
|
||||
|
||||
/** Every generated file, keyed by repo-relative path. One list keeps the writer and the drift
|
||||
* test from disagreeing about what is generated. */
|
||||
/** Every generated file, keyed by repo-relative path, for the schema writer. */
|
||||
export function pluginSchemaGeneratedFiles(): Record<string, string> {
|
||||
const manifest = pluginManifestJsonSchema()
|
||||
const catalog = pluginCatalogJsonSchema()
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { PLUGIN_SCHEMA_GIT_REF } from './plugin-json-schema'
|
||||
import { satisfiesOrcaEngineRange } from './plugin-manifest'
|
||||
import { PLUGIN_MANIFEST_V2_SINCE, PLUGIN_SCHEMA_SINCE_V1 } from './plugin-schema-field-docs'
|
||||
|
||||
const REPO_ROOT = path.resolve(import.meta.dirname, '..', '..', '..', '..')
|
||||
|
||||
function git(args: string[]): string | null {
|
||||
try {
|
||||
return execFileSync('git', args, { cwd: REPO_ROOT, encoding: 'utf8', windowsHide: true })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Release versions, oldest first. Empty in shallow CI checkouts, which skip these checks. */
|
||||
function releaseVersions(): string[] {
|
||||
return (git(['tag', '--list', 'v*']) ?? '')
|
||||
.split('\n')
|
||||
.filter((tag) => /^v\d+\.\d+\.\d+$/.test(tag))
|
||||
.map((tag) => tag.slice(1))
|
||||
.sort((a, b) => (a === b ? 0 : satisfiesOrcaEngineRange(a, `>=${b}`) ? 1 : -1))
|
||||
}
|
||||
|
||||
function fileAt(version: string, file: string): string | null {
|
||||
return git(['show', `v${version}:${file}`])
|
||||
}
|
||||
|
||||
function hasFileAt(version: string, file: string): boolean {
|
||||
return git(['cat-file', '-e', `v${version}:${file}`]) !== null
|
||||
}
|
||||
|
||||
/** First version holding `file`, by binary search: once plugins shipped, the file stayed. */
|
||||
function firstVersionWith(versions: readonly string[], file: string): string | undefined {
|
||||
let low = 0
|
||||
let high = versions.length
|
||||
while (low < high) {
|
||||
const middle = Math.floor((low + high) / 2)
|
||||
const version = versions[middle]
|
||||
if (version !== undefined && hasFileAt(version, file)) {
|
||||
high = middle
|
||||
} else {
|
||||
low = middle + 1
|
||||
}
|
||||
}
|
||||
return versions[low]
|
||||
}
|
||||
|
||||
describe('schema since-versions', () => {
|
||||
it('names the first release that read plugins as the v1 baseline', () => {
|
||||
const versions = releaseVersions()
|
||||
if (versions.length === 0) {
|
||||
return
|
||||
}
|
||||
const first = firstVersionWith(versions, 'src/shared/plugins/plugin-manifest.ts')
|
||||
expect(first).toBe(PLUGIN_SCHEMA_SINCE_V1)
|
||||
})
|
||||
|
||||
// Why: authors gate on this version, so it must name a release that really reads manifest v2.
|
||||
it('names the first release tag that ships manifest v2', () => {
|
||||
const first = releaseVersions().find((version) =>
|
||||
satisfiesOrcaEngineRange(version, `>=${PLUGIN_MANIFEST_V2_SINCE}`)
|
||||
)
|
||||
if (!first) {
|
||||
return
|
||||
}
|
||||
const manifest = fileAt(first, 'src/shared/plugins/plugin-manifest.ts') ?? ''
|
||||
expect(
|
||||
manifest.includes('z.literal(2)'),
|
||||
`v${first} ships without manifest v2: bump PLUGIN_MANIFEST_V2_SINCE to the release that does`
|
||||
).toBe(true)
|
||||
expect(
|
||||
PLUGIN_SCHEMA_GIT_REF,
|
||||
`v${first} shipped: point PLUGIN_SCHEMA_GIT_REF at its tag and regenerate`
|
||||
).toBe(`v${PLUGIN_MANIFEST_V2_SINCE}`)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,7 @@
|
||||
/** First release that read plugins: manifest v1, its first seven contribution points, every
|
||||
* capability, event and built-in command action of that era. */
|
||||
export const PLUGIN_SCHEMA_SINCE_V1 = '1.4.160'
|
||||
/** First release that reads manifest v2, the v2 catalog and the fields that arrived with them:
|
||||
* the release after the latest tag when v2 landed. A test fails once a tag at or above it
|
||||
* ships without v2, so the release that does ship v2 bumps this once if needed. */
|
||||
/** First release planned to read manifest v2, the v2 catalog and their new fields. */
|
||||
export const PLUGIN_MANIFEST_V2_SINCE = '1.4.245'
|
||||
|
||||
type PluginFieldDoc = {
|
||||
|
||||
Reference in New Issue
Block a user