/** * Unity Bridge Tests * * Testet die Unity AI Bridge Integration: * - Struktur-Tests (Dateien existieren) * - API Route Tests (Handler-Verhalten) * - Content-Tests (korrekter Inhalt) */ import { existsSync, readFileSync } from 'fs' import { join } from 'path' const ROOT_DIR = join(__dirname, '..') const APP_DIR = join(ROOT_DIR, 'app') const COMPONENTS_DIR = join(ROOT_DIR, 'components') const LIB_DIR = join(ROOT_DIR, 'lib') // ============================================== // Structure Tests // ============================================== describe('Unity Bridge Structure', () => { describe('Pages', () => { it('has dashboard page', () => { expect(existsSync(join(APP_DIR, 'admin', 'unity-bridge', 'page.tsx'))).toBe(true) }) it('has wizard page', () => { expect(existsSync(join(APP_DIR, 'admin', 'unity-bridge', 'wizard', 'page.tsx'))).toBe(true) }) }) describe('API Route', () => { it('has unity-bridge API route', () => { expect(existsSync(join(APP_DIR, 'api', 'admin', 'unity-bridge', 'route.ts'))).toBe(true) }) }) }) // ============================================== // API Route Content Tests // ============================================== describe('Unity Bridge API Route', () => { const apiPath = join(APP_DIR, 'api', 'admin', 'unity-bridge', 'route.ts') let content: string beforeAll(() => { content = readFileSync(apiPath, 'utf-8') }) describe('GET Handler', () => { it('has GET export', () => { expect(content).toContain('export async function GET') }) it('supports status action', () => { expect(content).toContain("status: '/status'") }) it('supports compile action', () => { expect(content).toContain("compile: '/compile'") }) it('supports logs action', () => { expect(content).toContain("logs:") expect(content).toContain('/logs') }) it('supports errors action', () => { expect(content).toContain("errors: '/logs/errors'") }) it('supports warnings action', () => { expect(content).toContain("warnings: '/logs/warnings'") }) it('supports scene action', () => { expect(content).toContain("scene: '/scene'") }) it('supports play action', () => { expect(content).toContain("play: '/play'") }) it('supports stop action', () => { expect(content).toContain("stop: '/stop'") }) it('supports quicksetup action', () => { expect(content).toContain("quicksetup: '/quicksetup'") }) it('supports object endpoint with name parameter', () => { expect(content).toContain('object') expect(content).toContain('objectName') }) it('handles connection errors gracefully', () => { expect(content).toContain('offline') expect(content).toContain('503') }) it('uses correct Unity Bridge URL', () => { expect(content).toContain('UNITY_BRIDGE_URL') expect(content).toContain('http://localhost:8090') }) it('has timeout for requests', () => { expect(content).toContain('AbortSignal.timeout') expect(content).toContain('3000') }) }) describe('POST Handler', () => { it('has POST export', () => { expect(content).toContain('export async function POST') }) it('supports diagnose action', () => { expect(content).toContain("action === 'diagnose'") expect(content).toContain('/diagnose') }) it('supports execute action', () => { expect(content).toContain("action === 'execute'") expect(content).toContain('/execute') }) it('supports clear-logs action', () => { expect(content).toContain("action === 'clear-logs'") expect(content).toContain('/logs/clear') }) it('validates POST body for execute', () => { expect(content).toContain('Invalid JSON body') expect(content).toContain('Missing "action" field') }) it('has longer timeout for diagnose', () => { expect(content).toContain('10000') }) }) describe('Error Handling', () => { it('returns 400 for unknown actions', () => { expect(content).toContain('400') expect(content).toContain('Unknown action') }) it('returns 503 when bridge offline', () => { expect(content).toContain('503') expect(content).toContain('offline: true') }) it('distinguishes timeout from connection refused', () => { expect(content).toContain('timeout') expect(content).toContain('aborted') }) }) describe('Type Definitions', () => { it('has BridgeStatus interface', () => { expect(content).toContain('interface BridgeStatus') }) it('has LogEntry interface', () => { expect(content).toContain('interface LogEntry') }) it('has LogsResponse interface', () => { expect(content).toContain('interface LogsResponse') }) it('has DiagnoseResponse interface', () => { expect(content).toContain('interface DiagnoseResponse') }) }) }) // ============================================== // Dashboard Page Tests // ============================================== describe('Unity Bridge Dashboard Page', () => { const dashboardPath = join(APP_DIR, 'admin', 'unity-bridge', 'page.tsx') let content: string beforeAll(() => { content = readFileSync(dashboardPath, 'utf-8') }) it('is a client component', () => { expect(content).toContain("'use client'") }) it('uses AdminLayout', () => { expect(content).toContain('AdminLayout') expect(content).toContain("title=\"Unity AI Bridge\"") }) it('has status polling', () => { expect(content).toContain('setInterval') expect(content).toContain('5000') // 5 second interval }) it('has log polling', () => { expect(content).toContain('10000') // 10 second interval }) it('has status badge component', () => { expect(content).toContain('StatusBadge') }) it('has stat cards', () => { expect(content).toContain('StatCard') }) it('has console log panel', () => { expect(content).toContain('ConsoleLogPanel') }) it('has diagnostic panel', () => { expect(content).toContain('DiagnosticPanel') }) it('has quick action buttons', () => { expect(content).toContain('Play') expect(content).toContain('Stop') expect(content).toContain('Quick Setup') expect(content).toContain('Diagnose') }) it('links to wizard', () => { expect(content).toContain('/admin/unity-bridge/wizard') expect(content).toContain('Wizard') }) it('handles offline state', () => { expect(content).toContain('offline') expect(content).toContain('Offline') }) it('shows error and warning counts', () => { expect(content).toContain('errors') expect(content).toContain('warnings') }) it('shows scene information', () => { expect(content).toContain('scene') }) it('shows play mode status', () => { expect(content).toContain('is_playing') }) }) // ============================================== // Wizard Page Tests // ============================================== describe('Unity Bridge Wizard Page', () => { const wizardPath = join(APP_DIR, 'admin', 'unity-bridge', 'wizard', 'page.tsx') let content: string beforeAll(() => { content = readFileSync(wizardPath, 'utf-8') }) it('is a client component', () => { expect(content).toContain("'use client'") }) it('uses AdminLayout', () => { expect(content).toContain('AdminLayout') expect(content).toContain("title=\"Unity AI Bridge Wizard\"") }) it('uses wizard components', () => { expect(content).toContain('WizardStepper') expect(content).toContain('WizardNavigation') expect(content).toContain('EducationCard') }) it('has 7 steps defined', () => { const stepMatches = content.match(/\{ id: '/g) expect(stepMatches).not.toBeNull() expect(stepMatches!.length).toBeGreaterThanOrEqual(7) }) it('has welcome step', () => { expect(content).toContain("id: 'welcome'") }) it('has what-is-bridge step', () => { expect(content).toContain("id: 'what-is-bridge'") }) it('has start-server step', () => { expect(content).toContain("id: 'start-server'") }) it('has api-endpoints step', () => { expect(content).toContain("id: 'api-endpoints'") }) it('has live-demo step', () => { expect(content).toContain("id: 'live-demo'") }) it('has claude-usage step', () => { expect(content).toContain("id: 'claude-usage'") }) it('has troubleshooting step', () => { expect(content).toContain("id: 'troubleshooting'") }) it('has LiveDemoPanel component', () => { expect(content).toContain('LiveDemoPanel') }) it('has navigation back link', () => { expect(content).toContain('/admin/unity-bridge') expect(content).toContain('Zurueck') }) it('has completion message', () => { expect(content).toContain('Wizard abgeschlossen') }) describe('Education Content', () => { it('explains what the bridge is', () => { expect(content).toContain('Unity AI Bridge') expect(content).toContain('REST API') expect(content).toContain('Port 8090') }) it('explains how to start the server', () => { expect(content).toContain('Server starten') expect(content).toContain('BreakpilotDrive') expect(content).toContain('AI Bridge') }) it('documents API endpoints', () => { expect(content).toContain('/status') expect(content).toContain('/logs') expect(content).toContain('/scene') expect(content).toContain('/play') expect(content).toContain('/diagnose') }) it('explains Claude integration', () => { expect(content).toContain('Claude') expect(content).toContain('UNITY_BRIDGE.md') }) it('has troubleshooting tips', () => { expect(content).toContain('Connection refused') expect(content).toContain('Port 8090') expect(content).toContain('lsof') }) }) }) // ============================================== // Architecture Data Tests // ============================================== describe('Unity Bridge Architecture Data', () => { const architecturePath = join(LIB_DIR, 'architecture-data.ts') let content: string beforeAll(() => { content = readFileSync(architecturePath, 'utf-8') }) it('has unity-bridge service definition', () => { expect(content).toContain("'unity-bridge':") expect(content).toContain("name: 'Unity AI Bridge'") }) it('defines correct port', () => { expect(content).toContain('port: 8090') }) it('has module architecture definition', () => { expect(content).toContain("displayName: 'Unity AI Bridge'") }) it('has correct data flow', () => { expect(content).toContain('Admin Panel') expect(content).toContain('API Proxy') expect(content).toContain('Unity Bridge') expect(content).toContain('Unity Editor') }) it('has wizard configuration', () => { expect(content).toContain("module: 'unity-bridge'") expect(content).toContain('steps: 7') }) }) // ============================================== // Navigation Tests // ============================================== describe('Admin Layout Navigation', () => { const layoutPath = join(COMPONENTS_DIR, 'admin', 'AdminLayout.tsx') let content: string beforeAll(() => { content = readFileSync(layoutPath, 'utf-8') }) it('has Unity Bridge navigation entry', () => { expect(content).toContain("name: 'Unity Bridge'") expect(content).toContain("href: '/admin/unity-bridge'") }) it('has Unity Bridge description', () => { expect(content).toContain('Unity Editor Steuerung') }) it('has Unity Bridge icon (lightning bolt)', () => { expect(content).toContain('M13 10V3L4 14h7v7l9-11h-7z') }) }) // ============================================== // Streaming Tests // ============================================== describe('Unity Bridge Streaming', () => { describe('API Route Streaming Endpoints', () => { const apiPath = join(APP_DIR, 'api', 'admin', 'unity-bridge', 'route.ts') let content: string beforeAll(() => { content = readFileSync(apiPath, 'utf-8') }) it('has streaming type definitions', () => { expect(content).toContain('interface StreamStatusResponse') expect(content).toContain('interface StreamFrameResponse') }) it('supports stream-start action', () => { expect(content).toContain("'stream-start': '/stream/start'") }) it('supports stream-stop action', () => { expect(content).toContain("'stream-stop': '/stream/stop'") }) it('supports stream-frame action', () => { expect(content).toContain("'stream-frame': '/stream/frame'") }) it('supports stream-status action', () => { expect(content).toContain("'stream-status': '/stream/status'") }) it('handles screenshot endpoint specially', () => { expect(content).toContain("action === 'screenshot'") expect(content).toContain('/screenshot') }) it('returns screenshot as binary image', () => { expect(content).toContain("'Content-Type': 'image/jpeg'") expect(content).toContain('arrayBuffer') }) it('has no-cache headers for screenshot', () => { expect(content).toContain('no-cache') expect(content).toContain('must-revalidate') }) it('has longer timeout for screenshot', () => { // Screenshot should have 5 second timeout expect(content).toContain('5000') }) }) describe('GameView Component', () => { const componentPath = join(COMPONENTS_DIR, 'admin', 'GameView.tsx') let content: string beforeAll(() => { content = readFileSync(componentPath, 'utf-8') }) it('exists', () => { expect(existsSync(componentPath)).toBe(true) }) it('is a client component', () => { expect(content).toContain("'use client'") }) it('has streaming state management', () => { expect(content).toContain('isStreaming') expect(content).toContain('setIsStreaming') }) it('has frame data state', () => { expect(content).toContain('frameData') expect(content).toContain('setFrameData') }) it('has FPS tracking', () => { expect(content).toContain('fps') expect(content).toContain('setFps') }) it('has start streaming function', () => { expect(content).toContain('startStreaming') expect(content).toContain('stream-start') }) it('has stop streaming function', () => { expect(content).toContain('stopStreaming') expect(content).toContain('stream-stop') }) it('has screenshot capture function', () => { expect(content).toContain('captureScreenshot') expect(content).toContain('screenshot') }) it('fetches frames during streaming', () => { expect(content).toContain('fetchFrame') expect(content).toContain('stream-frame') }) it('has streaming interval of 100ms (10 FPS)', () => { expect(content).toContain('100') expect(content).toContain('10 FPS') }) it('calculates FPS every second', () => { expect(content).toContain('1000') }) it('displays LIVE indicator when streaming', () => { expect(content).toContain('LIVE') expect(content).toContain('animate-pulse') }) it('displays resolution info', () => { expect(content).toContain('Resolution') expect(content).toContain('1280x720') }) it('displays quality info', () => { expect(content).toContain('Quality') expect(content).toContain('75%') }) it('handles offline state', () => { expect(content).toContain('isUnityOnline') expect(content).toContain('Unity Bridge offline') }) it('has screenshot button', () => { expect(content).toContain('Screenshot') }) it('has stream toggle button', () => { expect(content).toContain('Stream') expect(content).toContain('Stop') }) it('shows loading overlay', () => { expect(content).toContain('isLoading') expect(content).toContain('animate-spin') }) it('uses aspect-video for game view', () => { expect(content).toContain('aspect-video') }) it('shows frame as image', () => { expect(content).toContain(' { const dashboardPath = join(APP_DIR, 'admin', 'unity-bridge', 'page.tsx') let content: string beforeAll(() => { content = readFileSync(dashboardPath, 'utf-8') }) it('imports GameView component', () => { expect(content).toContain("import GameView from '@/components/admin/GameView'") }) it('renders GameView component', () => { expect(content).toContain(' { expect(content).toContain('isUnityOnline') }) it('passes isPlaying prop', () => { expect(content).toContain('isPlaying') }) it('lists streaming endpoints in API info', () => { expect(content).toContain('/screenshot') expect(content).toContain('/stream/start') expect(content).toContain('/stream/frame') }) }) }) // ============================================== // Python Backend Proxy Tests // ============================================== describe('Unity Bridge Python Backend Proxy', () => { const apiPath = join(APP_DIR, 'api', 'admin', 'unity-bridge', 'route.ts') let content: string beforeAll(() => { content = readFileSync(apiPath, 'utf-8') }) describe('Backend Configuration', () => { it('has BACKEND_URL constant', () => { expect(content).toContain('BACKEND_URL') expect(content).toContain('http://localhost:8000') }) it('has BackendType type definition', () => { expect(content).toContain("type BackendType = 'unity' | 'python'") }) it('has EndpointConfig interface', () => { expect(content).toContain('interface EndpointConfig') }) it('has pythonEndpoints configuration', () => { expect(content).toContain('pythonEndpoints') }) }) describe('Unit API Endpoints', () => { it('supports units-list action', () => { expect(content).toContain("'units-list'") expect(content).toContain('/api/units/definitions') }) it('supports units-get action with unit_id', () => { expect(content).toContain("action === 'units-get'") expect(content).toContain('unitId') }) it('supports units-health action', () => { expect(content).toContain("'units-health'") expect(content).toContain('/api/units/health') }) }) describe('Analytics Endpoints', () => { it('supports analytics-overview action', () => { expect(content).toContain("'analytics-overview'") expect(content).toContain('/api/analytics/dashboard/overview') }) it('supports analytics-misconceptions action', () => { expect(content).toContain("'analytics-misconceptions'") expect(content).toContain('/api/analytics/misconceptions') }) it('supports analytics-learning-gain action with unit_id', () => { expect(content).toContain("action === 'analytics-learning-gain'") expect(content).toContain('/api/analytics/learning-gain') }) it('supports analytics-stops action with unit_id', () => { expect(content).toContain("action === 'analytics-stops'") expect(content).toContain('/api/analytics/unit') }) it('supports time_range parameter for analytics', () => { expect(content).toContain('time_range') expect(content).toContain('timeRange') }) }) describe('Teacher Dashboard Endpoints', () => { it('supports teacher-dashboard action', () => { expect(content).toContain("'teacher-dashboard'") expect(content).toContain('/api/teacher/dashboard') }) it('supports teacher-units action', () => { expect(content).toContain("'teacher-units'") expect(content).toContain('/api/teacher/units/available') }) }) describe('Content Generation Endpoints', () => { it('supports content-h5p action', () => { expect(content).toContain("action === 'content-h5p'") expect(content).toContain('/h5p') }) it('supports content-worksheet action', () => { expect(content).toContain("action === 'content-worksheet'") expect(content).toContain('/worksheet') }) it('supports content-pdf action', () => { expect(content).toContain("action === 'content-pdf'") expect(content).toContain('/worksheet.pdf') }) }) describe('Helper Functions', () => { it('has handlePythonBackend function', () => { expect(content).toContain('async function handlePythonBackend') }) it('has fetchFromPython function', () => { expect(content).toContain('async function fetchFromPython') }) it('has fetchPdfFromPython function', () => { expect(content).toContain('async function fetchPdfFromPython') }) it('fetchPdfFromPython returns PDF with correct headers', () => { expect(content).toContain("'Content-Type': 'application/pdf'") expect(content).toContain("'Content-Disposition': 'attachment") }) it('has longer timeout for PDF generation', () => { expect(content).toContain('15000') // 15 seconds for PDF }) }) describe('Error Handling for Python Backend', () => { it('handles Python backend connection errors', () => { expect(content).toContain('Backend nicht erreichbar') }) it('handles Python backend timeout', () => { expect(content).toContain('Backend timed out') }) it('returns offline status for Python backend errors', () => { // Check that fetchFromPython returns offline: true expect(content).toContain('offline: true') }) }) }) // ============================================== // Tab System Tests // ============================================== describe('Unity Bridge Tab System', () => { const dashboardPath = join(APP_DIR, 'admin', 'unity-bridge', 'page.tsx') let content: string beforeAll(() => { content = readFileSync(dashboardPath, 'utf-8') }) describe('Tab State Management', () => { it('has activeTab state', () => { expect(content).toContain('activeTab') expect(content).toContain('setActiveTab') }) it('default tab is editor', () => { expect(content).toContain("useState('editor')") }) it('has TabId type definition', () => { expect(content).toContain('TabId') expect(content).toContain('editor') expect(content).toContain('units') expect(content).toContain('sessions') expect(content).toContain('analytics') expect(content).toContain('content') }) }) describe('Tab Navigation', () => { it('has 5 tabs', () => { expect(content).toContain('Editor') expect(content).toContain('Units') expect(content).toContain('Sessions') expect(content).toContain('Analytics') expect(content).toContain('Content') }) it('tabs are clickable buttons', () => { expect(content).toContain('onClick={() => setActiveTab') }) it('has active tab styling', () => { expect(content).toContain('activeTab ===') }) }) describe('Units Tab', () => { it('fetches units from backend', () => { expect(content).toContain('fetchUnits') expect(content).toContain('units-list') }) it('has units state', () => { expect(content).toContain('units') expect(content).toContain('setUnits') }) it('has UnitDefinition interface', () => { expect(content).toContain('interface UnitDefinition') }) it('has unit_id field', () => { expect(content).toContain('unit_id') }) it('has template field', () => { expect(content).toContain('template') }) it('has learning_objectives field', () => { expect(content).toContain('learning_objectives') }) it('has stops field', () => { expect(content).toContain('stops') }) it('can fetch unit details', () => { expect(content).toContain('fetchUnitDetails') expect(content).toContain('units-get') }) it('has selectedUnit state', () => { expect(content).toContain('selectedUnit') expect(content).toContain('setSelectedUnit') }) }) describe('Analytics Tab', () => { it('fetches analytics from backend', () => { expect(content).toContain('fetchAnalytics') expect(content).toContain('analytics-overview') }) it('has analytics state', () => { expect(content).toContain('analytics') expect(content).toContain('setAnalytics') }) it('has AnalyticsOverview interface', () => { expect(content).toContain('interface AnalyticsOverview') }) it('has total_sessions field', () => { expect(content).toContain('total_sessions') }) it('has unique_students field', () => { expect(content).toContain('unique_students') }) it('has avg_completion_rate field', () => { expect(content).toContain('avg_completion_rate') }) it('has avg_learning_gain field', () => { expect(content).toContain('avg_learning_gain') }) it('has most_played_units field', () => { expect(content).toContain('most_played_units') }) it('has struggling_concepts field', () => { expect(content).toContain('struggling_concepts') }) }) describe('Content Tab', () => { it('can generate H5P content', () => { expect(content).toContain('generateH5P') expect(content).toContain('content-h5p') }) it('can generate worksheet', () => { expect(content).toContain('generateWorksheet') expect(content).toContain('content-worksheet') }) it('can download PDF', () => { expect(content).toContain('downloadPdf') expect(content).toContain('content-pdf') }) it('has generatedContent state', () => { expect(content).toContain('generatedContent') expect(content).toContain('setGeneratedContent') }) it('has GeneratedContent interface', () => { expect(content).toContain('interface GeneratedContent') }) }) describe('Sessions Tab', () => { it('renders sessions tab content', () => { expect(content).toContain("activeTab === 'sessions'") }) it('has sessions placeholder or content', () => { // Sessions tab exists in the tab switch expect(content).toContain('sessions') }) }) })