fix: Restore all files lost during destructive rebase

A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,547 @@
"""
Unit Tests for Meetings API
Tests for Jitsi Meet integration endpoints
"""
import pytest
from unittest.mock import patch, AsyncMock, MagicMock
from fastapi.testclient import TestClient
from datetime import datetime, timedelta
# Import the app and router
import sys
sys.path.insert(0, '..')
from meetings_api import (
router,
generate_room_name,
generate_password,
build_jitsi_url,
MeetingConfig,
CreateMeetingRequest,
ScheduleMeetingRequest,
TrainingRequest,
ParentTeacherRequest,
scheduled_meetings,
active_meetings,
trainings
)
from fastapi import FastAPI
# Create test app
app = FastAPI()
app.include_router(router)
client = TestClient(app)
class TestHelperFunctions:
"""Test helper functions"""
def test_generate_room_name_default_prefix(self):
"""Test room name generation with default prefix"""
room_name = generate_room_name()
assert room_name.startswith("meeting-")
assert len(room_name) == len("meeting-") + 8
def test_generate_room_name_custom_prefix(self):
"""Test room name generation with custom prefix"""
room_name = generate_room_name("schulung")
assert room_name.startswith("schulung-")
def test_generate_room_name_unique(self):
"""Test that room names are unique"""
names = [generate_room_name() for _ in range(100)]
assert len(set(names)) == 100
def test_generate_password(self):
"""Test password generation"""
password = generate_password()
assert len(password) == 8
assert password.isalnum()
def test_generate_password_unique(self):
"""Test that passwords are unique"""
passwords = [generate_password() for _ in range(100)]
assert len(set(passwords)) == 100
def test_build_jitsi_url_basic(self):
"""Test basic Jitsi URL building"""
url = build_jitsi_url("test-room")
assert "localhost:8443/test-room" in url
assert "config.prejoinPageEnabled=false" in url
assert "config.defaultLanguage=de" in url
def test_build_jitsi_url_with_config(self):
"""Test Jitsi URL with config options"""
config = MeetingConfig(
start_with_audio_muted=True,
start_with_video_muted=True,
require_display_name=True
)
url = build_jitsi_url("test-room", config)
assert "config.startWithAudioMuted=true" in url
assert "config.startWithVideoMuted=true" in url
assert "config.requireDisplayName=true" in url
def test_build_jitsi_url_without_config(self):
"""Test Jitsi URL without config"""
url = build_jitsi_url("test-room", None)
assert "localhost:8443/test-room" in url
class TestMeetingStatsEndpoint:
"""Test /stats endpoint"""
def test_get_stats_empty(self):
"""Test stats with no meetings"""
# Clear any existing data
scheduled_meetings.clear()
active_meetings.clear()
response = client.get("/api/meetings/stats")
assert response.status_code == 200
data = response.json()
assert "active" in data
assert "scheduled" in data
assert "recordings" in data
assert "participants" in data
def test_get_stats_with_data(self):
"""Test stats with meetings"""
scheduled_meetings.clear()
active_meetings.clear()
# Add test data
scheduled_meetings.append({"room_name": "test", "title": "Test"})
active_meetings.append({"room_name": "active", "title": "Active", "participants": 5})
response = client.get("/api/meetings/stats")
assert response.status_code == 200
data = response.json()
assert data["scheduled"] == 1
assert data["active"] == 1
assert data["participants"] == 5
class TestActiveMeetingsEndpoint:
"""Test /active endpoint"""
def test_get_active_empty(self):
"""Test active meetings when empty"""
active_meetings.clear()
response = client.get("/api/meetings/active")
assert response.status_code == 200
assert response.json() == []
def test_get_active_with_meetings(self):
"""Test active meetings with data"""
active_meetings.clear()
active_meetings.append({
"room_name": "test-room",
"title": "Test Meeting",
"participants": 3,
"started_at": "2025-12-15T10:00:00"
})
response = client.get("/api/meetings/active")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["room_name"] == "test-room"
assert data[0]["title"] == "Test Meeting"
class TestCreateMeetingEndpoint:
"""Test /create endpoint"""
def test_create_quick_meeting(self):
"""Test creating a quick meeting"""
scheduled_meetings.clear()
response = client.post("/api/meetings/create", json={
"type": "quick",
"title": "Quick Meeting",
"duration": 30
})
assert response.status_code == 200
data = response.json()
assert "room_name" in data
assert data["room_name"].startswith("quick-")
assert "join_url" in data
def test_create_scheduled_meeting(self):
"""Test creating a scheduled meeting"""
scheduled_meetings.clear()
response = client.post("/api/meetings/create", json={
"type": "scheduled",
"title": "Scheduled Meeting",
"duration": 60,
"scheduled_at": "2025-12-20T14:00:00"
})
assert response.status_code == 200
data = response.json()
assert "room_name" in data
assert "join_url" in data
def test_create_training_meeting(self):
"""Test creating a training meeting"""
response = client.post("/api/meetings/create", json={
"type": "training",
"title": "Training Session",
"duration": 120
})
assert response.status_code == 200
data = response.json()
assert data["room_name"].startswith("schulung-")
def test_create_parent_meeting(self):
"""Test creating a parent meeting"""
response = client.post("/api/meetings/create", json={
"type": "parent",
"title": "Elterngespraech",
"duration": 30
})
assert response.status_code == 200
data = response.json()
assert data["room_name"].startswith("elterngespraech-")
def test_create_class_meeting(self):
"""Test creating a class meeting"""
response = client.post("/api/meetings/create", json={
"type": "class",
"title": "Klasse 5a",
"duration": 45
})
assert response.status_code == 200
data = response.json()
assert data["room_name"].startswith("klasse-")
def test_create_meeting_with_config(self):
"""Test creating meeting with custom config"""
response = client.post("/api/meetings/create", json={
"type": "quick",
"title": "Configured Meeting",
"duration": 60,
"config": {
"enable_lobby": True,
"enable_recording": True,
"start_with_audio_muted": True
}
})
assert response.status_code == 200
class TestScheduleMeetingEndpoint:
"""Test /schedule endpoint"""
def test_schedule_meeting(self):
"""Test scheduling a meeting"""
scheduled_meetings.clear()
response = client.post("/api/meetings/schedule", json={
"title": "Team Meeting",
"scheduled_at": "2025-12-20T14:00:00",
"duration": 60,
"description": "Weekly team sync"
})
assert response.status_code == 200
data = response.json()
assert "room_name" in data
assert "join_url" in data
assert len(scheduled_meetings) == 1
def test_schedule_meeting_with_invites(self):
"""Test scheduling with invites"""
scheduled_meetings.clear()
response = client.post("/api/meetings/schedule", json={
"title": "Team Meeting",
"scheduled_at": "2025-12-20T14:00:00",
"duration": 60,
"invites": ["user1@example.com", "user2@example.com"]
})
assert response.status_code == 200
class TestTrainingEndpoint:
"""Test /training endpoint"""
def test_create_training(self):
"""Test creating a training session"""
trainings.clear()
scheduled_meetings.clear()
response = client.post("/api/meetings/training", json={
"title": "Go Grundlagen",
"description": "Introduction to Go programming",
"scheduled_at": "2025-12-20T10:00:00",
"duration": 120,
"max_participants": 20,
"trainer": "Max Mustermann"
})
assert response.status_code == 200
data = response.json()
assert "schulung-" in data["room_name"]
assert "go-grundlagen" in data["room_name"].lower()
assert len(trainings) == 1
def test_create_training_with_config(self):
"""Test creating training with custom config"""
trainings.clear()
response = client.post("/api/meetings/training", json={
"title": "Docker Workshop",
"scheduled_at": "2025-12-21T14:00:00",
"duration": 180,
"max_participants": 15,
"trainer": "Lisa Schmidt",
"config": {
"enable_recording": True,
"enable_breakout": True
}
})
assert response.status_code == 200
class TestParentTeacherEndpoint:
"""Test /parent-teacher endpoint"""
def test_create_parent_teacher_meeting(self):
"""Test creating parent-teacher meeting"""
scheduled_meetings.clear()
response = client.post("/api/meetings/parent-teacher", json={
"student_name": "Max Müller",
"parent_name": "Herr Müller",
"parent_email": "mueller@example.com",
"scheduled_at": "2025-12-18T15:00:00",
"reason": "Halbjahresgespräch",
"send_invite": True
})
assert response.status_code == 200
data = response.json()
assert "elterngespraech-" in data["room_name"]
assert "max-m" in data["room_name"].lower()
assert "password" in data
assert len(data["password"]) == 8
def test_create_parent_teacher_without_email(self):
"""Test creating without email"""
response = client.post("/api/meetings/parent-teacher", json={
"student_name": "Anna Schmidt",
"parent_name": "Frau Schmidt",
"scheduled_at": "2025-12-19T14:30:00"
})
assert response.status_code == 200
class TestScheduledMeetingsEndpoint:
"""Test /scheduled endpoint"""
def test_get_scheduled_empty(self):
"""Test getting scheduled meetings when empty"""
scheduled_meetings.clear()
response = client.get("/api/meetings/scheduled")
assert response.status_code == 200
assert response.json() == []
def test_get_scheduled_with_data(self):
"""Test getting scheduled meetings with data"""
scheduled_meetings.clear()
scheduled_meetings.append({
"room_name": "test-123",
"title": "Test Meeting",
"scheduled_at": "2025-12-20T10:00:00"
})
response = client.get("/api/meetings/scheduled")
assert response.status_code == 200
assert len(response.json()) == 1
class TestTrainingsEndpoint:
"""Test /trainings endpoint"""
def test_get_trainings(self):
"""Test getting training sessions"""
trainings.clear()
trainings.append({
"room_name": "schulung-test",
"title": "Test Training",
"trainer": "Test Trainer"
})
response = client.get("/api/meetings/trainings")
assert response.status_code == 200
assert len(response.json()) == 1
class TestDeleteMeetingEndpoint:
"""Test DELETE endpoint"""
def test_delete_meeting(self):
"""Test deleting a meeting"""
# Clear and add a single meeting
scheduled_meetings.clear()
scheduled_meetings.append({
"room_name": "to-delete",
"title": "Delete Me"
})
initial_count = len(scheduled_meetings)
response = client.delete("/api/meetings/to-delete")
assert response.status_code == 200
assert response.json()["status"] == "deleted"
# Check that meeting was removed
assert len([m for m in scheduled_meetings if m["room_name"] == "to-delete"]) == 0
def test_delete_nonexistent_meeting(self):
"""Test deleting a non-existent meeting"""
initial_count = len(scheduled_meetings)
response = client.delete("/api/meetings/nonexistent")
assert response.status_code == 200
# Count should remain the same (nothing was deleted)
assert len(scheduled_meetings) == initial_count
class TestRecordingsEndpoints:
"""Test recordings endpoints"""
def test_get_recordings(self):
"""Test getting recordings list"""
response = client.get("/api/meetings/recordings")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) > 0
def test_get_recording_details(self):
"""Test getting recording details"""
response = client.get("/api/meetings/recordings/docker-basics")
assert response.status_code == 200
data = response.json()
assert data["id"] == "docker-basics"
assert "title" in data
assert "download_url" in data
def test_download_recording_demo_mode(self):
"""Test download in demo mode returns 404"""
response = client.get("/api/meetings/recordings/test/download")
assert response.status_code == 404
def test_delete_recording(self):
"""Test deleting a recording"""
response = client.delete("/api/meetings/recordings/test-recording")
assert response.status_code == 200
assert response.json()["status"] == "deleted"
class TestHealthEndpoint:
"""Test health check endpoint"""
@patch('meetings_api.httpx.AsyncClient')
def test_health_check_jitsi_available(self, mock_client):
"""Test health check when Jitsi is available"""
# Skip this test as it requires async mocking
pass
def test_health_check_returns_status(self):
"""Test health check returns expected fields"""
response = client.get("/api/meetings/health")
assert response.status_code == 200
data = response.json()
assert "status" in data
assert "jitsi_url" in data
assert "jitsi_available" in data
assert "scheduled_meetings" in data
assert "active_meetings" in data
class TestMeetingConfigModel:
"""Test MeetingConfig model"""
def test_default_config(self):
"""Test default config values"""
config = MeetingConfig()
assert config.enable_lobby is True
assert config.enable_recording is False
assert config.start_with_audio_muted is True
assert config.start_with_video_muted is False
assert config.require_display_name is True
assert config.enable_breakout is False
def test_custom_config(self):
"""Test custom config values"""
config = MeetingConfig(
enable_lobby=False,
enable_recording=True,
enable_breakout=True
)
assert config.enable_lobby is False
assert config.enable_recording is True
assert config.enable_breakout is True
class TestRequestModels:
"""Test request models"""
def test_create_meeting_request_defaults(self):
"""Test CreateMeetingRequest defaults"""
request = CreateMeetingRequest()
assert request.type == "quick"
assert request.title == "Neues Meeting"
assert request.duration == 60
assert request.scheduled_at is None
assert request.config is None
def test_schedule_meeting_request(self):
"""Test ScheduleMeetingRequest"""
request = ScheduleMeetingRequest(
title="Test",
scheduled_at="2025-12-20T10:00:00"
)
assert request.title == "Test"
assert request.duration == 60
def test_training_request(self):
"""Test TrainingRequest"""
request = TrainingRequest(
title="Test Training",
scheduled_at="2025-12-20T10:00:00",
trainer="Trainer"
)
assert request.title == "Test Training"
assert request.duration == 120
assert request.max_participants == 20
def test_parent_teacher_request(self):
"""Test ParentTeacherRequest"""
request = ParentTeacherRequest(
student_name="Max",
parent_name="Herr Müller",
scheduled_at="2025-12-20T10:00:00"
)
assert request.student_name == "Max"
assert request.duration == 30
assert request.send_invite is True