package api import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "eval/internal/config" "eval/internal/storage" "github.com/sirupsen/logrus" ) func setupTestEnvironment(t *testing.T) (*Handlers, string) { t.Helper() tempDir := t.TempDir() cfg := config.NewConfig() cfg.DataDir = tempDir logger := logrus.New() logger.SetLevel(logrus.FatalLevel) store := storage.NewStorage(tempDir) handlers := NewHandlers(store, cfg, logger) return handlers, tempDir } func TestHandlers_ListFiles_Empty(t *testing.T) { handlers, _ := setupTestEnvironment(t) req := httptest.NewRequest("GET", "/api/files", nil) w := httptest.NewRecorder() handlers.listFilesHandler(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var files []storage.FileMetadata if err := json.Unmarshal(w.Body.Bytes(), &files); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(files) != 0 { t.Errorf("expected 0 files, got %d", len(files)) } } func TestHandlers_ListFiles_Multiple(t *testing.T) { handlers, tempDir := setupTestEnvironment(t) // Create test files testFiles := []string{"file1.md", "file2.md", "file3.md"} for _, name := range testFiles { content := "# Test " + name if err := os.WriteFile(filepath.Join(tempDir, name), []byte(content), 0644); err != nil { t.Fatalf("failed to create test file: %v", err) } } req := httptest.NewRequest("GET", "/api/files", nil) w := httptest.NewRecorder() handlers.listFilesHandler(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var files []storage.FileMetadata if err := json.Unmarshal(w.Body.Bytes(), &files); err != nil { t.Fatalf("failed to parse response: %v", err) } if len(files) != 3 { t.Errorf("expected 3 files, got %d", len(files)) } } func TestHandlers_GetFile(t *testing.T) { handlers, tempDir := setupTestEnvironment(t) // Create test file filename := "test.md" content := "# Hello World\n\nThis is a test file." if err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644); err != nil { t.Fatalf("failed to create test file: %v", err) } req := httptest.NewRequest("GET", "/api/files/"+filename, nil) w := httptest.NewRecorder() handlers.getFileHandler(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var file storage.FileContent if err := json.Unmarshal(w.Body.Bytes(), &file); err != nil { t.Fatalf("failed to parse response: %v", err) } if file.Content != content { t.Errorf("expected content %s, got %s", content, file.Content) } if file.Filename != filename { t.Errorf("expected filename %s, got %s", filename, file.Filename) } } func TestHandlers_GetFile_NotFound(t *testing.T) { handlers, _ := setupTestEnvironment(t) req := httptest.NewRequest("GET", "/api/files/nonexistent.md", nil) w := httptest.NewRecorder() handlers.getFileHandler(w, req) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } var errResp ErrorResponse if err := json.Unmarshal(w.Body.Bytes(), &errResp); err != nil { t.Fatalf("failed to parse response: %v", err) } } func TestHandlers_CreateFile(t *testing.T) { handlers, _ := setupTestEnvironment(t) filename := "newfile.md" content := "# New File\n\nCreated via API." body := &storage.FileContent{ Filename: filename, Content: content, Title: "New File", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("POST", "/api/files", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handlers.createFileHandler(w, req) if w.Code != http.StatusCreated { t.Errorf("expected status 201, got %d", w.Code) } var file storage.FileContent if err := json.Unmarshal(w.Body.Bytes(), &file); err != nil { t.Fatalf("failed to parse response: %v", err) } if file.Content != content { t.Errorf("expected content %s, got %s", content, file.Content) } } func TestHandlers_CreateFile_Validation(t *testing.T) { handlers, _ := setupTestEnvironment(t) tests := []struct { name string filename string expected int }{ {"empty filename", "", http.StatusBadRequest}, {"missing extension", "noext", http.StatusInternalServerError}, {"invalid extension", "file.txt", http.StatusInternalServerError}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { body := &storage.FileContent{ Filename: tt.filename, Content: "content", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("POST", "/api/files", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handlers.createFileHandler(w, req) if w.Code != tt.expected { t.Errorf("expected status %d, got %d", tt.expected, w.Code) } }) } } func TestHandlers_UpdateFile(t *testing.T) { handlers, tempDir := setupTestEnvironment(t) // Create initial file filename := "update.md" content := "# Original" if err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644); err != nil { t.Fatalf("failed to create test file: %v", err) } // Update file newContent := "# Updated\n\nContent changed." body := &storage.FileContent{ Filename: filename, Content: newContent, Title: "Updated", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("PUT", "/api/files/"+filename, bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handlers.updateFileHandler(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } var file storage.FileContent if err := json.Unmarshal(w.Body.Bytes(), &file); err != nil { t.Fatalf("failed to parse response: %v", err) } if file.Content != newContent { t.Errorf("expected content %s, got %s", newContent, file.Content) } } func TestHandlers_UpdateFile_NotFound(t *testing.T) { handlers, _ := setupTestEnvironment(t) body := &storage.FileContent{ Filename: "nonexistent.md", Content: "content", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("PUT", "/api/files/nonexistent.md", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handlers.updateFileHandler(w, req) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } func TestHandlers_DeleteFile(t *testing.T) { handlers, tempDir := setupTestEnvironment(t) // Create file filename := "delete.md" content := "# To be deleted" if err := os.WriteFile(filepath.Join(tempDir, filename), []byte(content), 0644); err != nil { t.Fatalf("failed to create test file: %v", err) } // Delete file req := httptest.NewRequest("DELETE", "/api/files/"+filename, nil) w := httptest.NewRecorder() handlers.deleteFileHandler(w, req) if w.Code != http.StatusOK { t.Errorf("expected status 200, got %d", w.Code) } // Verify file is deleted if _, err := os.Stat(filepath.Join(tempDir, filename)); !os.IsNotExist(err) { t.Errorf("file should be deleted") } } func TestHandlers_DeleteFile_NotFound(t *testing.T) { handlers, _ := setupTestEnvironment(t) req := httptest.NewRequest("DELETE", "/api/files/nonexistent.md", nil) w := httptest.NewRecorder() handlers.deleteFileHandler(w, req) if w.Code != http.StatusNotFound { t.Errorf("expected status 404, got %d", w.Code) } } func TestHandlers_CRUDRoundTrip(t *testing.T) { handlers, tempDir := setupTestEnvironment(t) // Create filename := "roundtrip.md" content := "# Round Trip Test" body := &storage.FileContent{ Filename: filename, Content: content, Title: "Round Trip Test", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest("POST", "/api/files", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() handlers.createFileHandler(w, req) if w.Code != http.StatusCreated { t.Fatalf("Create failed with status %d", w.Code) } var created storage.FileContent json.Unmarshal(w.Body.Bytes(), &created) // Read req = httptest.NewRequest("GET", "/api/files/"+filename, nil) w = httptest.NewRecorder() handlers.getFileHandler(w, req) if w.Code != http.StatusOK { t.Fatalf("Read failed with status %d", w.Code) } var retrieved storage.FileContent json.Unmarshal(w.Body.Bytes(), &retrieved) if retrieved.Content != content { t.Errorf("Content mismatch after read: expected %s, got %s", content, retrieved.Content) } // Update newContent := "# Round Trip Updated" body = &storage.FileContent{ Filename: filename, Content: newContent, Title: "Round Trip Updated", } jsonBody, _ = json.Marshal(body) req = httptest.NewRequest("PUT", "/api/files/"+filename, bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") w = httptest.NewRecorder() handlers.updateFileHandler(w, req) if w.Code != http.StatusOK { t.Fatalf("Update failed with status %d", w.Code) } // Delete req = httptest.NewRequest("DELETE", "/api/files/"+filename, nil) w = httptest.NewRecorder() handlers.deleteFileHandler(w, req) if w.Code != http.StatusOK { t.Fatalf("Delete failed with status %d", w.Code) } // Verify deletion req = httptest.NewRequest("GET", "/api/files/"+filename, nil) w = httptest.NewRecorder() handlers.getFileHandler(w, req) if w.Code != http.StatusNotFound { t.Errorf("Expected 404 after delete, got %d", w.Code) } // Verify file is deleted on disk if _, err := os.Stat(filepath.Join(tempDir, filename)); !os.IsNotExist(err) { t.Errorf("file should be deleted on disk") } }