package sandbar import ( "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "time" ) // Every check lands in a per-day count that outlives the raw checks, and the status page // gets one [checks, failed] pair per day, oldest first and ending today. func TestUptimeBars(t *testing.T) { s, clk, _ := newTestServer(t) m := &Monitor{ID: "m", Name: "Site", Kind: "http", Target: "http://x", Enabled: true, Public: true, Retries: 1} s.st.put("monitor", m.ID, m) // Two days ago: 4 checks, 1 failed. Yesterday: nothing. Today: 2 checks, both fine. clk.Advance(-48 * time.Hour) for i := 0; i < 4; i++ { var err error if i == 3 { err = errors.New("timeout") } s.recordResult(m, s.monitorState(m.ID), clk.Now(), 10*time.Millisecond, err) } clk.Advance(48 * time.Hour) s.recordResult(m, s.monitorState(m.ID), clk.Now(), 10*time.Millisecond, nil) s.recordResult(m, s.monitorState(m.ID), clk.Now(), 10*time.Millisecond, nil) days := s.dailyUptime(m.ID, 5, clk.Now()) want := [][2]int{{0, 0}, {0, 0}, {4, 1}, {0, 0}, {2, 0}} for i := range want { if days[i] != want[i] { t.Fatalf("days = %v, want %v", days, want) } } // The rollup survives pruning the raw checks. s.st.prune(clk.Now().AddDate(0, 0, 60), 30, 1) if got := s.dailyUptime(m.ID, 5, clk.Now()); got[2] != [2]int{4, 1} { t.Fatalf("rollup lost after pruning: %v", got) } ts := httptest.NewServer(s.Handler()) defer ts.Close() resp, err := http.Get(ts.URL + "/status.json") if err != nil { t.Fatal(err) } defer resp.Body.Close() var out struct { Today string Monitors []struct { Uptime float64 Days [][2]int } } json.NewDecoder(resp.Body).Decode(&out) if out.Today != "2026-01-05" || len(out.Monitors) != 1 || len(out.Monitors[0].Days) != uptimeDays { t.Fatalf("status.json: %+v", out) } m0 := out.Monitors[0] if last := m0.Days[uptimeDays-1]; last != [2]int{2, 0} { t.Fatalf("today's bar: %v", last) } if want := 100 * 5.0 / 6.0; m0.Uptime < want-0.01 || m0.Uptime > want+0.01 { t.Fatalf("uptime %.2f, want %.2f", m0.Uptime, want) } } // A database from before the daily table gets it built from the checks it has. func TestDailyBackfill(t *testing.T) { s, clk, _ := newTestServer(t) now := clk.Now().Unix() s.st.db.Exec(`INSERT INTO checks(monitor_id,time,ok,ms,detail) VALUES('m',?,1,5,''),('m',?,0,5,'x')`, now, now+1) s.st.db.Exec(`DELETE FROM daily`) var have int s.st.db.QueryRow(`SELECT count(*) FROM daily`).Scan(&have) if have != 0 { t.Fatal("setup") } s.st.backfillDaily() if got := s.dailyUptime("m", 1, clk.Now()); got[0] != [2]int{2, 1} { t.Fatalf("backfill: %v", got) } }