- 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
187 lines
5.2 KiB
Go
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")
|
|
}
|
|
}
|