Files
sm-journal-plugin/site_cache_keys_test.go
Jakub Zych ebfb710197 test(19.3-05): Journal site-cache keys: framework keys win, alias keys deprecated
- site_cache_keys_test.go: wireSiteCache on a config without a database
- rename the Docker admin test to TestCategoriesAdminFrameworkKeysWin so the
  unit test owns TestSiteCacheFrameworkKeysWin (one name per test binary)
2026-10-15 19:56:16 +02:00

223 lines
7.4 KiB
Go

package journal
import (
"bytes"
"log/slog"
"strings"
"sync"
"testing"
"git.golem15.com/golem15/sm-journal-plugin/classes"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
"git.golem15.com/golem15/summercms/modules/cooler"
)
const siteCacheKeysToken = "journal-site-cache-keys-token-0123456789"
type keysLogBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (b *keysLogBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.Write(p)
}
func (b *keysLogBuffer) lines() []string {
b.mu.Lock()
defer b.mu.Unlock()
var out []string
for _, l := range strings.Split(strings.TrimSpace(b.buf.String()), "\n") {
if l != "" {
out = append(out, l)
}
}
return out
}
// siteCacheApp returns an app on a config without a database holding values,
// with slog.Default captured for the test.
func siteCacheApp(t *testing.T, values map[string]string) (*backpack.App, *keysLogBuffer) {
t.Helper()
cfg, err := compass.Open(compass.Options{Dir: t.TempDir(), Environ: []string{}})
if err != nil {
t.Fatal(err)
}
for k, v := range values {
if err := cfg.Set(k, v); err != nil {
t.Fatal(err)
}
}
buf := &keysLogBuffer{}
prev := slog.Default()
slog.SetDefault(slog.New(slog.NewTextHandler(buf, nil)))
t.Cleanup(func() { slog.SetDefault(prev) })
return backpack.New(cfg), buf
}
func assertNoPurgeToken(t *testing.T, lines []string) {
t.Helper()
for _, l := range lines {
if strings.Contains(l, siteCacheKeysToken) {
t.Fatalf("log line carries the token: %s", l)
}
}
}
func TestSiteCacheFrameworkKeysWin(t *testing.T) {
framework := map[string]string{
cooler.SiteCachePurgeURLKey: "http://127.0.0.1:3000/api/__site__/cache/purge",
cooler.SiteCachePurgeTokenKey: siteCacheKeysToken,
}
t.Run("framework keys only: the Journal publishes nothing and stays quiet", func(t *testing.T) {
app, logs := siteCacheApp(t, framework)
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatal(err)
}
if p, ok := app.Lookup[*cooler.SiteCachePurger](); ok && p != nil {
t.Fatal("the Journal published a purger although the framework keys are set")
}
if lines := logs.lines(); len(lines) != 0 {
t.Fatalf("log = %q, want nothing", lines)
}
// The framework builds the purger from its own keys.
if p := cooler.SiteCacheFromApp(app); p == nil || p.Endpoint() != "127.0.0.1:3000/api/__site__/cache/purge" {
t.Fatalf("cooler.SiteCacheFromApp = %v", p)
}
})
t.Run("alias keys too: one warning that they are ignored", func(t *testing.T) {
values := map[string]string{
classes.SiteCachePurgeURLKey: "http://127.0.0.1:4000/purge",
classes.SiteCachePurgeTokenKey: "x", // invalid, but ignored
}
for k, v := range framework {
values[k] = v
}
app, logs := siteCacheApp(t, values)
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatalf("ignored alias keys must not fail boot: %v", err)
}
if p, ok := app.Lookup[*cooler.SiteCachePurger](); ok && p != nil {
t.Fatal("the Journal published a purger from ignored keys")
}
lines := logs.lines()
if len(lines) != 1 || !strings.Contains(lines[0], "level=WARN") || !strings.Contains(lines[0], "ignored") ||
!strings.Contains(lines[0], cooler.SiteCachePurgeURLKey) {
t.Fatalf("log = %q, want one warning naming the framework keys", lines)
}
assertNoPurgeToken(t, lines)
})
t.Run("only the framework token set still counts as framework keys", func(t *testing.T) {
app, _ := siteCacheApp(t, map[string]string{
cooler.SiteCachePurgeTokenKey: siteCacheKeysToken,
classes.SiteCachePurgeURLKey: "http://127.0.0.1:4000/purge",
classes.SiteCachePurgeTokenKey: siteCacheKeysToken,
})
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatal(err)
}
if p, ok := app.Lookup[*cooler.SiteCachePurger](); ok && p != nil {
t.Fatal("alias keys were used although a framework key is set")
}
})
}
func TestSiteCacheAliasKeysDeprecated(t *testing.T) {
t.Run("valid alias keys publish a purger with one deprecation warning", func(t *testing.T) {
app, logs := siteCacheApp(t, map[string]string{
classes.SiteCachePurgeURLKey: "http://127.0.0.1:4000/api/__site__/cache/purge",
classes.SiteCachePurgeTokenKey: siteCacheKeysToken,
})
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatal(err)
}
p, ok := app.Lookup[*cooler.SiteCachePurger]()
if !ok || p == nil || p.Endpoint() != "127.0.0.1:4000/api/__site__/cache/purge" {
t.Fatalf("published purger = %v", p)
}
if got := cooler.SiteCacheFromApp(app); got != p {
t.Fatal("cooler.SiteCacheFromApp did not return the alias purger")
}
lines := logs.lines()
if len(lines) != 1 || !strings.Contains(lines[0], "level=WARN") || !strings.Contains(lines[0], "deprecated") ||
!strings.Contains(lines[0], cooler.SiteCachePurgeURLKey) || !strings.Contains(lines[0], cooler.SiteCachePurgeTokenKey) {
t.Fatalf("log = %q, want one deprecation warning naming both framework keys", lines)
}
assertNoPurgeToken(t, lines)
})
for _, tc := range []struct {
name, url, token, key string
}{
{"only the alias URL", "http://127.0.0.1:4000/purge", "", classes.SiteCachePurgeTokenKey},
{"only the alias token", "", siteCacheKeysToken, classes.SiteCachePurgeURLKey},
{"http to a remote host", "http://example.test/purge", siteCacheKeysToken, classes.SiteCachePurgeURLKey},
{"a short token", "http://127.0.0.1:4000/purge", "short", classes.SiteCachePurgeTokenKey},
} {
t.Run("invalid alias keys fail boot: "+tc.name, func(t *testing.T) {
app, _ := siteCacheApp(t, map[string]string{
classes.SiteCachePurgeURLKey: tc.url,
classes.SiteCachePurgeTokenKey: tc.token,
})
err := (&Plugin{}).wireSiteCache(app)
if err == nil || !strings.Contains(err.Error(), tc.key) {
t.Fatalf("wireSiteCache = %v, want an error naming %s", err, tc.key)
}
if tc.token != "" && strings.Contains(err.Error(), tc.token) {
t.Fatalf("error %q carries the token", err)
}
if p, ok := app.Lookup[*cooler.SiteCachePurger](); ok && p != nil {
t.Fatal("an invalid alias configuration published a purger")
}
})
}
t.Run("an already published purger is kept", func(t *testing.T) {
app, _ := siteCacheApp(t, map[string]string{
classes.SiteCachePurgeURLKey: "http://127.0.0.1:4000/purge",
classes.SiteCachePurgeTokenKey: siteCacheKeysToken,
})
first, err := cooler.NewSiteCachePurger("http://127.0.0.1:5000/purge", siteCacheKeysToken)
if err != nil {
t.Fatal(err)
}
if err := app.Publish(first); err != nil {
t.Fatal(err)
}
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatal(err)
}
if got, _ := app.Lookup[*cooler.SiteCachePurger](); got != first {
t.Fatal("wireSiteCache replaced a published purger")
}
})
}
func TestSiteCacheNoKeys(t *testing.T) {
app, logs := siteCacheApp(t, nil)
if err := (&Plugin{}).wireSiteCache(app); err != nil {
t.Fatal(err)
}
if p, ok := app.Lookup[*cooler.SiteCachePurger](); ok && p != nil {
t.Fatal("no keys published a purger")
}
if p := cooler.SiteCacheFromApp(app); p != nil {
t.Fatalf("cooler.SiteCacheFromApp = %v, want nil", p)
}
if lines := logs.lines(); len(lines) != 0 {
t.Fatalf("log = %q, want nothing", lines)
}
if err := (&Plugin{}).wireSiteCache(nil); err != nil {
t.Fatalf("wireSiteCache(nil) = %v", err)
}
if err := (&Plugin{}).wireSiteCache(backpack.New(nil)); err != nil {
t.Fatalf("wireSiteCache(no config) = %v", err)
}
}