package processor import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "apskel-pos-be/internal/models" ) // wib is a moment in the customer's time zone. func wib(year int, month time.Month, day, hour, minute int) time.Time { return time.Date(year, month, day, hour, minute, 0, 0, walletDisplayLocation) } // expiryDay is the day a lot expires on, as the apps show it. func expiryDay(t *testing.T, at *time.Time) string { t.Helper() require.NotNil(t, at) local := at.In(walletDisplayLocation) assert.Equal(t, "23:59:59", local.Format("15:04:05"), "a lot lasts to the end of its day") return local.Format("2006-01-02") } func fixedDate(dates ...string) models.LoyaltyExpirySettings { return models.LoyaltyExpirySettings{Enabled: true, Mode: "FIXED_DATE", FixedDates: dates, GraceMonths: 3} } func rolling(period int64, unit string, endOfMonth bool) models.LoyaltyExpirySettings { return models.LoyaltyExpirySettings{Enabled: true, Mode: "ROLLING", Period: period, Unit: unit, EndOfMonth: endOfMonth} } func TestComputeExpiry_OffNeverExpires(t *testing.T) { s := fixedDate("12-31") s.Enabled = false assert.Nil(t, ComputeExpiry(s, wib(2026, 5, 1, 10, 0))) } func TestComputeExpiry_FixedDateWithGrace(t *testing.T) { yearly := fixedDate("12-31") for received, want := range map[time.Time]string{ wib(2026, 1, 15, 9, 0): "2026-12-31", wib(2026, 9, 30, 23, 0): "2026-12-31", // 30 Dec + nothing: still this year wib(2026, 10, 1, 0, 30): "2027-12-31", // within 3 months of 31 Dec: next year wib(2026, 12, 31, 8, 0): "2027-12-31", } { assert.Equal(t, want, expiryDay(t, ComputeExpiry(yearly, received)), received.String()) } twice := fixedDate("12-31", "06-30") assert.Equal(t, "2026-06-30", expiryDay(t, ComputeExpiry(twice, wib(2026, 3, 1, 12, 0)))) assert.Equal(t, "2026-12-31", expiryDay(t, ComputeExpiry(twice, wib(2026, 4, 1, 12, 0)))) assert.Equal(t, "2027-06-30", expiryDay(t, ComputeExpiry(twice, wib(2026, 10, 1, 12, 0)))) noGrace := fixedDate("12-31") noGrace.GraceMonths = 0 assert.Equal(t, "2026-12-31", expiryDay(t, ComputeExpiry(noGrace, wib(2026, 12, 31, 22, 0)))) } // The day received is the customer's day: 20:00 UTC on 31 Mar is already 1 Apr in WIB. func TestComputeExpiry_UsesTheCustomersDay(t *testing.T) { received := time.Date(2026, 3, 31, 20, 0, 0, 0, time.UTC) assert.Equal(t, "2026-04-11", expiryDay(t, ComputeExpiry(rolling(10, "DAY", false), received))) } func TestComputeExpiry_Rolling(t *testing.T) { assert.Equal(t, "2027-03-14", expiryDay(t, ComputeExpiry(rolling(12, "MONTH", false), wib(2026, 3, 14, 10, 0)))) assert.Equal(t, "2027-03-31", expiryDay(t, ComputeExpiry(rolling(12, "MONTH", true), wib(2026, 3, 14, 10, 0)))) assert.Equal(t, "2026-03-24", expiryDay(t, ComputeExpiry(rolling(10, "DAY", false), wib(2026, 3, 14, 10, 0)))) // A month that is shorter keeps to its last day. assert.Equal(t, "2026-02-28", expiryDay(t, ComputeExpiry(rolling(1, "MONTH", false), wib(2026, 1, 31, 10, 0)))) } func TestActivationExpiry(t *testing.T) { // FIXED_DATE: the second fixed date after activation, so nobody loses a balance // weeks after the rule is announced. assert.Equal(t, "2027-12-31", expiryDay(t, ActivationExpiry(fixedDate("12-31"), wib(2026, 11, 20, 10, 0)))) assert.Equal(t, "2027-12-31", expiryDay(t, ActivationExpiry(fixedDate("12-31"), wib(2026, 12, 31, 10, 0)))) assert.Equal(t, "2027-06-30", expiryDay(t, ActivationExpiry(fixedDate("06-30", "12-31"), wib(2026, 11, 20, 10, 0)))) // ROLLING: a full period from activation. assert.Equal(t, "2027-11-20", expiryDay(t, ActivationExpiry(rolling(12, "MONTH", false), wib(2026, 11, 20, 10, 0)))) } func TestRefundExpiry(t *testing.T) { now := wib(2026, 6, 1, 10, 0) later := wib(2026, 12, 31, 23, 59) soon := wib(2026, 6, 3, 23, 59) past := wib(2026, 5, 1, 23, 59) assert.Nil(t, RefundExpiry(nil, now)) assert.Equal(t, &later, RefundExpiry(&later, now)) assert.Equal(t, "2026-06-08", expiryDay(t, RefundExpiry(&soon, now))) assert.Equal(t, "2026-06-08", expiryDay(t, RefundExpiry(&past, now))) } func TestEarlierExpiry(t *testing.T) { a, b := wib(2026, 1, 1, 0, 0), wib(2026, 2, 1, 0, 0) assert.Equal(t, &a, EarlierExpiry(&a, &b)) assert.Equal(t, &a, EarlierExpiry(&b, &a)) assert.Equal(t, &a, EarlierExpiry(nil, &a)) assert.Equal(t, &a, EarlierExpiry(&a, nil)) assert.Nil(t, EarlierExpiry(nil, nil)) } func TestNormalizeDaysOfYear(t *testing.T) { days, err := normalizeDaysOfYear([]string{" 12-31", "06-30", "12-31", ""}) require.NoError(t, err) assert.Equal(t, []string{"06-30", "12-31"}, days) for _, bad := range [][]string{{}, {"13-01"}, {"12-32"}, {"1-31"}, {"02-29"}, {"31-12"}} { _, err := normalizeDaysOfYear(bad) assert.Error(t, err, bad) } }