| 1 | package geoip |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | "time" |
| 7 | ) |
| 8 | |
| 9 | const sha = "9f2c000000000000000000000000000000000000000000000000000000000001" |
| 10 | |
| 11 | func TestParsePointerAcceptsTheSpecShape(t *testing.T) { |
| 12 | raw := `{"v":1,"sha256":"` + sha + `","key":"control/geoip/` + sha + `.mmdb","size":10, |
| 13 | "database_type":"DBIP-City-Lite","build_epoch":1790812800,"node_count":7, |
| 14 | "published":"2026-10-06T21:00:00Z","origin":"manual","edition":""}` |
| 15 | p, err := ParsePointer([]byte(raw)) |
| 16 | if err != nil { |
| 17 | t.Fatal(err) |
| 18 | } |
| 19 | if p.Key != BlobKey(sha) || p.Size != 10 || p.DatabaseType != "DBIP-City-Lite" || p.Build().Unix() != 1790812800 { |
| 20 | t.Fatalf("pointer = %+v", p) |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | func TestParsePointerRefusesWhatAnEdgeMustNotFollow(t *testing.T) { |
| 25 | good := `"sha256":"` + sha + `","key":"control/geoip/` + sha + `.mmdb","size":10,"database_type":"X","build_epoch":1` |
| 26 | bad := map[string]string{ |
| 27 | "unknown version": `{"v":2,` + good + `}`, |
| 28 | "short sha": `{"v":1,"sha256":"9f","key":"control/geoip/9f.mmdb","size":1,"database_type":"X","build_epoch":1}`, |
| 29 | "key not the blob key": `{"v":1,"sha256":"` + sha + `","key":"apps/x/secret","size":10,"database_type":"X","build_epoch":1}`, |
| 30 | "zero size": `{"v":1,"sha256":"` + sha + `","key":"control/geoip/` + sha + `.mmdb","size":0,"database_type":"X","build_epoch":1}`, |
| 31 | "oversize": `{"v":1,"sha256":"` + sha + `","key":"control/geoip/` + sha + `.mmdb","size":1073741825,"database_type":"X","build_epoch":1}`, |
| 32 | "not json": `nope`, |
| 33 | } |
| 34 | for name, raw := range bad { |
| 35 | if _, err := ParsePointer([]byte(raw)); err == nil { |
| 36 | t.Errorf("%s: accepted", name) |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | |
| 41 | func TestGeoLiteExpiresThirtyDaysAfterBuild(t *testing.T) { |
| 42 | build := time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC) |
| 43 | at, ok := ExpiresAt("GeoLite2-City", build) |
| 44 | if !ok || !at.Equal(build.Add(30*24*time.Hour)) { |
| 45 | t.Fatalf("ExpiresAt = (%v, %v)", at, ok) |
| 46 | } |
| 47 | if Expired("GeoLite2-City", build, build.Add(30*24*time.Hour-time.Second)) { |
| 48 | t.Fatal("expired a second early") |
| 49 | } |
| 50 | if !Expired("GeoLite2-Country", build, build.Add(30*24*time.Hour)) { |
| 51 | t.Fatal("not expired at 30 days") |
| 52 | } |
| 53 | if Expired("DBIP-City-Lite", build, build.Add(400*24*time.Hour)) { |
| 54 | t.Fatal("DB-IP must never expire (D1)") |
| 55 | } |
| 56 | if _, ok := ExpiresAt("GeoIP2-City", build); ok { |
| 57 | t.Fatal("only GeoLite2- expires") |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | func TestAttributionTable(t *testing.T) { |
| 62 | if a, ok := AttributionFor("DBIP-City-Lite"); !ok || a.Text != "IP Geolocation by DB-IP" || a.URL != "https://db-ip.com" { |
| 63 | t.Fatalf("dbip = %+v %v", a, ok) |
| 64 | } |
| 65 | if a, ok := AttributionFor("GeoLite2-Country"); !ok || !strings.Contains(a.Text, "GeoLite2 data created by MaxMind") { |
| 66 | t.Fatalf("geolite = %+v %v", a, ok) |
| 67 | } |
| 68 | if _, ok := AttributionFor("GeoIP2-City"); ok { |
| 69 | t.Fatal("no attribution is known for commercial GeoIP2; the operator's licence governs") |
| 70 | } |
| 71 | } |
| 72 | |