From 1ebf43355b35e476db04eb11f2fbd12d1596b757 Mon Sep 17 00:00:00 2001 From: dailz Date: Mon, 20 Apr 2026 10:39:16 +0800 Subject: [PATCH] test(server): add integration tests for task defaults and file_ids Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- cmd/server/integration_task_test.go | 267 +++++++++++++++++++++++++++- 1 file changed, 262 insertions(+), 5 deletions(-) diff --git a/cmd/server/integration_task_test.go b/cmd/server/integration_task_test.go index b0be16f..6346370 100644 --- a/cmd/server/integration_task_test.go +++ b/cmd/server/integration_task_test.go @@ -31,11 +31,38 @@ type taskListData struct { } type taskListItem struct { - ID int64 `json:"id"` - TaskName string `json:"task_name"` - AppID int64 `json:"app_id"` - Status string `json:"status"` - SlurmJobID *int32 `json:"slurm_job_id"` + ID int64 `json:"id"` + TaskName string `json:"task_name"` + AppID int64 `json:"app_id"` + Status string `json:"status"` + SlurmJobID *int32 `json:"slurm_job_id"` + Partition string `json:"partition,omitempty"` + Cpus *int32 `json:"cpus,omitempty"` + MemoryPerNode *int64 `json:"memory_per_node,omitempty"` + MemoryPerCpu *int64 `json:"memory_per_cpu,omitempty"` + TimeLimit *int32 `json:"time_limit,omitempty"` + QOS *string `json:"qos,omitempty"` + JobName *string `json:"job_name,omitempty"` + Nodes *string `json:"nodes,omitempty"` + Tasks *int32 `json:"tasks,omitempty"` + CpusPerTask *int32 `json:"cpus_per_task,omitempty"` + Constraints *string `json:"constraints,omitempty"` + Reservation *string `json:"reservation,omitempty"` + Account *string `json:"account,omitempty"` + Nice *int32 `json:"nice,omitempty"` + MailType *string `json:"mail_type,omitempty"` + MailUser *string `json:"mail_user,omitempty"` + StandardOutput *string `json:"standard_output,omitempty"` + StandardError *string `json:"standard_error,omitempty"` + StandardInput *string `json:"standard_input,omitempty"` + RequiredNodes *string `json:"required_nodes,omitempty"` + ExcludedNodes *string `json:"excluded_nodes,omitempty"` + BeginTime *int64 `json:"begin_time,omitempty"` + Deadline *int64 `json:"deadline,omitempty"` + Array *string `json:"array,omitempty"` + Dependency *string `json:"dependency,omitempty"` + Requeue *bool `json:"requeue,omitempty"` + KillOnNodeFail *bool `json:"kill_on_node_fail,omitempty"` } // taskSendReq sends an HTTP request via the test env and returns the response. @@ -259,3 +286,233 @@ func TestIntegration_Task_Validation(t *testing.T) { t.Error("expected non-empty error message") } } + +func TestIntegration_Task_WithSchedulingParams(t *testing.T) { + env := testenv.NewTestEnv(t) + + appID, err := env.CreateApp("sched-param-app", "#!/bin/bash\necho hello", nil) + if err != nil { + t.Fatalf("create app: %v", err) + } + + body := fmt.Sprintf(`{ + "app_id": %d, + "task_name": "sched-task", + "values": {}, + "file_ids": [], + "partition": "gpu", + "cpus": 16, + "memory_per_node": 32768, + "time_limit": 120 + }`, appID) + + resp := taskSendReq(t, env, http.MethodPost, "/api/v1/tasks", body) + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + b, _ := io.ReadAll(resp.Body) + t.Fatalf("expected 201, got %d: %s", resp.StatusCode, string(b)) + } + + parsed := taskParseResp(t, resp) + if !parsed.Success { + t.Fatalf("expected success=true, got error: %s", parsed.Error) + } + var data taskCreateData + if err := json.Unmarshal(parsed.Data, &data); err != nil { + t.Fatalf("unmarshal create data: %v", err) + } + if data.ID == 0 { + t.Fatal("expected non-zero task ID") + } +} + +func TestIntegration_Task_ListWithScheduling(t *testing.T) { + env := testenv.NewTestEnv(t) + + appID, err := env.CreateApp("sched-list-app", "#!/bin/bash\necho hello", nil) + if err != nil { + t.Fatalf("create app: %v", err) + } + + body := fmt.Sprintf(`{ + "app_id": %d, + "task_name": "sched-list-task", + "values": {}, + "file_ids": [], + "partition": "gpu", + "cpus": 16, + "memory_per_node": 32768, + "time_limit": 120 + }`, appID) + + createResp := taskSendReq(t, env, http.MethodPost, "/api/v1/tasks", body) + defer createResp.Body.Close() + + if createResp.StatusCode != http.StatusCreated { + b, _ := io.ReadAll(createResp.Body) + t.Fatalf("expected 201 creating task, got %d: %s", createResp.StatusCode, string(b)) + } + + createParsed := taskParseResp(t, createResp) + var createData taskCreateData + if err := json.Unmarshal(createParsed.Data, &createData); err != nil { + t.Fatalf("unmarshal create data: %v", err) + } + + time.Sleep(200 * time.Millisecond) + + listResp := taskSendReq(t, env, http.MethodGet, "/api/v1/tasks", "") + defer listResp.Body.Close() + + if listResp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(listResp.Body) + t.Fatalf("expected 200 listing tasks, got %d: %s", listResp.StatusCode, string(b)) + } + + listParsed := taskParseResp(t, listResp) + var listData taskListData + if err := json.Unmarshal(listParsed.Data, &listData); err != nil { + t.Fatalf("unmarshal list data: %v", err) + } + + var found *taskListItem + for i := range listData.Items { + if listData.Items[i].ID == createData.ID { + found = &listData.Items[i] + break + } + } + if found == nil { + t.Fatalf("task %d not found in list", createData.ID) + } + + if found.Partition != "gpu" { + t.Errorf("expected partition=gpu, got %q", found.Partition) + } + if found.Cpus == nil || *found.Cpus != 16 { + t.Errorf("expected cpus=16, got %v", found.Cpus) + } + if found.MemoryPerNode == nil || *found.MemoryPerNode != 32768 { + t.Errorf("expected memory_per_node=32768, got %v", found.MemoryPerNode) + } + if found.TimeLimit == nil || *found.TimeLimit != 120 { + t.Errorf("expected time_limit=120, got %v", found.TimeLimit) + } +} + +func TestIntegration_Task_PartialScheduling(t *testing.T) { + env := testenv.NewTestEnv(t) + + appID, err := env.CreateApp("partial-sched-app", "#!/bin/bash\necho hello", nil) + if err != nil { + t.Fatalf("create app: %v", err) + } + + body := fmt.Sprintf(`{ + "app_id": %d, + "task_name": "partial-sched-task", + "values": {}, + "file_ids": [], + "partition": "gpu", + "cpus": 8 + }`, appID) + + createResp := taskSendReq(t, env, http.MethodPost, "/api/v1/tasks", body) + defer createResp.Body.Close() + + if createResp.StatusCode != http.StatusCreated { + b, _ := io.ReadAll(createResp.Body) + t.Fatalf("expected 201, got %d: %s", createResp.StatusCode, string(b)) + } + + createParsed := taskParseResp(t, createResp) + var createData taskCreateData + if err := json.Unmarshal(createParsed.Data, &createData); err != nil { + t.Fatalf("unmarshal create data: %v", err) + } + + time.Sleep(200 * time.Millisecond) + + listResp := taskSendReq(t, env, http.MethodGet, "/api/v1/tasks", "") + defer listResp.Body.Close() + + listParsed := taskParseResp(t, listResp) + var listData taskListData + if err := json.Unmarshal(listParsed.Data, &listData); err != nil { + t.Fatalf("unmarshal list data: %v", err) + } + + var found *taskListItem + for i := range listData.Items { + if listData.Items[i].ID == createData.ID { + found = &listData.Items[i] + break + } + } + if found == nil { + t.Fatalf("task %d not found in list", createData.ID) + } + + if found.Partition != "gpu" { + t.Errorf("expected partition=gpu, got %q", found.Partition) + } + if found.Cpus == nil || *found.Cpus != 8 { + t.Errorf("expected cpus=8, got %v", found.Cpus) + } + if found.MemoryPerNode != nil { + t.Errorf("expected memory_per_node=nil, got %v", found.MemoryPerNode) + } + if found.TimeLimit != nil { + t.Errorf("expected time_limit=nil, got %v", found.TimeLimit) + } +} + +func TestIntegration_Task_BackwardCompat(t *testing.T) { + env := testenv.NewTestEnv(t) + + appID, err := env.CreateApp("compat-app", "#!/bin/bash\necho hello", nil) + if err != nil { + t.Fatalf("create app: %v", err) + } + + taskID := taskCreateViaAPI(t, env, appID, "compat-task") + + time.Sleep(200 * time.Millisecond) + + listResp := taskSendReq(t, env, http.MethodGet, "/api/v1/tasks", "") + defer listResp.Body.Close() + + listParsed := taskParseResp(t, listResp) + var listData taskListData + if err := json.Unmarshal(listParsed.Data, &listData); err != nil { + t.Fatalf("unmarshal list data: %v", err) + } + + var found *taskListItem + for i := range listData.Items { + if listData.Items[i].ID == taskID { + found = &listData.Items[i] + break + } + } + if found == nil { + t.Fatalf("task %d not found in list", taskID) + } + + if found.Partition != "" { + t.Errorf("expected empty partition, got %q", found.Partition) + } + if found.Cpus != nil { + t.Errorf("expected nil cpus, got %v", found.Cpus) + } + if found.MemoryPerNode != nil { + t.Errorf("expected nil memory_per_node, got %v", found.MemoryPerNode) + } + if found.TimeLimit != nil { + t.Errorf("expected nil time_limit, got %v", found.TimeLimit) + } + if found.QOS != nil { + t.Errorf("expected nil qos, got %v", found.QOS) + } +}