package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/google/uuid" ) func TestMediaNotesEndpoints(t *testing.T) { t.Log("🔧 Testing Media Notes Endpoints") // Test GET /api/media-items/:id/notes (without auth - should fail) t.Run("GET notes without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/media-items/"+uuid.New().String()+"/notes", nil) rr := httptest.NewRecorder() // Simulate missing auth middleware handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message":"missing or malformed jwt"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, rr.Code) } }) // Test POST /api/media-items/:id/notes request validation t.Run("POST notes validation", func(t *testing.T) { mediaItemID := uuid.New() invalidPayload := `{"content": ""}` // Empty content should fail req := httptest.NewRequest("POST", "/api/media-items/"+mediaItemID.String()+"/notes", bytes.NewBufferString(invalidPayload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Content string `json:"content"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid request"}`)) return } if req.Content == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"content is required"}`)) return } w.WriteHeader(http.StatusCreated) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rr.Code) } }) // Test valid note creation request payload t.Run("Valid note creation payload", func(t *testing.T) { mediaItemID := uuid.New() validPayload := map[string]interface{}{ "content": "This is a test note.", "position": "page:45", } payloadBytes, _ := json.Marshal(validPayload) req := httptest.NewRequest("POST", "/api/media-items/"+mediaItemID.String()+"/notes", bytes.NewBuffer(payloadBytes)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Content string `json:"content"` Position string `json:"position"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid request"}`)) return } if req.Content == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"content is required"}`)) return } // Simulate successful creation response := map[string]interface{}{ "id": uuid.New().String(), "media_item_id": mediaItemID.String(), "content": req.Content, "position": req.Position, } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusCreated { t.Errorf("Expected status %d, got %d", http.StatusCreated, rr.Code) } var response map[string]interface{} if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { t.Errorf("Failed to decode response: %v", err) } if response["content"] != "This is a test note." { t.Errorf("Expected content 'This is a test note.', got %v", response["content"]) } }) } func TestMediaHighlightsEndpoints(t *testing.T) { t.Log("🔧 Testing Media Highlights Endpoints") // Test GET /api/media-items/:id/highlights (without auth - should fail) t.Run("GET highlights without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/media-items/"+uuid.New().String()+"/highlights", nil) rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message":"missing or malformed jwt"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, rr.Code) } }) // Test POST /api/media-items/:id/highlights request validation t.Run("POST highlights validation", func(t *testing.T) { mediaItemID := uuid.New() invalidPayload := `{"selection_text": ""}` // Empty selection should fail req := httptest.NewRequest("POST", "/api/media-items/"+mediaItemID.String()+"/highlights", bytes.NewBufferString(invalidPayload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { SelectionText string `json:"selection_text"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid request"}`)) return } if req.SelectionText == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"selection_text is required"}`)) return } w.WriteHeader(http.StatusCreated) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rr.Code) } }) // Test valid highlight creation request payload t.Run("Valid highlight creation payload", func(t *testing.T) { mediaItemID := uuid.New() validPayload := map[string]interface{}{ "selection_text": "This is highlighted text.", "start_position": "page:45:offset:120", "end_position": "page:45:offset:145", "color": "#ffff00", } payloadBytes, _ := json.Marshal(validPayload) req := httptest.NewRequest("POST", "/api/media-items/"+mediaItemID.String()+"/highlights", bytes.NewBuffer(payloadBytes)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { SelectionText string `json:"selection_text"` StartPosition string `json:"start_position"` EndPosition string `json:"end_position"` Color string `json:"color"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid request"}`)) return } if req.SelectionText == "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"selection_text is required"}`)) return } // Simulate successful creation response := map[string]interface{}{ "id": uuid.New().String(), "media_item_id": mediaItemID.String(), "selection_text": req.SelectionText, "start_position": req.StartPosition, "end_position": req.EndPosition, "color": req.Color, } w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(response) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusCreated { t.Errorf("Expected status %d, got %d", http.StatusCreated, rr.Code) } var response map[string]interface{} if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil { t.Errorf("Failed to decode response: %v", err) } if response["selection_text"] != "This is highlighted text." { t.Errorf("Expected selection_text 'This is highlighted text.', got %v", response["selection_text"]) } }) // Test color validation t.Run("Highlight color validation", func(t *testing.T) { mediaItemID := uuid.New() invalidColorPayload := map[string]interface{}{ "selection_text": "This is highlighted text.", "start_position": "page:45:offset:120", "end_position": "page:45:offset:145", "color": "invalid-color", // Should be hex format } payloadBytes, _ := json.Marshal(invalidColorPayload) req := httptest.NewRequest("POST", "/api/media-items/"+mediaItemID.String()+"/highlights", bytes.NewBuffer(payloadBytes)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer valid-token") rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req struct { Color string `json:"color"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"invalid request"}`)) return } // Simple validation for hex color if req.Color != "" && len(req.Color) != 7 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(`{"error":"color must be in hex format"}`)) return } w.WriteHeader(http.StatusCreated) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusBadRequest { t.Errorf("Expected status %d, got %d", http.StatusBadRequest, rr.Code) } }) } func TestEbookNotesAndHighlightsBackwardCompatibility(t *testing.T) { t.Log("🔧 Testing Ebook Notes and Highlights Backward Compatibility") // Test GET /api/ebooks/:id/notes (backward compatibility) t.Run("GET ebook notes without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/ebooks/"+uuid.New().String()+"/notes", nil) rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message":"missing or malformed jwt"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, rr.Code) } }) // Test GET /api/ebooks/:id/highlights (backward compatibility) t.Run("GET ebook highlights without auth", func(t *testing.T) { req := httptest.NewRequest("GET", "/api/ebooks/"+uuid.New().String()+"/highlights", nil) rr := httptest.NewRecorder() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(`{"message":"missing or malformed jwt"}`)) return } w.WriteHeader(http.StatusOK) }) handler.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, rr.Code) } }) }