mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
122 lines
4.3 KiB
TypeScript
122 lines
4.3 KiB
TypeScript
import { readdirSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
import { buildHandlerRoutes, dispatch, type HandlerContext } from './dispatch'
|
|
import { HANDLER_GROUPS, type HandlerGroup } from './handler-group-manifest'
|
|
|
|
// Why: dispatch trusts the manifest's eager key lists to route without loading a
|
|
// group. These tests are the only thing standing between that trust and a
|
|
// silently unreachable command, so they load every group for real.
|
|
|
|
describe('handler group manifest', () => {
|
|
it('lists a loadable group for every entry', async () => {
|
|
for (const group of HANDLER_GROUPS) {
|
|
const loaded = await group.load()
|
|
expect(loaded, `${group.name} resolved to a non-record`).toBeTypeOf('object')
|
|
}
|
|
})
|
|
|
|
it('matches each group export key-for-key', async () => {
|
|
const drift: string[] = []
|
|
for (const group of HANDLER_GROUPS) {
|
|
const actual = Object.keys(await group.load()).sort()
|
|
const declared = [...group.keys].sort()
|
|
if (JSON.stringify(actual) !== JSON.stringify(declared)) {
|
|
drift.push(
|
|
`${group.name}: manifest ${JSON.stringify(declared)} !== export ${JSON.stringify(actual)}`
|
|
)
|
|
}
|
|
}
|
|
expect(drift).toEqual([])
|
|
})
|
|
|
|
it('exposes every declared key as a callable handler', async () => {
|
|
const notCallable: string[] = []
|
|
for (const group of HANDLER_GROUPS) {
|
|
const loaded = await group.load()
|
|
for (const key of group.keys) {
|
|
if (typeof loaded[key] !== 'function') {
|
|
notCallable.push(`${group.name}/${key}`)
|
|
}
|
|
}
|
|
}
|
|
expect(notCallable).toEqual([])
|
|
})
|
|
|
|
it('reaches every group through dispatch routing', () => {
|
|
const routes = buildHandlerRoutes(HANDLER_GROUPS)
|
|
const reached = new Set([...routes.values()].map((group) => group.name))
|
|
const unreachable = HANDLER_GROUPS.filter((group) => !reached.has(group.name)).map(
|
|
(group) => group.name
|
|
)
|
|
expect(unreachable).toEqual([])
|
|
})
|
|
|
|
// Why: dropping a group from the manifest silently unregisters its commands —
|
|
// scan the directory so a new or forgotten handler file fails here, not in prod.
|
|
it('registers every handler module that exports a handler group', async () => {
|
|
// Why: __dirname works under both Vitest and the CommonJS tsc emit that
|
|
// build:cli type-checks this file against; import.meta.dirname does not.
|
|
const dir = join(__dirname, 'handlers')
|
|
const modules = readdirSync(dir).filter(
|
|
(file) => file.endsWith('.ts') && !file.endsWith('.test.ts')
|
|
)
|
|
const registered = new Set(HANDLER_GROUPS.map((group) => group.name))
|
|
const missing: string[] = []
|
|
for (const file of modules) {
|
|
const name = file.slice(0, -'.ts'.length)
|
|
const exports: Record<string, unknown> = await import(join(dir, file))
|
|
const exportsGroup = Object.keys(exports).some((key) => key.endsWith('_HANDLERS'))
|
|
if (exportsGroup && !registered.has(name)) {
|
|
missing.push(name)
|
|
}
|
|
}
|
|
expect(missing).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('duplicate command keys', () => {
|
|
const group = (name: string, keys: string[]): HandlerGroup => ({
|
|
name,
|
|
keys,
|
|
load: async () => ({})
|
|
})
|
|
|
|
it('rejects the same key claimed by two groups', () => {
|
|
expect(() =>
|
|
buildHandlerRoutes([group('alpha', ['ship it']), group('beta', ['ship it'])])
|
|
).toThrow('Duplicate CLI handler registration for "ship it" (alpha and beta)')
|
|
})
|
|
|
|
it('rejects a key duplicated inside one group list', () => {
|
|
expect(() => buildHandlerRoutes([group('alpha', ['ship it', 'ship it'])])).toThrow(
|
|
'Duplicate CLI handler registration for "ship it"'
|
|
)
|
|
})
|
|
|
|
it('accepts distinct keys across groups', () => {
|
|
const routes = buildHandlerRoutes([group('alpha', ['a']), group('beta', ['b'])])
|
|
expect([...routes.keys()]).toEqual(['a', 'b'])
|
|
})
|
|
|
|
it('holds for the live manifest', () => {
|
|
expect(() => buildHandlerRoutes(HANDLER_GROUPS)).not.toThrow()
|
|
})
|
|
})
|
|
|
|
describe('dispatch errors', () => {
|
|
const ctx = {
|
|
flags: new Map(),
|
|
cwd: '/tmp',
|
|
json: false
|
|
} as unknown as HandlerContext
|
|
|
|
it('reports an unknown command with the joined path', async () => {
|
|
await expect(dispatch(['not', 'a', 'command'], ctx)).rejects.toMatchObject({
|
|
code: 'invalid_argument',
|
|
message: 'Unknown command: not a command'
|
|
})
|
|
})
|
|
})
|