This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_meetings_api.py
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +01:00

548 lines
17 KiB
Python

"""
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