package handler import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "gcy_hpc_server/internal/service" "gcy_hpc_server/internal/slurm" "github.com/gin-gonic/gin" "go.uber.org/zap" "go.uber.org/zap/zapcore" "go.uber.org/zap/zaptest/observer" ) func setupJobRouter(h *JobHandler) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() v1 := r.Group("/api/v1") jobs := v1.Group("/jobs") { jobs.POST("/submit", h.SubmitJob) jobs.GET("", h.GetJobs) jobs.GET("/history", h.GetJobHistory) jobs.GET("/:id", h.GetJob) jobs.DELETE("/:id", h.CancelJob) } return r } func setupJobHandler(mux *http.ServeMux) (*httptest.Server, *JobHandler) { srv := httptest.NewServer(mux) client, _ := slurm.NewClient(srv.URL, srv.Client()) jobSvc := service.NewJobService(client, zap.NewNop()) return srv, NewJobHandler(jobSvc, zap.NewNop()) } func setupJobHandlerWithObserver(mux *http.ServeMux) (*httptest.Server, *JobHandler, *observer.ObservedLogs) { core, recorded := observer.New(zapcore.DebugLevel) l := zap.New(core) srv := httptest.NewServer(mux) client, _ := slurm.NewClient(srv.URL, srv.Client()) jobSvc := service.NewJobService(client, l) return srv, NewJobHandler(jobSvc, l), recorded } func handlerLogs(logs *observer.ObservedLogs) []observer.LoggedEntry { var handler []observer.LoggedEntry for _, e := range logs.All() { for _, f := range e.Context { if f.Key == "method" { handler = append(handler, e) break } } } return handler } func TestSubmitJob_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/submit", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobSubmitResponse{ Result: &slurm.JobSubmitResponseMsg{JobID: slurm.Ptr(int32(123))}, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) body := `{"script":"#!/bin/bash\necho hello"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if !resp["success"].(bool) { t.Fatal("expected success=true") } data := resp["data"].(map[string]interface{}) if int(data["job_id"].(float64)) != 123 { t.Errorf("expected job_id=123, got %v", data["job_id"]) } } func TestSubmitJob_MissingScript(t *testing.T) { mux := http.NewServeMux() srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) body := `{"partition":"normal"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if resp["success"].(bool) { t.Fatal("expected success=false") } if resp["error"] != "invalid request body" && resp["error"] != "script is required" { t.Errorf("expected validation error, got %v", resp["error"]) } } func TestSubmitJob_EmptyScript(t *testing.T) { mux := http.NewServeMux() srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) body := `{"script":""}` req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if resp["success"].(bool) { t.Fatal("expected success=false") } } func TestSubmitJob_SlurmError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/submit", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, `{"errors":[{"error":"internal error"}]}`) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) body := `{"script":"#!/bin/bash\necho hello"}` req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("expected 502, got %d: %s", w.Code, w.Body.String()) } } func TestGetJobs_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{ Jobs: []slurm.JobInfo{ {JobID: slurm.Ptr(int32(1)), Name: slurm.Ptr("job1")}, {JobID: slurm.Ptr(int32(2)), Name: slurm.Ptr("job2")}, }, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs?page=1&page_size=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if !resp["success"].(bool) { t.Fatal("expected success=true") } data := resp["data"].(map[string]interface{}) jobs := data["jobs"].([]interface{}) if len(jobs) != 2 { t.Fatalf("expected 2 jobs, got %d", len(jobs)) } if int(data["total"].(float64)) != 2 { t.Errorf("expected total=2, got %v", data["total"]) } if int(data["page"].(float64)) != 1 { t.Errorf("expected page=1, got %v", data["page"]) } if int(data["page_size"].(float64)) != 10 { t.Errorf("expected page_size=10, got %v", data["page_size"]) } } func TestGetJobs_Pagination(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{ Jobs: []slurm.JobInfo{ {JobID: slurm.Ptr(int32(1)), Name: slurm.Ptr("job1")}, {JobID: slurm.Ptr(int32(2)), Name: slurm.Ptr("job2")}, {JobID: slurm.Ptr(int32(3)), Name: slurm.Ptr("job3")}, }, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs?page=2&page_size=1", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) jobs := data["jobs"].([]interface{}) if len(jobs) != 1 { t.Fatalf("expected 1 job on page 2, got %d", len(jobs)) } if int(data["total"].(float64)) != 3 { t.Errorf("expected total=3, got %v", data["total"]) } jobData := jobs[0].(map[string]interface{}) if int(jobData["job_id"].(float64)) != 2 { t.Errorf("expected job_id=2 on page 2, got %v", jobData["job_id"]) } } func TestGetJobs_DefaultPagination(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{Jobs: []slurm.JobInfo{}}) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) if int(data["page"].(float64)) != 1 { t.Errorf("expected default page=1, got %v", data["page"]) } if int(data["page_size"].(float64)) != 20 { t.Errorf("expected default page_size=20, got %v", data["page_size"]) } } func TestGetJob_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{ Jobs: []slurm.JobInfo{ {JobID: slurm.Ptr(int32(42)), Name: slurm.Ptr("test-job")}, }, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/42", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) if int(data["job_id"].(float64)) != 42 { t.Errorf("expected job_id=42, got %v", data["job_id"]) } } func TestGetJob_NotFound(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/999", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{ Jobs: []slurm.JobInfo{}, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/999", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if resp["error"].(string) != "job not found" { t.Errorf("expected 'job not found' error, got %v", resp["error"]) } } func TestCancelJob_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiResp{}) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodDelete, "/api/v1/jobs/42", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) if !resp["success"].(bool) { t.Fatal("expected success=true") } data := resp["data"].(map[string]interface{}) if data["message"].(string) != "job cancelled" { t.Errorf("expected 'job cancelled', got %v", data["message"]) } } func TestGetJobHistory_Success(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurmdb/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiSlurmdbdJobsResp{ Jobs: []slurm.Job{ {JobID: slurm.Ptr(int32(1)), Name: slurm.Ptr("hist1")}, {JobID: slurm.Ptr(int32(2)), Name: slurm.Ptr("hist2")}, {JobID: slurm.Ptr(int32(3)), Name: slurm.Ptr("hist3")}, }, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/history?page=1&page_size=2", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) jobs := data["jobs"].([]interface{}) if len(jobs) != 2 { t.Fatalf("expected 2 jobs on page 1, got %d", len(jobs)) } if int(data["total"].(float64)) != 3 { t.Errorf("expected total=3, got %v", data["total"]) } if int(data["page"].(float64)) != 1 { t.Errorf("expected page=1, got %v", data["page"]) } if int(data["page_size"].(float64)) != 2 { t.Errorf("expected page_size=2, got %v", data["page_size"]) } } func TestGetJobHistory_DefaultPagination(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurmdb/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiSlurmdbdJobsResp{ Jobs: []slurm.Job{ {JobID: slurm.Ptr(int32(1)), Name: slurm.Ptr("h1")}, }, }) }) srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/history", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) } var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) data := resp["data"].(map[string]interface{}) if int(data["page"].(float64)) != 1 { t.Errorf("expected default page=1, got %v", data["page"]) } if int(data["page_size"].(float64)) != 20 { t.Errorf("expected default page_size=20, got %v", data["page_size"]) } } func TestSubmitJob_InvalidBody(t *testing.T) { mux := http.NewServeMux() srv, handler := setupJobHandler(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(`not json`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } } // --- Logging verification tests --- func TestSubmitJob_InvalidBody_LogsWarn(t *testing.T) { mux := http.NewServeMux() srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(`not json`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.WarnLevel { t.Errorf("expected Warn level, got %v", entry.Level) } if entry.Context[0].Key != "method" || entry.Context[0].String != "SubmitJob" { t.Errorf("expected method=SubmitJob, got %v", entry.Context[0]) } } func TestSubmitJob_EmptyScript_LogsWarn(t *testing.T) { mux := http.NewServeMux() srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(`{"script":""}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.WarnLevel { t.Errorf("expected Warn level, got %v", entry.Level) } } func TestSubmitJob_SlurmError_LogsError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/submit", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, `{"errors":[{"error":"internal error"}]}`) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(`{"script":"#!/bin/bash\necho hello"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("expected 502, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.ErrorLevel { t.Errorf("expected Error level, got %v", entry.Level) } foundMethod := false foundStatus := false for _, f := range entry.Context { if f.Key == "method" && f.String == "SubmitJob" { foundMethod = true } if f.Key == "status" && f.Integer == http.StatusBadGateway { foundStatus = true } } if !foundMethod { t.Error("expected method=SubmitJob in log fields") } if !foundStatus { t.Error("expected status=502 in log fields") } } func TestSubmitJob_Success_NoHandlerLogs(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/submit", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobSubmitResponse{ Result: &slurm.JobSubmitResponseMsg{JobID: slurm.Ptr(int32(123))}, }) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodPost, "/api/v1/jobs/submit", bytes.NewBufferString(`{"script":"#!/bin/bash\necho hello"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusCreated { t.Fatalf("expected 201, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 0 { t.Errorf("expected no handler log entries on success, got %d", len(hLogs)) } } func TestGetJobs_Error_LogsError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusInternalServerError { t.Fatalf("expected 500, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.ErrorLevel { t.Errorf("expected Error level, got %v", entry.Level) } foundMethod := false for _, f := range entry.Context { if f.Key == "method" && f.String == "GetJobs" { foundMethod = true } } if !foundMethod { t.Error("expected method=GetJobs in log fields") } } func TestGetJob_NotFound_LogsWarn(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/999", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{Jobs: []slurm.JobInfo{}}) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/999", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.WarnLevel { t.Errorf("expected Warn level, got %v", entry.Level) } foundMethod := false for _, f := range entry.Context { if f.Key == "method" && f.String == "GetJob" { foundMethod = true } } if !foundMethod { t.Error("expected method=GetJob in log fields") } } func TestGetJob_Error_LogsError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/42", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusInternalServerError { t.Fatalf("expected 500, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } if hLogs[0].Level != zapcore.ErrorLevel { t.Errorf("expected Error level, got %v", hLogs[0].Level) } } func TestCancelJob_SlurmError_LogsError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodDelete, "/api/v1/jobs/42", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadGateway { t.Fatalf("expected 502, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.ErrorLevel { t.Errorf("expected Error level, got %v", entry.Level) } foundMethod := false for _, f := range entry.Context { if f.Key == "method" && f.String == "CancelJob" { foundMethod = true } } if !foundMethod { t.Error("expected method=CancelJob in log fields") } } func TestGetJobHistory_InvalidQuery_LogsWarn(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurmdb/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiSlurmdbdJobsResp{}) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/history?page=abc", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusBadRequest { t.Fatalf("expected 400, got %d: %s", w.Code, w.Body.String()) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } entry := hLogs[0] if entry.Level != zapcore.WarnLevel { t.Errorf("expected Warn level, got %v", entry.Level) } foundMethod := false for _, f := range entry.Context { if f.Key == "method" && f.String == "GetJobHistory" { foundMethod = true } } if !foundMethod { t.Error("expected method=GetJobHistory in log fields") } } func TestGetJobHistory_Error_LogsError(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurmdb/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/history?page=1&page_size=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusInternalServerError { t.Fatalf("expected 500, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 1 { t.Fatalf("expected 1 handler log entry, got %d", len(hLogs)) } if hLogs[0].Level != zapcore.ErrorLevel { t.Errorf("expected Error level, got %v", hLogs[0].Level) } } func TestGetJobHistory_Success_NoHandlerLogs(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurmdb/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiSlurmdbdJobsResp{ Jobs: []slurm.Job{ {JobID: slurm.Ptr(int32(1)), Name: slurm.Ptr("h1")}, }, }) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs/history?page=1&page_size=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 0 { t.Errorf("expected no handler log entries on success, got %d", len(hLogs)) } } func TestGetJobs_Success_NoHandlerLogs(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/jobs", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiJobInfoResp{Jobs: []slurm.JobInfo{}}) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodGet, "/api/v1/jobs", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 0 { t.Errorf("expected no handler log entries on success, got %d", len(hLogs)) } } func TestCancelJob_Success_NoHandlerLogs(t *testing.T) { mux := http.NewServeMux() mux.HandleFunc("/slurm/v0.0.40/job/42", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(slurm.OpenapiResp{}) }) srv, handler, logs := setupJobHandlerWithObserver(mux) defer srv.Close() router := setupJobRouter(handler) req := httptest.NewRequest(http.MethodDelete, "/api/v1/jobs/42", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("expected 200, got %d", w.Code) } hLogs := handlerLogs(logs) if len(hLogs) != 0 { t.Errorf("expected no handler log entries on success, got %d", len(hLogs)) } }