feat(api): wire build-time version through StatusHandler (bf-iekf)
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run
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.
This commit is contained in:
parent
cc24a1cf23
commit
47caab4568
3 changed files with 137 additions and 3 deletions
|
|
@ -4159,7 +4159,7 @@ func main() {
|
|||
log.Printf("[INFO] Tracks API registered at /api/tracks")
|
||||
|
||||
// System status and occupancy REST API
|
||||
statusHandler := api.NewStatusHandler(startupTotalStart, func() int { return len(ingestSrv.GetConnectedNodes()) })
|
||||
statusHandler := api.NewStatusHandler(startupTotalStart, func() int { return len(ingestSrv.GetConnectedNodes()) }, version)
|
||||
statusHandler.SetProcessorManager(pm)
|
||||
statusHandler.SetZonesManager(zonesMgr)
|
||||
statusHandler.RegisterRoutes(r)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ type StatusHandler struct {
|
|||
zonesMgr ZonesManagerProvider
|
||||
startTime time.Time
|
||||
getNodeCount func() int
|
||||
version string
|
||||
}
|
||||
|
||||
// ProcessorManagerProvider provides access to signal processor data.
|
||||
|
|
@ -33,10 +34,14 @@ type ZonesManagerProvider interface {
|
|||
}
|
||||
|
||||
// NewStatusHandler creates a new status handler.
|
||||
func NewStatusHandler(startTime time.Time, getNodeCount func() int) *StatusHandler {
|
||||
//
|
||||
// version is the build-time application version (injected via ldflags
|
||||
// `-X main.version` in cmd/mothership/main.go) surfaced via GET /api/status.
|
||||
func NewStatusHandler(startTime time.Time, getNodeCount func() int, version string) *StatusHandler {
|
||||
return &StatusHandler{
|
||||
startTime: startTime,
|
||||
getNodeCount: getNodeCount,
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +100,7 @@ func (h *StatusHandler) getStatus(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"version": "1.0.0", // TODO: get from build info
|
||||
"version": h.version,
|
||||
"nodes": nodes,
|
||||
"blobs": blobs,
|
||||
"uptime_s": uptime,
|
||||
|
|
|
|||
129
mothership/internal/api/status_test.go
Normal file
129
mothership/internal/api/status_test.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue