diff options
Diffstat (limited to 'internal/handlers/budget_logic_test.go')
| -rw-r--r-- | internal/handlers/budget_logic_test.go | 61 |
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) |
