summaryrefslogtreecommitdiff
path: root/internal/handlers/budget_logic_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/budget_logic_test.go')
-rw-r--r--internal/handlers/budget_logic_test.go61
1 files changed, 61 insertions, 0 deletions
diff --git a/internal/handlers/budget_logic_test.go b/internal/handlers/budget_logic_test.go
index 1e16595..dad8079 100644
--- a/internal/handlers/budget_logic_test.go
+++ b/internal/handlers/budget_logic_test.go
@@ -74,6 +74,67 @@ func TestComputeBudgetPeriod_AvailableNeverGoesNegative(t *testing.T) {
}
}
+func TestComputeBudgetPeriod_OverlappingEventsUnionNotSummed(t *testing.T) {
+ loc := time.UTC
+ // A Wednesday: 2026-07-15 is a Wednesday.
+ day := mustParseInLoc(t, "2006-01-02", "2026-07-15", loc)
+ start := day
+ end := day.AddDate(0, 0, 1)
+
+ blocks := []models.AvailabilityBlock{
+ {ID: "b1", Weekday: int(day.Weekday()), StartTime: "18:00", EndTime: "20:00"}, // 120 min
+ }
+ events := []models.CalendarEvent{
+ // Event A: 18:00-19:00 (60 min overlap with block)
+ {ID: "e1", Start: mustParseInLoc(t, "2006-01-02 15:04", "2026-07-15 18:00", loc), End: mustParseInLoc(t, "2006-01-02 15:04", "2026-07-15 19:00", loc)},
+ // Event B: 18:30-19:30 (60 min overlap with block), overlapping event A from 18:30-19:00.
+ {ID: "e2", Start: mustParseInLoc(t, "2006-01-02 15:04", "2026-07-15 18:30", loc), End: mustParseInLoc(t, "2006-01-02 15:04", "2026-07-15 19:30", loc)},
+ }
+
+ // Union of busy time is 18:00-19:30 = 90 min, so available should be
+ // 120 - 90 = 30. A sum-based (rather than union-based) implementation
+ // would incorrectly compute 120 - 60 - 60 = 0.
+ status := ComputeBudgetPeriod(blocks, events, nil, nil, nil, start, end)
+ if status.AvailableMinutes != 30 {
+ t.Errorf("AvailableMinutes = %d, want 30 (120 - 90 union of overlapping events, not 120 - 60 - 60)", status.AvailableMinutes)
+ }
+}
+
+func TestComputeBudgetPeriod_MultiDayWindowSumsPerMatchingWeekdayAndExcludesEnd(t *testing.T) {
+ loc := time.UTC
+ // 2026-07-13 is a Monday, 2026-07-14 Tuesday, 2026-07-15 Wednesday,
+ // 2026-07-16 Thursday, 2026-07-17 Friday.
+ start := mustParseInLoc(t, "2006-01-02", "2026-07-13", loc) // Monday
+ end := mustParseInLoc(t, "2006-01-02", "2026-07-17", loc) // Friday (exclusive)
+
+ monday := int(time.Monday)
+ wednesday := int(time.Wednesday)
+ thursday := int(time.Thursday)
+ friday := int(time.Friday)
+
+ blocks := []models.AvailabilityBlock{
+ // Occurs once, on 07-13 (Monday) -- 60 min.
+ {ID: "mon", Weekday: monday, StartTime: "09:00", EndTime: "10:00"},
+ // Occurs once, on 07-15 (Wednesday) -- 90 min.
+ {ID: "wed", Weekday: wednesday, StartTime: "08:00", EndTime: "09:30"},
+ // Occurs once, on 07-16 (Thursday) -- 60 min.
+ {ID: "thu", Weekday: thursday, StartTime: "12:00", EndTime: "13:00"},
+ // Weekday matches `end` (07-17, Friday) itself, which is one day
+ // PAST the last iterated day (07-16). If the loop incorrectly
+ // iterated through (or including) `end`, this block would wrongly
+ // contribute 60 more minutes.
+ {ID: "fri-at-end", Weekday: friday, StartTime: "09:00", EndTime: "10:00"},
+ }
+
+ // No events, so no overlap to subtract.
+ status := ComputeBudgetPeriod(blocks, nil, nil, nil, nil, start, end)
+
+ want := 60 + 90 + 60 // Monday + Wednesday + Thursday; Friday-at-end excluded.
+ if status.AvailableMinutes != want {
+ t.Errorf("AvailableMinutes = %d, want %d (60 Mon + 90 Wed + 60 Thu; the Friday block at `end` must not be counted)", status.AvailableMinutes, want)
+ }
+}
+
func TestComputeBudgetPeriod_OnlySumsTrackedIncompleteTasksDueInWindow(t *testing.T) {
loc := time.UTC
start := mustParseInLoc(t, "2006-01-02", "2026-07-15", loc)