carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package geoip
2
3import (
4 "net/http"
5 "testing"
6
7 "github.com/carlosframework/platform/internal/geo"
8)
9
10func TestFromSnapshotStates(t *testing.T) {
11 if a := FromSnapshot(geo.Snapshot{}); a.State != StateOff {
12 t.Fatalf("not loaded = %+v, want off", a)
13 }
14 if a := FromSnapshot(geo.Snapshot{Loaded: true, Source: "DBIP-City-Lite"}); a.State != StateMiss || a.Source != "DBIP-City-Lite" {
15 t.Fatalf("loaded miss = %+v", a)
16 }
17 hit := FromSnapshot(geo.Snapshot{Loaded: true, Found: true, Source: "GeoLite2-City",
18 Result: geo.Result{Country: "GB", Subdivision: "GB-ENG", Point: geo.Point{Lat: 51.5074, Lon: -0.1278}, HasPoint: true}})
19 if hit.State != StateHit || hit.Lat != 51.51 || hit.Lon != -0.13 {
20 t.Fatalf("hit = %+v (coordinates must be rounded to 2 places)", hit)
21 }
22}
23
24func TestWriteAppHeaders(t *testing.T) {
25 h := http.Header{}
26 h.Set(HeaderCountry, "XX") // stale value must go
27 Answer{State: StateHit, Country: "IE", Source: "DBIP-City-Lite", Lat: 53.34, Lon: -6.27, HasPoint: true}.WriteApp(h)
28 if h.Get(HeaderCountry) != "IE" || h.Get(HeaderLat) != "53.34" || h.Get(HeaderLon) != "-6.27" ||
29 h.Get(HeaderSource) != "DBIP-City-Lite" || h.Get(HeaderSubdivision) != "" || h.Get(HeaderHop) != "" {
30 t.Fatalf("headers = %v", h)
31 }
32 h2 := http.Header{}
33 h2.Set(HeaderSource, "forged")
34 Answer{State: StateMiss, Source: "DBIP-City-Lite"}.WriteApp(h2)
35 if len(h2) != 0 {
36 t.Fatalf("a miss sends no app headers, not even Source: %v", h2)
37 }
38}
39
40func TestHopValueRoundTrip(t *testing.T) {
41 for _, a := range []Answer{
42 {State: StateHit, Country: "GB", Subdivision: "GB-ENG", Source: "GeoLite2-City", Lat: 51.51, Lon: -0.09, HasPoint: true},
43 {State: StateHit, Source: "GeoLite2-City", Lat: 10, Lon: 20, HasPoint: true}, // coordinate-only (codex R3-3)
44 {State: StateMiss, Source: "DBIP-City-Lite"},
45 Off(),
46 } {
47 h := http.Header{}
48 a.WriteApp(h)
49 h.Set(HeaderHop, a.HopValue())
50 got := Capture(h).Adopt()
51 if got != a {
52 t.Errorf("round trip %+v -> %+v", a, got)
53 }
54 if len(h) != 0 {
55 t.Errorf("Capture must strip every geo header, left %v", h)
56 }
57 }
58}
59
60func TestAdoptRefusesWhatItCannotTrust(t *testing.T) {
61 cases := map[string]http.Header{
62 "no hop value": {HeaderCountry: {"GB"}, HeaderSource: {"x"}},
63 "malformed hop value": {HeaderHop: {"maybe"}},
64 "hit with nothing": {HeaderHop: {"hit;src=x"}},
65 "hit with bad country": {HeaderHop: {"hit;src=x"}, HeaderCountry: {"gb"}},
66 "bad source": {HeaderHop: {"hit;src=a b"}, HeaderCountry: {"GB"}},
67 }
68 for name, h := range cases {
69 if got := Capture(h).Adopt(); got.State != StateOff {
70 t.Errorf("%s: adopted %+v, want off", name, got)
71 }
72 }
73 // Field-level: an invalid subdivision or half a coordinate pair is
74 // dropped on its own, the rest survives.
75 h := http.Header{HeaderHop: {"hit;src=GeoLite2-City"}, HeaderCountry: {"GB"},
76 HeaderSubdivision: {"FR-IDF"}, HeaderLat: {"91"}, HeaderLon: {"0"}}
77 got := Capture(h).Adopt()
78 if got.State != StateHit || got.Country != "GB" || got.Subdivision != "" || got.HasPoint {
79 t.Fatalf("partial = %+v", got)
80 }
81}
82
83// Plan review R1-1: the whole reserved namespace goes, not just today's names.
84func TestStripAllRemovesTheWholeNamespace(t *testing.T) {
85 h := http.Header{}
86 h.Set("X-Carlos-Geo-City", "Atlantis")
87 h.Set("X-Carlos-Geo-Country", "KP")
88 h["x-carlos-geo-asn"] = []string{"1"} // non-canonical key, as a hand-built map might hold
89 h.Set(HeaderHop, "hit;src=x")
90 h.Set("X-Carlos-Version", "keep me")
91 h.Set("X-Geo-Country", "keep me too")
92 StripAll(h)
93 if len(h) != 2 || h.Get("X-Carlos-Version") == "" || h.Get("X-Geo-Country") == "" {
94 t.Fatalf("after strip = %v", h)
95 }
96 h2 := http.Header{}
97 h2.Set("X-Carlos-Geo-City", "Atlantis")
98 Capture(h2)
99 if len(h2) != 0 {
100 t.Fatalf("Capture must strip the namespace too: %v", h2)
101 }
102}
103
104// Code review R12-1: underscore spellings are the same header to CGI/WSGI.
105func TestStripAllCatchesUnderscoreSpellings(t *testing.T) {
106 h := http.Header{}
107 for _, k := range []string{"X_Carlos_Geo_Country", "X_CARLOS_GEO_SOURCE", "X-Carlos_Geo-Lat", "X_Carlos_Hop_Geo", "x_carlos_hop_geo"} {
108 h[k] = []string{"forged"}
109 }
110 h.Set("X-Carlos-Version", "keep")
111 h["X_Forwarded_For"] = []string{"keep"}
112 StripAll(h)
113 if len(h) != 2 {
114 t.Fatalf("after strip = %v", h)
115 }
116}
117