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>
408 lines
12 KiB
Go
408 lines
12 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// TestGetCommunicationStatus_NoServices tests status with no services configured
|
|
func TestGetCommunicationStatus_NoServices_ReturnsDisabled(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Create handler with no services
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.GET("/api/v1/communication/status", handler.GetCommunicationStatus)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/communication/status", nil)
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var response map[string]interface{}
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("Failed to parse response: %v", err)
|
|
}
|
|
|
|
// Check matrix is disabled
|
|
matrix, ok := response["matrix"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("Expected matrix in response")
|
|
}
|
|
if matrix["enabled"] != false {
|
|
t.Error("Expected matrix.enabled to be false")
|
|
}
|
|
|
|
// Check jitsi is disabled
|
|
jitsi, ok := response["jitsi"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatal("Expected jitsi in response")
|
|
}
|
|
if jitsi["enabled"] != false {
|
|
t.Error("Expected jitsi.enabled to be false")
|
|
}
|
|
|
|
// Check timestamp exists
|
|
if _, ok := response["timestamp"]; !ok {
|
|
t.Error("Expected timestamp in response")
|
|
}
|
|
}
|
|
|
|
// TestCreateRoom_NoMatrixService tests room creation without Matrix
|
|
func TestCreateRoom_NoMatrixService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/rooms", handler.CreateRoom)
|
|
|
|
body := `{"type": "class_info", "class_name": "5b"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/rooms", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
|
|
var response map[string]string
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("Failed to parse response: %v", err)
|
|
}
|
|
|
|
if response["error"] != "Matrix service not configured" {
|
|
t.Errorf("Unexpected error message: %s", response["error"])
|
|
}
|
|
}
|
|
|
|
// TestCreateRoom_InvalidBody tests room creation with invalid body
|
|
func TestCreateRoom_InvalidBody_Returns400(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/rooms", handler.CreateRoom)
|
|
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/rooms", bytes.NewBufferString("{invalid"))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Service unavailable check happens first, so we get 503
|
|
// This is expected behavior - service check before body validation
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestInviteUser_NoMatrixService tests invite without Matrix
|
|
func TestInviteUser_NoMatrixService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/rooms/invite", handler.InviteUser)
|
|
|
|
body := `{"room_id": "!abc:server", "user_id": "@user:server"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/rooms/invite", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestSendMessage_NoMatrixService tests message sending without Matrix
|
|
func TestSendMessage_NoMatrixService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/messages", handler.SendMessage)
|
|
|
|
body := `{"room_id": "!abc:server", "message": "Hello"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/messages", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestSendNotification_NoMatrixService tests notification without Matrix
|
|
func TestSendNotification_NoMatrixService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/notifications", handler.SendNotification)
|
|
|
|
body := `{"room_id": "!abc:server", "type": "absence", "student_name": "Max"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/notifications", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCreateMeeting_NoJitsiService tests meeting creation without Jitsi
|
|
func TestCreateMeeting_NoJitsiService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/meetings", handler.CreateMeeting)
|
|
|
|
body := `{"type": "quick", "display_name": "Teacher"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/meetings", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
|
|
var response map[string]string
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("Failed to parse response: %v", err)
|
|
}
|
|
|
|
if response["error"] != "Jitsi service not configured" {
|
|
t.Errorf("Unexpected error message: %s", response["error"])
|
|
}
|
|
}
|
|
|
|
// TestGetEmbedURL_NoJitsiService tests embed URL without Jitsi
|
|
func TestGetEmbedURL_NoJitsiService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/meetings/embed", handler.GetEmbedURL)
|
|
|
|
body := `{"room_name": "test-room", "display_name": "User"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/meetings/embed", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestGetJitsiInfo_NoJitsiService tests Jitsi info without service
|
|
func TestGetJitsiInfo_NoJitsiService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.GET("/api/v1/communication/jitsi/info", handler.GetJitsiInfo)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/communication/jitsi/info", nil)
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestRegisterMatrixUser_NoMatrixService tests user registration without Matrix
|
|
func TestRegisterMatrixUser_NoMatrixService_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/admin/matrix/users", handler.RegisterMatrixUser)
|
|
|
|
body := `{"username": "testuser", "display_name": "Test User"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/admin/matrix/users", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestGetAdminStats_NoServices tests admin stats without services
|
|
func TestGetAdminStats_NoServices_ReturnsDisabledStats(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.GET("/api/v1/communication/admin/stats", handler.GetAdminStats)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/v1/communication/admin/stats", nil)
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
var response CommunicationStats
|
|
if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("Failed to parse response: %v", err)
|
|
}
|
|
|
|
if response.Matrix.Enabled {
|
|
t.Error("Expected matrix.enabled to be false")
|
|
}
|
|
|
|
if response.Jitsi.Enabled {
|
|
t.Error("Expected jitsi.enabled to be false")
|
|
}
|
|
}
|
|
|
|
// TestErrToString tests the helper function
|
|
func TestErrToString_NilError_ReturnsEmpty(t *testing.T) {
|
|
result := errToString(nil)
|
|
if result != "" {
|
|
t.Errorf("Expected empty string, got %s", result)
|
|
}
|
|
}
|
|
|
|
// TestErrToString_WithError_ReturnsMessage tests error string conversion
|
|
func TestErrToString_WithError_ReturnsMessage(t *testing.T) {
|
|
err := &testError{"test error message"}
|
|
result := errToString(err)
|
|
if result != "test error message" {
|
|
t.Errorf("Expected 'test error message', got %s", result)
|
|
}
|
|
}
|
|
|
|
// testError is a simple error implementation for testing
|
|
type testError struct {
|
|
message string
|
|
}
|
|
|
|
func (e *testError) Error() string {
|
|
return e.message
|
|
}
|
|
|
|
// TestCreateRoomRequest_Types tests different room types validation
|
|
func TestCreateRoom_InvalidType_Returns400(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Since we don't have Matrix service, we get 503 first
|
|
// This test documents expected behavior when Matrix IS available
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/rooms", handler.CreateRoom)
|
|
|
|
body := `{"type": "invalid_type", "class_name": "5b"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/rooms", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Without Matrix service, we get 503 before type validation
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestCreateMeeting_InvalidType tests invalid meeting type
|
|
func TestCreateMeeting_InvalidType_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/meetings", handler.CreateMeeting)
|
|
|
|
body := `{"type": "invalid", "display_name": "User"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/meetings", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Without Jitsi service, we get 503 before type validation
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestSendNotification_InvalidType tests invalid notification type
|
|
func TestSendNotification_InvalidType_Returns503(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
router := gin.New()
|
|
router.POST("/api/v1/communication/notifications", handler.SendNotification)
|
|
|
|
body := `{"room_id": "!abc:server", "type": "invalid", "student_name": "Max"}`
|
|
req, _ := http.NewRequest("POST", "/api/v1/communication/notifications", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
router.ServeHTTP(w, req)
|
|
|
|
// Without Matrix service, we get 503 before type validation
|
|
if w.Code != http.StatusServiceUnavailable {
|
|
t.Errorf("Expected status 503, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
// TestNewCommunicationHandlers tests constructor
|
|
func TestNewCommunicationHandlers_WithNilServices_CreatesHandler(t *testing.T) {
|
|
handler := NewCommunicationHandlers(nil, nil)
|
|
|
|
if handler == nil {
|
|
t.Fatal("Expected handler to be created")
|
|
}
|
|
|
|
if handler.matrixService != nil {
|
|
t.Error("Expected matrixService to be nil")
|
|
}
|
|
|
|
if handler.jitsiService != nil {
|
|
t.Error("Expected jitsiService to be nil")
|
|
}
|
|
}
|