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/consent-service/internal/services/matrix/matrix_service_test.go
Benjamin Admin 21a844cb8a 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>
2026-02-09 09:51:32 +01:00

792 lines
24 KiB
Go

package matrix
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// ========================================
// Test Helpers
// ========================================
// createTestServer creates a mock Matrix server for testing
func createTestServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server, *MatrixService) {
server := httptest.NewServer(handler)
service := NewMatrixService(Config{
HomeserverURL: server.URL,
AccessToken: "test-access-token",
ServerName: "test.local",
})
return server, service
}
// ========================================
// Unit Tests: Service Creation
// ========================================
func TestNewMatrixService_ValidConfig_CreatesService(t *testing.T) {
cfg := Config{
HomeserverURL: "http://localhost:8008",
AccessToken: "test-token",
ServerName: "breakpilot.local",
}
service := NewMatrixService(cfg)
if service == nil {
t.Fatal("Expected service to be created, got nil")
}
if service.homeserverURL != cfg.HomeserverURL {
t.Errorf("Expected homeserverURL %s, got %s", cfg.HomeserverURL, service.homeserverURL)
}
if service.accessToken != cfg.AccessToken {
t.Errorf("Expected accessToken %s, got %s", cfg.AccessToken, service.accessToken)
}
if service.serverName != cfg.ServerName {
t.Errorf("Expected serverName %s, got %s", cfg.ServerName, service.serverName)
}
if service.httpClient == nil {
t.Error("Expected httpClient to be initialized")
}
if service.httpClient.Timeout != 30*time.Second {
t.Errorf("Expected timeout 30s, got %v", service.httpClient.Timeout)
}
}
func TestGetServerName_ReturnsConfiguredName(t *testing.T) {
service := NewMatrixService(Config{
HomeserverURL: "http://localhost:8008",
AccessToken: "test-token",
ServerName: "school.example.com",
})
result := service.GetServerName()
if result != "school.example.com" {
t.Errorf("Expected 'school.example.com', got '%s'", result)
}
}
func TestGenerateUserID_ValidUsername_ReturnsFormattedID(t *testing.T) {
tests := []struct {
name string
serverName string
username string
expected string
}{
{
name: "simple username",
serverName: "breakpilot.local",
username: "max.mustermann",
expected: "@max.mustermann:breakpilot.local",
},
{
name: "teacher username",
serverName: "school.de",
username: "lehrer_mueller",
expected: "@lehrer_mueller:school.de",
},
{
name: "parent username with numbers",
serverName: "test.local",
username: "eltern123",
expected: "@eltern123:test.local",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
service := NewMatrixService(Config{
HomeserverURL: "http://localhost:8008",
AccessToken: "test-token",
ServerName: tt.serverName,
})
result := service.GenerateUserID(tt.username)
if result != tt.expected {
t.Errorf("Expected '%s', got '%s'", tt.expected, result)
}
})
}
}
// ========================================
// Unit Tests: Health Check
// ========================================
func TestHealthCheck_ServerHealthy_ReturnsNil(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/_matrix/client/versions" {
t.Errorf("Expected path /_matrix/client/versions, got %s", r.URL.Path)
}
if r.Method != "GET" {
t.Errorf("Expected GET method, got %s", r.Method)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"versions": []string{"v1.1", "v1.2"},
})
})
defer server.Close()
err := service.HealthCheck(context.Background())
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestHealthCheck_ServerUnreachable_ReturnsError(t *testing.T) {
service := NewMatrixService(Config{
HomeserverURL: "http://localhost:59999", // Non-existent server
AccessToken: "test-token",
ServerName: "test.local",
})
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err := service.HealthCheck(ctx)
if err == nil {
t.Error("Expected error for unreachable server, got nil")
}
if !strings.Contains(err.Error(), "unreachable") {
t.Errorf("Expected 'unreachable' in error message, got: %v", err)
}
}
func TestHealthCheck_ServerReturns500_ReturnsError(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
})
defer server.Close()
err := service.HealthCheck(context.Background())
if err == nil {
t.Error("Expected error for 500 response, got nil")
}
if !strings.Contains(err.Error(), "500") {
t.Errorf("Expected '500' in error message, got: %v", err)
}
}
// ========================================
// Unit Tests: Room Creation
// ========================================
func TestCreateRoom_ValidRequest_ReturnsRoomID(t *testing.T) {
expectedRoomID := "!abc123:test.local"
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/_matrix/client/v3/createRoom" {
t.Errorf("Expected path /_matrix/client/v3/createRoom, got %s", r.URL.Path)
}
if r.Method != "POST" {
t.Errorf("Expected POST method, got %s", r.Method)
}
// Verify authorization header
auth := r.Header.Get("Authorization")
if auth != "Bearer test-access-token" {
t.Errorf("Expected 'Bearer test-access-token', got '%s'", auth)
}
// Verify content type
contentType := r.Header.Get("Content-Type")
if contentType != "application/json" {
t.Errorf("Expected 'application/json', got '%s'", contentType)
}
// Decode and verify request body
var req CreateRoomRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Fatalf("Failed to decode request body: %v", err)
}
if req.Name != "Test Room" {
t.Errorf("Expected name 'Test Room', got '%s'", req.Name)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(CreateRoomResponse{
RoomID: expectedRoomID,
})
})
defer server.Close()
req := CreateRoomRequest{
Name: "Test Room",
Visibility: "private",
}
result, err := service.CreateRoom(context.Background(), req)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if result.RoomID != expectedRoomID {
t.Errorf("Expected room ID '%s', got '%s'", expectedRoomID, result.RoomID)
}
}
func TestCreateRoom_ServerError_ReturnsError(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{
"errcode": "M_FORBIDDEN",
"error": "Not allowed to create rooms",
})
})
defer server.Close()
req := CreateRoomRequest{Name: "Test"}
_, err := service.CreateRoom(context.Background(), req)
if err == nil {
t.Error("Expected error, got nil")
}
if !strings.Contains(err.Error(), "M_FORBIDDEN") {
t.Errorf("Expected 'M_FORBIDDEN' in error, got: %v", err)
}
}
func TestCreateClassInfoRoom_ValidInput_CreatesRoomWithCorrectPowerLevels(t *testing.T) {
var receivedRequest CreateRoomRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedRequest)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(CreateRoomResponse{RoomID: "!class:test.local"})
})
defer server.Close()
teacherIDs := []string{"@lehrer1:test.local", "@lehrer2:test.local"}
result, err := service.CreateClassInfoRoom(context.Background(), "5a", "Grundschule Musterstadt", teacherIDs)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if result.RoomID != "!class:test.local" {
t.Errorf("Expected room ID '!class:test.local', got '%s'", result.RoomID)
}
// Verify room name format
expectedName := "5a - Grundschule Musterstadt (Info)"
if receivedRequest.Name != expectedName {
t.Errorf("Expected name '%s', got '%s'", expectedName, receivedRequest.Name)
}
// Verify power levels
if receivedRequest.PowerLevelContentOverride == nil {
t.Fatal("Expected power level override, got nil")
}
if receivedRequest.PowerLevelContentOverride.EventsDefault != 50 {
t.Errorf("Expected EventsDefault 50, got %d", receivedRequest.PowerLevelContentOverride.EventsDefault)
}
if receivedRequest.PowerLevelContentOverride.UsersDefault != 0 {
t.Errorf("Expected UsersDefault 0, got %d", receivedRequest.PowerLevelContentOverride.UsersDefault)
}
// Verify teachers have power level 50
for _, teacherID := range teacherIDs {
if level, ok := receivedRequest.PowerLevelContentOverride.Users[teacherID]; !ok || level != 50 {
t.Errorf("Expected teacher %s to have power level 50, got %d", teacherID, level)
}
}
}
func TestCreateStudentDMRoom_ValidInput_CreatesEncryptedRoom(t *testing.T) {
var receivedRequest CreateRoomRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedRequest)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(CreateRoomResponse{RoomID: "!dm:test.local"})
})
defer server.Close()
teacherIDs := []string{"@lehrer:test.local"}
parentIDs := []string{"@eltern1:test.local", "@eltern2:test.local"}
result, err := service.CreateStudentDMRoom(context.Background(), "Max Mustermann", "5a", teacherIDs, parentIDs)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if result.RoomID != "!dm:test.local" {
t.Errorf("Expected room ID '!dm:test.local', got '%s'", result.RoomID)
}
// Verify room name
expectedName := "Max Mustermann (5a) - Dialog"
if receivedRequest.Name != expectedName {
t.Errorf("Expected name '%s', got '%s'", expectedName, receivedRequest.Name)
}
// Verify encryption is enabled
foundEncryption := false
for _, state := range receivedRequest.InitialState {
if state.Type == "m.room.encryption" {
foundEncryption = true
// Content comes as map[string]interface{} from JSON unmarshaling
content, ok := state.Content.(map[string]interface{})
if !ok {
t.Errorf("Expected encryption content to be map[string]interface{}, got %T", state.Content)
continue
}
if algo, ok := content["algorithm"].(string); !ok || algo != "m.megolm.v1.aes-sha2" {
t.Errorf("Expected algorithm 'm.megolm.v1.aes-sha2', got '%v'", content["algorithm"])
}
}
}
if !foundEncryption {
t.Error("Expected encryption state event, not found")
}
// Verify all users are invited
expectedInvites := append(teacherIDs, parentIDs...)
for _, expected := range expectedInvites {
found := false
for _, invited := range receivedRequest.Invite {
if invited == expected {
found = true
break
}
}
if !found {
t.Errorf("Expected user %s to be invited", expected)
}
}
}
func TestCreateParentRepRoom_ValidInput_CreatesRoom(t *testing.T) {
var receivedRequest CreateRoomRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedRequest)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(CreateRoomResponse{RoomID: "!rep:test.local"})
})
defer server.Close()
teacherIDs := []string{"@lehrer:test.local"}
repIDs := []string{"@elternvertreter1:test.local", "@elternvertreter2:test.local"}
result, err := service.CreateParentRepRoom(context.Background(), "5a", teacherIDs, repIDs)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if result.RoomID != "!rep:test.local" {
t.Errorf("Expected room ID '!rep:test.local', got '%s'", result.RoomID)
}
// Verify room name
expectedName := "5a - Elternvertreter"
if receivedRequest.Name != expectedName {
t.Errorf("Expected name '%s', got '%s'", expectedName, receivedRequest.Name)
}
// Verify all participants can write (power level 50)
allUsers := append(teacherIDs, repIDs...)
for _, userID := range allUsers {
if level, ok := receivedRequest.PowerLevelContentOverride.Users[userID]; !ok || level != 50 {
t.Errorf("Expected user %s to have power level 50, got %d", userID, level)
}
}
}
// ========================================
// Unit Tests: User Management
// ========================================
func TestSetDisplayName_ValidRequest_Succeeds(t *testing.T) {
var receivedPath string
var receivedBody map[string]string
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
receivedPath = r.URL.Path
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
})
defer server.Close()
err := service.SetDisplayName(context.Background(), "@user:test.local", "Max Mustermann")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
// Path may or may not be URL-encoded depending on Go version
if !strings.Contains(receivedPath, "/profile/") || !strings.Contains(receivedPath, "/displayname") {
t.Errorf("Expected path to contain '/profile/' and '/displayname', got '%s'", receivedPath)
}
if receivedBody["displayname"] != "Max Mustermann" {
t.Errorf("Expected displayname 'Max Mustermann', got '%s'", receivedBody["displayname"])
}
}
// ========================================
// Unit Tests: Room Membership
// ========================================
func TestInviteUser_ValidRequest_Succeeds(t *testing.T) {
var receivedBody InviteRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.URL.Path, "/invite") {
t.Errorf("Expected path to contain '/invite', got '%s'", r.URL.Path)
}
if r.Method != "POST" {
t.Errorf("Expected POST method, got %s", r.Method)
}
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
})
defer server.Close()
err := service.InviteUser(context.Background(), "!room:test.local", "@user:test.local")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if receivedBody.UserID != "@user:test.local" {
t.Errorf("Expected user_id '@user:test.local', got '%s'", receivedBody.UserID)
}
}
func TestInviteUser_UserAlreadyInRoom_ReturnsError(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{
"errcode": "M_FORBIDDEN",
"error": "User is already in the room",
})
})
defer server.Close()
err := service.InviteUser(context.Background(), "!room:test.local", "@user:test.local")
if err == nil {
t.Error("Expected error, got nil")
}
}
func TestJoinRoom_ValidRequest_Succeeds(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.URL.Path, "/join/") {
t.Errorf("Expected path to contain '/join/', got '%s'", r.URL.Path)
}
if r.Method != "POST" {
t.Errorf("Expected POST method, got %s", r.Method)
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"room_id": "!room:test.local"})
})
defer server.Close()
err := service.JoinRoom(context.Background(), "!room:test.local")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
}
// ========================================
// Unit Tests: Messaging
// ========================================
func TestSendMessage_ValidRequest_Succeeds(t *testing.T) {
var receivedBody SendMessageRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.URL.Path, "/send/m.room.message/") {
t.Errorf("Expected path to contain '/send/m.room.message/', got '%s'", r.URL.Path)
}
if r.Method != "PUT" {
t.Errorf("Expected PUT method, got %s", r.Method)
}
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"event_id": "$event123"})
})
defer server.Close()
err := service.SendMessage(context.Background(), "!room:test.local", "Hello, World!")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if receivedBody.MsgType != "m.text" {
t.Errorf("Expected msgtype 'm.text', got '%s'", receivedBody.MsgType)
}
if receivedBody.Body != "Hello, World!" {
t.Errorf("Expected body 'Hello, World!', got '%s'", receivedBody.Body)
}
}
func TestSendHTMLMessage_ValidRequest_IncludesFormattedBody(t *testing.T) {
var receivedBody SendMessageRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"event_id": "$event123"})
})
defer server.Close()
err := service.SendHTMLMessage(context.Background(), "!room:test.local", "Plain text", "<b>Bold text</b>")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if receivedBody.Format != "org.matrix.custom.html" {
t.Errorf("Expected format 'org.matrix.custom.html', got '%s'", receivedBody.Format)
}
if receivedBody.Body != "Plain text" {
t.Errorf("Expected body 'Plain text', got '%s'", receivedBody.Body)
}
if receivedBody.FormattedBody != "<b>Bold text</b>" {
t.Errorf("Expected formatted_body '<b>Bold text</b>', got '%s'", receivedBody.FormattedBody)
}
}
func TestSendAbsenceNotification_ValidRequest_FormatsCorrectly(t *testing.T) {
var receivedBody SendMessageRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"event_id": "$event123"})
})
defer server.Close()
err := service.SendAbsenceNotification(context.Background(), "!room:test.local", "Max Mustermann", "15.12.2025", 3)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
// Verify plain text contains key information
if !strings.Contains(receivedBody.Body, "Max Mustermann") {
t.Error("Expected body to contain student name")
}
if !strings.Contains(receivedBody.Body, "15.12.2025") {
t.Error("Expected body to contain date")
}
if !strings.Contains(receivedBody.Body, "3. Stunde") {
t.Error("Expected body to contain lesson number")
}
if !strings.Contains(receivedBody.Body, "Abwesenheitsmeldung") {
t.Error("Expected body to contain 'Abwesenheitsmeldung'")
}
// Verify HTML is set
if receivedBody.FormattedBody == "" {
t.Error("Expected formatted body to be set")
}
}
func TestSendGradeNotification_ValidRequest_FormatsCorrectly(t *testing.T) {
var receivedBody SendMessageRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"event_id": "$event123"})
})
defer server.Close()
err := service.SendGradeNotification(context.Background(), "!room:test.local", "Max Mustermann", "Mathematik", "Klassenarbeit", 2.3)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if !strings.Contains(receivedBody.Body, "Max Mustermann") {
t.Error("Expected body to contain student name")
}
if !strings.Contains(receivedBody.Body, "Mathematik") {
t.Error("Expected body to contain subject")
}
if !strings.Contains(receivedBody.Body, "Klassenarbeit") {
t.Error("Expected body to contain grade type")
}
if !strings.Contains(receivedBody.Body, "2.3") {
t.Error("Expected body to contain grade")
}
}
func TestSendClassAnnouncement_ValidRequest_FormatsCorrectly(t *testing.T) {
var receivedBody SendMessageRequest
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
json.NewDecoder(r.Body).Decode(&receivedBody)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"event_id": "$event123"})
})
defer server.Close()
err := service.SendClassAnnouncement(context.Background(), "!room:test.local", "Elternabend", "Am 20.12. findet der Elternabend statt.", "Frau Müller")
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if !strings.Contains(receivedBody.Body, "Elternabend") {
t.Error("Expected body to contain title")
}
if !strings.Contains(receivedBody.Body, "20.12.") {
t.Error("Expected body to contain content")
}
if !strings.Contains(receivedBody.Body, "Frau Müller") {
t.Error("Expected body to contain teacher name")
}
}
// ========================================
// Unit Tests: Power Levels
// ========================================
func TestSetUserPowerLevel_ValidRequest_UpdatesPowerLevel(t *testing.T) {
callCount := 0
var putBody PowerLevels
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
callCount++
if r.Method == "GET" {
// Return current power levels
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(PowerLevels{
Users: map[string]int{
"@admin:test.local": 100,
},
UsersDefault: 0,
})
} else if r.Method == "PUT" {
// Update power levels
json.NewDecoder(r.Body).Decode(&putBody)
w.WriteHeader(http.StatusOK)
}
})
defer server.Close()
err := service.SetUserPowerLevel(context.Background(), "!room:test.local", "@newuser:test.local", 50)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if callCount != 2 {
t.Errorf("Expected 2 API calls (GET then PUT), got %d", callCount)
}
if putBody.Users["@newuser:test.local"] != 50 {
t.Errorf("Expected user power level 50, got %d", putBody.Users["@newuser:test.local"])
}
// Verify existing users are preserved
if putBody.Users["@admin:test.local"] != 100 {
t.Errorf("Expected admin power level 100 to be preserved, got %d", putBody.Users["@admin:test.local"])
}
}
// ========================================
// Unit Tests: Error Handling
// ========================================
func TestParseError_MatrixError_ExtractsFields(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{
"errcode": "M_UNKNOWN",
"error": "Something went wrong",
})
})
defer server.Close()
_, err := service.CreateRoom(context.Background(), CreateRoomRequest{Name: "Test"})
if err == nil {
t.Fatal("Expected error, got nil")
}
if !strings.Contains(err.Error(), "M_UNKNOWN") {
t.Errorf("Expected error to contain 'M_UNKNOWN', got: %v", err)
}
if !strings.Contains(err.Error(), "Something went wrong") {
t.Errorf("Expected error to contain 'Something went wrong', got: %v", err)
}
}
func TestParseError_NonJSONError_ReturnsRawBody(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Internal Server Error"))
})
defer server.Close()
_, err := service.CreateRoom(context.Background(), CreateRoomRequest{Name: "Test"})
if err == nil {
t.Fatal("Expected error, got nil")
}
if !strings.Contains(err.Error(), "500") {
t.Errorf("Expected error to contain '500', got: %v", err)
}
}
// ========================================
// Unit Tests: Context Handling
// ========================================
func TestCreateRoom_ContextCanceled_ReturnsError(t *testing.T) {
server, service := createTestServer(t, func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond)
w.WriteHeader(http.StatusOK)
})
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
_, err := service.CreateRoom(ctx, CreateRoomRequest{Name: "Test"})
if err == nil {
t.Error("Expected error for canceled context, got nil")
}
}
// ========================================
// Integration Tests (require running Synapse)
// ========================================
// These tests are skipped by default as they require a running Matrix server
// Run with: go test -tags=integration ./...
func TestIntegration_HealthCheck(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
service := NewMatrixService(Config{
HomeserverURL: "http://localhost:8008",
AccessToken: "", // Not needed for health check
ServerName: "breakpilot.local",
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := service.HealthCheck(ctx)
if err != nil {
t.Skipf("Matrix server not available: %v", err)
}
}