package api import ( "strings" "testing" "time" ) func stepsByName(steps []CheckStep) map[string]CheckStep { out := map[string]CheckStep{} for _, s := range steps { out[s.Name] = s } return out } func healthySite() *SiteHealth { now := time.Now().UTC() return &SiteHealth{ SiteID: siteA, Name: "Chennai", Online: true, LastHeartbeatAt: now.Add(-20 * time.Second).Format(time.RFC3339), LastEventAt: now.Add(-3 * time.Minute).Format(time.RFC3339), RecognitionModel: "w600k_r50.onnx", CamerasUp: 2, CamerasTotal: 2, FractionBelowGate: 0.11, } } func connectedCameras(n int) []Camera { up := true out := make([]Camera, n) for i := range out { out[i] = Camera{ID: "c", SiteID: siteA, CameraID: "entrance", Connected: &up} } return out } func TestAHealthySitePassesEveryStep(t *testing.T) { steps := BuildSiteSteps(healthySite(), connectedCameras(2), time.Now().UTC()) for _, s := range steps { if s.Status != "pass" { t.Errorf("%q: %s — %s", s.Name, s.Status, s.Detail) } } } // The point of the whole check: a site whose PC is off cannot be judged on // anything else, and printing guesses next to the real failure buries it. func TestAnOfflinePCStopsTheRestBeingJudged(t *testing.T) { site := healthySite() site.Online = false site.LastHeartbeatAt = time.Now().Add(-3 * time.Hour).UTC().Format(time.RFC3339) steps := BuildSiteSteps(site, connectedCameras(2), time.Now().UTC()) by := stepsByName(steps) if by["The shop's PC is online"].Status != "fail" { t.Fatal("an offline PC was not reported as a failure") } for _, name := range []string{"Cameras are connected", "Cameras can recognise faces", "Visits are reaching head office"} { if got := by[name].Status; got != "unknown" { t.Errorf("%q reported %q on an offline PC - it cannot be known", name, got) } } } // A PC that has never reported needs installing, not restarting. Different // sentence, different action. func TestAPCThatHasNeverReportedIsToldToInstall(t *testing.T) { site := healthySite() site.Online, site.LastHeartbeatAt = false, "" by := stepsByName(BuildSiteSteps(site, nil, time.Now().UTC())) advice := by["The shop's PC is online"].Advice if !strings.Contains(advice, "enrolment code") { t.Fatalf("advice does not say how to claim the PC: %q", advice) } } // The Office1 case, and the reason this check exists at all: everything is // plugged in, everything is online, and almost nobody is being recognised. func TestASiteWhereMostVisitorsAreMissedFailsEvenThoughEverythingIsOnline(t *testing.T) { site := healthySite() site.FractionBelowGate = 0.727 // the number measured on Office1 steps := BuildSiteSteps(site, connectedCameras(2), time.Now().UTC()) by := stepsByName(steps) if by["The shop's PC is online"].Status != "pass" { t.Error("the PC is fine and should say so") } faces := by["Cameras can recognise faces"] if faces.Status != "fail" { t.Fatalf("73%% of visitors missed was reported as %q", faces.Status) } if !strings.Contains(faces.Detail, "73%") { t.Errorf("the number is not shown: %q", faces.Detail) } if !strings.Contains(faces.Advice, "moving") { t.Errorf("advice does not say what to do: %q", faces.Advice) } // And the site as a whole must not read as working. ok := true for _, s := range steps { if s.Status != "pass" { ok = false } } if ok { t.Fatal("a site missing 73% of its visitors was reported as working") } } // A shop set up before opening has seen nobody. That is not a fault, and // calling it one sends an installer looking for a problem that is not there. func TestAShopThatHasSeenNobodyYetIsUnknownNotBroken(t *testing.T) { site := healthySite() site.LastEventAt = "" site.FractionBelowGate = 0 by := stepsByName(BuildSiteSteps(site, connectedCameras(1), time.Now().UTC())) if got := by["Cameras can recognise faces"].Status; got != "unknown" { t.Errorf("a new shop reported %q, want unknown", got) } if got := by["Visits are reaching head office"].Status; got != "unknown" { t.Errorf("no visits yet reported %q, want unknown", got) } // But it must still tell them how to prove it before opening. if !strings.Contains(by["Cameras can recognise faces"].Advice, "walk-past") { t.Error("no advice on how to prove the camera before the shop opens") } } // Lost footfall can never be recovered, so it has to be visible rather than // inferred from a report that is quietly short. func TestDroppedVisitsAreAFailureAndSayTheyCannotBeRecovered(t *testing.T) { site := healthySite() site.Dropped = 412 by := stepsByName(BuildSiteSteps(site, connectedCameras(1), time.Now().UTC())) send := by["Visits are reaching head office"] if send.Status != "fail" { t.Fatalf("lost visits reported as %q", send.Status) } if !strings.Contains(send.Detail, "412") { t.Errorf("the count is not shown: %q", send.Detail) } if !strings.Contains(send.Advice, "cannot be recovered") { t.Errorf("advice implies they might come back: %q", send.Advice) } } // Recording but not sending is its own state: the shop is working, head office // is blind, and the two look identical on a footfall report. func TestABackedUpQueueIsAWarningNotAFailure(t *testing.T) { site := healthySite() site.Queued = 340 by := stepsByName(BuildSiteSteps(site, connectedCameras(1), time.Now().UTC())) if got := by["Visits are reaching head office"].Status; got != "warn" { t.Fatalf("a backed-up queue reported as %q", got) } } func TestASiteWithNoCamerasSaysToAddOne(t *testing.T) { by := stepsByName(BuildSiteSteps(healthySite(), nil, time.Now().UTC())) cam := by["Cameras are connected"] if cam.Status != "fail" { t.Fatalf("no cameras reported as %q", cam.Status) } if !strings.Contains(cam.Advice, "Add a camera") { t.Errorf("advice: %q", cam.Advice) } } // A camera nobody has tried is not a camera that is down. func TestCamerasNotYetTriedAreAWarningNotAFailure(t *testing.T) { cams := []Camera{{ID: "c1", SiteID: siteA, CameraID: "entrance"}} // Connected nil by := stepsByName(BuildSiteSteps(healthySite(), cams, time.Now().UTC())) cam := by["Cameras are connected"] if cam.Status != "warn" { t.Fatalf("an untried camera reported as %q, want warn", cam.Status) } if !strings.Contains(cam.Detail, "none tried yet") { t.Errorf("detail: %q", cam.Detail) } } func TestADownCameraFails(t *testing.T) { down := false up := true cams := []Camera{ {ID: "c1", SiteID: siteA, CameraID: "entrance", Connected: &up}, {ID: "c2", SiteID: siteA, CameraID: "till", Connected: &down}, } by := stepsByName(BuildSiteSteps(healthySite(), cams, time.Now().UTC())) if got := by["Cameras are connected"].Status; got != "fail" { t.Fatalf("a down camera reported as %q", got) } } // A running process is not a working engine: on a memory-starved box the large // model loses the fallback chain and the process stays up regardless. func TestAnEngineThatHasNotSaidWhichModelItLoadedIsAWarning(t *testing.T) { site := healthySite() site.RecognitionModel = "" by := stepsByName(BuildSiteSteps(site, connectedCameras(1), time.Now().UTC())) if got := by["Recognition is running"].Status; got != "warn" { t.Fatalf("reported %q", got) } } func TestHumanAgoReadsLikeAPersonWouldSayIt(t *testing.T) { now := time.Date(2026, 9, 2, 12, 0, 0, 0, time.UTC) for _, tc := range []struct { ago time.Duration want string }{ {10 * time.Second, "just now"}, {20 * time.Minute, "20 min ago"}, {5 * time.Hour, "5 h ago"}, {80 * time.Hour, "3 days ago"}, } { got := humanAgo(now.Add(-tc.ago).Format(time.RFC3339), now) if got != tc.want { t.Errorf("%s ago -> %q, want %q", tc.ago, got, tc.want) } } if got := humanAgo("not a time", now); got != "at an unknown time" { t.Errorf("unparseable -> %q", got) } }