package console import ( "archive/tar" "bytes" "compress/gzip" "context" "crypto/sha256" "crypto/tls" "encoding/hex" "encoding/json" "errors" "io" "net/http" "net/http/httptest" "os" "strings" "sync/atomic" "testing" "time" "github.com/carlosframework/platform/internal/geoip" "github.com/carlosframework/platform/internal/release" ) func gzipBytes(b []byte) []byte { var buf bytes.Buffer w := gzip.NewWriter(&buf) w.Write(b) w.Close() return buf.Bytes() } func tarGzBytes(name string, body []byte) []byte { var buf bytes.Buffer zw := gzip.NewWriter(&buf) tw := tar.NewWriter(zw) tw.WriteHeader(&tar.Header{Name: "GeoLite2-City_20261006/COPYRIGHT.txt", Mode: 0o644, Size: 5, Typeflag: tar.TypeReg}) tw.Write([]byte("(c) x")) tw.WriteHeader(&tar.Header{Name: name, Mode: 0o644, Size: int64(len(body)), Typeflag: tar.TypeReg}) tw.Write(body) tw.Close() zw.Close() return buf.Bytes() } var feedNow = time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC) func newTestFeeder(t *testing.T, provider string, h http.Handler) (*geoipFeeder, release.Store) { t.Helper() srv := httptest.NewTLSServer(h) t.Cleanup(srv.Close) p, s := newTestPublisher(t) f := &geoipFeeder{ pub: p, store: s, spec: geoip.FeedSpec{Name: "test-feed", Type: "GeoIP2-City", Provider: provider}, account: "acct", key: "s3cret-key", box: "console-box", client: &http.Client{Timeout: time.Minute, CheckRedirect: geoipFeedRedirect, Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}, now: func() time.Time { return feedNow }, maxExpanded: geoipFeedMaxExpanded, dbipBase: srv.URL, maxmindBase: srv.URL, } return f, s } func feedStatus(t *testing.T, s release.Store) geoip.FeedStatus { t.Helper() b, _, err := s.Get(context.Background(), geoip.FeedKey) if err != nil { t.Fatalf("feed.json: %v", err) } st, err := geoip.ParseFeedStatus(b) if err != nil { t.Fatal(err) } return st } func TestFeedDBIPPublishesThisMonthThenRests(t *testing.T) { withFixtureProbes(t) body := gzipBytes(fixture(t, "GeoIP2-City-Test.mmdb")) var gets atomic.Int32 f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gets.Add(1) if r.URL.Path != "/free/dbip-test-feed-lite-2026-10.mmdb.gz" && !strings.HasSuffix(r.URL.Path, "-2026-10.mmdb.gz") { http.NotFound(w, r) return } w.Write(body) })) if err := f.Check(context.Background()); err != nil { t.Fatal(err) } cur, _, present, _ := f.pub.Current(context.Background()) if !present || cur.Origin != "feed:test-feed" || cur.Edition != "2026-10" { t.Fatalf("pointer = %+v", cur) } st := feedStatus(t, s) if st.LastEdition != "2026-10" || st.ConsecutiveFailures != 0 || st.Box != "console-box" { t.Fatalf("feed.json = %+v", st) } before := gets.Load() if err := f.Check(context.Background()); err != nil { t.Fatal(err) } if gets.Load() != before { t.Fatal("a second check in the same month downloaded again") } } func TestFeedDBIPFallsBackToLastMonthOnlyWithoutAPointerOfItsOwn(t *testing.T) { withFixtureProbes(t) body := gzipBytes(fixture(t, "GeoIP2-City-Test.mmdb")) f, _ := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasSuffix(r.URL.Path, "-2026-09.mmdb.gz") { w.Write(body) return } http.NotFound(w, r) })) if err := f.Check(context.Background()); err != nil { t.Fatal(err) } if cur, _, _, _ := f.pub.Current(context.Background()); cur.Edition != "2026-09" { t.Fatalf("edition = %q, want last month's", cur.Edition) } } func TestFeedNotFoundCountsTowardTheAlert(t *testing.T) { f, s := newTestFeeder(t, "dbip", http.NotFoundHandler()) for i := 1; i <= 7; i++ { if err := f.Check(context.Background()); !errors.Is(err, errFeedNotFound) { t.Fatalf("check %d = %v", i, err) } st := feedStatus(t, s) if st.ConsecutiveNotFound != i || st.Failing() != (i >= 7) { t.Fatalf("after %d checks feed.json = %+v", i, st) } } } func maxmindHandler(t *testing.T, archive []byte, sum string, lastModified string, gets *atomic.Int32) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() if !ok || user != "acct" || pass != "s3cret-key" { http.Error(w, "no auth", http.StatusUnauthorized) return } if strings.Contains(r.URL.String(), "s3cret") { t.Error("the key appeared in a URL") } w.Header().Set("Last-Modified", lastModified) if r.Method == http.MethodHead { return } gets.Add(1) switch r.URL.Query().Get("suffix") { case "tar.gz.sha256": w.Write([]byte(sum + " GeoLite2-City_20261006.tar.gz\n")) case "tar.gz": w.Write(archive) default: http.NotFound(w, r) } }) } func TestFeedMaxMindHeadThenChecksumThenArchive(t *testing.T) { withFixtureProbes(t) archive := tarGzBytes("GeoLite2-City_20261006/GeoLite2-City.mmdb", fixture(t, "GeoIP2-City-Test.mmdb")) sum := sha256.Sum256(archive) var gets atomic.Int32 f, _ := newTestFeeder(t, "maxmind", maxmindHandler(t, archive, hex.EncodeToString(sum[:]), "Tue, 06 Oct 2026 00:00:00 GMT", &gets)) if err := f.Check(context.Background()); err != nil { t.Fatal(err) } cur, _, _, _ := f.pub.Current(context.Background()) if cur.Edition != "Tue, 06 Oct 2026 00:00:00 GMT" { t.Fatalf("edition = %q", cur.Edition) } before := gets.Load() if err := f.Check(context.Background()); err != nil { t.Fatal(err) } if gets.Load() != before { t.Fatal("an unchanged Last-Modified still downloaded") } } func TestFeedMaxMindWrongChecksumPublishesNothing(t *testing.T) { withFixtureProbes(t) archive := tarGzBytes("x/GeoLite2-City.mmdb", fixture(t, "GeoIP2-City-Test.mmdb")) var gets atomic.Int32 f, s := newTestFeeder(t, "maxmind", maxmindHandler(t, archive, strings.Repeat("0", 64), "Tue, 06 Oct 2026 00:00:00 GMT", &gets)) if err := f.Check(context.Background()); err == nil { t.Fatal("a bad checksum published") } if _, _, present, _ := f.pub.Current(context.Background()); present { t.Fatal("pointer written despite the checksum") } if st := feedStatus(t, s); st.LastFailure != "checksum" || st.ConsecutiveFailures != 1 { t.Fatalf("feed.json = %+v", st) } if entries, _ := os.ReadDir(f.pub.tmpRoot); len(entries) != 0 { t.Fatalf("staging left behind: %v", entries) } } func TestFeedRedirectDropsCredentialsAcrossHosts(t *testing.T) { var sawAuth atomic.Bool other := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "" { sawAuth.Store(true) } w.WriteHeader(http.StatusTeapot) })) t.Cleanup(other.Close) f, _ := newTestFeeder(t, "maxmind", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, other.URL+"/presigned", http.StatusFound) })) res, err := f.get(context.Background(), f.maxmindBase+"/x", true) if err != nil { t.Fatal(err) } res.Body.Close() if sawAuth.Load() { t.Fatal("the MaxMind credentials followed a redirect to another host") } loop := 0 f2, _ := newTestFeeder(t, "maxmind", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { loop++ http.Redirect(w, r, "/again", http.StatusFound) })) if _, err := f2.get(context.Background(), f2.maxmindBase+"/x", true); err == nil { t.Fatal("an endless redirect was followed") } } func TestFeedRefusesADecompressionBomb(t *testing.T) { var buf bytes.Buffer zw := gzip.NewWriter(&buf) zero := make([]byte, 1<<20) for i := 0; i < 8; i++ { zw.Write(zero) } zw.Close() bomb := buf.Bytes() f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(bomb) })) f.maxExpanded = 1 << 20 if err := f.Check(context.Background()); err == nil { t.Fatal("a bomb was accepted") } if st := feedStatus(t, s); st.LastFailure != "too large" { t.Fatalf("feed.json = %+v", st) } if entries, _ := os.ReadDir(f.pub.tmpRoot); len(entries) != 0 { t.Fatalf("staging left behind: %v", entries) } } func TestFeedRefusesTheWrongProductEvenFirstTime(t *testing.T) { withFixtureProbes(t) body := gzipBytes(fixture(t, "GeoIP2-Country-Test.mmdb")) // feed says City f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(body) })) if err := f.Check(context.Background()); err == nil { t.Fatal("a Country database was published by a City feed") } if st := feedStatus(t, s); st.LastFailure != "wrong type" { t.Fatalf("feed.json = %+v", st) } } func TestFeedSkipsWhileSomeoneIsPublishing(t *testing.T) { body := gzipBytes(fixture(t, "GeoIP2-City-Test.mmdb")) f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(body) })) f.pub.mu.Lock() err := f.Check(context.Background()) f.pub.mu.Unlock() if !errors.Is(err, errGeoipBusy) { t.Fatalf("check while busy = %v", err) } if st := feedStatus(t, s); st.ConsecutiveFailures != 0 { t.Fatalf("a busy slot counted as a failure: %+v", st) } } // Code review R2-2: a check that used up its deadline still records the // failure. func TestFeedRecordsAFailureAfterItsDeadline(t *testing.T) { f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { select { case <-r.Context().Done(): case <-time.After(5 * time.Second): } })) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() if err := f.Check(ctx); err == nil { t.Fatal("a stalled provider succeeded") } if st := feedStatus(t, s); st.ConsecutiveFailures != 1 || st.LastFailure != "download" { t.Fatalf("feed.json after a timeout = %+v", st) } } // Code review R2-1: an oversized feed.json is replaced, never read whole. func TestFeedReplacesAnOversizedStatus(t *testing.T) { f, s := newTestFeeder(t, "dbip", http.NotFoundHandler()) s.Put(context.Background(), geoip.FeedKey, bytes.Repeat([]byte("x"), geoip.MaxPointerBytes+10), "") f.Check(context.Background()) if st := feedStatus(t, s); st.ConsecutiveNotFound != 1 { t.Fatalf("feed.json = %+v", st) } } type flakyFeedStore struct { release.Store failOpen atomic.Bool } func (s *flakyFeedStore) Open(ctx context.Context, key string) (io.ReadCloser, string, error) { if key == geoip.FeedKey && s.failOpen.Load() { return nil, "", errors.New("transient") } return release.OpenObject(ctx, s.Store, key) } // Code review R3-2: an unreadable feed.json is never overwritten. func TestFeedNeverOverwritesAnUnreadableHistory(t *testing.T) { f, s := newTestFeeder(t, "dbip", http.NotFoundHandler()) raw, _ := json.Marshal(geoip.FeedStatus{V: 1, Feed: f.spec.Name, Box: "b", ConsecutiveFailures: 7}) s.Put(context.Background(), geoip.FeedKey, raw, "") flaky := &flakyFeedStore{Store: s} flaky.failOpen.Store(true) f.store = flaky f.Check(context.Background()) if st := feedStatus(t, s); st.ConsecutiveFailures != 7 { t.Fatalf("history rewritten after an unreadable read: %+v", st) } } type failingDeleteStore struct { release.Store fails atomic.Int32 } func (s *failingDeleteStore) Delete(ctx context.Context, key string) error { if s.fails.Add(-1) >= 0 { return errors.New("transient") } return s.Store.Delete(ctx, key) } // Code review R6-1: a failed clear is retried until it lands. func TestRetryClearFeedStatusRecoversWithoutARestart(t *testing.T) { base := release.NewDirStore(t.TempDir()) base.Put(context.Background(), geoip.FeedKey, []byte(`{"v":1}`), "") s := &failingDeleteStore{Store: base} s.fails.Store(2) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() retryClearFeedStatus(ctx, s, time.Millisecond, 4*time.Millisecond) if _, _, err := base.Get(context.Background(), geoip.FeedKey); !errors.Is(err, os.ErrNotExist) { t.Fatalf("feed.json survived the retries: %v", err) } } // Code review R7-1: a Last-Modified that is not an HTTP date never becomes // an edition, and the published generation survives. func TestFeedRefusesAnInvalidEdition(t *testing.T) { withFixtureProbes(t) archive := tarGzBytes("x/GeoLite2-City.mmdb", fixture(t, "GeoIP2-City-Test.mmdb")) sum := sha256.Sum256(archive) var gets atomic.Int32 f, s := newTestFeeder(t, "maxmind", maxmindHandler(t, archive, hex.EncodeToString(sum[:]), strings.Repeat("x", 70<<10), &gets)) good, err := f.pub.Publish(context.Background(), bytes.NewReader(fixture(t, "GeoIP2-Country-Test.mmdb")), geoipPublishOpts{Origin: "manual"}) if err != nil { t.Fatal(err) } if err := f.Check(context.Background()); err == nil { t.Fatal("a junk Last-Modified was accepted") } if cur, _, _, _ := f.pub.Current(context.Background()); cur.SHA256 != good.SHA256 { t.Fatal("the usable generation was replaced") } if gets.Load() != 0 { t.Fatal("downloaded on a junk Last-Modified") } _ = s } // Code review R7-2: re-enabling a feed whose edition is already published // records a success, not "not run yet". func TestFeedUnchangedEditionIsASuccess(t *testing.T) { withFixtureProbes(t) body := gzipBytes(fixture(t, "GeoIP2-City-Test.mmdb")) f, s := newTestFeeder(t, "dbip", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write(body) })) if err := f.Check(context.Background()); err != nil { t.Fatal(err) } s.Delete(context.Background(), geoip.FeedKey) // disabled and cleared, then re-enabled if err := f.Check(context.Background()); err != nil { t.Fatal(err) } if st := feedStatus(t, s); st.LastSuccess.IsZero() || st.LastEdition != "2026-10" { t.Fatalf("feed.json after an unchanged check = %+v", st) } }