Files
DevRunbook-Public/apps/web/src/app/api/v1/playbooks/route.test.ts
T
DevRunbook release export cfd2804e27
Managed validation / full (push) Successful in 3m18s
Publish DevRunbook source
2026-09-03 04:09:17 +02:00

118 lines
3.5 KiB
TypeScript

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')
})
})