mirror of
https://github.com/hanzoai/types.git
synced 2026-10-01 00:45:52 +00:00
A value belongs here when two or more services must agree on it exactly and neither should inherit the other's machinery to do so. The package imports the standard library only; go list -deps is 63, all stdlib. Seeded with the metered-series window. Cloud and ai must resolve ?range/?start/ ?end to the same [start, end) and the same bucket interval, and cloud reached that 30-line rule through a package that carries 1925 dependencies. Interval is a closed type. The bucket function is not bindable in the engines that serve these series, so the interval reaches a query by interpolation; ParseWindow yields Hour or Day and nothing else can be constructed through it. ParseWindow is total: it returns a window or an error, never a zero window a caller could read as a valid range. It also carries the resolved label, which every call site previously re-derived. Co-authored-by: Hanzo Dev <[email protected]>
290 lines
9.4 KiB
Go
290 lines
9.4 KiB
Go
package types
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
var now = time.Date(2026, 7, 26, 15, 4, 5, 0, time.UTC)
|
|
|
|
func TestParseWindowRelativeLabels(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
label string
|
|
back time.Duration
|
|
want Interval
|
|
}{
|
|
{"", 24 * time.Hour, Hour},
|
|
{"24h", 24 * time.Hour, Hour},
|
|
{"1d", 24 * time.Hour, Hour},
|
|
{"day", 24 * time.Hour, Hour},
|
|
{"today", 24 * time.Hour, Hour},
|
|
{"7d", 7 * 24 * time.Hour, Day},
|
|
{"week", 7 * 24 * time.Hour, Day},
|
|
{"30d", 30 * 24 * time.Hour, Day},
|
|
{"month", 30 * 24 * time.Hour, Day},
|
|
{" 7D ", 7 * 24 * time.Hour, Day},
|
|
} {
|
|
t.Run(strings.TrimSpace(tc.label), func(t *testing.T) {
|
|
w, err := ParseWindow(tc.label, "", "", now)
|
|
if err != nil {
|
|
t.Fatalf("ParseWindow(%q) errored: %v", tc.label, err)
|
|
}
|
|
if !w.End.Equal(now) {
|
|
t.Errorf("End = %v, want %v", w.End, now)
|
|
}
|
|
if !w.Start.Equal(now.Add(-tc.back)) {
|
|
t.Errorf("Start = %v, want %v", w.Start, now.Add(-tc.back))
|
|
}
|
|
if w.Interval != tc.want {
|
|
t.Errorf("Interval = %q, want %q", w.Interval, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The label a response echoes is the caller's, defaulted when absent. Every
|
|
// caller used to re-derive this after the call; the window now carries it.
|
|
func TestParseWindowLabelIsDefaultedNotCanonicalised(t *testing.T) {
|
|
for _, tc := range []struct{ in, want string }{
|
|
{"", "24h"},
|
|
{" ", "24h"},
|
|
{"7d", "7d"},
|
|
{" 30d ", "30d"},
|
|
{"7D", "7D"},
|
|
{"custom", "custom"},
|
|
} {
|
|
in := tc.in
|
|
start := ""
|
|
if strings.TrimSpace(in) == "custom" {
|
|
start = "2026-07-01T00:00:00Z"
|
|
}
|
|
w, err := ParseWindow(in, start, "", now)
|
|
if err != nil {
|
|
t.Fatalf("ParseWindow(%q) errored: %v", in, err)
|
|
}
|
|
if w.Label != tc.want {
|
|
t.Errorf("ParseWindow(%q).Label = %q, want %q", in, w.Label, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseWindowCustom(t *testing.T) {
|
|
t.Run("rfc3339 under 48h buckets hourly", func(t *testing.T) {
|
|
w, err := ParseWindow("custom", "2026-07-25T00:00:00Z", "2026-07-26T00:00:00Z", now)
|
|
if err != nil {
|
|
t.Fatalf("errored: %v", err)
|
|
}
|
|
if w.Interval != Hour {
|
|
t.Errorf("Interval = %q, want %q", w.Interval, Hour)
|
|
}
|
|
if !w.Start.Equal(time.Date(2026, 7, 25, 0, 0, 0, 0, time.UTC)) {
|
|
t.Errorf("Start = %v", w.Start)
|
|
}
|
|
})
|
|
|
|
t.Run("over 48h buckets daily", func(t *testing.T) {
|
|
w, err := ParseWindow("custom", "2026-07-01T00:00:00Z", "2026-07-26T00:00:00Z", now)
|
|
if err != nil {
|
|
t.Fatalf("errored: %v", err)
|
|
}
|
|
if w.Interval != Day {
|
|
t.Errorf("Interval = %q, want %q", w.Interval, Day)
|
|
}
|
|
})
|
|
|
|
t.Run("exactly 48h stays hourly", func(t *testing.T) {
|
|
w, err := ParseWindow("custom", "2026-07-24T00:00:00Z", "2026-07-26T00:00:00Z", now)
|
|
if err != nil {
|
|
t.Fatalf("errored: %v", err)
|
|
}
|
|
if w.Interval != Hour {
|
|
t.Errorf("Interval = %q, want %q", w.Interval, Hour)
|
|
}
|
|
})
|
|
|
|
t.Run("unix seconds accepted", func(t *testing.T) {
|
|
w, err := ParseWindow("custom", "1753401600", "1753488000", now)
|
|
if err != nil {
|
|
t.Fatalf("errored: %v", err)
|
|
}
|
|
if w.Start.Location() != time.UTC || w.End.Location() != time.UTC {
|
|
t.Errorf("bounds must be UTC, got %v/%v", w.Start.Location(), w.End.Location())
|
|
}
|
|
})
|
|
|
|
t.Run("absent end defaults to now", func(t *testing.T) {
|
|
w, err := ParseWindow("custom", "2026-07-25T00:00:00Z", "", now)
|
|
if err != nil {
|
|
t.Fatalf("errored: %v", err)
|
|
}
|
|
if !w.End.Equal(now) {
|
|
t.Errorf("End = %v, want %v", w.End, now)
|
|
}
|
|
})
|
|
}
|
|
|
|
// A rejected window must not hand back a zero Window a caller could mistake for
|
|
// a valid range.
|
|
func TestParseWindowRejects(t *testing.T) {
|
|
for _, tc := range []struct{ name, label, start, end, wantErr string }{
|
|
{"unknown label", "yesterday", "", "", `unknown range "yesterday"`},
|
|
{"unknown label keeps caller casing", "LastWeek", "", "", `unknown range "LastWeek"`},
|
|
{"custom without start", "custom", "", "", "custom range requires a valid start: empty time"},
|
|
{"custom bad start", "custom", "nonsense", "", `custom range requires a valid start: not RFC3339 or unix seconds: "nonsense"`},
|
|
{"custom bad end", "custom", "2026-07-01T00:00:00Z", "nope", `custom range has an invalid end: not RFC3339 or unix seconds: "nope"`},
|
|
{"custom end before start", "custom", "2026-07-26T00:00:00Z", "2026-07-01T00:00:00Z", "custom range end must be after start"},
|
|
{"custom end equals start", "custom", "2026-07-01T00:00:00Z", "2026-07-01T00:00:00Z", "custom range end must be after start"},
|
|
{"custom non-positive unix start", "custom", "0", "", `custom range requires a valid start: not RFC3339 or unix seconds: "0"`},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, err := ParseWindow(tc.label, tc.start, tc.end, now)
|
|
if err == nil {
|
|
t.Fatalf("want error, got window %+v", w)
|
|
}
|
|
if err.Error() != tc.wantErr {
|
|
t.Errorf("error = %q, want %q", err.Error(), tc.wantErr)
|
|
}
|
|
if w != (Window{}) {
|
|
t.Errorf("rejected window must be zero, got %+v", w)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntervalStep(t *testing.T) {
|
|
if got := Hour.Step(); got != time.Hour {
|
|
t.Errorf("Hour.Step() = %v, want %v", got, time.Hour)
|
|
}
|
|
if got := Day.Step(); got != 24*time.Hour {
|
|
t.Errorf("Day.Step() = %v, want %v", got, 24*time.Hour)
|
|
}
|
|
}
|
|
|
|
// ParseWindow only ever yields Hour or Day, so a caller interpolating the
|
|
// interval into a query has no third case to guard against.
|
|
func TestParseWindowOnlyYieldsTheClosedSet(t *testing.T) {
|
|
labels := []string{"", "24h", "1d", "day", "today", "7d", "week", "30d", "month", "custom"}
|
|
for _, l := range labels {
|
|
start := ""
|
|
if l == "custom" {
|
|
start = "2026-07-01T00:00:00Z"
|
|
}
|
|
w, err := ParseWindow(l, start, "", now)
|
|
if err != nil {
|
|
t.Fatalf("ParseWindow(%q) errored: %v", l, err)
|
|
}
|
|
if w.Interval != Hour && w.Interval != Day {
|
|
t.Fatalf("ParseWindow(%q) yielded interval %q outside {hour, day}", l, w.Interval)
|
|
}
|
|
}
|
|
}
|
|
|
|
// resolveLegacy is the implementation this package replaced, kept verbatim as
|
|
// the oracle the parity test compares against.
|
|
func resolveLegacy(rangeLabel, startStr, endStr string, now time.Time) (start, end time.Time, interval string, err error) {
|
|
parseParam := func(s string) (time.Time, error) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
return time.Time{}, fmt.Errorf("empty time")
|
|
}
|
|
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
|
return t.UTC(), nil
|
|
}
|
|
if secs, err := strconv.ParseInt(s, 10, 64); err == nil && secs > 0 {
|
|
return time.Unix(secs, 0).UTC(), nil
|
|
}
|
|
return time.Time{}, fmt.Errorf("not RFC3339 or unix seconds: %q", s)
|
|
}
|
|
|
|
now = now.UTC()
|
|
switch strings.ToLower(strings.TrimSpace(rangeLabel)) {
|
|
case "", "24h", "1d", "day", "today":
|
|
return now.Add(-24 * time.Hour), now, "hour", nil
|
|
case "7d", "week":
|
|
return now.Add(-7 * 24 * time.Hour), now, "day", nil
|
|
case "30d", "month":
|
|
return now.Add(-30 * 24 * time.Hour), now, "day", nil
|
|
case "custom":
|
|
start, err = parseParam(startStr)
|
|
if err != nil {
|
|
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range requires a valid start: %w", err)
|
|
}
|
|
if strings.TrimSpace(endStr) == "" {
|
|
end = now
|
|
} else if end, err = parseParam(endStr); err != nil {
|
|
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range has an invalid end: %w", err)
|
|
}
|
|
if !end.After(start) {
|
|
return time.Time{}, time.Time{}, "", fmt.Errorf("custom range end must be after start")
|
|
}
|
|
interval = "hour"
|
|
if end.Sub(start) > 48*time.Hour {
|
|
interval = "day"
|
|
}
|
|
return start.UTC(), end.UTC(), interval, nil
|
|
default:
|
|
return time.Time{}, time.Time{}, "", fmt.Errorf("unknown range %q", rangeLabel)
|
|
}
|
|
}
|
|
|
|
// Bounds, interval and error text must match the replaced implementation
|
|
// exactly: these values reach a live dashboard and a 400 body.
|
|
func TestParseWindowMatchesTheReplacedImplementation(t *testing.T) {
|
|
labels := []string{
|
|
"", " ", "24h", "1d", "day", "today", "7d", "week", "30d", "month",
|
|
"24H", " 7d ", "custom", "CUSTOM", "yesterday", "1h", "all",
|
|
}
|
|
bounds := []string{
|
|
"", " ", "2026-07-01T00:00:00Z", "2026-07-25T12:00:00Z", "2026-07-26T00:00:00Z",
|
|
"1753401600", "0", "-5", "nonsense", "2026-07-01",
|
|
}
|
|
|
|
cases := 0
|
|
for _, l := range labels {
|
|
for _, s := range bounds {
|
|
for _, e := range bounds {
|
|
cases++
|
|
w, gotErr := ParseWindow(l, s, e, now)
|
|
wantStart, wantEnd, wantInterval, wantErr := resolveLegacy(l, s, e, now)
|
|
|
|
switch {
|
|
case gotErr == nil && wantErr != nil:
|
|
t.Fatalf("ParseWindow(%q,%q,%q) succeeded; legacy errored %v", l, s, e, wantErr)
|
|
case gotErr != nil && wantErr == nil:
|
|
t.Fatalf("ParseWindow(%q,%q,%q) errored %v; legacy succeeded", l, s, e, gotErr)
|
|
case gotErr != nil:
|
|
if gotErr.Error() != wantErr.Error() {
|
|
t.Fatalf("ParseWindow(%q,%q,%q) error = %q, legacy = %q", l, s, e, gotErr, wantErr)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if !w.Start.Equal(wantStart) {
|
|
t.Fatalf("ParseWindow(%q,%q,%q) Start = %v, legacy = %v", l, s, e, w.Start, wantStart)
|
|
}
|
|
if !w.End.Equal(wantEnd) {
|
|
t.Fatalf("ParseWindow(%q,%q,%q) End = %v, legacy = %v", l, s, e, w.End, wantEnd)
|
|
}
|
|
if string(w.Interval) != wantInterval {
|
|
t.Fatalf("ParseWindow(%q,%q,%q) Interval = %q, legacy = %q", l, s, e, w.Interval, wantInterval)
|
|
}
|
|
if got, want := w.Interval.Step(), legacyStep(wantInterval); got != want {
|
|
t.Fatalf("Step(%q) = %v, legacy = %v", w.Interval, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
t.Logf("compared %d label/start/end combinations against the replaced implementation", cases)
|
|
}
|
|
|
|
// legacyStep is the bucket-width helper this package replaced.
|
|
func legacyStep(interval string) time.Duration {
|
|
if strings.EqualFold(interval, "day") {
|
|
return 24 * time.Hour
|
|
}
|
|
return time.Hour
|
|
}
|