package testenv import ( "encoding/json" "io" "net/http" "testing" "time" "gcy_hpc_server/internal/model" "gcy_hpc_server/internal/storage" ) func TestNewTestEnv(t *testing.T) { env := NewTestEnv(t) defer func() { env.taskSvc.StopProcessor() env.poller.Stop() env.srv.Close() }() if env.DB == nil { t.Fatal("DB is nil") } if env.MockSlurm == nil { t.Fatal("MockSlurm is nil") } if env.MockMinIO == nil { t.Fatal("MockMinIO is nil") } if env.srv == nil { t.Fatal("srv is nil") } resp, err := http.Get(env.srv.URL + "/api/v1/applications") if err != nil { t.Fatalf("GET /api/v1/applications: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } } func TestAllRoutesRegistered(t *testing.T) { env := NewTestEnv(t) routes := []struct { method string path string }{ {"POST", "/api/v1/jobs/submit"}, {"GET", "/api/v1/jobs"}, {"GET", "/api/v1/jobs/history"}, {"GET", "/api/v1/jobs/1"}, {"DELETE", "/api/v1/jobs/1"}, {"GET", "/api/v1/nodes"}, {"GET", "/api/v1/nodes/node01"}, {"GET", "/api/v1/partitions"}, {"GET", "/api/v1/partitions/normal"}, {"GET", "/api/v1/diag"}, {"GET", "/api/v1/applications"}, {"POST", "/api/v1/applications"}, {"GET", "/api/v1/applications/1"}, {"PUT", "/api/v1/applications/1"}, {"DELETE", "/api/v1/applications/1"}, // {"POST", "/api/v1/applications/1/submit"}, // [已禁用] 已被 POST /tasks 取代 {"POST", "/api/v1/files/uploads"}, {"GET", "/api/v1/files/uploads/1"}, {"PUT", "/api/v1/files/uploads/1/chunks/0"}, {"POST", "/api/v1/files/uploads/1/complete"}, {"DELETE", "/api/v1/files/uploads/1"}, {"GET", "/api/v1/files"}, {"GET", "/api/v1/files/1"}, {"GET", "/api/v1/files/1/download"}, {"DELETE", "/api/v1/files/1"}, {"POST", "/api/v1/files/folders"}, {"GET", "/api/v1/files/folders"}, {"GET", "/api/v1/files/folders/1"}, {"DELETE", "/api/v1/files/folders/1"}, {"POST", "/api/v1/tasks"}, {"GET", "/api/v1/tasks"}, } if len(routes) != 30 { t.Fatalf("expected 31 routes, got %d", len(routes)) } for _, r := range routes { req, err := http.NewRequest(r.method, env.srv.URL+r.path, nil) if err != nil { t.Fatalf("%s %s: create request: %v", r.method, r.path, err) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("%s %s: %v", r.method, r.path, err) } body, _ := io.ReadAll(resp.Body) resp.Body.Close() // Gin returns 404 for unregistered routes WITHOUT a JSON body. // Handler-level 404s return JSON with {"success":false,...}. // So a 404 that decodes as valid JSON means the route IS registered. if resp.StatusCode == http.StatusNotFound { var apiResp struct { Success bool `json:"success"` } if json.Unmarshal(body, &apiResp) != nil { t.Errorf("%s %s: got router-level 404 (route not registered)", r.method, r.path) } } } } func TestMakeTaskStale(t *testing.T) { env := NewTestEnv(t) ctx := t.Context() task := &model.Task{ TaskName: "stale-test", AppID: 1, AppName: "test-app", Status: model.TaskStatusSubmitted, } taskID, err := env.taskStore.Create(ctx, task) if err != nil { t.Fatalf("create task: %v", err) } before := time.Now() if err := env.MakeTaskStale(taskID); err != nil { t.Fatalf("MakeTaskStale: %v", err) } updated, err := env.taskStore.GetByID(ctx, taskID) if err != nil { t.Fatalf("get task: %v", err) } if !updated.UpdatedAt.Before(before.Add(-25 * time.Second)) { t.Errorf("expected updated_at to be >25s in the past, got %v (before=%v)", updated.UpdatedAt, before) } } func TestUploadTestData(t *testing.T) { env := NewTestEnv(t) content := []byte("hello testenv") fileID, blobID := env.UploadTestData("test.txt", content) if fileID == 0 { t.Fatal("fileID is 0") } if blobID == 0 { t.Fatal("blobID is 0") } ctx := t.Context() file, err := env.fileStore.GetByID(ctx, fileID) if err != nil { t.Fatalf("get file: %v", err) } if file == nil { t.Fatal("file not found") } if file.Name != "test.txt" { t.Errorf("expected name 'test.txt', got %q", file.Name) } blob, err := env.blobStore.GetBySHA256(ctx, file.BlobSHA256) if err != nil { t.Fatalf("get blob: %v", err) } if blob == nil { t.Fatal("blob not found") } if blob.FileSize != int64(len(content)) { t.Errorf("expected size %d, got %d", len(content), blob.FileSize) } obj, info, err := env.MockMinIO.GetObject(ctx, "files", blob.MinioKey, storage.GetOptions{}) if err != nil { t.Fatalf("get object: %v", err) } defer obj.Close() got, _ := io.ReadAll(obj) if string(got) != string(content) { t.Errorf("expected content %q, got %q", content, got) } if info.Size != int64(len(content)) { t.Errorf("expected object size %d, got %d", len(content), info.Size) } } func TestCreateApp(t *testing.T) { env := NewTestEnv(t) params := json.RawMessage(`[{"name":"cores","type":"integer","required":true}]`) appID, err := env.CreateApp("test-app", "#!/bin/bash\necho hello", params) if err != nil { t.Fatalf("CreateApp: %v", err) } if appID == 0 { t.Fatal("appID is 0") } ctx := t.Context() app, err := env.appStore.GetByID(ctx, appID) if err != nil { t.Fatalf("get app: %v", err) } if app == nil { t.Fatal("app not found") } if app.Name != "test-app" { t.Errorf("expected name 'test-app', got %q", app.Name) } } func TestDoRequest_SetsJSONContentType(t *testing.T) { env := NewTestEnv(t) resp := env.DoRequest("POST", "/api/v1/applications", nil) defer resp.Body.Close() if resp.StatusCode == 0 { t.Fatal("no status code") } } func TestDecodeResponse(t *testing.T) { env := NewTestEnv(t) resp := env.DoRequest("GET", "/api/v1/applications", nil) defer resp.Body.Close() success, data, err := env.DecodeResponse(resp) if err != nil { t.Fatalf("DecodeResponse: %v", err) } if !success { t.Error("expected success=true for list applications") } if data == nil { t.Error("expected non-nil data") } }