Files
hpc/cmd/server/integration_cluster_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

187 lines
5.2 KiB
Go

package main
import (
"encoding/json"
"net/http"
"testing"
"gcy_hpc_server/internal/testutil/testenv"
)
// clusterNodeData mirrors the NodeResponse DTO returned by the API.
type clusterNodeData struct {
Name string `json:"name"`
State []string `json:"state"`
CPUs int32 `json:"cpus"`
RealMemory int64 `json:"real_memory"`
}
// clusterPartitionData mirrors the PartitionResponse DTO returned by the API.
type clusterPartitionData struct {
Name string `json:"name"`
State []string `json:"state"`
TotalNodes int32 `json:"total_nodes,omitempty"`
TotalCPUs int32 `json:"total_cpus,omitempty"`
}
// clusterDiagStat mirrors a single entry from the diag statistics.
type clusterDiagStat struct {
Parts []struct {
Param string `json:"param"`
} `json:"parts,omitempty"`
}
// clusterDecodeAll decodes the response and returns status, success, and raw data.
func clusterDecodeAll(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
}
// TestIntegration_Cluster_Nodes verifies GET /api/v1/nodes returns the 3 pre-loaded mock nodes.
func TestIntegration_Cluster_Nodes(t *testing.T) {
env := testenv.NewTestEnv(t)
resp := env.DoRequest(http.MethodGet, "/api/v1/nodes", nil)
status, success, data, err := clusterDecodeAll(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 nodes []clusterNodeData
if err := json.Unmarshal(data, &nodes); err != nil {
t.Fatalf("unmarshal nodes: %v", err)
}
if len(nodes) != 3 {
t.Fatalf("expected 3 nodes, got %d", len(nodes))
}
names := make(map[string]bool, len(nodes))
for _, n := range nodes {
names[n.Name] = true
}
for _, expected := range []string{"node01", "node02", "node03"} {
if !names[expected] {
t.Errorf("missing expected node %q", expected)
}
}
}
// TestIntegration_Cluster_NodeByName verifies GET /api/v1/nodes/:name returns a single node.
func TestIntegration_Cluster_NodeByName(t *testing.T) {
env := testenv.NewTestEnv(t)
resp := env.DoRequest(http.MethodGet, "/api/v1/nodes/node01", nil)
status, success, data, err := clusterDecodeAll(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 node clusterNodeData
if err := json.Unmarshal(data, &node); err != nil {
t.Fatalf("unmarshal node: %v", err)
}
if node.Name != "node01" {
t.Fatalf("expected name=node01, got %q", node.Name)
}
}
// TestIntegration_Cluster_Partitions verifies GET /api/v1/partitions returns the 2 pre-loaded partitions.
func TestIntegration_Cluster_Partitions(t *testing.T) {
env := testenv.NewTestEnv(t)
resp := env.DoRequest(http.MethodGet, "/api/v1/partitions", nil)
status, success, data, err := clusterDecodeAll(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 partitions []clusterPartitionData
if err := json.Unmarshal(data, &partitions); err != nil {
t.Fatalf("unmarshal partitions: %v", err)
}
if len(partitions) != 2 {
t.Fatalf("expected 2 partitions, got %d", len(partitions))
}
names := make(map[string]bool, len(partitions))
for _, p := range partitions {
names[p.Name] = true
}
if !names["normal"] {
t.Error("missing expected partition \"normal\"")
}
if !names["gpu"] {
t.Error("missing expected partition \"gpu\"")
}
}
// TestIntegration_Cluster_PartitionByName verifies GET /api/v1/partitions/:name returns a single partition.
func TestIntegration_Cluster_PartitionByName(t *testing.T) {
env := testenv.NewTestEnv(t)
resp := env.DoRequest(http.MethodGet, "/api/v1/partitions/normal", nil)
status, success, data, err := clusterDecodeAll(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 part clusterPartitionData
if err := json.Unmarshal(data, &part); err != nil {
t.Fatalf("unmarshal partition: %v", err)
}
if part.Name != "normal" {
t.Fatalf("expected name=normal, got %q", part.Name)
}
}
// TestIntegration_Cluster_Diag verifies GET /api/v1/diag returns diagnostics data.
func TestIntegration_Cluster_Diag(t *testing.T) {
env := testenv.NewTestEnv(t)
resp := env.DoRequest(http.MethodGet, "/api/v1/diag", nil)
status, success, data, err := clusterDecodeAll(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")
}
// Verify the response contains a "statistics" field (non-empty JSON object).
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
t.Fatalf("unmarshal diag top-level: %v", err)
}
if _, ok := raw["statistics"]; !ok {
t.Fatal("diag response missing \"statistics\" field")
}
}