package geoip import ( "net/http" "testing" "github.com/carlosframework/platform/internal/geo" ) func TestFromSnapshotStates(t *testing.T) { if a := FromSnapshot(geo.Snapshot{}); a.State != StateOff { t.Fatalf("not loaded = %+v, want off", a) } if a := FromSnapshot(geo.Snapshot{Loaded: true, Source: "DBIP-City-Lite"}); a.State != StateMiss || a.Source != "DBIP-City-Lite" { t.Fatalf("loaded miss = %+v", a) } hit := FromSnapshot(geo.Snapshot{Loaded: true, Found: true, Source: "GeoLite2-City", Result: geo.Result{Country: "GB", Subdivision: "GB-ENG", Point: geo.Point{Lat: 51.5074, Lon: -0.1278}, HasPoint: true}}) if hit.State != StateHit || hit.Lat != 51.51 || hit.Lon != -0.13 { t.Fatalf("hit = %+v (coordinates must be rounded to 2 places)", hit) } } func TestWriteAppHeaders(t *testing.T) { h := http.Header{} h.Set(HeaderCountry, "XX") // stale value must go Answer{State: StateHit, Country: "IE", Source: "DBIP-City-Lite", Lat: 53.34, Lon: -6.27, HasPoint: true}.WriteApp(h) if h.Get(HeaderCountry) != "IE" || h.Get(HeaderLat) != "53.34" || h.Get(HeaderLon) != "-6.27" || h.Get(HeaderSource) != "DBIP-City-Lite" || h.Get(HeaderSubdivision) != "" || h.Get(HeaderHop) != "" { t.Fatalf("headers = %v", h) } h2 := http.Header{} h2.Set(HeaderSource, "forged") Answer{State: StateMiss, Source: "DBIP-City-Lite"}.WriteApp(h2) if len(h2) != 0 { t.Fatalf("a miss sends no app headers, not even Source: %v", h2) } } func TestHopValueRoundTrip(t *testing.T) { for _, a := range []Answer{ {State: StateHit, Country: "GB", Subdivision: "GB-ENG", Source: "GeoLite2-City", Lat: 51.51, Lon: -0.09, HasPoint: true}, {State: StateHit, Source: "GeoLite2-City", Lat: 10, Lon: 20, HasPoint: true}, // coordinate-only (codex R3-3) {State: StateMiss, Source: "DBIP-City-Lite"}, Off(), } { h := http.Header{} a.WriteApp(h) h.Set(HeaderHop, a.HopValue()) got := Capture(h).Adopt() if got != a { t.Errorf("round trip %+v -> %+v", a, got) } if len(h) != 0 { t.Errorf("Capture must strip every geo header, left %v", h) } } } func TestAdoptRefusesWhatItCannotTrust(t *testing.T) { cases := map[string]http.Header{ "no hop value": {HeaderCountry: {"GB"}, HeaderSource: {"x"}}, "malformed hop value": {HeaderHop: {"maybe"}}, "hit with nothing": {HeaderHop: {"hit;src=x"}}, "hit with bad country": {HeaderHop: {"hit;src=x"}, HeaderCountry: {"gb"}}, "bad source": {HeaderHop: {"hit;src=a b"}, HeaderCountry: {"GB"}}, } for name, h := range cases { if got := Capture(h).Adopt(); got.State != StateOff { t.Errorf("%s: adopted %+v, want off", name, got) } } // Field-level: an invalid subdivision or half a coordinate pair is // dropped on its own, the rest survives. h := http.Header{HeaderHop: {"hit;src=GeoLite2-City"}, HeaderCountry: {"GB"}, HeaderSubdivision: {"FR-IDF"}, HeaderLat: {"91"}, HeaderLon: {"0"}} got := Capture(h).Adopt() if got.State != StateHit || got.Country != "GB" || got.Subdivision != "" || got.HasPoint { t.Fatalf("partial = %+v", got) } } // Plan review R1-1: the whole reserved namespace goes, not just today's names. func TestStripAllRemovesTheWholeNamespace(t *testing.T) { h := http.Header{} h.Set("X-Carlos-Geo-City", "Atlantis") h.Set("X-Carlos-Geo-Country", "KP") h["x-carlos-geo-asn"] = []string{"1"} // non-canonical key, as a hand-built map might hold h.Set(HeaderHop, "hit;src=x") h.Set("X-Carlos-Version", "keep me") h.Set("X-Geo-Country", "keep me too") StripAll(h) if len(h) != 2 || h.Get("X-Carlos-Version") == "" || h.Get("X-Geo-Country") == "" { t.Fatalf("after strip = %v", h) } h2 := http.Header{} h2.Set("X-Carlos-Geo-City", "Atlantis") Capture(h2) if len(h2) != 0 { t.Fatalf("Capture must strip the namespace too: %v", h2) } } // Code review R12-1: underscore spellings are the same header to CGI/WSGI. func TestStripAllCatchesUnderscoreSpellings(t *testing.T) { h := http.Header{} for _, k := range []string{"X_Carlos_Geo_Country", "X_CARLOS_GEO_SOURCE", "X-Carlos_Geo-Lat", "X_Carlos_Hop_Geo", "x_carlos_hop_geo"} { h[k] = []string{"forged"} } h.Set("X-Carlos-Version", "keep") h["X_Forwarded_For"] = []string{"keep"} StripAll(h) if len(h) != 2 { t.Fatalf("after strip = %v", h) } }