- journal-post registers a pact.ContentDescriptor (posts controller, Post morph name, Describe with per-locale URL, title, excerpt, image, words) - descriptors read through cabana.TxFromContext inside a save - TestRecordSavedDescribesNewURL: an admin slug change reaches a subscriber after commit and Describe answers the new URL; a failed save fires nothing
166 lines
5.4 KiB
Go
166 lines
5.4 KiB
Go
package journal_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-journal-plugin/classes"
|
|
"git.golem15.com/golem15/sm-journal-plugin/controllers"
|
|
"git.golem15.com/golem15/sm-journal-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
"git.golem15.com/golem15/summercms/modules/signpost"
|
|
)
|
|
|
|
// savedEvent is one RecordSaved a test listener heard, with the URL the
|
|
// subscriber read back through signpost.
|
|
type savedEvent struct {
|
|
ev pact.RecordSaved
|
|
key string
|
|
url string
|
|
err error
|
|
}
|
|
|
|
// recordListener collects the record events of an application.
|
|
type recordListener struct {
|
|
mu sync.Mutex
|
|
saved []savedEvent
|
|
deleted []pact.RecordDeleted
|
|
}
|
|
|
|
func (l *recordListener) savedEvents() []savedEvent {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return append([]savedEvent(nil), l.saved...)
|
|
}
|
|
|
|
func (l *recordListener) deletedEvents() []pact.RecordDeleted {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
return append([]pact.RecordDeleted(nil), l.deleted...)
|
|
}
|
|
|
|
func (l *recordListener) reset() {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
l.saved = nil
|
|
l.deleted = nil
|
|
}
|
|
|
|
// listenRecords registers a subscriber the way a shared plugin would: on
|
|
// RecordSaved it maps the event's model to its content type and describes
|
|
// the record, reading the URL of the first locale.
|
|
func listenRecords(t *testing.T, env *adminEnv) *recordListener {
|
|
t.Helper()
|
|
reg, ok := env.app.Lookup[*signpost.Registry]()
|
|
if !ok || reg == nil {
|
|
t.Fatal("signpost registry is not published")
|
|
}
|
|
l := &recordListener{}
|
|
env.app.Events.Listen("test.records", func(ctx context.Context, ev *pact.RecordSaved) error {
|
|
got := savedEvent{ev: *ev}
|
|
if typ, ok := reg.ContentTypeForModel(ev.Model); ok {
|
|
got.key = typ.Key
|
|
rec, err := reg.Describe(ctx, typ.Key, ev.ID, "")
|
|
got.err = err
|
|
if rec != nil && len(rec.Locales) > 0 {
|
|
got.url = rec.Locales[0].URL
|
|
}
|
|
}
|
|
l.mu.Lock()
|
|
l.saved = append(l.saved, got)
|
|
l.mu.Unlock()
|
|
return nil
|
|
})
|
|
env.app.Events.Listen("test.records", func(ctx context.Context, ev *pact.RecordDeleted) error {
|
|
l.mu.Lock()
|
|
l.deleted = append(l.deleted, *ev)
|
|
l.mu.Unlock()
|
|
return nil
|
|
})
|
|
return l
|
|
}
|
|
|
|
// findChange returns the change of attribute in locale.
|
|
func findChange(changes []pact.RecordChange, attribute, locale string) (pact.RecordChange, bool) {
|
|
for _, c := range changes {
|
|
if c.Attribute == attribute && c.Locale == locale {
|
|
return c, true
|
|
}
|
|
}
|
|
return pact.RecordChange{}, false
|
|
}
|
|
|
|
// TestRecordSavedDescribesNewURL is the D-92/D-93 tracer: an admin slug
|
|
// change on a Journal post reaches a subscriber after commit, and the
|
|
// subscriber reads the new URL through the post's content descriptor. A save
|
|
// that fails validation fires nothing.
|
|
func TestRecordSavedDescribesNewURL(t *testing.T) {
|
|
env := newPostsEnv(t)
|
|
token := env.admin(t,
|
|
controllers.PermissionAccessPosts,
|
|
controllers.PermissionAccessPublish,
|
|
controllers.PermissionAccessOtherPosts,
|
|
)
|
|
rec := env.expect(t, http.StatusCreated, http.MethodPost, adminAPI(adminPostsPath), token, map[string]any{
|
|
"title": map[string]any{"en": "Tracer", "pl": "Tropiciel"},
|
|
"slug": map[string]any{"en": "tracer-old", "pl": "tropiciel-stary"},
|
|
"content": map[string]any{"en": "<p>Body</p>"},
|
|
"excerpt": map[string]any{"en": "Excerpt"},
|
|
})
|
|
id := adminCreatedID(t, rec)
|
|
l := listenRecords(t, env)
|
|
|
|
path := adminAPI(fmt.Sprintf("%s/%d", adminPostsPath, id))
|
|
env.expect(t, http.StatusOK, http.MethodPut, path, token, map[string]any{
|
|
"title": map[string]any{"en": "Tracer", "pl": "Tropiciel"},
|
|
"slug": map[string]any{"en": "tracer-new", "pl": "tropiciel-nowy"},
|
|
"content": map[string]any{"en": "<p>Body</p>"},
|
|
"excerpt": map[string]any{"en": "Excerpt"},
|
|
})
|
|
got := l.savedEvents()
|
|
if len(got) != 1 {
|
|
t.Fatalf("RecordSaved events = %d, want 1: %+v", len(got), got)
|
|
}
|
|
ev := got[0]
|
|
if ev.ev.Controller != controllers.PostsControllerID {
|
|
t.Fatalf("Controller = %q, want %q", ev.ev.Controller, controllers.PostsControllerID)
|
|
}
|
|
if ev.ev.Model != (models.Post{}).MorphName() {
|
|
t.Fatalf("Model = %q, want the Post morph name", ev.ev.Model)
|
|
}
|
|
if ev.ev.ID != fmt.Sprint(id) || ev.ev.Created {
|
|
t.Fatalf("ID = %q Created = %v, want %d and false", ev.ev.ID, ev.ev.Created, id)
|
|
}
|
|
slug, ok := findChange(ev.ev.Changes, "slug", "")
|
|
if !ok || slug.Old != "tracer-old" || slug.New != "tracer-new" {
|
|
t.Fatalf("default slug change = %+v (found %v), want tracer-old -> tracer-new; changes %+v", slug, ok, ev.ev.Changes)
|
|
}
|
|
plSlug, ok := findChange(ev.ev.Changes, "slug", "pl")
|
|
if !ok || plSlug.Old != "tropiciel-stary" || plSlug.New != "tropiciel-nowy" {
|
|
t.Fatalf("pl slug change = %+v (found %v), want tropiciel-stary -> tropiciel-nowy; changes %+v", plSlug, ok, ev.ev.Changes)
|
|
}
|
|
if _, ok := findChange(ev.ev.Changes, "title", ""); ok {
|
|
t.Fatalf("unchanged title reported: %+v", ev.ev.Changes)
|
|
}
|
|
if ev.key != classes.MenuTypePost || ev.err != nil {
|
|
t.Fatalf("content type = %q err = %v, want %q", ev.key, ev.err, classes.MenuTypePost)
|
|
}
|
|
if ev.url != "/blog/tracer-new" {
|
|
t.Fatalf("described URL = %q, want /blog/tracer-new", ev.url)
|
|
}
|
|
|
|
l.reset()
|
|
bad := env.call(t, http.MethodPut, path, token, map[string]any{
|
|
"slug": map[string]any{"en": ""},
|
|
})
|
|
if bad.Code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("invalid save = %d, want 422: %s", bad.Code, bad.Body.String())
|
|
}
|
|
if got := l.savedEvents(); len(got) != 0 {
|
|
t.Fatalf("failed save fired %d events: %+v", len(got), got)
|
|
}
|
|
}
|