feat: add testing infrastructure and webhook handler

- Vitest setup with React Testing Library
- Initial pipeline manager tests
- Openwork webhook endpoint for team events
- ESLint configuration
- Updated package.json with test dependencies
This commit is contained in:
Chora
2026-02-01 20:09:16 -06:00
parent ce684b70c0
commit 2e99e60d0c
6 changed files with 173 additions and 2 deletions

9
.eslintrc.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": ["next/core-web-vitals"],
"rules": {
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"prefer-const": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}

View File

@@ -7,8 +7,13 @@
"build": "prisma generate && next build",
"start": "next start",
"lint": "next lint",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"db:push": "prisma db push",
"db:studio": "prisma studio",
"db:generate": "prisma generate",
"postinstall": "prisma generate"
},
"dependencies": {
@@ -21,16 +26,24 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1"
},
"devDependencies": {
"@testing-library/react": "^16.1.0",
"@types/node": "^22.10.7",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^3.0.4",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.0",
"eslint-config-next": "14.2.21",
"jsdom": "^26.0.0",
"postcss": "^8.4.49",
"prisma": "^6.3.1",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"vitest": "^3.0.4"
}
}

View File

@@ -0,0 +1,54 @@
import { NextRequest, NextResponse } from 'next/server'
// Webhook events from Openwork
type WebhookEvent = {
type: 'team.member_joined' | 'team.member_left' | 'job.submission' | 'job.completed'
timestamp: string
data: Record<string, unknown>
}
export async function POST(request: NextRequest) {
try {
const event: WebhookEvent = await request.json()
console.log('[Openwork Webhook]', event.type, JSON.stringify(event.data, null, 2))
switch (event.type) {
case 'team.member_joined':
// A new agent joined the team
console.log(`🎉 New team member: ${event.data.agent_name} (${event.data.role})`)
break
case 'team.member_left':
console.log(`👋 Team member left: ${event.data.agent_name}`)
break
case 'job.submission':
console.log(`📥 New job submission for: ${event.data.job_title}`)
break
case 'job.completed':
console.log(`✅ Job completed: ${event.data.job_title}`)
break
default:
console.log(`Unknown event type: ${event.type}`)
}
return NextResponse.json({ received: true })
} catch (error) {
console.error('Webhook error:', error)
return NextResponse.json(
{ error: 'Webhook processing failed' },
{ status: 500 }
)
}
}
// Verify endpoint exists
export async function GET() {
return NextResponse.json({
status: 'ok',
service: 'cutroom-openwork-webhook'
})
}

View File

@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest'
import { STAGE_ORDER, getNextStageName } from './manager'
describe('Pipeline Manager', () => {
describe('STAGE_ORDER', () => {
it('should have 7 stages in correct order', () => {
expect(STAGE_ORDER).toHaveLength(7)
expect(STAGE_ORDER[0]).toBe('RESEARCH')
expect(STAGE_ORDER[6]).toBe('PUBLISH')
})
it('should have all required stages', () => {
const required = ['RESEARCH', 'SCRIPT', 'VOICE', 'MUSIC', 'VISUAL', 'EDITOR', 'PUBLISH']
required.forEach(stage => {
expect(STAGE_ORDER).toContain(stage)
})
})
})
describe('getNextStageName', () => {
it('should return next stage for RESEARCH', () => {
expect(getNextStageName('RESEARCH')).toBe('SCRIPT')
})
it('should return next stage for SCRIPT', () => {
expect(getNextStageName('SCRIPT')).toBe('VOICE')
})
it('should return null for PUBLISH (last stage)', () => {
expect(getNextStageName('PUBLISH')).toBeNull()
})
it('should return null for invalid stage', () => {
expect(getNextStageName('INVALID' as any)).toBeNull()
})
})
})

29
src/test/setup.ts Normal file
View File

@@ -0,0 +1,29 @@
import '@testing-library/jest-dom'
// Mock Prisma client for tests
vi.mock('@/lib/db/client', () => ({
default: {
pipeline: {
create: vi.fn(),
findUnique: vi.fn(),
findMany: vi.fn(),
update: vi.fn(),
},
stage: {
findUnique: vi.fn(),
update: vi.fn(),
},
},
prisma: {
pipeline: {
create: vi.fn(),
findUnique: vi.fn(),
findMany: vi.fn(),
update: vi.fn(),
},
stage: {
findUnique: vi.fn(),
update: vi.fn(),
},
},
}))

29
vitest.config.ts Normal file
View File

@@ -0,0 +1,29 @@
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
include: ['src/**/*.{test,spec}.{js,ts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'src/test/',
'**/*.d.ts',
'**/*.config.*',
'**/layout.tsx',
],
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})