diff --git a/classes/site_cache_test.go b/classes/site_cache_test.go index a3a7f17..d7033fa 100644 --- a/classes/site_cache_test.go +++ b/classes/site_cache_test.go @@ -11,6 +11,7 @@ import ( "strings" "sync" "testing" + "time" ) const ( @@ -206,10 +207,16 @@ func TestSiteCachePurgeRequest(t *testing.T) { } }) - t.Run("outside a transaction the after-commit purge runs at once", func(t *testing.T) { + t.Run("outside a transaction the after-commit purge is queued at once", func(t *testing.T) { captureLogs(t) srv, hits := purgeReceiver(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"purged":2}`)) }) - PurgeSiteCacheAfterCommit(context.Background(), nil, newTestPurger(t, srv), "category", "now") + p := newTestPurger(t, srv) + PurgeSiteCacheAfterCommit(context.Background(), nil, p, "category", "now") + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := p.Wait(ctx); err != nil { + t.Fatalf("Wait: %v", err) + } if got := hits(); len(got) != 1 || got[0].body["kind"] != "category" || got[0].body["slug"] != "now" { t.Fatalf("requests = %#v, want one", got) } diff --git a/site_cache_admin_test.go b/site_cache_admin_test.go index 0df67a8..42984e4 100644 --- a/site_cache_admin_test.go +++ b/site_cache_admin_test.go @@ -1,6 +1,7 @@ package journal_test import ( + "context" "encoding/json" "fmt" "io" @@ -9,9 +10,12 @@ import ( "strings" "sync" "testing" + "time" "git.golem15.com/golem15/sm-journal-plugin/controllers" "git.golem15.com/golem15/sm-journal-plugin/models" + "git.golem15.com/golem15/summercms/modules/backpack" + "git.golem15.com/golem15/summercms/modules/cooler" ) // purgeTestToken is a 40-character purge token for the receiver. @@ -38,6 +42,9 @@ type purgeReceiver struct { got []purgeRequest status int probe func() string + // drain waits for the queued purges of the app under test; see + // drainFrom. + drain func() } func newPurgeReceiver(t *testing.T) *purgeReceiver { @@ -82,8 +89,15 @@ func (r *purgeReceiver) env() []string { } } -// take returns the requests received so far and forgets them. +// take returns the requests received so far and forgets them, after +// draining the queued purges (see drainFrom). func (r *purgeReceiver) take() []purgeRequest { + r.mu.Lock() + drain := r.drain + r.mu.Unlock() + if drain != nil { + drain() + } r.mu.Lock() defer r.mu.Unlock() got := r.got @@ -91,6 +105,21 @@ func (r *purgeReceiver) take() []purgeRequest { return got } +// drainFrom makes every read of r first wait for the purges queued on app's +// site-cache purger: cooler queues them after commit and sends them in the +// background, so a save's purge is asserted only once it was sent. +func (r *purgeReceiver) drainFrom(t *testing.T, app *backpack.App) { + r.mu.Lock() + defer r.mu.Unlock() + r.drain = func() { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := cooler.SiteCacheFromApp(app).Wait(ctx); err != nil { + t.Errorf("waiting for the queued site-cache purge: %v", err) + } + } +} + func (r *purgeReceiver) setStatus(status int) { r.mu.Lock() r.status = status @@ -133,6 +162,7 @@ func (r *purgeReceiver) expectOnePurge(t *testing.T, stage, kind, slug string) p func TestCategoriesAdminPurgesSiteCache(t *testing.T) { recv := newPurgeReceiver(t) env := newPostsEnv(t, recv.env()...) + recv.drainFrom(t, env.app) token := env.admin(t, controllers.PermissionAccessCategories) // The probe reads the committed row through the pool while the purge @@ -210,6 +240,7 @@ func TestCategoriesAdminPurgesSiteCache(t *testing.T) { func TestPostsAdminPurgesSiteCache(t *testing.T) { recv := newPurgeReceiver(t) env := newPostsEnv(t, recv.env()...) + recv.drainFrom(t, env.app) token := env.admin(t, controllers.PermissionAccessPosts) recv.setProbe(func() string { @@ -340,6 +371,8 @@ func TestCategoriesAdminFrameworkKeysWin(t *testing.T) { "SUMMER_SITE_CACHE__PURGE_URL="+framework.srv.URL+"/api/__site__/cache/purge", "SUMMER_SITE_CACHE__PURGE_TOKEN="+purgeTestToken, )...) + framework.drainFrom(t, env.app) + alias.drainFrom(t, env.app) token := env.admin(t, controllers.PermissionAccessCategories) env.expect(t, http.StatusCreated, http.MethodPost, adminAPI(adminCategoriesPath), token, map[string]any{ "name": map[string]any{"en": "Framework keys"},