All checks were successful
ci / build (push) Successful in 32s
Replace /api/v1/metrics/run/{runID} and /api/v1/metrics/repo/{org}/{repo}
with /api/v1/metrics/repo/{org}/{repo}/{workflow}/{job} for more precise
filtering by workflow and job name.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
197 lines
5.1 KiB
Go
197 lines
5.1 KiB
Go
package receiver
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"edp.buildth.ing/DevFW-CICD/forgejo-runner-resource-collector/internal/summary"
|
|
)
|
|
|
|
func TestHandler_ReceiveMetrics(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
payload := MetricsPayload{
|
|
Execution: ExecutionContext{
|
|
Organization: "test-org",
|
|
Repository: "test-repo",
|
|
Workflow: "ci.yml",
|
|
Job: "build",
|
|
RunID: "run-123",
|
|
},
|
|
Summary: summary.RunSummary{
|
|
DurationSeconds: 60.0,
|
|
SampleCount: 12,
|
|
},
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/metrics", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusCreated {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusCreated)
|
|
}
|
|
|
|
var resp map[string]any
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["status"] != "created" {
|
|
t.Errorf("response status = %v, want %q", resp["status"], "created")
|
|
}
|
|
if resp["id"] == nil || resp["id"].(float64) == 0 {
|
|
t.Error("response id is missing or zero")
|
|
}
|
|
}
|
|
|
|
func TestHandler_ReceiveMetrics_InvalidJSON(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/metrics", bytes.NewReader([]byte("not json")))
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
func TestHandler_ReceiveMetrics_MissingRunID(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
payload := MetricsPayload{
|
|
Execution: ExecutionContext{
|
|
Organization: "test-org",
|
|
Repository: "test-repo",
|
|
// RunID is missing
|
|
},
|
|
Summary: summary.RunSummary{},
|
|
}
|
|
|
|
body, _ := json.Marshal(payload)
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/metrics", bytes.NewReader(body))
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusBadRequest {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
func TestHandler_GetByWorkflowJob(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
// Save metrics for different workflow/job combinations
|
|
payloads := []*MetricsPayload{
|
|
{Execution: ExecutionContext{Organization: "org-x", Repository: "repo-y", Workflow: "ci.yml", Job: "build", RunID: "r1"}},
|
|
{Execution: ExecutionContext{Organization: "org-x", Repository: "repo-y", Workflow: "ci.yml", Job: "build", RunID: "r2"}},
|
|
{Execution: ExecutionContext{Organization: "org-x", Repository: "repo-y", Workflow: "ci.yml", Job: "test", RunID: "r3"}},
|
|
}
|
|
for _, p := range payloads {
|
|
if _, err := h.store.SaveMetric(p); err != nil {
|
|
t.Fatalf("SaveMetric() error = %v", err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/metrics/repo/org-x/repo-y/ci.yml/build", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
var metrics []Metric
|
|
if err := json.NewDecoder(rec.Body).Decode(&metrics); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if len(metrics) != 2 {
|
|
t.Errorf("got %d metrics, want 2", len(metrics))
|
|
}
|
|
}
|
|
|
|
func TestHandler_GetByWorkflowJob_NotFound(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/metrics/repo/org/repo/workflow/job", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
var metrics []Metric
|
|
if err := json.NewDecoder(rec.Body).Decode(&metrics); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if len(metrics) != 0 {
|
|
t.Errorf("got %d metrics, want 0", len(metrics))
|
|
}
|
|
}
|
|
|
|
func TestHandler_Health(t *testing.T) {
|
|
h, cleanup := newTestHandler(t)
|
|
defer cleanup()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
|
rec := httptest.NewRecorder()
|
|
|
|
mux := http.NewServeMux()
|
|
h.RegisterRoutes(mux)
|
|
mux.ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
|
|
var resp map[string]string
|
|
if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
if resp["status"] != "ok" {
|
|
t.Errorf("status = %q, want %q", resp["status"], "ok")
|
|
}
|
|
}
|
|
|
|
func newTestHandler(t *testing.T) (*Handler, func()) {
|
|
t.Helper()
|
|
dbPath := filepath.Join(t.TempDir(), "test.db")
|
|
store, err := NewStore(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("NewStore() error = %v", err)
|
|
}
|
|
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
handler := NewHandler(store, logger)
|
|
|
|
return handler, func() { _ = store.Close() }
|
|
}
|