carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "net"
6 "os"
7 "path/filepath"
8 "strconv"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/carlosframework/platform/internal/geo"
14 "github.com/carlosframework/platform/internal/geoip"
15)
16
17func writeLocal(t *testing.T, dir, name string, body []byte, typ string, build time.Time) string {
18 t.Helper()
19 os.MkdirAll(dir, 0o755)
20 sha := name
21 if err := os.WriteFile(filepath.Join(dir, sha+".mmdb"), body, 0o644); err != nil {
22 t.Fatal(err)
23 }
24 cur := `{"sha256":"` + sha + `","database_type":"` + typ + `","build_epoch":` + itoa(build.Unix()) + `}`
25 if err := os.WriteFile(filepath.Join(dir, geoip.LocalCurrentFile), []byte(cur), 0o644); err != nil {
26 t.Fatal(err)
27 }
28 return sha
29}
30
31func itoa(n int64) string { return strconv.FormatInt(n, 10) }
32
33func TestFollowerTracksTheEdgesGeneration(t *testing.T) {
34 dir := filepath.Join(t.TempDir(), "geoip")
35 sw := geo.NewSwappable()
36 f := NewGeoipFollower(dir, sw)
37 f.Tick() // nothing yet: the normal empty start
38 if _, ok := sw.Locate(net.ParseIP("81.2.69.142")); ok {
39 t.Fatal("located with nothing published")
40 }
41 shaA := strings.Repeat("a", 64)
42 writeLocal(t, dir, shaA, fixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now())
43 f.Tick()
44 if snap := sw.Lookup(net.ParseIP("81.2.69.142")); !snap.Found || snap.Source != "GeoIP2-City" {
45 t.Fatalf("after publish = %+v", snap)
46 }
47 // Off: the edge empties current.
48 os.WriteFile(filepath.Join(dir, geoip.LocalCurrentFile), []byte(`{}`), 0o644)
49 f.Tick()
50 if sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
51 t.Fatal("still loaded after off")
52 }
53 // current removed entirely: also off.
54 writeLocal(t, dir, shaA, fixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now())
55 f.Tick()
56 os.Remove(filepath.Join(dir, geoip.LocalCurrentFile))
57 f.Tick()
58 if sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
59 t.Fatal("still loaded with current gone")
60 }
61}
62
63func TestFollowerDropsAnExpiredGeoLite(t *testing.T) {
64 dir := filepath.Join(t.TempDir(), "geoip")
65 sw := geo.NewSwappable()
66 f := NewGeoipFollower(dir, sw)
67 sha := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
68 now := time.Now()
69 writeLocal(t, dir, sha, fixture(t, "GeoIP2-City-Test.mmdb"), "GeoLite2-City", now)
70 f.Tick()
71 if !sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
72 t.Fatal("not loaded")
73 }
74 f.now = func() time.Time { return now.Add(31 * 24 * time.Hour) }
75 f.Tick()
76 if sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
77 t.Fatal("an expired GeoLite2 is still in use")
78 }
79}
80
81func TestFollowerKeepsTheDatabaseOnAnUnreadableState(t *testing.T) {
82 dir := filepath.Join(t.TempDir(), "geoip")
83 sw := geo.NewSwappable()
84 f := NewGeoipFollower(dir, sw)
85 sha := "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
86 writeLocal(t, dir, sha, fixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now())
87 f.Tick()
88 os.WriteFile(filepath.Join(dir, geoip.LocalCurrentFile), []byte(`not json`), 0o644)
89 f.Tick()
90 if !sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
91 t.Fatal("a garbled current dropped the database")
92 }
93}
94
95// Code review R3-1: the loaded reader's own expiry is checked every tick,
96// even when `current` is unreadable or names an unopenable generation.
97func TestFollowerExpiresTheLoadedReaderWhateverCurrentSays(t *testing.T) {
98 for name, breakCurrent := range map[string]func(dir string){
99 "unreadable current": func(dir string) {
100 os.WriteFile(filepath.Join(dir, geoip.LocalCurrentFile), []byte("garbled"), 0o644)
101 },
102 "unopenable candidate": func(dir string) {
103 writeLocal(t, dir, strings.Repeat("e", 64), []byte("not a database"), "GeoIP2-City", time.Now())
104 },
105 } {
106 dir := filepath.Join(t.TempDir(), "geoip")
107 sw := geo.NewSwappable()
108 f := NewGeoipFollower(dir, sw)
109 writeLocal(t, dir, strings.Repeat("d", 64), fixture(t, "GeoIP2-City-Test.mmdb"), "GeoIP2-City", time.Now())
110 f.Tick()
111 breakCurrent(dir)
112 followerExpired = func(string, time.Time, time.Time) bool { return true }
113 f.Tick()
114 followerExpired = geoip.Expired
115 if sw.Lookup(net.ParseIP("81.2.69.142")).Loaded {
116 t.Errorf("%s: an expired reader kept serving", name)
117 }
118 }
119}
120
121// Code review R4-4: the guess's credit names the reader that answered.
122func TestRecordingLocatorNamesTheAnsweringReader(t *testing.T) {
123 sw := geo.NewSwappable()
124 rec := &recordingLocator{sw: sw}
125 if _, ok := rec.Locate(net.ParseIP("81.2.69.142")); ok || rec.used {
126 t.Fatal("an empty reader answered")
127 }
128 db, _ := geo.OpenMMDB("../geo/testdata/GeoIP2-City-Test.mmdb")
129 sw.Swap(db)
130 if _, ok := rec.Locate(net.ParseIP("81.2.69.142")); !ok || !rec.used || rec.source != "GeoIP2-City" {
131 t.Fatalf("recorded = %+v", rec)
132 }
133}
134
135// TestRealDatabaseVerifiesInTheChild runs only with GEOIP_REAL_MMDB set:
136// a real database must fit the verifier child's memory cap.
137func TestRealDatabaseVerifiesInTheChild(t *testing.T) {
138 p := os.Getenv("GEOIP_REAL_MMDB")
139 if p == "" {
140 t.Skip("set GEOIP_REAL_MMDB to a real .mmdb")
141 }
142 res, err := verifyInChild(context.Background(), p, []string{"8.8.8.8", "1.1.1.1"})
143 if err != nil || res.Countries != 2 {
144 t.Fatalf("real database in the child = (%+v, %v)", res, err)
145 }
146 t.Logf("%s, %d nodes", res.Source, res.NodeCount)
147}
148