Files
doormile_backend/utils/epoch_test.go

117 lines
4.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package utils
import (
"testing"
"time"
)
// The customer contract asks for epoch milliseconds, UTC, integer. This
// database stores IST wall-clock digits (see DBNow), so the conversion is the
// one place a 5h30m error can enter every timestamp the app renders — which is
// exactly the defect class §3.3 of the contract warns about after it produced
// "yesterday's work shown as today" on the miler app.
func TestEpochMillisTreatsStoredWallClockAsIST(t *testing.T) {
// A row written as 2026-09-05 14:30:00 by this database means 2:30pm in
// Chennai, whatever zone the time.Time happens to carry.
stored := time.Date(2026, 9, 5, 14, 30, 0, 0, time.UTC)
got := EpochMillis(stored)
want := time.Date(2026, 9, 5, 9, 0, 0, 0, time.UTC).UnixMilli() // 14:30 IST == 09:00 UTC
if got != want {
t.Errorf("EpochMillis(2026-09-05 14:30 stored) = %d, want %d (a %d ms error)",
got, want, got-want)
}
}
func TestEpochMillisAgreesForBothTaggings(t *testing.T) {
// The driver can hand back the same instant either tagged UTC with IST
// digits, or correctly tagged +05:30. Both must name one moment, or a
// column type change silently shifts every timestamp on the tracking screen.
ist := ISTLocation()
taggedUTC := time.Date(2026, 9, 5, 14, 30, 0, 0, time.UTC)
taggedIST := time.Date(2026, 9, 5, 14, 30, 0, 0, ist)
if EpochMillis(taggedUTC) != EpochMillis(taggedIST) {
t.Errorf("EpochMillis disagrees on tagging: utc-tagged=%d ist-tagged=%d",
EpochMillis(taggedUTC), EpochMillis(taggedIST))
}
}
func TestEpochMillisPtrKeepsNullNull(t *testing.T) {
// A nil timestamp must stay null in JSON. Coercing it to 0 renders as
// 1 Jan 1970 on the timeline, which reads as a real event.
if got := EpochMillisPtr(nil); got != nil {
t.Errorf("EpochMillisPtr(nil) = %v, want nil", got)
}
var zero time.Time
if got := EpochMillisPtr(&zero); got != nil {
t.Errorf("EpochMillisPtr(zero time) = %v, want nil", got)
}
stored := time.Date(2026, 9, 5, 14, 30, 0, 0, time.UTC)
got := EpochMillisPtr(&stored)
if got == nil || *got != EpochMillis(stored) {
t.Errorf("EpochMillisPtr(%v) = %v, want %d", stored, got, EpochMillis(stored))
}
}
func TestFormatISTWindowMatchesTheDesign(t *testing.T) {
// "2:00 – 4:00 PM" — en dash, spaced, meridiem stated once when both ends
// share it. The client renders this string verbatim.
cases := []struct {
name string
fromH, fromM int
toH, toM int
want string
}{
{"afternoon window", 14, 0, 16, 0, "2:00 – 4:00 PM"},
{"morning window", 8, 0, 10, 0, "8:00 – 10:00 AM"},
{"straddles noon", 10, 0, 14, 0, "10:00 AM – 2:00 PM"},
{"half hour bounds", 18, 30, 20, 30, "6:30 – 8:30 PM"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
from := time.Date(2026, 9, 5, tc.fromH, tc.fromM, 0, 0, time.UTC)
to := time.Date(2026, 9, 5, tc.toH, tc.toM, 0, 0, time.UTC)
if got := FormatISTWindow(from, to); got != tc.want {
t.Errorf("FormatISTWindow = %q, want %q", got, tc.want)
}
})
}
}
func TestFormatISTDayUsesRelativeWordsOnlyWhenTrue(t *testing.T) {
now := ISTNow()
if got := FormatISTDay(now); got != "Today" {
t.Errorf("FormatISTDay(now) = %q, want \"Today\"", got)
}
if got := FormatISTDay(now.AddDate(0, 0, 1)); got != "Tomorrow" {
t.Errorf("FormatISTDay(tomorrow) = %q, want \"Tomorrow\"", got)
}
// Three days out has no relative word — it falls back to the dated form,
// which must not read as "Today".
got := FormatISTDay(now.AddDate(0, 0, 3))
if got == "Today" || got == "Tomorrow" {
t.Errorf("FormatISTDay(+3 days) = %q, want a dated label", got)
}
}
// FormatISTDay crossing a month boundary is the case a naive day+1 comparison
// gets wrong: 30 September plus one day is 1 October, not 31 September.
func TestFormatISTDayAcrossMonthEnd(t *testing.T) {
now := ISTNow()
tomorrow := now.AddDate(0, 0, 1)
if now.Month() == tomorrow.Month() {
t.Skip("not a month boundary today; the AddDate path is exercised by the general case")
}
if got := FormatISTDay(tomorrow); got != "Tomorrow" {
t.Errorf("FormatISTDay across a month end = %q, want \"Tomorrow\"", got)
}
}