import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { POST } from '@/app/api/chat/route' import { NextRequest } from 'next/server' import { resetFlagsCache } from '@/lib/flags' // Mock fetch globally global.fetch = vi.fn() describe('/api/chat', () => { const originalEnv = { ...process.env } beforeEach(() => { resetFlagsCache() vi.clearAllMocks() // Set up default agent process.env.AGENT_1_URL = 'https://example.com/webhook/test' process.env.IMAGE_UPLOADS_ENABLED = 'true' process.env.DIFF_TOOL_ENABLED = 'true' }) afterEach(() => { process.env = { ...originalEnv } vi.restoreAllMocks() }) it('requires message field', async () => { const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(400) expect(data.error).toBe('Message is required') }) it('requires agentId field', async () => { const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hello', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(400) expect(data.error).toBe('Agent ID is required') }) it('rejects images when IMAGE_UPLOADS_ENABLED is false', async () => { process.env.IMAGE_UPLOADS_ENABLED = 'false' resetFlagsCache() const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hello', agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), images: ['data:image/png;base64,abc123'], }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(403) expect(data.error).toBe('Image uploads are currently disabled') }) it('forwards message to webhook successfully', async () => { const mockResponse = JSON.stringify({ response: 'Hello back!' }) ;(global.fetch as any).mockResolvedValueOnce({ ok: true, status: 200, text: async () => mockResponse, }) const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hello', agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(200) expect(data.response).toBe('Hello back!') expect(global.fetch).toHaveBeenCalledWith( 'https://example.com/webhook/test', expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json' }, }) ) }) it('handles streaming response with chunks', async () => { const mockResponse = `{"type":"item","content":"Hello "} {"type":"item","content":"World!"}` ;(global.fetch as any).mockResolvedValueOnce({ ok: true, status: 200, text: async () => mockResponse, }) const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hi', agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(200) expect(data.response).toBe('Hello World!') }) it('converts diff tool calls to markdown when enabled', async () => { const mockResponse = JSON.stringify({ type: 'tool_call', name: 'show_diff', args: { oldCode: 'const x = 1', newCode: 'const x = 2', title: 'Update value', language: 'javascript', }, }) ;(global.fetch as any).mockResolvedValueOnce({ ok: true, status: 200, text: async () => mockResponse, }) const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Show me diff', agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(200) expect(data.response).toContain('```diff-tool') expect(data.response).toContain('oldCode') expect(data.response).toContain('newCode') }) it('converts diff tool calls to plain markdown when disabled', async () => { process.env.DIFF_TOOL_ENABLED = 'false' resetFlagsCache() const mockResponse = JSON.stringify({ type: 'tool_call', name: 'show_diff', args: { oldCode: 'const x = 1', newCode: 'const x = 2', title: 'Update value', language: 'javascript', }, }) ;(global.fetch as any).mockResolvedValueOnce({ ok: true, status: 200, text: async () => mockResponse, }) const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Show me diff', agentId: 'agent-1', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(200) expect(data.response).not.toContain('```diff-tool') expect(data.response).toContain('**Before:**') expect(data.response).toContain('**After:**') }) it('returns error for unconfigured agent', async () => { const request = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', body: JSON.stringify({ message: 'Hello', agentId: 'agent-99', sessionId: 'test-session', timestamp: new Date().toISOString(), }), }) const response = await POST(request) const data = await response.json() expect(response.status).toBe(400) expect(data.error).toContain('not properly configured') }) })