Files
backrest/internal/api/summarydashboard_test.go
2026-07-08 17:24:37 -07:00

120 lines
4.5 KiB
Go

package api
import (
"testing"
"time"
)
func TestSummaryOverdueFlags(t *testing.T) {
// Fixed UTC reference: "today" is Jun 30 2026; the window is the prior 9 days.
day := func(n int) time.Time { return time.Date(2026, 6, 21, 0, 0, 0, 0, time.UTC).AddDate(0, 0, n) }
at := func(n int, hour int) time.Time { return day(n).Add(time.Duration(hour) * time.Hour) }
now := at(9, 12) // noon on the last day
tests := []struct {
name string
lastOkBackupBeforeWindow time.Time
okBackupDates []time.Time
allowedGap time.Duration
expected []int // indices of days expected to be flagged overdue
}{
{
name: "no backups ever means no expectation",
okBackupDates: nil,
allowedGap: 24 * time.Hour,
expected: nil,
},
{
name: "daily cadence kept",
okBackupDates: []time.Time{at(0, 1), at(1, 1), at(2, 1), at(3, 1), at(4, 1), at(5, 1), at(6, 1), at(7, 1), at(8, 1), at(9, 1)},
allowedGap: 30 * time.Hour, // 24h * 1.25 grace
expected: nil,
},
{
name: "daily cadence with one skipped day",
okBackupDates: []time.Time{at(0, 1), at(1, 1), at(2, 1), at(4, 1), at(5, 1), at(6, 1), at(7, 1), at(8, 1), at(9, 1)},
allowedGap: 30 * time.Hour,
// At day3's close the day2 01:00 backup is 47h old; day4 recovers with a
// backup of its own (and renders as backed-up, not overdue).
expected: []int{3},
},
{
name: "weekly cadence never overdue within window",
okBackupDates: []time.Time{at(1, 2), at(8, 2)},
allowedGap: 8 * 24 * time.Hour,
expected: nil,
},
{
name: "stalled plan flags tail days",
okBackupDates: []time.Time{at(0, 1), at(1, 1)},
allowedGap: 30 * time.Hour,
// At day2's close the day1 01:00 backup is 47h old, and it only ages from there.
expected: []int{2, 3, 4, 5, 6, 7, 8, 9},
},
{
name: "pre-window staleness resolved in window stays muted",
lastOkBackupBeforeWindow: day(-5),
okBackupDates: []time.Time{at(3, 1), at(5, 1), at(7, 1), at(9, 1)},
allowedGap: 48 * time.Hour,
// The stale day(-5) anchor is ignored because the window has its own good
// backups: the overdue stretch ended before day3 and is resolved history.
expected: nil,
},
{
name: "fully stalled plan flags every day",
lastOkBackupBeforeWindow: day(-5),
okBackupDates: nil,
allowedGap: 48 * time.Hour,
// No good backup in the window: the anchor drives staleness, and every
// day closes more than 48h past it.
expected: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
},
{
name: "sub-daily schedule with a backup each day is never overdue",
okBackupDates: []time.Time{at(0, 2), at(1, 2), at(2, 2), at(3, 2), at(4, 2), at(5, 2), at(6, 2), at(7, 2), at(8, 2), at(9, 2)},
allowedGap: 75 * time.Minute, // hourly cadence * 1.25 grace
// Every day has an OK backup at 02:00. Its close (midnight) is ~22h past
// that, far beyond the gap, but a day that backed up is never overdue.
expected: nil,
},
{
name: "sub-daily schedule flags a fully missed day",
okBackupDates: []time.Time{at(0, 2), at(1, 2), at(3, 2), at(4, 2), at(5, 2), at(6, 2), at(7, 2), at(8, 2), at(9, 2)}, // day2 missing
allowedGap: 75 * time.Minute,
// Day 2 has no OK backup of its own and closes far past the allowed gap;
// day 3 recovers with its own backup and stays backed-up.
expected: []int{2},
},
{
name: "gap exactly at allowed staleness is not overdue",
okBackupDates: []time.Time{at(0, 0), at(2, 0), at(4, 0), at(6, 0), at(8, 0)},
allowedGap: 48 * time.Hour,
// Every day's close lands at most exactly on a deadline, never past one.
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
acc := newSummaryAcc(day(0))
acc.reachedCutoff = true // span the full window from day 0
acc.okBackupDates = tt.okBackupDates
acc.lastOkBackupBeforeWindow = tt.lastOkBackupBeforeWindow
history := acc.finalize("test", now, tt.allowedGap).HistoryLast_30Days
if len(history) != 10 {
t.Fatalf("expected 10 day buckets, got %d", len(history))
}
expectedSet := make(map[int]bool)
for _, i := range tt.expected {
expectedSet[i] = true
}
for i, bucket := range history {
if bucket.Overdue != expectedSet[i] {
t.Errorf("day %d: overdue = %v, want %v", i, bucket.Overdue, expectedSet[i])
}
}
})
}
}