package release import ( "testing" "time" ) func TestCanaryExpiredFalseForFreshPromotion(t *testing.T) { p := &ChannelPointer{App: "myapp", Channel: "canary/x", Version: "v1", PromotedAt: time.Now()} if CanaryExpired(p, time.Now()) { t.Error("a canary promoted moments ago must not be expired") } } func TestCanaryExpiredTrueAfterTTL(t *testing.T) { old := time.Now().Add(-(CanaryTTL + time.Hour)) p := &ChannelPointer{App: "myapp", Channel: "canary/x", Version: "v1", PromotedAt: old} if !CanaryExpired(p, time.Now()) { t.Error("a canary promoted more than CanaryTTL ago must be expired") } } // Right at the boundary: exactly CanaryTTL old is NOT yet expired — the TTL // names the lease's length, not a moment strictly before it. func TestCanaryExpiredBoundary(t *testing.T) { now := time.Now() atBoundary := &ChannelPointer{Channel: "canary/x", PromotedAt: now.Add(-CanaryTTL)} if CanaryExpired(atBoundary, now) { t.Error("exactly CanaryTTL old must not be expired yet") } pastBoundary := &ChannelPointer{Channel: "canary/x", PromotedAt: now.Add(-CanaryTTL - time.Second)} if !CanaryExpired(pastBoundary, now) { t.Error("one second past CanaryTTL must be expired") } } // Re-promoting a canary refreshes PromotedAt, which is the whole point of // keying age off the pointer's own lease timestamp: an actively-updated // canary never expires, however old the channel name itself is. func TestCanaryExpiredRefreshesOnRepromotion(t *testing.T) { now := time.Now() stale := &ChannelPointer{Channel: "canary/x", PromotedAt: now.Add(-(CanaryTTL + 24*time.Hour))} if !CanaryExpired(stale, now) { t.Fatal("setup: this pointer should read as expired before the repromotion") } refreshed := &ChannelPointer{Channel: "canary/x", PromotedAt: now} if CanaryExpired(refreshed, now) { t.Error("a freshly re-promoted canary must not be expired, however old the channel is") } } // Only canary/* channels carry a TTL at all — edge, beta and stable are // never flagged, however old their PromotedAt is. func TestCanaryExpiredFalseForNonCanaryChannels(t *testing.T) { now := time.Now() ancient := now.Add(-365 * 24 * time.Hour) for _, ch := range []string{"stable", "beta", "edge", "canary"} { p := &ChannelPointer{Channel: ch, PromotedAt: ancient} if CanaryExpired(p, now) { t.Errorf("channel %q must never be flagged expired (not a canary/* channel)", ch) } } } // A pointer written before PromotedAt existed (or any zero value) must not // be flagged expired — unknown age is not proof of age, and validatePointer // already refuses a zero PromotedAt on anything read off the wire, so this // is defense for a directly-constructed pointer, not a live-fleet case. func TestCanaryExpiredFalseForZeroPromotedAt(t *testing.T) { p := &ChannelPointer{Channel: "canary/x"} if CanaryExpired(p, time.Now()) { t.Error("a zero PromotedAt must not be flagged expired") } } func TestCanaryExpiredNilPointer(t *testing.T) { if CanaryExpired(nil, time.Now()) { t.Error("a nil pointer must not be flagged expired") } } // ---- CanaryAge / FormatCanaryAge (2026-08-03 review, IMPORTANT 2) --------- // CanaryAge must report time PAST the TTL, not total age — a pointer // CanaryTTL+1h old has been expired for about an hour, not 7 days 1 hour. func TestCanaryAgeIsTimePastTTLNotTotalAge(t *testing.T) { now := time.Now() p := &ChannelPointer{Channel: "canary/x", PromotedAt: now.Add(-(CanaryTTL + time.Hour))} got := CanaryAge(p, now) if got < 59*time.Minute || got > 61*time.Minute { t.Errorf("CanaryAge = %s, want ~1h (total age minus CanaryTTL, not the 169h total age)", got) } } func TestFormatCanaryAge(t *testing.T) { cases := []struct { d time.Duration want string }{ {0, "<1h"}, // the instant it expires {24 * time.Minute, "<1h"}, // MINOR 7: must not read "0h" {59 * time.Minute, "<1h"}, {time.Hour, "1h"}, // review's own example: 7d1h old -> "1h" {90 * time.Minute, "2h"}, // rounds to nearest hour {23 * time.Hour, "23h"}, {24 * time.Hour, "1d"}, {47 * time.Hour, "1d"}, // deliberate coarseness: truncates, not rounds {8 * 24 * time.Hour, "8d"}, {30 * 24 * time.Hour, "30d"}, } for _, c := range cases { if got := FormatCanaryAge(c.d); got != c.want { t.Errorf("FormatCanaryAge(%s) = %q, want %q", c.d, got, c.want) } } }