feat: 添加 Job 领域类型和 JobsService

约 93 个结构体覆盖 JobInfo、JobDescMsg、SlurmDBD Job/Step/QOS/Account/User 等类型。JobsService 提供 GetJobs、GetJob、PostJob、DeleteJob、SubmitJob 5 个方法,并定义 JobFlag* 和 JobSignalFlag* 常量。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
dailz
2026-04-08 18:29:07 +08:00
parent 5873dc5b72
commit 2c84930983
4 changed files with 2384 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
package slurm
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
func testMethod(t *testing.T, r *http.Request, expected string) {
t.Helper()
if r.Method != expected {
t.Errorf("expected method %s, got %s", expected, r.Method)
}
}
func TestJobsService_GetJobs(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"jobs": [], "last_backfill": {}, "last_update": {}}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Jobs.GetJobs(context.Background(), nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if len(resp.Jobs) != 0 {
t.Errorf("expected empty jobs, got %d", len(resp.Jobs))
}
}
func TestJobsService_GetJobs_WithOptions(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
q := r.URL.Query()
if q.Get("update_time") != "12345" {
t.Errorf("expected update_time=12345, got %s", q.Get("update_time"))
}
if q.Get("flags") != JobFlagDetail {
t.Errorf("expected flags=%s, got %s", JobFlagDetail, q.Get("flags"))
}
fmt.Fprint(w, `{"jobs": [], "last_update": {}}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
opts := &GetJobsOptions{
UpdateTime: Ptr("12345"),
Flags: Ptr(JobFlagDetail),
}
resp, _, err := client.Jobs.GetJobs(context.Background(), opts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestJobsService_GetJob(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{"jobs": [{"job_id": 42, "name": "test-job"}], "last_update": {}}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Jobs.GetJob(context.Background(), "42", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if len(resp.Jobs) != 1 {
t.Fatalf("expected 1 job, got %d", len(resp.Jobs))
}
if resp.Jobs[0].JobID == nil || *resp.Jobs[0].JobID != 42 {
t.Errorf("expected job_id=42, got %v", resp.Jobs[0].JobID)
}
}
func TestJobsService_PostJob(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
t.Errorf("expected Content-Type application/json, got %s", ct)
}
fmt.Fprint(w, `{"job_id": "42", "step_id": "0", "results": []}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
desc := &JobDescMsg{
Name: Ptr("updated-job"),
}
resp, _, err := client.Jobs.PostJob(context.Background(), "42", desc)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.JobID == nil || *resp.JobID != "42" {
t.Errorf("expected job_id=42, got %v", resp.JobID)
}
}
func TestJobsService_DeleteJob(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
q := r.URL.Query()
if q.Get("signal") != "SIGTERM" {
t.Errorf("expected signal=SIGTERM, got %s", q.Get("signal"))
}
fmt.Fprint(w, `{}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
opts := &DeleteJobOptions{
Signal: Ptr("SIGTERM"),
}
resp, _, err := client.Jobs.DeleteJob(context.Background(), "42", opts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestJobsService_SubmitJob(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/submit", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
t.Errorf("expected Content-Type application/json, got %s", ct)
}
fmt.Fprint(w, `{"job_id": 100, "step_id": "0", "result": {"job_id": 100, "step_id": "0"}}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
reqBody := &JobSubmitReq{
Script: Ptr("#!/bin/bash\necho hello"),
Job: &JobDescMsg{
Name: Ptr("test-submit"),
},
}
resp, _, err := client.Jobs.SubmitJob(context.Background(), reqBody)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.JobID == nil || *resp.JobID != 100 {
t.Errorf("expected job_id=100, got %v", resp.JobID)
}
}
func TestJobsService_GetJobs_Error(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, `{"errors": [{"error": "internal error"}]}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
_, _, err := client.Jobs.GetJobs(context.Background(), nil)
if err == nil {
t.Fatal("expected error for 500 response")
}
if !strings.Contains(err.Error(), "500") {
t.Errorf("expected error to contain 500, got %v", err)
}
}
func TestJobsService_GetJob_Error(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/999", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `{"errors": [{"error": "job not found"}]}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
_, _, err := client.Jobs.GetJob(context.Background(), "999", nil)
if err == nil {
t.Fatal("expected error for 404 response")
}
}
func TestJobsService_DeleteJob_NoOptions(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
q := r.URL.Query()
if len(q) != 0 {
t.Errorf("expected no query params, got %v", q)
}
fmt.Fprint(w, `{}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Jobs.DeleteJob(context.Background(), "42", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestJobsService_GetJobPathEscaping(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/job/", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
parts := strings.Split(r.URL.Path, "/")
jobID := parts[len(parts)-1]
if jobID != "123" {
t.Errorf("expected job ID 123, got %s", jobID)
}
fmt.Fprint(w, `{"jobs": [{"job_id": 123}]}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
resp, _, err := client.Jobs.GetJob(context.Background(), "123", nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp == nil {
t.Fatal("expected non-nil response")
}
}
func TestJobsService_GetJobsWithURLQuery(t *testing.T) {
var capturedQuery url.Values
mux := http.NewServeMux()
mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) {
capturedQuery = r.URL.Query()
testMethod(t, r, "GET")
fmt.Fprint(w, `{"jobs": []}`)
})
server := httptest.NewServer(mux)
defer server.Close()
client, _ := NewClient(server.URL, nil)
opts := &GetJobsOptions{
UpdateTime: Ptr("1700000000"),
}
_, _, err := client.Jobs.GetJobs(context.Background(), opts)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if capturedQuery.Get("update_time") != "1700000000" {
t.Errorf("expected update_time=1700000000, got %s", capturedQuery.Get("update_time"))
}
}