RecoverStuckTasks now skips tasks that already have a slurm_job_id, and ProcessTask adds a guard before the submitting step to prevent re-submission even if a task is incorrectly re-enqueued. Also deprecates POST /api/v1/jobs/submit endpoint (replaced by POST /tasks) and comments out related handlers and tests.
225 lines
6.2 KiB
Go
225 lines
6.2 KiB
Go
package main
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"net/http"
|
||
"testing"
|
||
|
||
"gcy_hpc_server/internal/testutil/testenv"
|
||
)
|
||
|
||
// jobItemData mirrors the JobResponse DTO for a single job.
|
||
type jobItemData struct {
|
||
JobID int32 `json:"job_id"`
|
||
Name string `json:"name"`
|
||
State []string `json:"job_state"`
|
||
Partition string `json:"partition"`
|
||
}
|
||
|
||
// jobListData mirrors the paginated JobListResponse DTO.
|
||
type jobListData struct {
|
||
Jobs []jobItemData `json:"jobs"`
|
||
Total int `json:"total"`
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
}
|
||
|
||
// jobCancelData mirrors the cancel response message.
|
||
type jobCancelData struct {
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// jobDecodeAll decodes the response and returns status, success, and raw data.
|
||
func jobDecodeAll(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
|
||
}
|
||
|
||
// jobSubmitBody builds a JSON body for job submit requests.
|
||
func jobSubmitBody(script string) *bytes.Reader {
|
||
body, _ := json.Marshal(map[string]string{"script": script})
|
||
return bytes.NewReader(body)
|
||
}
|
||
|
||
// jobSubmitViaAPI submits a job and returns the job ID. Fatals on failure.
|
||
func jobSubmitViaAPI(t *testing.T, env *testenv.TestEnv, script string) int32 {
|
||
t.Helper()
|
||
resp := env.DoRequest(http.MethodPost, "/api/v1/jobs/submit", jobSubmitBody(script))
|
||
status, success, data, err := jobDecodeAll(env, resp)
|
||
if err != nil {
|
||
t.Fatalf("submit job decode: %v", err)
|
||
}
|
||
if status != http.StatusCreated {
|
||
t.Fatalf("expected status 201, got %d", status)
|
||
}
|
||
if !success {
|
||
t.Fatal("expected success=true on submit")
|
||
}
|
||
|
||
var job jobItemData
|
||
if err := json.Unmarshal(data, &job); err != nil {
|
||
t.Fatalf("unmarshal submitted job: %v", err)
|
||
}
|
||
return job.JobID
|
||
}
|
||
|
||
// [已弃用] 以下测试依赖 POST /api/v1/jobs/submit,该接口已被 POST /tasks 取代。
|
||
/*
|
||
// TestIntegration_Jobs_Submit verifies POST /api/v1/jobs/submit creates a new job.
|
||
func TestIntegration_Jobs_Submit(t *testing.T) {
|
||
env := testenv.NewTestEnv(t)
|
||
|
||
script := "#!/bin/bash\necho hello"
|
||
resp := env.DoRequest(http.MethodPost, "/api/v1/jobs/submit", jobSubmitBody(script))
|
||
status, success, data, err := jobDecodeAll(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 job jobItemData
|
||
if err := json.Unmarshal(data, &job); err != nil {
|
||
t.Fatalf("unmarshal job: %v", err)
|
||
}
|
||
if job.JobID <= 0 {
|
||
t.Fatalf("expected positive job_id, got %d", job.JobID)
|
||
}
|
||
}
|
||
|
||
// TestIntegration_Jobs_List verifies GET /api/v1/jobs returns a paginated job list.
|
||
func TestIntegration_Jobs_List(t *testing.T) {
|
||
env := testenv.NewTestEnv(t)
|
||
|
||
// Submit a job so the list is not empty.
|
||
jobSubmitViaAPI(t, env, "#!/bin/bash\necho list-test")
|
||
|
||
resp := env.DoRequest(http.MethodGet, "/api/v1/jobs", nil)
|
||
status, success, data, err := jobDecodeAll(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 list jobListData
|
||
if err := json.Unmarshal(data, &list); err != nil {
|
||
t.Fatalf("unmarshal job list: %v", err)
|
||
}
|
||
if list.Total < 1 {
|
||
t.Fatalf("expected at least 1 job, got total=%d", list.Total)
|
||
}
|
||
if list.Page != 1 {
|
||
t.Fatalf("expected page=1, got %d", list.Page)
|
||
}
|
||
}
|
||
|
||
// TestIntegration_Jobs_Get verifies GET /api/v1/jobs/:id returns a single job.
|
||
func TestIntegration_Jobs_Get(t *testing.T) {
|
||
env := testenv.NewTestEnv(t)
|
||
|
||
jobID := jobSubmitViaAPI(t, env, "#!/bin/bash\necho get-test")
|
||
|
||
path := fmt.Sprintf("/api/v1/jobs/%d", jobID)
|
||
resp := env.DoRequest(http.MethodGet, path, nil)
|
||
status, success, data, err := jobDecodeAll(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 job jobItemData
|
||
if err := json.Unmarshal(data, &job); err != nil {
|
||
t.Fatalf("unmarshal job: %v", err)
|
||
}
|
||
if job.JobID != jobID {
|
||
t.Fatalf("expected job_id=%d, got %d", jobID, job.JobID)
|
||
}
|
||
}
|
||
|
||
// TestIntegration_Jobs_Cancel verifies DELETE /api/v1/jobs/:id cancels a job.
|
||
func TestIntegration_Jobs_Cancel(t *testing.T) {
|
||
env := testenv.NewTestEnv(t)
|
||
|
||
jobID := jobSubmitViaAPI(t, env, "#!/bin/bash\necho cancel-test")
|
||
|
||
path := fmt.Sprintf("/api/v1/jobs/%d", jobID)
|
||
resp := env.DoRequest(http.MethodDelete, path, nil)
|
||
status, success, data, err := jobDecodeAll(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 jobCancelData
|
||
if err := json.Unmarshal(data, &msg); err != nil {
|
||
t.Fatalf("unmarshal cancel response: %v", err)
|
||
}
|
||
if msg.Message == "" {
|
||
t.Fatal("expected non-empty cancel message")
|
||
}
|
||
}
|
||
|
||
// TestIntegration_Jobs_History verifies GET /api/v1/jobs/history returns historical jobs.
|
||
func TestIntegration_Jobs_History(t *testing.T) {
|
||
env := testenv.NewTestEnv(t)
|
||
|
||
// Submit and cancel a job so it moves from active to history queue.
|
||
jobID := jobSubmitViaAPI(t, env, "#!/bin/bash\necho history-test")
|
||
path := fmt.Sprintf("/api/v1/jobs/%d", jobID)
|
||
env.DoRequest(http.MethodDelete, path, nil)
|
||
|
||
resp := env.DoRequest(http.MethodGet, "/api/v1/jobs/history", nil)
|
||
status, success, data, err := jobDecodeAll(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 list jobListData
|
||
if err := json.Unmarshal(data, &list); err != nil {
|
||
t.Fatalf("unmarshal history: %v", err)
|
||
}
|
||
if list.Total < 1 {
|
||
t.Fatalf("expected at least 1 history job, got total=%d", list.Total)
|
||
}
|
||
|
||
// Verify the cancelled job appears in history.
|
||
found := false
|
||
for _, j := range list.Jobs {
|
||
if j.JobID == jobID {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
t.Fatalf("cancelled job %d not found in history", jobID)
|
||
}
|
||
}
|
||
*/
|