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
This commit is contained in:
170
cmd/server/integration_file_test.go
Normal file
170
cmd/server/integration_file_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"gcy_hpc_server/internal/model"
|
||||
"gcy_hpc_server/internal/testutil/testenv"
|
||||
)
|
||||
|
||||
// fileAPIResp mirrors server.APIResponse for file integration tests.
|
||||
type fileAPIResp struct {
|
||||
Success bool `json:"success"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// fileDecode parses an HTTP response body into fileAPIResp.
|
||||
func fileDecode(t *testing.T, body io.Reader) fileAPIResp {
|
||||
t.Helper()
|
||||
data, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
t.Fatalf("fileDecode: read body: %v", err)
|
||||
}
|
||||
var r fileAPIResp
|
||||
if err := json.Unmarshal(data, &r); err != nil {
|
||||
t.Fatalf("fileDecode: unmarshal: %v (body: %s)", err, string(data))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func TestIntegration_File_List(t *testing.T) {
|
||||
env := testenv.NewTestEnv(t)
|
||||
|
||||
// Upload a file so the list is non-empty.
|
||||
env.UploadTestData("list_test.txt", []byte("hello list"))
|
||||
|
||||
resp := env.DoRequest("GET", "/api/v1/files", nil)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
r := fileDecode(t, resp.Body)
|
||||
if !r.Success {
|
||||
t.Fatalf("response not success: %s", r.Error)
|
||||
}
|
||||
|
||||
var listResp model.ListFilesResponse
|
||||
if err := json.Unmarshal(r.Data, &listResp); err != nil {
|
||||
t.Fatalf("unmarshal list response: %v", err)
|
||||
}
|
||||
|
||||
if len(listResp.Files) == 0 {
|
||||
t.Fatal("expected at least 1 file in list, got 0")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, f := range listResp.Files {
|
||||
if f.Name == "list_test.txt" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("expected to find list_test.txt in file list")
|
||||
}
|
||||
|
||||
if listResp.Total < 1 {
|
||||
t.Fatalf("expected total >= 1, got %d", listResp.Total)
|
||||
}
|
||||
if listResp.Page < 1 {
|
||||
t.Fatalf("expected page >= 1, got %d", listResp.Page)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_File_Get(t *testing.T) {
|
||||
env := testenv.NewTestEnv(t)
|
||||
|
||||
fileID, _ := env.UploadTestData("get_test.txt", []byte("hello get"))
|
||||
|
||||
resp := env.DoRequest("GET", fmt.Sprintf("/api/v1/files/%d", fileID), nil)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
r := fileDecode(t, resp.Body)
|
||||
if !r.Success {
|
||||
t.Fatalf("response not success: %s", r.Error)
|
||||
}
|
||||
|
||||
var fileResp model.FileResponse
|
||||
if err := json.Unmarshal(r.Data, &fileResp); err != nil {
|
||||
t.Fatalf("unmarshal file response: %v", err)
|
||||
}
|
||||
|
||||
if fileResp.ID != fileID {
|
||||
t.Fatalf("expected file ID %d, got %d", fileID, fileResp.ID)
|
||||
}
|
||||
if fileResp.Name != "get_test.txt" {
|
||||
t.Fatalf("expected name get_test.txt, got %s", fileResp.Name)
|
||||
}
|
||||
if fileResp.Size != int64(len("hello get")) {
|
||||
t.Fatalf("expected size %d, got %d", len("hello get"), fileResp.Size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_File_Download(t *testing.T) {
|
||||
env := testenv.NewTestEnv(t)
|
||||
|
||||
content := []byte("hello world")
|
||||
fileID, _ := env.UploadTestData("download_test.txt", content)
|
||||
|
||||
resp := env.DoRequest("GET", fmt.Sprintf("/api/v1/files/%d/download", fileID), nil)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("read download body: %v", err)
|
||||
}
|
||||
|
||||
if string(body) != string(content) {
|
||||
t.Fatalf("downloaded content mismatch: got %q, want %q", string(body), string(content))
|
||||
}
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
if contentType == "" {
|
||||
t.Fatal("expected Content-Type header to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegration_File_Delete(t *testing.T) {
|
||||
env := testenv.NewTestEnv(t)
|
||||
|
||||
fileID, _ := env.UploadTestData("delete_test.txt", []byte("hello delete"))
|
||||
|
||||
// Delete the file.
|
||||
resp := env.DoRequest("DELETE", fmt.Sprintf("/api/v1/files/%d", fileID), nil)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
r := fileDecode(t, resp.Body)
|
||||
if !r.Success {
|
||||
t.Fatalf("delete response not success: %s", r.Error)
|
||||
}
|
||||
|
||||
// Verify the file is gone — GET should return 500 (internal error) or 404.
|
||||
getResp := env.DoRequest("GET", fmt.Sprintf("/api/v1/files/%d", fileID), nil)
|
||||
defer getResp.Body.Close()
|
||||
|
||||
if getResp.StatusCode == http.StatusOK {
|
||||
gr := fileDecode(t, getResp.Body)
|
||||
if gr.Success {
|
||||
t.Fatal("expected file to be deleted, but GET still returns success")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user