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:
9
.eslintrc.json
Normal file
9
.eslintrc.json
Normal 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"] }]
|
||||
}
|
||||
}
|
||||
17
package.json
17
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
54
src/app/api/webhooks/openwork/route.ts
Normal file
54
src/app/api/webhooks/openwork/route.ts
Normal 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'
|
||||
})
|
||||
}
|
||||
37
src/lib/pipeline/manager.test.ts
Normal file
37
src/lib/pipeline/manager.test.ts
Normal 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
29
src/test/setup.ts
Normal 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
29
vitest.config.ts
Normal 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'),
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user