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