carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package edge
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "sync"
7 "testing"
8
9 "github.com/carlosframework/platform/internal/analytics"
10 "github.com/carlosframework/platform/internal/registry"
11 "github.com/carlosframework/platform/internal/release"
12)
13
14type memAnalytics struct {
15 mu sync.Mutex
16 obs []analytics.Obs
17}
18
19func (m *memAnalytics) Observe(o analytics.Obs) { m.mu.Lock(); m.obs = append(m.obs, o); m.mu.Unlock() }
20func (m *memAnalytics) snap() []analytics.Obs {
21 m.mu.Lock()
22 defer m.mu.Unlock()
23 return append([]analytics.Obs(nil), m.obs...)
24}
25
26func TestClientIP(t *testing.T) {
27 r := httptest.NewRequest("GET", "http://h/", nil)
28 r.RemoteAddr = "203.0.113.5:4444"
29 r.Header.Set("X-Forwarded-For", "198.51.100.1")
30 if ip := clientIP(r); ip != "203.0.113.5" {
31 t.Fatalf("unverified: %q", ip)
32 }
33 r2 := httptest.NewRequest("GET", "http://h/", nil)
34 r2 = r2.WithContext(withHopVerified(r2.Context()))
35 r2.RemoteAddr = "203.0.113.5:4444"
36 r2.Header.Set("X-Forwarded-For", "10.0.0.1, 198.51.100.7")
37 if ip := clientIP(r2); ip != "198.51.100.7" {
38 t.Fatalf("verified hop: %q", ip)
39 }
40}
41
42func TestEdgeObservesOnlyWhenSwitchedOn(t *testing.T) {
43 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44 w.Header().Set("Content-Type", "text/html; charset=utf-8")
45 w.Write([]byte("<h1>hi</h1>"))
46 }))
47 t.Cleanup(backend.Close)
48 reg := openReg(t)
49 rt := registry.Route{Host: "app.example.com", Kind: "service", Addr: backend.Listener.Addr().String(), Status: "ready", App: "web"}
50 if err := reg.Upsert(rt); err != nil {
51 t.Fatal(err)
52 }
53 e, err := New(reg, nil)
54 if err != nil {
55 t.Fatal(err)
56 }
57 m := &memAnalytics{}
58 e.EnableAnalytics(m)
59 srv := httptest.NewServer(e)
60 t.Cleanup(srv.Close)
61 get := func(path string, hdr map[string]string) {
62 req, _ := http.NewRequest("GET", srv.URL+path, nil)
63 req.Host = "app.example.com"
64 for k, v := range hdr {
65 req.Header.Set(k, v)
66 }
67 resp, err := http.DefaultClient.Do(req)
68 if err != nil {
69 t.Fatal(err)
70 }
71 resp.Body.Close()
72 }
73 get("/", nil)
74 if len(m.snap()) != 0 {
75 t.Fatal("nothing is observed while the app is off")
76 }
77 on := map[string]bool{release.OpsAccount + "/web": true}
78 e.SetAnalyticsOn(on)
79 // The switch map is copied, so a caller may keep writing to its own:
80 // the edge reads its copy from every serving goroutine without a lock,
81 // and a shared map would be a fatal concurrent map read-and-write.
82 delete(on, release.OpsAccount+"/web")
83 on["scribble"] = true
84 get("/pricing?x=1", map[string]string{"Referer": "https://news.ycombinator.com/item?id=1", "User-Agent": "Mozilla/5.0 Safari", "Sec-Fetch-Dest": "document"})
85 obs := m.snap()
86 if len(obs) != 1 {
87 t.Fatalf("observed %d", len(obs))
88 }
89 o := obs[0]
90 if o.Acct != release.OpsAccount || o.App != "web" || o.Host != "app.example.com" || o.Method != "GET" || o.Status != 200 ||
91 o.ContentType != "text/html; charset=utf-8" || o.Path != "/pricing" || o.RefererHost != "news.ycombinator.com" ||
92 o.FetchDest != "document" || o.Bytes == 0 || o.ClientIP == "" || o.When.IsZero() {
93 t.Fatalf("obs = %+v", o)
94 }
95 get("/x", map[string]string{"Purpose": "prefetch"})
96 if !m.snap()[1].Prefetch {
97 t.Fatal("prefetch header must be carried")
98 }
99 // A speculation-rules prerender arrives as Sec-Purpose:
100 // prefetch;prerender with Sec-Fetch-Dest: document — a pageview
101 // nobody looked at unless the first token is matched.
102 get("/y", map[string]string{"Sec-Purpose": "prefetch;prerender", "Sec-Fetch-Dest": "document"})
103 if !m.snap()[2].Prefetch {
104 t.Fatal("prefetch;prerender is a prefetch")
105 }
106 e.SetAnalyticsOn(nil)
107 get("/", nil)
108 if len(m.snap()) != 3 {
109 t.Fatal("switching off stops observation")
110 }
111}
112