package api_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" "time" "github.com/go-chi/chi/v5" "vetting/internal/api" "vetting/internal/db" "vetting/internal/model" "vetting/internal/store" ) // setupHeartbeat wires just enough of UI to exercise the heartbeat // handler. Runner is left nil — the handler no-ops the SSE publish in // that case, which matches "tests don't assert on SSE" (covered by // integration-style runner tests). func setupHeartbeat(t *testing.T) (*api.UI, *store.Hosts) { t.Helper() conn, err := db.Open(filepath.Join(t.TempDir(), "vetting.db")) if err != nil { t.Fatalf("open db: %v", err) } t.Cleanup(func() { _ = conn.Close() }) hosts := &store.Hosts{DB: conn} return &api.UI{Hosts: hosts}, hosts } func heartbeatReq(mac string) *http.Request { req := httptest.NewRequest(http.MethodPost, "/api/v1/hosts/"+mac+"/heartbeat", nil) rctx := chi.NewRouteContext() rctx.URLParams.Add("mac", mac) return req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) } func TestUIHeartbeat_Success(t *testing.T) { ui, hosts := setupHeartbeat(t) id, err := hosts.Create(context.Background(), model.Host{ Name: "hb-host", MAC: "aa:bb:cc:dd:ee:10", WoLBroadcastIP: "10.0.0.255", WoLPort: 9, ExpectedSpecYAML: "memory:\n total_gib: 16\n", }) if err != nil { t.Fatalf("create: %v", err) } before := time.Now().UTC().Add(-time.Second) rr := httptest.NewRecorder() ui.Heartbeat(rr, heartbeatReq("aa:bb:cc:dd:ee:10")) if rr.Code != http.StatusOK { t.Fatalf("status = %d, body = %q", rr.Code, rr.Body.String()) } var resp map[string]any if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if resp["ok"] != true { t.Fatalf("response = %v, want ok:true", resp) } got, err := hosts.Get(context.Background(), id) if err != nil { t.Fatalf("Get: %v", err) } if got.LastSeenAt == nil { t.Fatalf("LastSeenAt not stamped") } if got.LastSeenAt.Before(before) { t.Fatalf("LastSeenAt = %v, want >= %v", got.LastSeenAt, before) } } func TestUIHeartbeat_UnknownMAC(t *testing.T) { ui, _ := setupHeartbeat(t) rr := httptest.NewRecorder() ui.Heartbeat(rr, heartbeatReq("aa:bb:cc:dd:ee:ff")) if rr.Code != http.StatusNotFound { t.Fatalf("status = %d, want 404", rr.Code) } var resp map[string]string _ = json.NewDecoder(rr.Body).Decode(&resp) if resp["error"] == "" { t.Fatalf("missing error body") } } func TestUIHeartbeat_BadMAC(t *testing.T) { ui, _ := setupHeartbeat(t) rr := httptest.NewRecorder() ui.Heartbeat(rr, heartbeatReq("not-a-mac")) if rr.Code != http.StatusBadRequest { t.Fatalf("status = %d, want 400", rr.Code) } }