| new file mode 100644 |
| index 000000000..485413f26 |
| --- /dev/null |
| +++ b/internal/edge/purge_test.go |
| @@ -0,0 +1,109 @@ |
| +package edge |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "encoding/json" |
| + "net" |
| + "net/http" |
| + "os" |
| + "path/filepath" |
| + "testing" |
| + "time" |
| +) |
| + |
| +func dialPurgeSocket(t *testing.T, socketPath string) *http.Client { |
| + t.Helper() |
| + return &http.Client{ |
| + Transport: &http.Transport{ |
| + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { |
| + return net.Dial("unix", socketPath) |
| + }, |
| + }, |
| + } |
| +} |
| + |
| +func TestListenPurgeRemovesCachedEntry(t *testing.T) { |
| + dir := t.TempDir() |
| + sock := filepath.Join(dir, "instance.sock") |
| + cache := NewCache(DefaultCacheMaxEntries, DefaultCacheMaxBodyBytes) |
| + cache.Put(CacheKey{Host: "a.example", Method: "GET", Path: "/event"}, &CachedResponse{ |
| + StatusCode: 200, Header: http.Header{}, Body: []byte("hi"), StoredAt: time.Now(), TTL: time.Minute, |
| + }) |
| + |
| + pl, err := ListenPurge(sock, "a.example", cache) |
| + if err != nil { |
| + t.Fatalf("ListenPurge: %v", err) |
| + } |
| + defer pl.Close() |
| + |
| + body, _ := json.Marshal(map[string]string{"path": "/event", "query": ""}) |
| + client := dialPurgeSocket(t, sock) |
| + resp, err := client.Post("http://unix/purge", "application/json", bytes.NewReader(body)) |
| + if err != nil { |
| + t.Fatalf("purge POST: %v", err) |
| + } |
| + resp.Body.Close() |
| + if resp.StatusCode != http.StatusOK { |
| + t.Fatalf("purge status = %d, want 200", resp.StatusCode) |
| + } |
| + |
| + if _, ok := cache.Get(CacheKey{Host: "a.example", Method: "GET", Path: "/event"}); ok { |
| + t.Fatal("expected entry to be purged") |
| + } |
| +} |
| + |
| +func TestListenPurgeCannotAffectAnotherHost(t *testing.T) { |
| + dir := t.TempDir() |
| + sock := filepath.Join(dir, "instance.sock") |
| + cache := NewCache(DefaultCacheMaxEntries, DefaultCacheMaxBodyBytes) |
| + cache.Put(CacheKey{Host: "other.example", Method: "GET", Path: "/event"}, &CachedResponse{ |
| + StatusCode: 200, Header: http.Header{}, Body: []byte("hi"), StoredAt: time.Now(), TTL: time.Minute, |
| + }) |
| + |
| + pl, err := ListenPurge(sock, "a.example", cache) // bound to a.example, not other.example |
| + if err != nil { |
| + t.Fatalf("ListenPurge: %v", err) |
| + } |
| + defer pl.Close() |
| + |
| + body, _ := json.Marshal(map[string]string{"path": "/event", "query": ""}) |
| + client := dialPurgeSocket(t, sock) |
| + resp, _ := client.Post("http://unix/purge", "application/json", bytes.NewReader(body)) |
| + resp.Body.Close() |
| + |
| + if _, ok := cache.Get(CacheKey{Host: "other.example", Method: "GET", Path: "/event"}); !ok { |
| + t.Fatal("a purge listener bound to a.example must never affect other.example's cache entries") |
| + } |
| +} |
| + |
| +func TestListenPurgeSocketFileIsOwnerOnly(t *testing.T) { |
| + dir := t.TempDir() |
| + sock := filepath.Join(dir, "instance.sock") |
| + pl, err := ListenPurge(sock, "a.example", NewCache(10, 1<<20)) |
| + if err != nil { |
| + t.Fatalf("ListenPurge: %v", err) |
| + } |
| + defer pl.Close() |
| + |
| + fi, err := os.Stat(sock) |
| + if err != nil { |
| + t.Fatalf("stat socket: %v", err) |
| + } |
| + if mode := fi.Mode().Perm(); mode != 0600 { |
| + t.Fatalf("socket mode = %o, want 0600", mode) |
| + } |
| +} |
| + |
| +func TestListenPurgeRemovesStaleSocketFile(t *testing.T) { |
| + dir := t.TempDir() |
| + sock := filepath.Join(dir, "instance.sock") |
| + if err := os.WriteFile(sock, []byte("stale"), 0600); err != nil { |
| + t.Fatal(err) |
| + } |
| + pl, err := ListenPurge(sock, "a.example", NewCache(10, 1<<20)) |
| + if err != nil { |
| + t.Fatalf("ListenPurge should clean up a stale socket file, got: %v", err) |
| + } |
| + pl.Close() |
| +} |