carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "io"
8 "net/http"
9 "net/http/httptest"
10 "os"
11 "path/filepath"
12 "strings"
13 "sync/atomic"
14 "testing"
15 "time"
16)
17
18func TestSystemVerbKnowsGeoip(t *testing.T) {
19 if verb, rest, _ := systemVerb([]string{"geoip", "publish", "x.mmdb"}); verb != "geoip" || len(rest) != 2 {
20 t.Fatalf("systemVerb = %q %v", verb, rest)
21 }
22}
23
24func TestPublishGeoipStreamsARawBodyAndFollowsTheJob(t *testing.T) {
25 geoipPollInterval = time.Millisecond
26 t.Cleanup(func() { geoipPollInterval = 2 * time.Second })
27 db := bytes.Repeat([]byte("m"), 4096)
28 path := filepath.Join(t.TempDir(), "db.mmdb")
29 if err := os.WriteFile(path, db, 0o600); err != nil {
30 t.Fatal(err)
31 }
32 var polls atomic.Int32
33 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
34 switch {
35 case r.Method == http.MethodPut && r.URL.Path == "/api/cli/system/geoip":
36 if r.Header.Get("Content-Type") != "application/octet-stream" || r.ContentLength != int64(len(db)) {
37 http.Error(w, "bad upload headers", http.StatusBadRequest)
38 return
39 }
40 if r.URL.Query().Get("force") != "1" {
41 http.Error(w, "force not passed", http.StatusBadRequest)
42 return
43 }
44 b, _ := io.ReadAll(r.Body)
45 if !bytes.Equal(b, db) {
46 http.Error(w, "body mangled", http.StatusBadRequest)
47 return
48 }
49 w.WriteHeader(http.StatusAccepted)
50 w.Write([]byte(`{"job":"j1"}`))
51 case r.URL.Path == "/api/cli/system/jobs/j1":
52 if polls.Add(1) < 2 {
53 w.Write([]byte(`{"steps":["received"],"done":false,"error":""}`))
54 return
55 }
56 w.Write([]byte(`{"steps":["received","verifying"],"done":true,"error":"","result":{"sha256":"9f2c0000aaaa","database_type":"DBIP-City-Lite","build_epoch":1790812800}}`))
57 default:
58 http.NotFound(w, r)
59 }
60 }))
61 t.Cleanup(ts.Close)
62 sess := &apiSession{c: newConsoleClient(ts.URL, "tok"), console: ts.URL}
63 var out bytes.Buffer
64 ptr, err := sess.publishGeoip(context.Background(), path, true, &out)
65 if err != nil {
66 t.Fatal(err)
67 }
68 if ptr.DatabaseType != "DBIP-City-Lite" || ptr.SHA256 != "9f2c0000aaaa" {
69 t.Fatalf("pointer = %+v", ptr)
70 }
71 if !strings.Contains(out.String(), "verifying") {
72 t.Fatalf("steps not printed: %q", out.String())
73 }
74}
75
76func TestPublishGeoipReportsTheJobsRefusal(t *testing.T) {
77 geoipPollInterval = time.Millisecond
78 t.Cleanup(func() { geoipPollInterval = 2 * time.Second })
79 path := filepath.Join(t.TempDir(), "db.mmdb")
80 os.WriteFile(path, []byte("junk"), 0o600)
81 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82 if r.Method == http.MethodPut {
83 io.Copy(io.Discard, r.Body)
84 w.WriteHeader(http.StatusAccepted)
85 w.Write([]byte(`{"job":"j2"}`))
86 return
87 }
88 json.NewEncoder(w).Encode(map[string]any{"steps": []string{}, "done": true, "error": "geoip database refused: not a valid MaxMind-format database"})
89 }))
90 t.Cleanup(ts.Close)
91 sess := &apiSession{c: newConsoleClient(ts.URL, "tok"), console: ts.URL}
92 if _, err := sess.publishGeoip(context.Background(), path, false, io.Discard); err == nil || !strings.Contains(err.Error(), "not a valid MaxMind-format database") {
93 t.Fatalf("err = %v", err)
94 }
95}
96
97func TestPrintGeoipStatusVerdicts(t *testing.T) {
98 var resp geoipStatusResponse
99 json.Unmarshal([]byte(`{"pointer":{"sha256":"9f2c0000aaaa","database_type":"GeoLite2-City","build":"2026-10-01T00:00:00Z","origin":"manual","expires_at":"2026-10-31T00:00:00Z","attribution":"This product includes GeoLite2 data"},
100 "boxes":[{"label":"ie-1","state":"ok"},{"label":"node-1","fleet":"tito","state":"old binary"}],
101 "ready":false,"blocking":["tito/node-1: runs a binary that does not strip forged geo headers"]}`), &resp)
102 var out bytes.Buffer
103 printGeoipStatus(&out, resp, time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC))
104 s := out.String()
105 for _, want := range []string{"GeoLite2-City", "5 days ago", "expires 2026-10-31", "tito/node-1", "not ready", "1 in the way"} {
106 if !strings.Contains(s, want) {
107 t.Errorf("status lacks %q:\n%s", want, s)
108 }
109 }
110 out.Reset()
111 printGeoipStatus(&out, geoipStatusResponse{Ready: true}, time.Now())
112 if !strings.Contains(out.String(), "none published") || !strings.Contains(out.String(), "ready") {
113 t.Errorf("empty status = %q", out.String())
114 }
115}
116
117func TestPrintGeoipOffWarnsAboutTheFeed(t *testing.T) {
118 var out bytes.Buffer
119 printGeoipOff(&out, true)
120 if !strings.Contains(out.String(), "unset CONSOLE_GEOIP_FEED") {
121 t.Fatalf("off with the feed set = %q", out.String())
122 }
123 out.Reset()
124 printGeoipOff(&out, false)
125 if strings.Contains(out.String(), "CONSOLE_GEOIP_FEED") {
126 t.Fatalf("off without a feed mentioned it: %q", out.String())
127 }
128}
129
130func TestPrintGeoipStatusFeedLine(t *testing.T) {
131 var resp geoipStatusResponse
132 json.Unmarshal([]byte(`{"feed":{"name":"dbip-city-lite","failures":3,"last_failure":"download"},"ready":true}`), &resp)
133 var out bytes.Buffer
134 printGeoipStatus(&out, resp, time.Now())
135 if !strings.Contains(out.String(), "feed: dbip-city-lite, failing (3 in a row, last: download)") {
136 t.Fatalf("feed line = %q", out.String())
137 }
138 json.Unmarshal([]byte(`{"feed":{"name":"dbip-city-lite","last_success":"2026-10-01T00:00:00Z"},"ready":true}`), &resp)
139 resp.Feed.Failures = 0
140 out.Reset()
141 printGeoipStatus(&out, resp, time.Now())
142 if !strings.Contains(out.String(), "feed: dbip-city-lite, last success 2026-10-01") {
143 t.Fatalf("feed line = %q", out.String())
144 }
145}
146