spaxel/mothership/internal/api/status_test.go
jedarden 47caab4568
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
feat(api): wire build-time version through StatusHandler (bf-iekf)
Replace the hardcoded "1.0.0" literal in GET /api/status with the
build-time version var. Add a version field to StatusHandler, extend
NewStatusHandler to accept it, and pass the existing main.version
ldflag var at the call site. Add table-driven handler tests.
2026-07-04 04:29:59 -04:00

129 lines
3.8 KiB
Go

// Package api provides tests for the system status API handler.
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/go-chi/chi/v5"
)
// statusResponse is the JSON shape returned by GET /api/status.
// Mirrors the map written by StatusHandler.getStatus.
type statusResponse struct {
Version string `json:"version"`
Nodes int `json:"nodes"`
Blobs int `json:"blobs"`
UptimeS int64 `json:"uptime_s"`
DetectionQuality int `json:"detection_quality"`
}
// newStatusRouter wires a StatusHandler into a chi router for handler-level tests.
func newStatusRouter(h *StatusHandler) http.Handler {
r := chi.NewRouter()
h.RegisterRoutes(r)
return r
}
// TestStatusHandlerVersionWired is the regression test for the hardcoded
// "1.0.0" version string (bf-5hz): the version passed to NewStatusHandler —
// which mirrors the build-time `-X main.version` ldflag injected in
// cmd/mothership/main.go — must be the value surfaced at GET /api/status, not
// a constant. Table-driven across representative version strings.
func TestStatusHandlerVersionWired(t *testing.T) {
tests := []struct {
name string
version string
}{
{"release_semver", "0.1.357"},
{"dev_build", "dev"},
{"dirty_tree", "0.1.358-dirty"},
{"empty_string", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := NewStatusHandler(time.Now(), func() int { return 0 }, tt.version)
server := newStatusRouter(h)
req := httptest.NewRequest("GET", "/api/status", nil)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", w.Code)
}
var resp statusResponse
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { //nolint:errcheck
t.Fatalf("failed to decode response: %v", err)
}
if resp.Version != tt.version {
t.Errorf("version = %q, want %q (must not be hardcoded \"1.0.0\")", resp.Version, tt.version)
}
})
}
}
// TestStatusHandlerFields verifies the non-version fields of GET /api/status:
// node count is sourced from the callback, uptime is non-negative, and a nil
// processor manager yields zero blobs and zero detection quality.
func TestStatusHandlerFields(t *testing.T) {
start := time.Now().Add(-30 * time.Second)
h := NewStatusHandler(start, func() int { return 4 }, "0.1.357")
server := newStatusRouter(h)
req := httptest.NewRequest("GET", "/api/status", nil)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", w.Code)
}
var resp statusResponse
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { //nolint:errcheck
t.Fatalf("failed to decode response: %v", err)
}
if resp.Nodes != 4 {
t.Errorf("nodes = %d, want 4", resp.Nodes)
}
if resp.Blobs != 0 {
t.Errorf("blobs = %d, want 0 (no processor manager)", resp.Blobs)
}
if resp.UptimeS < 0 {
t.Errorf("uptime_s = %d, want >= 0", resp.UptimeS)
}
if resp.DetectionQuality != 0 {
t.Errorf("detection_quality = %d, want 0 (no processor manager)", resp.DetectionQuality)
}
}
// TestStatusHandlerNilNodeCountCallback confirms a nil getNodeCount callback
// is tolerated and reports zero nodes rather than panicking.
func TestStatusHandlerNilNodeCountCallback(t *testing.T) {
h := NewStatusHandler(time.Now(), nil, "0.1.357")
server := newStatusRouter(h)
req := httptest.NewRequest("GET", "/api/status", nil)
w := httptest.NewRecorder()
server.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", w.Code)
}
var resp statusResponse
if err := json.NewDecoder(w.Body).Decode(&resp); err != nil { //nolint:errcheck
t.Fatalf("failed to decode response: %v", err)
}
if resp.Nodes != 0 {
t.Errorf("nodes = %d, want 0 for nil callback", resp.Nodes)
}
}