backend requirements onthe xustomer app

This commit is contained in:
2026-09-07 10:55:11 +05:30
parent 35675d8a9b
commit 1b2690b21a
56 changed files with 13342 additions and 1071 deletions

116
utils/epoch_test.go Normal file
View File

@@ -0,0 +1,116 @@
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)
}
}