forgejo-runner-optimiser/internal/receiver/handler_test.go

198 lines
5.1 KiB
Go
Raw Normal View History

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 []MetricResponse
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 []MetricResponse
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() }
}