package handler import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "gcy_hpc_server/internal/model" "gcy_hpc_server/internal/service" "gcy_hpc_server/internal/store" "github.com/gin-gonic/gin" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" gormlogger "gorm.io/gorm/logger" ) func setupFolderHandler() (*FolderHandler, *gorm.DB) { db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Silent)}) db.AutoMigrate(&model.Folder{}, &model.File{}) folderStore := store.NewFolderStore(db) fileStore := store.NewFileStore(db) svc := service.NewFolderService(folderStore, fileStore, zap.NewNop()) h := NewFolderHandler(svc, zap.NewNop()) return h, db } func setupFolderRouter(h *FolderHandler) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() v1 := r.Group("/api/v1") folders := v1.Group("/files/folders") folders.POST("", h.CreateFolder) folders.GET("/:id", h.GetFolder) folders.GET("", h.ListFolders) folders.DELETE("/:id", h.DeleteFolder) return r } func createTestFolder(h *FolderHandler, r *gin.Engine) int64 { body, _ := json.Marshal(model.CreateFolderRequest{Name: "test-folder"}) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/api/v1/files/folders", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) return int64(data["id"].(float64)) } func TestCreateFolder_Success(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) body, _ := json.Marshal(model.CreateFolderRequest{Name: "my-folder"}) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/api/v1/files/folders", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if !resp["success"].(bool) { t.Fatal("expected success=true") } data := resp["data"].(map[string]interface{}) if data["name"] != "my-folder" { t.Fatalf("expected name=my-folder, got %v", data["name"]) } if data["path"] != "/my-folder/" { t.Fatalf("expected path=/my-folder/, got %v", data["path"]) } } func TestCreateFolder_PathTraversal(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) body, _ := json.Marshal(model.CreateFolderRequest{Name: ".."}) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/api/v1/files/folders", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } } func TestCreateFolder_MissingName(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) body, _ := json.Marshal(map[string]interface{}{}) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodPost, "/api/v1/files/folders", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } } func TestGetFolder_Success(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) id := createTestFolder(h, r) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/api/v1/files/folders/"+itoa(id), nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) if data["name"] != "test-folder" { t.Fatalf("expected name=test-folder, got %v", data["name"]) } } func TestGetFolder_NotFound(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/api/v1/files/folders/999", nil) r.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d: %s", w.Code, w.Body.String()) } } func TestListFolders_Success(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) createTestFolder(h, r) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodGet, "/api/v1/files/folders", nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if !resp["success"].(bool) { t.Fatal("expected success=true") } data := resp["data"].([]interface{}) if len(data) < 1 { t.Fatal("expected at least 1 folder") } } func TestDeleteFolder_Success(t *testing.T) { h, _ := setupFolderHandler() r := setupFolderRouter(h) id := createTestFolder(h, r) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodDelete, "/api/v1/files/folders/"+itoa(id), nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } } func TestDeleteFolder_NonEmpty(t *testing.T) { h, db := setupFolderHandler() r := setupFolderRouter(h) parentID := createTestFolder(h, r) child := &model.Folder{ Name: "child-folder", ParentID: &parentID, Path: "/test-folder/child-folder/", } db.Create(child) w := httptest.NewRecorder() req, _ := http.NewRequest(http.MethodDelete, "/api/v1/files/folders/"+itoa(parentID), nil) r.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } }