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
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.
548 lines
17 KiB
Python
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
|