test(19.3-06): unit tests for the review fixes

- TestCORSCredentialedOriginRules, TestCORSAnchoredPatternNoSuffixMatch (D-04)
- TestAPIDocsCSPNoHostSource (D-17)
- TestLoadCookieConfigProductionInferredSecure, TestZeroCookieConfigDefaults (D-03)
- TestSiteCachePurgeWaitAndClose, TestSiteCacheBulkCommitCoalesced (D-10)
This commit is contained in:
Jakub Zych
2026-10-15 22:45:16 +02:00
parent 4d4e37932c
commit 8d80580083
4 changed files with 409 additions and 0 deletions

View File

@@ -717,3 +717,162 @@ func TestSiteCachePurgeAsyncCoalesced(t *testing.T) {
}
assertNoToken(t, logs)
}
// blockingReceiver answers once release is closed (or the request ends).
func blockingReceiver(t *testing.T) (*httptest.Server, func() []purgeHit, func()) {
t.Helper()
release := make(chan struct{})
var once sync.Once
srv, hits := purgeReceiver(t, func(w http.ResponseWriter, r *http.Request) {
select {
case <-release:
_, _ = w.Write([]byte(`{"purged":1}`))
case <-r.Context().Done():
}
})
free := func() { once.Do(func() { close(release) }) }
t.Cleanup(free)
return srv, hits, free
}
func TestSiteCachePurgeWaitAndClose(t *testing.T) {
t.Run("nil and idle purgers return at once", func(t *testing.T) {
var nilPurger *SiteCachePurger
if err := nilPurger.Wait(context.Background()); err != nil {
t.Fatalf("nil Wait = %v", err)
}
srv, _ := purgeReceiver(t, func(http.ResponseWriter, *http.Request) {})
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := newTestPurger(t, srv).Wait(ctx); err != nil {
t.Fatalf("idle Wait under a cancelled context = %v, want nil", err)
}
})
t.Run("Wait gives up with the context and the purge is still sent", func(t *testing.T) {
captureLogs(t)
srv, hits, free := blockingReceiver(t)
p := newTestPurger(t, srv)
PurgeSiteCacheAfterCommit(context.Background(), nil, p, "post", "pending")
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
defer cancel()
if err := p.Wait(ctx); !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("Wait = %v, want context.DeadlineExceeded", err)
}
free()
waitPurges(t, p)
if got := hits(); len(got) != 1 || got[0].body["slug"] != "pending" {
t.Fatalf("requests = %#v, want the queued purge", got)
}
})
install := func(t *testing.T, srv *httptest.Server) (*Handle, *SiteCachePurger) {
t.Helper()
cfg := testConfig(t)
if err := cfg.Set(SiteCachePurgeURLKey, srv.URL+"/api/__site__/cache/purge"); err != nil {
t.Fatal(err)
}
if err := cfg.Set(SiteCachePurgeTokenKey, purgeToken40); err != nil {
t.Fatal(err)
}
app := backpack.New(cfg)
h, err := Install(context.Background(), app)
if err != nil {
t.Fatal(err)
}
p := SiteCacheFromApp(app)
if p == nil {
t.Fatal("Install published no purger")
}
return h, p
}
t.Run("Handle.Close waits for a queued purge", func(t *testing.T) {
captureLogs(t)
srv, hits := purgeReceiver(t, func(w http.ResponseWriter, _ *http.Request) {
time.Sleep(200 * time.Millisecond)
_, _ = w.Write([]byte(`{"purged":1}`))
})
h, p := install(t, srv)
PurgeSiteCacheAfterCommit(context.Background(), nil, p, "post", "at-shutdown")
if err := h.Close(); err != nil {
t.Fatal(err)
}
if got := hits(); len(got) != 1 || got[0].body["slug"] != "at-shutdown" {
t.Fatalf("requests after Close = %#v, want the queued purge", got)
}
})
t.Run("Handle.Close is bounded when the site hangs", func(t *testing.T) {
captureLogs(t)
srv, _, _ := blockingReceiver(t)
h, p := install(t, srv)
PurgeSiteCacheAfterCommit(context.Background(), nil, p, "post", "hung")
start := time.Now()
if err := h.Close(); err != nil {
t.Fatal(err)
}
if elapsed := time.Since(start); elapsed > SiteCachePurgeTimeout+time.Second+500*time.Millisecond {
t.Fatalf("Close took %s, want at most %s plus a second", elapsed, SiteCachePurgeTimeout)
}
waitPurges(t, p)
})
}
func TestSiteCacheBulkCommitCoalesced(t *testing.T) {
gdb := purgeDB(t)
captureLogs(t)
ctx := context.Background()
run := func(t *testing.T, save func(p *SiteCachePurger) error, last string) {
t.Helper()
srv, hits, free := blockingReceiver(t)
p := newTestPurger(t, srv)
start := time.Now()
if err := save(p); err != nil {
t.Fatal(err)
}
if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
t.Fatalf("the saves took %s while the site was blocked, want under 500ms", elapsed)
}
free()
waitPurges(t, p)
got := hits()
if len(got) != 2 {
t.Fatalf("%d requests, want 2 (one in flight, one coalesced follow-up)", len(got))
}
if got[1].body["slug"] != last {
t.Fatalf("follow-up slug = %q, want the last, %q", got[1].body["slug"], last)
}
}
t.Run("25 purges in one transaction", func(t *testing.T) {
run(t, func(p *SiteCachePurger) error {
return lagoon.Transaction(ctx, gdb, func(ctx context.Context, tx *gorm.DB) error {
for i := range 25 {
slug := "bulk-" + strconv.Itoa(i)
if err := tx.Exec(`INSERT INTO purge_posts (slug) VALUES (?)`, slug).Error; err != nil {
return err
}
PurgeSiteCacheAfterCommit(ctx, tx, p, "post", slug)
}
return nil
})
}, "bulk-24")
})
t.Run("10 separate transactions", func(t *testing.T) {
run(t, func(p *SiteCachePurger) error {
for i := range 10 {
slug := "each-" + strconv.Itoa(i)
if err := lagoon.Transaction(ctx, gdb, func(ctx context.Context, tx *gorm.DB) error {
PurgeSiteCacheAfterCommit(ctx, tx, p, "post", slug)
return tx.Exec(`INSERT INTO purge_posts (slug) VALUES (?)`, slug).Error
}); err != nil {
return err
}
}
return nil
}, "each-9")
})
}