This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import type { ActorContext } from '@devrunbook/application'
|
||||
import type { PlaybookCatalogSummary } from '@devrunbook/db'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { AuthenticatedWorkspaceContextError } from '../../../../server/authenticated-workspace-context'
|
||||
import {
|
||||
handleListPlaybooks,
|
||||
type ListPlaybooksRouteDependencies,
|
||||
} from './route'
|
||||
|
||||
const actor: ActorContext = {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
workspaceId: '00000000-0000-4000-8000-000000000002',
|
||||
instanceRole: 'user',
|
||||
workspaceRole: 'viewer',
|
||||
}
|
||||
|
||||
function item(index: number): PlaybookCatalogSummary {
|
||||
return {
|
||||
id: `00000000-0000-4000-8000-${String(index).padStart(12, '0')}`,
|
||||
slug: `playbook-${index}`,
|
||||
title: `Playbook ${index}`,
|
||||
summary: 'A governed outcome.',
|
||||
category: 'audit',
|
||||
source: 'built_in',
|
||||
currentVersion: '1.0.0',
|
||||
lifecycle: 'validated',
|
||||
riskTier: 'low',
|
||||
type: 'guided',
|
||||
defaultMode: 'inspect',
|
||||
defaultAutonomy: 'diagnose',
|
||||
supportedModes: ['inspect'],
|
||||
autonomyMin: 'observe',
|
||||
autonomyMax: 'verify',
|
||||
stacks: ['typescript'],
|
||||
qualityStatus: 'technical-reviewed',
|
||||
publishedAt: new Date('2026-01-01T00:00:00Z'),
|
||||
favorite: false,
|
||||
tags: ['audit'],
|
||||
matchReasons: [],
|
||||
digest: String(index).padStart(64, '0'),
|
||||
}
|
||||
}
|
||||
|
||||
function dependencies(
|
||||
overrides: Partial<ListPlaybooksRouteDependencies> = {},
|
||||
): ListPlaybooksRouteDependencies {
|
||||
return {
|
||||
resolveContext: vi.fn(async () => actor),
|
||||
search: vi.fn(async (query) =>
|
||||
Object.keys(query).length ? [item(1), item(2)] : [item(1), item(2)],
|
||||
),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('playbook list route', () => {
|
||||
it('authorizes, scopes filters and returns deterministic cursor pagination', async () => {
|
||||
const context = dependencies()
|
||||
const response = await handleListPlaybooks(
|
||||
new Request(
|
||||
'https://runbook.example.test/api/v1/playbooks?type=guided&limit=1',
|
||||
),
|
||||
context,
|
||||
)
|
||||
const body = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(body.items).toHaveLength(1)
|
||||
expect(body.nextCursor).toBeTypeOf('string')
|
||||
expect(body.search).toEqual({ status: 'ready', total: 2 })
|
||||
expect(context.search).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
{ type: ['guided'] },
|
||||
{ workspaceId: actor.workspaceId, userId: actor.userId },
|
||||
)
|
||||
})
|
||||
|
||||
it('authenticates before reporting query validation details', async () => {
|
||||
const response = await handleListPlaybooks(
|
||||
new Request(
|
||||
'https://runbook.example.test/api/v1/playbooks?unsupported=true',
|
||||
),
|
||||
dependencies({
|
||||
resolveContext: vi.fn(async () => {
|
||||
throw new AuthenticatedWorkspaceContextError(
|
||||
'authentication_required',
|
||||
)
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(await response.json()).toMatchObject({
|
||||
error: { code: 'authentication_required' },
|
||||
})
|
||||
})
|
||||
|
||||
it('returns governed validation and degraded catalog states', async () => {
|
||||
const invalid = await handleListPlaybooks(
|
||||
new Request('https://runbook.example.test/api/v1/playbooks?limit=0'),
|
||||
dependencies(),
|
||||
)
|
||||
const degraded = await handleListPlaybooks(
|
||||
new Request('https://runbook.example.test/api/v1/playbooks'),
|
||||
dependencies({
|
||||
search: vi.fn(async () => {
|
||||
throw new Error('postgresql://operator:secret@database/internal')
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
expect(invalid.status).toBe(422)
|
||||
expect(degraded.status).toBe(503)
|
||||
expect(JSON.stringify(await degraded.json())).not.toContain('secret')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user