Files
hpc/cmd/server/integration_folder_test.go
dailz b9b2f0d9b4 feat(testutil): add MockSlurm, MockMinIO, TestEnv and 37 integration tests
- mockminio: in-memory ObjectStorage with all 11 methods, thread-safe, SHA256 ETag, Range support
- mockslurm: httptest server with 11 Slurm REST API endpoints, job eviction from active to history queue
- testenv: one-line test environment factory (SQLite + MockSlurm + MockMinIO + all stores/services/handlers + httptest server)
- integration tests: 37 tests covering Jobs(5), Cluster(5), App(6), Upload(5), File(4), Folder(4), Task(4), E2E(1)
- no external dependencies, no existing files modified
2026-04-16 13:23:27 +08:00

194 lines
5.5 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
"gcy_hpc_server/internal/testutil/testenv"
)
// folderData mirrors the FolderResponse DTO returned by the API.
type folderData struct {
ID int64 `json:"id"`
Name string `json:"name"`
ParentID *int64 `json:"parent_id,omitempty"`
Path string `json:"path"`
FileCount int64 `json:"file_count"`
SubFolderCount int64 `json:"subfolder_count"`
}
// folderMessageData mirrors the delete endpoint response data structure.
type folderMessageData struct {
Message string `json:"message"`
}
// folderDoRequest marshals body and calls env.DoRequest.
func folderDoRequest(env *testenv.TestEnv, method, path string, body interface{}) *http.Response {
var r io.Reader
if body != nil {
b, err := json.Marshal(body)
if err != nil {
panic(fmt.Sprintf("folderDoRequest marshal: %v", err))
}
r = bytes.NewReader(b)
}
return env.DoRequest(method, path, r)
}
// folderDecodeAll decodes the response and returns status, success, and raw data.
func folderDecodeAll(env *testenv.TestEnv, resp *http.Response) (statusCode int, success bool, data json.RawMessage, err error) {
statusCode = resp.StatusCode
success, data, err = env.DecodeResponse(resp)
return
}
// folderSeed creates a folder via HTTP and returns its ID.
func folderSeed(env *testenv.TestEnv, name string) int64 {
body := map[string]interface{}{"name": name}
resp := folderDoRequest(env, http.MethodPost, "/api/v1/files/folders", body)
status, success, data, err := folderDecodeAll(env, resp)
if err != nil {
panic(fmt.Sprintf("folderSeed decode: %v", err))
}
if status != http.StatusCreated {
panic(fmt.Sprintf("folderSeed: expected 201, got %d", status))
}
if !success {
panic("folderSeed: expected success=true")
}
var f folderData
if err := json.Unmarshal(data, &f); err != nil {
panic(fmt.Sprintf("folderSeed unmarshal: %v", err))
}
return f.ID
}
// TestIntegration_Folder_Create verifies POST /api/v1/files/folders creates a folder.
func TestIntegration_Folder_Create(t *testing.T) {
env := testenv.NewTestEnv(t)
body := map[string]interface{}{"name": "test-folder-create"}
resp := folderDoRequest(env, http.MethodPost, "/api/v1/files/folders", body)
status, success, data, err := folderDecodeAll(env, resp)
if err != nil {
t.Fatalf("decode response: %v", err)
}
if status != http.StatusCreated {
t.Fatalf("expected status 201, got %d", status)
}
if !success {
t.Fatal("expected success=true")
}
var created folderData
if err := json.Unmarshal(data, &created); err != nil {
t.Fatalf("unmarshal created data: %v", err)
}
if created.ID <= 0 {
t.Fatalf("expected positive id, got %d", created.ID)
}
if created.Name != "test-folder-create" {
t.Fatalf("expected name=test-folder-create, got %s", created.Name)
}
}
// TestIntegration_Folder_List verifies GET /api/v1/files/folders returns a list.
func TestIntegration_Folder_List(t *testing.T) {
env := testenv.NewTestEnv(t)
// Seed two folders.
folderSeed(env, "list-folder-1")
folderSeed(env, "list-folder-2")
resp := env.DoRequest(http.MethodGet, "/api/v1/files/folders", nil)
status, success, data, err := folderDecodeAll(env, resp)
if err != nil {
t.Fatalf("decode response: %v", err)
}
if status != http.StatusOK {
t.Fatalf("expected status 200, got %d", status)
}
if !success {
t.Fatal("expected success=true")
}
var folders []folderData
if err := json.Unmarshal(data, &folders); err != nil {
t.Fatalf("unmarshal list data: %v", err)
}
if len(folders) < 2 {
t.Fatalf("expected at least 2 folders, got %d", len(folders))
}
}
// TestIntegration_Folder_Get verifies GET /api/v1/files/folders/:id returns folder details.
func TestIntegration_Folder_Get(t *testing.T) {
env := testenv.NewTestEnv(t)
id := folderSeed(env, "test-folder-get")
path := fmt.Sprintf("/api/v1/files/folders/%d", id)
resp := env.DoRequest(http.MethodGet, path, nil)
status, success, data, err := folderDecodeAll(env, resp)
if err != nil {
t.Fatalf("decode response: %v", err)
}
if status != http.StatusOK {
t.Fatalf("expected status 200, got %d", status)
}
if !success {
t.Fatal("expected success=true")
}
var f folderData
if err := json.Unmarshal(data, &f); err != nil {
t.Fatalf("unmarshal folder data: %v", err)
}
if f.ID != id {
t.Fatalf("expected id=%d, got %d", id, f.ID)
}
if f.Name != "test-folder-get" {
t.Fatalf("expected name=test-folder-get, got %s", f.Name)
}
}
// TestIntegration_Folder_Delete verifies DELETE /api/v1/files/folders/:id removes a folder.
func TestIntegration_Folder_Delete(t *testing.T) {
env := testenv.NewTestEnv(t)
id := folderSeed(env, "test-folder-delete")
path := fmt.Sprintf("/api/v1/files/folders/%d", id)
resp := env.DoRequest(http.MethodDelete, path, nil)
status, success, data, err := folderDecodeAll(env, resp)
if err != nil {
t.Fatalf("decode response: %v", err)
}
if status != http.StatusOK {
t.Fatalf("expected status 200, got %d", status)
}
if !success {
t.Fatal("expected success=true")
}
var msg folderMessageData
if err := json.Unmarshal(data, &msg); err != nil {
t.Fatalf("unmarshal message data: %v", err)
}
if msg.Message != "folder deleted" {
t.Fatalf("expected message 'folder deleted', got %q", msg.Message)
}
// Verify it's gone via GET → 404.
getResp := env.DoRequest(http.MethodGet, path, nil)
getStatus, getSuccess, _, _ := folderDecodeAll(env, getResp)
if getStatus != http.StatusNotFound {
t.Fatalf("expected status 404 after delete, got %d", getStatus)
}
if getSuccess {
t.Fatal("expected success=false after delete")
}
}