package edge import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "net" "os" "path/filepath" "strings" "testing" "time" "github.com/carlosframework/platform/internal/geo" "github.com/carlosframework/platform/internal/geoip" "github.com/carlosframework/platform/internal/metrics" "github.com/carlosframework/platform/internal/release" ) func geoFixture(t *testing.T, name string) []byte { b, err := os.ReadFile(filepath.Join("..", "geo", "testdata", name)) if err != nil { t.Fatal(err) } return b } // publishFixture writes blob + pointer the way the console does. func publishFixture(t *testing.T, s release.Store, body []byte, dbType string, build time.Time) geoip.Pointer { t.Helper() sum := sha256.Sum256(body) sha := hex.EncodeToString(sum[:]) ctx := context.Background() if _, err := s.Put(ctx, geoip.BlobKey(sha), body, ""); err != nil && !errors.Is(err, release.ErrCASConflict) { t.Fatal(err) // an existing blob is the same bytes: content-addressed } p := geoip.Pointer{V: 1, SHA256: sha, Key: geoip.BlobKey(sha), Size: int64(len(body)), DatabaseType: dbType, BuildEpoch: build.Unix(), Origin: "manual"} raw, _ := json.Marshal(p) _, etag, _ := s.Get(ctx, geoip.PointerKey) if _, err := s.Put(ctx, geoip.PointerKey, raw, etag); err != nil { t.Fatal(err) } return p } var testMirrorStoreDir = map[release.Store]string{} func newTestMirror(t *testing.T) (*geoMirror, release.Store, string) { root := t.TempDir() s := release.NewDirStore(root) testMirrorStoreDir[s] = root dir := filepath.Join(t.TempDir(), "carlos", "geoip") return newGeoMirror(s, dir, geo.NewSwappable()), s, dir } func lookupGB(m *geoMirror) bool { snap := m.sw.Lookup(net.ParseIP("81.2.69.142")) return snap.Found && snap.Result.Country == "GB" } func TestMirrorInstallsAPublishedDatabase(t *testing.T) { m, s, dir := newTestMirror(t) p := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } if !lookupGB(m) { t.Fatal("not serving after poll") } if _, err := os.Stat(filepath.Join(dir, p.SHA256+".mmdb")); err != nil { t.Fatal(err) } if st := m.State(); st.SHA256 != p.SHA256 || st.Type != "GeoIP2-City" { t.Fatalf("state = %+v", st) } fi, _ := os.Stat(dir) if fi.Mode().Perm() != 0o755 { t.Fatalf("dir mode = %v, want 0755 (the console reads it)", fi.Mode().Perm()) } } func TestMirrorBootsFromTheLocalGenerationWithoutTheBucket(t *testing.T) { m, s, dir := newTestMirror(t) publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } // A fresh process, a dead bucket. m2 := newGeoMirror(release.NewDirStore(filepath.Join(t.TempDir(), "empty")), dir, geo.NewSwappable()) m2.Boot() if !lookupGB(m2) { t.Fatal("boot did not serve the local generation") } } func TestMirrorBootWithCurrentNamingAMissingFile(t *testing.T) { m, _, dir := newTestMirror(t) os.MkdirAll(dir, 0o755) os.WriteFile(filepath.Join(dir, "current"), []byte(`{"sha256":"`+strings.Repeat("d", 64)+`","database_type":"X","build_epoch":1}`), 0o644) m.Boot() if snap := m.sw.Lookup(net.ParseIP("81.2.69.142")); snap.Loaded { t.Fatal("loaded something from a dangling current") } if _, err := os.Stat(filepath.Join(dir, "current")); !os.IsNotExist(err) { t.Fatal("dangling current was not removed") } } func TestMirrorRejectsADigestMismatchOnce(t *testing.T) { m, s, _ := newTestMirror(t) p := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) // Corrupt the blob after publishing. os.WriteFile(filepath.Join(testMirrorStoreDir[s], p.Key), append(geoFixture(t, "GeoIP2-City-Test.mmdb")[:p.Size-1], 'x'), 0o644) if err := m.Poll(context.Background()); err == nil { t.Fatal("a corrupt blob installed") } if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("serving a corrupt blob") } if err := m.Poll(context.Background()); err != nil { t.Fatalf("second poll of the same rejected sha should be a quiet no-op, got %v", err) } } func TestMirrorOffClearsEverything(t *testing.T) { m, s, dir := newTestMirror(t) publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) m.Poll(context.Background()) s.Delete(context.Background(), geoip.PointerKey) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("still serving after off") } if matches, _ := filepath.Glob(filepath.Join(dir, "*.mmdb")); len(matches) != 0 { t.Fatalf("generations left: %v", matches) } } func TestMirrorNeverInstallsAnExpiredGeoLite(t *testing.T) { m, s, _ := newTestMirror(t) now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m.now = func() time.Time { return now } // The fixture's own metadata says GeoIP2-City (no expiry), so make the // POINTER claim GeoLite2 with an old build: expired before download. publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoLite2-City", now.Add(-31*24*time.Hour)) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("installed an expired pointer") } // Repeated polls of the same unmarked expired pointer stay quiet. if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } } func TestMirrorExpiryTickRemovesOnlyTheCurrentExpiredGeneration(t *testing.T) { m, s, _ := newTestMirror(t) now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m.now = func() time.Time { return now } publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) m.Poll(context.Background()) // Pretend what is loaded is a GeoLite2 built 31 days ago. m.genMu.Lock() m.loadedType, m.loadedBuild = "GeoLite2-City", now.Add(-31*24*time.Hour) m.genMu.Unlock() m.ExpireTick() if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("expired generation still serving") } // A fresh, non-expiring install afterwards is untouched by later ticks. publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", now) m.Poll(context.Background()) m.ExpireTick() if !lookupGB(m) { t.Fatal("a later tick removed a fresh generation") } } func TestMirrorRefusesAnOversizeBlob(t *testing.T) { m, s, dir := newTestMirror(t) p := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) m.maxBytes = p.Size - 1 if err := m.Poll(context.Background()); err == nil { t.Fatal("oversize installed") } if matches, _ := filepath.Glob(filepath.Join(dir, ".incoming-*")); len(matches) != 0 { t.Fatalf("incoming left: %v", matches) } } // Plan review R1-7: a pointer this binary cannot read turns GeoIP off. func TestMirrorUnknownPointerVersionTurnsOff(t *testing.T) { m, s, _ := newTestMirror(t) publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) if err := m.Poll(context.Background()); err != nil || !lookupGB(m) { t.Fatalf("setup: %v", err) } ctx := context.Background() raw, etag, _ := s.Get(ctx, geoip.PointerKey) var p map[string]any json.Unmarshal(raw, &p) p["v"] = 2 raw2, _ := json.Marshal(p) if _, err := s.Put(ctx, geoip.PointerKey, raw2, etag); err != nil { t.Fatal(err) } if err := m.Poll(ctx); err != nil { t.Fatal(err) } if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("still serving under a pointer version this binary cannot read") } } // Plan review R1-8: a rejection lasts only while the pointer names that sha. func TestMirrorRetriesARejectedShaAfterThePointerMoves(t *testing.T) { m, s, _ := newTestMirror(t) ctx := context.Background() city := geoFixture(t, "GeoIP2-City-Test.mmdb") b := publishFixture(t, s, city, "GeoIP2-City", time.Now()) good := append([]byte(nil), city...) os.WriteFile(filepath.Join(testMirrorStoreDir[s], b.Key), append(city[:len(city)-1], 'x'), 0o644) // corrupt B if err := m.Poll(ctx); err == nil { t.Fatal("corrupt B installed") } publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", time.Now()) // A if err := m.Poll(ctx); err != nil { t.Fatal(err) } os.WriteFile(filepath.Join(testMirrorStoreDir[s], b.Key), good, 0o644) // B repaired raw, _ := json.Marshal(b) _, etag, _ := s.Get(ctx, geoip.PointerKey) if _, err := s.Put(ctx, geoip.PointerKey, raw, etag); err != nil { t.Fatal(err) } if err := m.Poll(ctx); err != nil { t.Fatalf("repaired B after the pointer moved away and back: %v", err) } if st := m.State(); st.SHA256 != b.SHA256 { t.Fatalf("loaded %s, want repaired B %s", st.SHA256, b.SHA256) } } // Plan review R1-3: an expiry tick during a replacement download must not // unlink the download. func TestMirrorExpiryDuringADownloadKeepsTheDownload(t *testing.T) { m, s, _ := newTestMirror(t) now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m.now = func() time.Time { return now } publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) m.Poll(context.Background()) m.genMu.Lock() m.loadedType, m.loadedBuild = "GeoLite2-City", now.Add(-31*24*time.Hour) // A is now expired m.genMu.Unlock() publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", now) // B downloadHook = func() { m.ExpireTick() } t.Cleanup(func() { downloadHook = nil }) if err := m.Poll(context.Background()); err != nil { t.Fatalf("replacement broken by a concurrent expiry: %v", err) } if !lookupGB(m) { t.Fatal("B not serving") } } // Plan review R1-4: a directory fsync failing after `current` is renamed // must not delete the generation `current` names. func TestMirrorSyncFailureAfterRenameKeepsTheGeneration(t *testing.T) { m, s, dir := newTestMirror(t) syncDirFn = func(string) error { return errors.New("injected") } t.Cleanup(func() { syncDirFn = syncDir }) p := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) if err := m.Poll(context.Background()); err != nil { t.Fatalf("poll: %v", err) } if _, err := os.Stat(filepath.Join(dir, p.SHA256+".mmdb")); err != nil { t.Fatal("the generation current names was deleted") } // A second install while the first rename is still not durable keeps // BOTH generations: a crash could bring back either `current` (code // review R1-4). q := publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", time.Now()) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } for _, sha := range []string{p.SHA256, q.SHA256} { if _, err := os.Stat(filepath.Join(dir, sha+".mmdb")); err != nil { t.Fatalf("generation %s deleted while the selection was not durable", sha[:8]) } } syncDirFn = syncDir m2 := newGeoMirror(release.NewDirStore(t.TempDir()), dir, geo.NewSwappable()) m2.Boot() if !lookupGB(m2) { t.Fatal("restart after the failed sync did not recover the generation") } } // Codex R8-2: an off that lands during a download takes effect at the end // of that pass; the superseded download is never installed. func TestMirrorOffDuringADownloadNeverInstalls(t *testing.T) { m, s, dir := newTestMirror(t) publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) downloadHook = func() { s.Delete(context.Background(), geoip.PointerKey) } // off, mid-download t.Cleanup(func() { downloadHook = nil }) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } if m.sw.Lookup(net.ParseIP("81.2.69.142")).Loaded { t.Fatal("a download superseded by off was installed") } if matches, _ := filepath.Glob(filepath.Join(dir, "*.mmdb")); len(matches) != 0 { t.Fatalf("generations left: %v", matches) } } // ...and a newer publish during a download installs the newer one in the // same pass. func TestMirrorNewerPublishDuringADownloadInstallsTheNewer(t *testing.T) { m, s, _ := newTestMirror(t) publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) var newer geoip.Pointer downloadHook = func() { downloadHook = nil // only the first download is interrupted newer = publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", time.Now()) } t.Cleanup(func() { downloadHook = nil }) if err := m.Poll(context.Background()); err != nil { t.Fatal(err) } if st := m.State(); st.SHA256 != newer.SHA256 { t.Fatalf("loaded %s, want the newer %s", st.SHA256, newer.SHA256) } } // Plan 2 Task 4 / spec ยง10.4: the geoip rule's reason codes. func TestMirrorAlertCodes(t *testing.T) { now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) m.now = func() time.Time { return now } ctx := context.Background() if c, _ := m.AlertCode(now, "box"); c != GeoAlertNone { t.Fatalf("nothing published = %v", c) } publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) m.Poll(ctx) // The fixture was built 2026-02-04: more than 45 days before now. if c, _ := m.AlertCode(now, "box"); c != GeoAlertStale { t.Fatalf("an old database = %v, want stale", c) } if c, _ := m.AlertCode(time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC), "box"); c != GeoAlertNone { t.Fatalf("a fresh database = %v", c) } // GeoLite2 at 22 days: expiring wins over stale's absence. m.genMu.Lock() m.loadedType, m.loadedBuild = "GeoLite2-City", now.Add(-22*24*time.Hour) m.genMu.Unlock() if c, _ := m.AlertCode(now, "box"); c != GeoAlertExpiring { t.Fatalf("GeoLite2 at 22 days = %v, want expiring", c) } } // Codex R11-1: an expired pointer keeps the alert firing after the reader // is dropped and before the console marks the bucket; off clears it. func TestMirrorAlertSurvivesExpiryUntilOff(t *testing.T) { now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) m.now = func() time.Time { return now } ctx := context.Background() publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoLite2-City", now.Add(-31*24*time.Hour)) // unmarked, past D1 m.Poll(ctx) if c, _ := m.AlertCode(now, "box"); c != GeoAlertExpired { t.Fatalf("expired, unmarked = %v, want expired", c) } // A restart with the same unmarked pointer: still expired. m2 := newGeoMirror(s, m.dir, geo.NewSwappable()) m2.now = m.now m2.Boot() m2.Poll(ctx) if c, _ := m2.AlertCode(now, "box"); c != GeoAlertExpired { t.Fatalf("after restart = %v, want expired", c) } // A fresh publish clears it. publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", now) m.Poll(ctx) if c, _ := m.AlertCode(time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC), "box"); c == GeoAlertExpired { t.Fatal("a fresh publish left the expired reason") } // Expired again, then off: off clears every pointer reason. publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoLite2-City", now.Add(-31*24*time.Hour)) m.Poll(ctx) s.Delete(ctx, geoip.PointerKey) m.Poll(ctx) if c, _ := m.AlertCode(now, "box"); c != GeoAlertNone { t.Fatalf("after off = %v, want none", c) } } func TestMirrorAlertNotLoadedAfterAnHour(t *testing.T) { now := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) clock := now m.now = func() time.Time { return clock } p := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) os.WriteFile(filepath.Join(testMirrorStoreDir[s], p.Key), []byte("corrupt"), 0o644) m.Poll(context.Background()) if c, _ := m.AlertCode(clock.Add(59*time.Minute), "box"); c != GeoAlertNone { t.Fatalf("not loaded for 59 minutes = %v", c) } if c, _ := m.AlertCode(clock.Add(61*time.Minute), "box"); c != GeoAlertNotLoaded { t.Fatalf("not loaded for 61 minutes = %v, want not loaded", c) } } func TestMirrorAlertFeedReasonOnlyOnTheConsoleBox(t *testing.T) { now := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) m.now = func() time.Time { return now } raw, _ := json.Marshal(geoip.FeedStatus{V: 1, Feed: "dbip-city-lite", Box: "console-box", ConsecutiveNotFound: 7}) if _, err := s.Put(context.Background(), geoip.FeedKey, raw, ""); err != nil { t.Fatal(err) } m.Poll(context.Background()) // no pointer at all: a fresh deployment if c, _ := m.AlertCode(now, "console-box"); c != GeoAlertFeed { t.Fatalf("console box = %v, want feed", c) } if c, _ := m.AlertCode(now, "other-box"); c != GeoAlertNone { t.Fatalf("another box = %v, want none", c) } } // Code review R2 on R1-4: while directory syncs keep failing, every // generation a crash could bring back is kept. func TestMirrorKeepsEveryUnsyncedGeneration(t *testing.T) { m, s, dir := newTestMirror(t) a := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now()) m.Poll(context.Background()) // A, durable syncDirFn = func(string) error { return errors.New("injected") } t.Cleanup(func() { syncDirFn = syncDir }) b := publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", time.Now()) m.Poll(context.Background()) c := publishFixture(t, s, geoFixture(t, "MaxMind-DB-test-ipv4-24.mmdb"), "Test", time.Now()) m.Poll(context.Background()) // C will be refused? the mirror does not probe; it installs whatever verifies for _, sha := range []string{a.SHA256, b.SHA256} { if _, err := os.Stat(filepath.Join(dir, sha+".mmdb")); err != nil { t.Fatalf("generation %s deleted while syncs keep failing", sha[:8]) } } _ = c syncDirFn = syncDir publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now().Add(time.Second)) } // Code review R2-3: a read blip keeps the last known feed state. func TestMirrorFeedStateSurvivesAReadBlip(t *testing.T) { now := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) m.now = func() time.Time { return now } raw, _ := json.Marshal(geoip.FeedStatus{V: 1, Feed: "f", Box: "console-box", ConsecutiveFailures: 7}) s.Put(context.Background(), geoip.FeedKey, raw, "") m.Poll(context.Background()) _, etag, _ := s.Get(context.Background(), geoip.FeedKey) s.Put(context.Background(), geoip.FeedKey, []byte("garbled"), etag) m.Poll(context.Background()) if c, _ := m.AlertCode(now, "console-box"); c != GeoAlertFeed { t.Fatalf("a garbled read cleared the feed reason: %v", c) } s.Delete(context.Background(), geoip.FeedKey) m.Poll(context.Background()) if c, _ := m.AlertCode(now, "console-box"); c != GeoAlertNone { t.Fatalf("a deleted feed.json must clear it: %v", c) } } // Code review R2-4: an unusable local directory still lets the alert see // the published pointer. func TestMirrorUnusableDirectoryStillTracksThePointer(t *testing.T) { now := time.Date(2026, 2, 10, 0, 0, 0, 0, time.UTC) s := release.NewDirStore(t.TempDir()) blocker := filepath.Join(t.TempDir(), "file") os.WriteFile(blocker, []byte("x"), 0o644) m := newGeoMirror(s, filepath.Join(blocker, "geoip"), geo.NewSwappable()) // a path under a file: MkdirAll fails m.now = func() time.Time { return now } publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) if err := m.Poll(context.Background()); err == nil { t.Fatal("an unusable directory reported success") } if c, _ := m.AlertCode(now.Add(61*time.Minute), "box"); c != GeoAlertNotLoaded { t.Fatalf("code = %v, want not loaded", c) } } // Code review R3 on R1-4: an install error path never deletes a generation // a crash could bring back. func TestRemoveGenerationKeepsRetainedOnes(t *testing.T) { m, _, dir := newTestMirror(t) os.MkdirAll(dir, 0o755) a, b, c := strings.Repeat("a", 64), strings.Repeat("b", 64), strings.Repeat("c", 64) for _, sha := range []string{a, b, c} { os.WriteFile(filepath.Join(dir, sha+".mmdb"), []byte("x"), 0o644) } m.loadedSHA, m.unsynced = a, map[string]geoCurrent{b: {SHA256: b}} for _, sha := range []string{a, b, c} { m.removeGenerationLocked(sha) } for sha, want := range map[string]bool{a: true, b: true, c: false} { if _, err := os.Stat(filepath.Join(dir, sha+".mmdb")); (err == nil) != want { t.Errorf("%s present=%v, want %v", sha[:1], err == nil, want) } } } // Code review R4-2: a cold mirror has no reading until it has read both // the pointer and the feed status. func TestMirrorAlertUnknownUntilStateIsRead(t *testing.T) { m, s, _ := newTestMirror(t) if _, known := m.AlertCode(time.Now(), "box"); known { t.Fatal("a cold mirror claimed a reading") } s.Put(context.Background(), geoip.FeedKey, []byte("garbled"), "") // feed unreadable m.Poll(context.Background()) if _, known := m.AlertCode(time.Now(), "box"); known { t.Fatal("an unreadable feed status gave a reading") } _, etag, _ := s.Get(context.Background(), geoip.FeedKey) _ = etag s.Delete(context.Background(), geoip.FeedKey) m.Poll(context.Background()) if c, known := m.AlertCode(time.Now(), "box"); !known || c != GeoAlertNone { t.Fatalf("after both reads = (%v, %v)", c, known) } } // Code review R5-1: a confirmed expiry is reported even when feed.json is // unreadable, through the evaluator. func TestMirrorExpiryReportedWithAnUnreadableFeed(t *testing.T) { now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m, s, _ := newTestMirror(t) m.now = func() time.Time { return now } s.Put(context.Background(), geoip.FeedKey, []byte("garbled"), "") publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoLite2-City", now.Add(-31*24*time.Hour)) m.Poll(context.Background()) c, known := m.AlertCode(now, "box") if !known || c != GeoAlertExpired { t.Fatalf("expired with an unreadable feed = (%v, %v), want (3, true)", c, known) } r := metrics.DefaultRules() st := metrics.AlertState{V: 1, Box: "box"} st, _ = metrics.Evaluate(r, st, metrics.Observation{At: now, GeoIP: &c}) _, ev := metrics.Evaluate(r, st, metrics.Observation{At: now.Add(30 * time.Minute), GeoIP: &c}) if len(ev) != 1 || ev[0].Value != GeoAlertExpired { t.Fatalf("evaluator = %v", ev) } } // Code review R10-2: one failed sync during a GeoLite2 -> DB-IP move; the // retained GeoLite2 copy goes once a later sync lands, or at its expiry. func TestMirrorRetainedCopyIsCleanedOrExpired(t *testing.T) { for _, mode := range []string{"sync recovers", "expiry"} { now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) m, s, dir := newTestMirror(t) m.now = func() time.Time { return now } a := publishFixture(t, s, geoFixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", now) m.Poll(context.Background()) m.genMu.Lock() m.loadedType, m.loadedBuild = "GeoLite2-City", now.Add(-10*24*time.Hour) // A is a GeoLite2, 10 days old m.genMu.Unlock() syncDirFn = func(string) error { return errors.New("injected") } publishFixture(t, s, geoFixture(t, "GeoIP2-Country-Test.mmdb"), "GeoIP2-Country", now) m.Poll(context.Background()) if _, err := os.Stat(filepath.Join(dir, a.SHA256+".mmdb")); err != nil { t.Fatalf("%s: A not retained", mode) } if mode == "sync recovers" { syncDirFn = syncDir m.Poll(context.Background()) // unchanged pointer } else { now = now.Add(21 * 24 * time.Hour) // A past its 30 days m.ExpireTick() syncDirFn = syncDir } if _, err := os.Stat(filepath.Join(dir, a.SHA256+".mmdb")); !os.IsNotExist(err) { t.Fatalf("%s: A survived (%v)", mode, err) } } syncDirFn = syncDir }