From 62330537108a542ee2f75e3f33363a65bd12b53c Mon Sep 17 00:00:00 2001 From: Ashish Patel Date: Mon, 25 May 2026 15:01:11 +0530 Subject: [PATCH] test: cover date-range logic and wire go test into CI Add table tests for model.FromDates/FromMonth/Preset boundaries and collect.anchorMidnight, including a regression guard for the date-skew bug fixed in #2. Run 'go test -race ./...' in CI (previously only gofmt/vet/build). --- .github/workflows/ci.yml | 1 + internal/collect/git_test.go | 25 ++++++++ internal/model/range_test.go | 121 +++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 internal/collect/git_test.go create mode 100644 internal/model/range_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fef82e5..d09062c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,3 +16,4 @@ jobs: - run: gofmt -l cmd internal | tee /dev/stderr | (! read) - run: go vet ./... - run: go build ./... + - run: go test -race ./... diff --git a/internal/collect/git_test.go b/internal/collect/git_test.go new file mode 100644 index 0000000..40073ed --- /dev/null +++ b/internal/collect/git_test.go @@ -0,0 +1,25 @@ +package collect + +import "testing" + +func TestAnchorMidnight(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {"bare ISO date gets midnight", "2026-05-24", "2026-05-24 00:00:00"}, + {"month rollover date", "2026-02-01", "2026-02-01 00:00:00"}, + {"relative string untouched", "7 days ago", "7 days ago"}, + {"yesterday keyword untouched", "yesterday", "yesterday"}, + {"empty untouched", "", ""}, + {"date with time untouched", "2026-05-24 12:00:00", "2026-05-24 12:00:00"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := anchorMidnight(tt.in); got != tt.want { + t.Errorf("anchorMidnight(%q) = %q, want %q", tt.in, got, tt.want) + } + }) + } +} diff --git a/internal/model/range_test.go b/internal/model/range_test.go new file mode 100644 index 0000000..2cddb49 --- /dev/null +++ b/internal/model/range_test.go @@ -0,0 +1,121 @@ +package model + +import ( + "testing" + "time" +) + +func TestFromDates(t *testing.T) { + tests := []struct { + name string + from, to string + wantSince string + wantUntil string + wantLabel string + wantErr bool + }{ + { + name: "single day makes until exclusive next day", + from: "2026-05-24", to: "2026-05-24", + wantSince: "2026-05-24", wantUntil: "2026-05-25", wantLabel: "2026-05-24", + }, + { + name: "multi-day range", + from: "2026-05-01", to: "2026-05-03", + wantSince: "2026-05-01", wantUntil: "2026-05-04", wantLabel: "2026-05-01 → 2026-05-03", + }, + { + name: "until rolls over month boundary", + from: "2026-01-31", to: "2026-01-31", + wantSince: "2026-01-31", wantUntil: "2026-02-01", wantLabel: "2026-01-31", + }, + {name: "bad from", from: "nope", to: "2026-05-24", wantErr: true}, + {name: "bad to", from: "2026-05-24", to: "nope", wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r, err := FromDates(tt.from, tt.to) + if tt.wantErr { + if err == nil { + t.Fatalf("expected error, got %+v", r) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if r.Since != tt.wantSince || r.Until != tt.wantUntil || r.Label != tt.wantLabel { + t.Errorf("got {Since:%q Until:%q Label:%q}, want {Since:%q Until:%q Label:%q}", + r.Since, r.Until, r.Label, tt.wantSince, tt.wantUntil, tt.wantLabel) + } + }) + } +} + +func TestFromDatesEmptyToDefaultsToday(t *testing.T) { + r, err := FromDates("2026-05-01", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + wantUntil := time.Now().AddDate(0, 0, 1).Format(isoDate) + if r.Until != wantUntil { + t.Errorf("Until = %q, want %q (today + 1, exclusive)", r.Until, wantUntil) + } +} + +func TestFromMonth(t *testing.T) { + r, err := FromMonth("2026-02") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if r.Since != "2026-02-01" || r.Until != "2026-03-01" { + t.Errorf("got {Since:%q Until:%q}, want {2026-02-01 2026-03-01}", r.Since, r.Until) + } + if r.Label != "February 2026" { + t.Errorf("Label = %q, want %q", r.Label, "February 2026") + } + if _, err := FromMonth("bad"); err == nil { + t.Error("expected error for bad month") + } +} + +// TestPresetBoundaries guards the regression behind the date-skew bug: each +// preset must produce a half-open [Since, Until) window aligned to whole days, +// independent of the current time of day. +func TestPresetBoundaries(t *testing.T) { + now := time.Now() + today := now.Format(isoDate) + yesterday := now.AddDate(0, 0, -1).Format(isoDate) + tomorrow := now.AddDate(0, 0, 1).Format(isoDate) + + tests := []struct { + choice string + wantSince string + wantUntil string + }{ + {"Today", today, tomorrow}, + {"Yesterday", yesterday, today}, + {"Last 7 days", now.AddDate(0, 0, -6).Format(isoDate), tomorrow}, + {"Last 30 days", now.AddDate(0, 0, -29).Format(isoDate), tomorrow}, + } + for _, tt := range tests { + t.Run(tt.choice, func(t *testing.T) { + r := Preset(tt.choice) + if r.Since != tt.wantSince { + t.Errorf("Since = %q, want %q", r.Since, tt.wantSince) + } + if r.Until != tt.wantUntil { + t.Errorf("Until = %q, want %q", r.Until, tt.wantUntil) + } + }) + } +} + +func TestPresetUnknownFallsBackToWeek(t *testing.T) { + r := Preset("not a preset") + want := Preset("Last 7 days") + if r.Since != want.Since || r.Until != want.Until { + t.Errorf("unknown preset = {%q,%q}, want last-7-days {%q,%q}", + r.Since, r.Until, want.Since, want.Until) + } +}