This repository has no description
1package microvm
2
3import (
4 "io"
5 "log/slog"
6 "net/http"
7 "net/http/httptest"
8 "strings"
9 "testing"
10 "time"
11)
12
13func TestCacheProxyFallsBackOnNotFound(t *testing.T) {
14 first := httptest.NewServer(http.NotFoundHandler())
15 defer first.Close()
16 second := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
17 if req.URL.Path != "/abc.narinfo" {
18 t.Fatalf("path: got %q, want /abc.narinfo", req.URL.Path)
19 }
20 _, _ = io.WriteString(w, "ok")
21 }))
22 defer second.Close()
23
24 upstreams, err := parseCacheUpstreams([]string{first.URL, second.URL})
25 if err != nil {
26 t.Fatal(err)
27 }
28
29 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
30 rec := httptest.NewRecorder()
31 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
32
33 if rec.Code != http.StatusOK {
34 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
35 }
36 if got := rec.Body.String(); got != "ok" {
37 t.Fatalf("body: got %q, want ok", got)
38 }
39}
40
41func TestCacheProxyServesNixCacheInfoItself(t *testing.T) {
42 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
43 t.Errorf("upstream should not be hit, got request for %q", req.URL.Path)
44 }))
45 defer upstream.Close()
46
47 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
48 if err != nil {
49 t.Fatal(err)
50 }
51
52 req := httptest.NewRequest(http.MethodGet, "http://guest/nix-cache-info", nil)
53 rec := httptest.NewRecorder()
54 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
55
56 if rec.Code != http.StatusOK {
57 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
58 }
59 if got := rec.Body.String(); got != nixCacheInfo {
60 t.Fatalf("body: got %q, want %q", got, nixCacheInfo)
61 }
62}
63
64func TestCacheProxyErrorStatusDoesNotWinRace(t *testing.T) {
65 erroring := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
66 http.Error(w, "misdirected", http.StatusMisdirectedRequest)
67 }))
68 defer erroring.Close()
69 healthy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
70 time.Sleep(50 * time.Millisecond) // lose the race to the erroring upstream
71 _, _ = io.WriteString(w, "ok")
72 }))
73 defer healthy.Close()
74
75 upstreams, err := parseCacheUpstreams([]string{erroring.URL, healthy.URL})
76 if err != nil {
77 t.Fatal(err)
78 }
79
80 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
81 rec := httptest.NewRecorder()
82 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
83
84 if rec.Code != http.StatusOK {
85 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
86 }
87 if got := rec.Body.String(); got != "ok" {
88 t.Fatalf("body: got %q, want ok", got)
89 }
90}
91
92func TestCacheProxyJoinsSubpathQueryAndAuth(t *testing.T) {
93 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
94 if req.URL.Path != "/sub/cache/abc.narinfo" {
95 t.Errorf("path: got %q, want /sub/cache/abc.narinfo", req.URL.Path)
96 }
97 if got := req.URL.Query().Get("token"); got != "s3cret" {
98 t.Errorf("token: got %q, want s3cret", got)
99 }
100 if user, pass, ok := req.BasicAuth(); !ok || user != "dawn" || pass != "woof" {
101 t.Errorf("basic auth: got %q/%q/%v, want dawn/woof/true", user, pass, ok)
102 }
103 _, _ = io.WriteString(w, "ok")
104 }))
105 defer upstream.Close()
106
107 upstreamURL := "http://dawn:woof@" + strings.TrimPrefix(upstream.URL, "http://") + "/sub/cache/?token=s3cret"
108 upstreams, err := parseCacheUpstreams([]string{upstreamURL})
109 if err != nil {
110 t.Fatal(err)
111 }
112
113 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
114 rec := httptest.NewRecorder()
115 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
116
117 if rec.Code != http.StatusOK {
118 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
119 }
120 if got := rec.Body.String(); got != "ok" {
121 t.Fatalf("body: got %q, want ok", got)
122 }
123}
124
125func TestCacheProxyGuardedUpstreamCannotReachBlockedRanges(t *testing.T) {
126 // httptest listens on 127.0.0.1, which is in the blocked ranges; reaching
127 // it would mean a workflow-defined cache can hit the host's loopback
128 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
129 t.Errorf("guarded upstream on loopback should not be reachable, got request for %q", req.URL.Path)
130 }))
131 defer upstream.Close()
132
133 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
134 if err != nil {
135 t.Fatal(err)
136 }
137
138 req := httptest.NewRequest(http.MethodGet, "http://guest/abc.narinfo", nil)
139 rec := httptest.NewRecorder()
140 cacheProxyHandler(mergeCacheUpstreams(nil, upstreams), slog.Default()).ServeHTTP(rec, req)
141
142 if rec.Code != http.StatusBadGateway {
143 t.Fatalf("status: got %d, want 502; body=%q", rec.Code, rec.Body.String())
144 }
145}
146
147func TestCacheProxyRewritesHostHeader(t *testing.T) {
148 var upstreamHost string
149 upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
150 if req.Host != upstreamHost {
151 t.Errorf("host: got %q, want %q", req.Host, upstreamHost)
152 }
153 _, _ = io.WriteString(w, "ok")
154 }))
155 defer upstream.Close()
156 upstreamHost = strings.TrimPrefix(upstream.URL, "http://")
157
158 upstreams, err := parseCacheUpstreams([]string{upstream.URL})
159 if err != nil {
160 t.Fatal(err)
161 }
162
163 req := httptest.NewRequest(http.MethodGet, "http://127.0.0.1:10500/abc.narinfo", nil)
164 req.Host = "127.0.0.1:10500"
165 rec := httptest.NewRecorder()
166 cacheProxyHandler(mergeCacheUpstreams(upstreams, nil), slog.Default()).ServeHTTP(rec, req)
167
168 if rec.Code != http.StatusOK {
169 t.Fatalf("status: got %d, want 200; body=%q", rec.Code, rec.Body.String())
170 }
171}