orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package tools
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "sync/atomic"
8 "testing"
9 "time"
10)
11
12// The point of the pool: once it is spent, no request leaves. A limit that
13// still sends the call and throws the answer away is not a limit.
14func TestSpentPoolSendsNothing(t *testing.T) {
15 var hits int32
16 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17 atomic.AddInt32(&hits, 1)
18 _, _ = w.Write([]byte("ok"))
19 }))
20 defer srv.Close()
21
22 d := &Deps{HTTP: srv.Client(), Public: srv.Client(), Now: time.Now,
23 Guard: NewGuard(time.Minute), Budgets: NewBudgets()}
24 // A tiny pool on this test's host.
25 host := hostOf(srv.URL)
26 budgets[host] = budget{gap: 0, minute: 2, hour: 2, day: 2}
27 defer delete(budgets, host)
28
29 for i := 0; i < 2; i++ {
30 if _, err := get(context.Background(), d, srv.URL, ""); err != nil {
31 t.Fatalf("call %d refused early: %v", i+1, err)
32 }
33 }
34 before := atomic.LoadInt32(&hits)
35 for i := 0; i < 5; i++ {
36 if _, err := get(context.Background(), d, srv.URL, ""); err == nil {
37 t.Fatal("a spent pool still allowed a call")
38 }
39 }
40 if got := atomic.LoadInt32(&hits); got != before {
41 t.Errorf("%d requests went out after the pool was spent", got-before)
42 }
43}
44
45// And a host that refused is not called again either, which is the poking.
46func TestBoxedHostSendsNothing(t *testing.T) {
47 var hits int32
48 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
49 atomic.AddInt32(&hits, 1)
50 w.WriteHeader(http.StatusAccepted)
51 }))
52 defer srv.Close()
53
54 d := &Deps{HTTP: srv.Client(), Public: srv.Client(), Now: time.Now,
55 Guard: NewGuard(time.Minute), Budgets: NewBudgets()}
56 if _, err := get(context.Background(), d, srv.URL, ""); err == nil {
57 t.Fatal("a 202 was not treated as a refusal")
58 }
59 after := atomic.LoadInt32(&hits)
60 for i := 0; i < 5; i++ {
61 _, _ = get(context.Background(), d, srv.URL, "")
62 }
63 if got := atomic.LoadInt32(&hits); got != after {
64 t.Errorf("%d requests were sent to a host that already refused", got-after)
65 }
66}