Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

241 lines
5.9 KiB
Go

package timeutil
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPtr(t *testing.T) {
tests := []struct {
name string
in time.Time
want *string
}{
{
name: "zero time returns nil",
in: time.Time{},
want: nil,
},
{
name: "non-zero returns RFC3339Nano UTC",
in: time.Date(2024, 6, 15, 12, 30, 45, 123000000, time.UTC),
want: new("2024-06-15T12:30:45.123Z"),
},
{
name: "converts to UTC",
in: time.Date(2024, 6, 15, 7, 30, 0, 0, time.FixedZone("EST", -5*60*60)),
want: new("2024-06-15T12:30:00Z"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Ptr(tt.in)
if tt.want == nil {
assert.Nil(t, got)
return
}
require.NotNil(t, got)
assert.Equal(t, *tt.want, *got)
})
}
}
func TestFormat(t *testing.T) {
tests := []struct {
name string
in time.Time
want string
}{
{"zero time returns empty", time.Time{}, ""},
{"non-zero returns RFC3339Nano UTC", time.Date(2024, 6, 15, 12, 30, 45, 0, time.UTC), "2024-06-15T12:30:45Z"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, Format(tt.in))
})
}
}
func TestIsValidDate(t *testing.T) {
tests := []struct {
name string
in string
want bool
}{
{"valid date", "2024-06-15", true},
{"empty string", "", false},
{"wrong separator", "2024/06/15", false},
{"two-digit year", "24-06-15", false},
{"includes time", "2024-06-15T00:00:00Z", false},
{"impossible month", "2024-13-01", false},
{"impossible day", "2024-02-30", false},
{"non-numeric", "not-a-date", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, IsValidDate(tt.in))
})
}
}
func TestIsValidTimestamp(t *testing.T) {
tests := []struct {
name string
in string
want bool
}{
{"RFC3339 UTC", "2024-06-15T12:30:45Z", true},
{"RFC3339 offset", "2024-06-15T12:30:45-05:00", true},
{"RFC3339Nano", "2024-06-15T12:30:45.123456789Z", true},
{"empty string", "", false},
{"date only", "2024-06-15", false},
{"missing timezone", "2024-06-15T12:30:45", false},
{"non-numeric", "not-a-timestamp", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, IsValidTimestamp(tt.in))
})
}
}
func TestParseSince(t *testing.T) {
now := time.Date(2026, 3, 15, 10, 0, 0, 0, time.UTC)
tests := []struct {
name string
in string
want time.Time
}{
{"hours", "3h", now.Add(-3 * time.Hour)},
{"days", "14d", now.Add(-14 * 24 * time.Hour)},
{"weeks", "2w", now.Add(-14 * 24 * time.Hour)},
{"months", "3m", now.AddDate(0, -3, 0)},
{"years", "1y", now.AddDate(-1, 0, 0)},
{"single digit unit", "1d", now.Add(-24 * time.Hour)},
{
"absolute date is midnight in now's location",
"2026-01-01",
time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseSince(now, tt.in)
require.NoError(t, err)
assert.True(t, tt.want.Equal(got),
"ParseSince(%q) = %v, want %v", tt.in, got, tt.want)
})
}
}
// TestParseSince_MonthArithmeticCrossesYearBoundary verifies month/year units
// use calendar-aware AddDate rather than a fixed-duration approximation, so
// "2m" from mid-January lands in the previous November/year rather than a
// rough 60-day subtraction.
func TestParseSince_MonthArithmeticCrossesYearBoundary(t *testing.T) {
now := time.Date(2026, 1, 15, 8, 30, 0, 0, time.UTC)
got, err := ParseSince(now, "2m")
require.NoError(t, err)
want := time.Date(2025, 11, 15, 8, 30, 0, 0, time.UTC)
assert.True(t, want.Equal(got), "got %v, want %v", got, want)
}
// TestParseSince_AbsoluteDateUsesNowsLocation verifies the YYYY-MM-DD form
// resolves to that date's midnight in now's location rather than always UTC.
func TestParseSince_AbsoluteDateUsesNowsLocation(t *testing.T) {
ny, err := time.LoadLocation("America/New_York")
require.NoError(t, err)
now := time.Date(2026, 3, 15, 10, 0, 0, 0, ny)
got, err := ParseSince(now, "2026-01-01")
require.NoError(t, err)
want := time.Date(2026, 1, 1, 0, 0, 0, 0, ny)
assert.True(t, want.Equal(got), "got %v, want %v", got, want)
assert.Equal(t, ny.String(), got.Location().String())
}
func TestParseSince_RejectsInvalidForms(t *testing.T) {
now := time.Date(2026, 3, 15, 10, 0, 0, 0, time.UTC)
tests := []struct {
name string
in string
}{
{"empty string", ""},
{"unknown unit", "3x"},
{"unit before number", "m3"},
{"negative number", "-3d"},
{"zero is not positive", "0d"},
{"unit only", "d"},
{"double unit", "3dd"},
{"decimal number", "3.5d"},
{"trailing space", "3d "},
{"leading space", " 3d"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseSince(now, tt.in)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.in)
})
}
}
func TestBestEffortLocalTimezone(t *testing.T) {
la, err := time.LoadLocation("America/Los_Angeles")
require.NoError(t, err)
berlin, err := time.LoadLocation("Europe/Berlin")
require.NoError(t, err)
tests := []struct {
name string
envTZ string
loc *time.Location
want string
}{
{
name: "valid env wins over local",
envTZ: "Europe/Berlin",
loc: la,
want: "Europe/Berlin",
},
{
name: "invalid env falls back to local",
envTZ: "not/a-zone",
loc: la,
want: "America/Los_Angeles",
},
{
name: "local sentinel is blank",
envTZ: "",
loc: time.FixedZone("Local", 0),
want: "",
},
{
name: "invalid local name is blank",
envTZ: "",
loc: time.FixedZone("DuckLocal", 0),
want: "",
},
{
name: "valid local IANA name passes through",
envTZ: "",
loc: berlin,
want: "Europe/Berlin",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want,
bestEffortLocalTimezone(tt.envTZ, tt.loc))
})
}
}