| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "net" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | "time" |
| 10 | "unicode/utf8" |
| 11 | |
| 12 | "github.com/carlosframework/platform/internal/geoip" |
| 13 | ) |
| 14 | |
| 15 | func TestOpenGeoIPIsOptionalAndNeverFatal(t *testing.T) { |
| 16 | if openGeoIP("") != nil { |
| 17 | t.Fatal("unset CONSOLE_GEOIP_DB must mean no locator") |
| 18 | } |
| 19 | if openGeoIP(filepath.Join(t.TempDir(), "missing.mmdb")) != nil { |
| 20 | t.Fatal("an unopenable file must mean no locator, not a crash") |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | func TestAppRegionsSwitchParses(t *testing.T) { |
| 25 | for v, want := range map[string]bool{"on": true, "ON": true, " on ": true, "": false, "off": false, "true": false} { |
| 26 | if got := appRegionsSwitch(v); got != want { |
| 27 | t.Errorf("appRegionsSwitch(%q) = %v, want %v", v, got, want) |
| 28 | } |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | // OpenMMDB's error can carry decoded database contents over many lines, so |
| 33 | // the console logs only its first line, capped at 200 bytes. |
| 34 | func TestFirstLineCapped(t *testing.T) { |
| 35 | cases := []struct { |
| 36 | name string |
| 37 | err error |
| 38 | want string |
| 39 | }{ |
| 40 | {"single line", errors.New("open x: no such file"), "open x: no such file"}, |
| 41 | {"multi-line keeps the first", errors.New("bad metadata\nsecret decoded body\nmore"), "bad metadata"}, |
| 42 | {"crlf", errors.New("first\r\nsecond"), "first"}, |
| 43 | {"leading newline is an empty first line", errors.New("\nbody"), ""}, |
| 44 | {"exactly 200 bytes", errors.New(strings.Repeat("a", 200)), strings.Repeat("a", 200)}, |
| 45 | {"long line is cut at 200 bytes", errors.New(strings.Repeat("a", 500)), strings.Repeat("a", 200)}, |
| 46 | {"long first line with more lines", errors.New(strings.Repeat("b", 300) + "\nrest"), strings.Repeat("b", 200)}, |
| 47 | {"nil", nil, ""}, |
| 48 | } |
| 49 | for _, c := range cases { |
| 50 | if got := firstLineCapped(c.err); got != c.want { |
| 51 | t.Errorf("%s: firstLineCapped = %q, want %q", c.name, got, c.want) |
| 52 | } |
| 53 | } |
| 54 | // A cut must not leave half of a multi-byte rune. |
| 55 | got := firstLineCapped(errors.New(strings.Repeat("é", 150))) |
| 56 | if len(got) > 200 || !utf8.ValidString(got) { |
| 57 | t.Errorf("cut through a rune: len %d valid %v", len(got), utf8.ValidString(got)) |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | // Codex R10-1: the override obeys D1. The repo's fixtures are GeoIP2 (they |
| 62 | // never expire), so the tick is driven with a clock far in the future and |
| 63 | // checked to leave them alone, and the Expired rule itself is geoip's own, |
| 64 | // tested there. |
| 65 | func TestGeoIPOverrideExpiryTick(t *testing.T) { |
| 66 | sw := openGeoIPAt("../../internal/geo/testdata/GeoIP2-City-Test.mmdb", time.Now()) |
| 67 | if sw == nil { |
| 68 | t.Fatal("fixture did not open") |
| 69 | } |
| 70 | if geoIPExpiryTick(sw, "x", time.Now().Add(400*24*time.Hour)) { |
| 71 | t.Fatal("a GeoIP2 database must never expire") |
| 72 | } |
| 73 | if _, ok := sw.Locate(net.ParseIP("81.2.69.142")); !ok { |
| 74 | t.Fatal("the override stopped answering") |
| 75 | } |
| 76 | sw.Swap(nil) |
| 77 | if !geoIPExpiryTick(sw, "x", time.Now()) { |
| 78 | t.Fatal("an empty reader leaves nothing to watch") |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | func TestGeoIPOverrideIsClosedAtExpiryAndRefusedWhenExpired(t *testing.T) { |
| 83 | const fx = "../../internal/geo/testdata/GeoIP2-City-Test.mmdb" |
| 84 | sw := openGeoIPAt(fx, time.Now()) |
| 85 | geoipExpired = func(string, time.Time, time.Time) bool { return true } |
| 86 | t.Cleanup(func() { geoipExpired = geoip.Expired }) |
| 87 | if !geoIPExpiryTick(sw, fx, time.Now()) { |
| 88 | t.Fatal("an expired override was not closed") |
| 89 | } |
| 90 | if _, ok := sw.Locate(net.ParseIP("81.2.69.142")); ok { |
| 91 | t.Fatal("region guesses still use an expired override") |
| 92 | } |
| 93 | if openGeoIPAt(fx, time.Now()) != nil { |
| 94 | t.Fatal("an already-expired override was opened at startup") |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | func TestConsoleGeoReaderNeverYieldsATypedNil(t *testing.T) { |
| 99 | sw := consoleGeoReader(filepath.Join(t.TempDir(), "missing.mmdb")) |
| 100 | if sw != nil { |
| 101 | t.Fatal("an unusable override must give no reader") |
| 102 | } |
| 103 | if geoLocator(sw) != nil { |
| 104 | t.Fatal("a nil reader must be an untyped nil Locator") |
| 105 | } |
| 106 | } |
| 107 | |