- App-scoped typed Publish/Lookup with duplicate-provider errors - Set plugin IDs before Register so HasPlugin sees the full set - Greeter uses pact.OptionalMessage without importing optional
228 lines
6.4 KiB
Go
228 lines
6.4 KiB
Go
package party
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"git.golem15.com/golem15/summercms/backpack"
|
|
"git.golem15.com/golem15/summercms/compass"
|
|
)
|
|
|
|
type tracePlugin struct {
|
|
id string
|
|
req []string
|
|
log *[]string
|
|
}
|
|
|
|
func (p *tracePlugin) ID() string { return p.id }
|
|
func (p *tracePlugin) Requires() []string { return p.req }
|
|
|
|
func (p *tracePlugin) Register(*backpack.App) error {
|
|
*p.log = append(*p.log, p.id+":register")
|
|
return nil
|
|
}
|
|
|
|
func (p *tracePlugin) Boot(*backpack.App) error {
|
|
*p.log = append(*p.log, p.id+":boot")
|
|
return nil
|
|
}
|
|
|
|
type configPlugin struct {
|
|
id string
|
|
fsys fs.FS
|
|
got *int
|
|
}
|
|
|
|
func (p *configPlugin) ID() string { return p.id }
|
|
func (p *configPlugin) Requires() []string { return nil }
|
|
func (p *configPlugin) ConfigFS() fs.FS { return p.fsys }
|
|
|
|
func (p *configPlugin) Register(app *backpack.App) error {
|
|
if app != nil && app.Config != nil {
|
|
*p.got = app.Config.Int("golem15.hello.posts_per_page")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p *configPlugin) Boot(*backpack.App) error { return nil }
|
|
|
|
func TestActivateMergesHasConfigBeforeRegister(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: app\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := compass.Load(dir)
|
|
if err != nil {
|
|
t.Fatalf("Load: %v", err)
|
|
}
|
|
var got int
|
|
p := &configPlugin{
|
|
id: "golem15.hello",
|
|
got: &got,
|
|
fsys: fstest.MapFS{
|
|
"config/config.yaml": {Data: []byte("posts_per_page: 10\n")},
|
|
},
|
|
}
|
|
app := backpack.New(cfg)
|
|
if _, err := activate([]Plugin{p}, app, []string{"golem15.hello"}); err != nil {
|
|
t.Fatalf("activate: %v", err)
|
|
}
|
|
if got != 10 {
|
|
t.Fatalf("posts_per_page during Register = %d, want 10 (HasConfig must merge before Register)", got)
|
|
}
|
|
}
|
|
|
|
func TestActivateMissingRequiresNamesIDsAndSkipsBoot(t *testing.T) {
|
|
var log []string
|
|
p := &tracePlugin{id: "golem15.greeter", req: []string{"golem15.hello"}, log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
_, err := activate([]Plugin{p}, app, []string{"golem15.greeter"})
|
|
if err == nil {
|
|
t.Fatal("expected missing dependency error")
|
|
}
|
|
msg := err.Error()
|
|
if !strings.Contains(msg, "golem15.greeter") || !strings.Contains(msg, "golem15.hello") {
|
|
t.Fatalf("error %q should name both plugin IDs", msg)
|
|
}
|
|
if strings.Contains(strings.Join(log, ","), "boot") {
|
|
t.Fatalf("Boot ran despite missing dependency: %v", log)
|
|
}
|
|
}
|
|
|
|
func TestActivateCycleNamesIDsAndSkipsBoot(t *testing.T) {
|
|
var log []string
|
|
a := &tracePlugin{id: "golem15.a", req: []string{"golem15.b"}, log: &log}
|
|
b := &tracePlugin{id: "golem15.b", req: []string{"golem15.a"}, log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
_, err := activate([]Plugin{a, b}, app, []string{"golem15.a", "golem15.b"})
|
|
if err == nil {
|
|
t.Fatal("expected cycle error")
|
|
}
|
|
msg := err.Error()
|
|
if !strings.Contains(msg, "golem15.a") || !strings.Contains(msg, "golem15.b") {
|
|
t.Fatalf("error %q should name cycle members", msg)
|
|
}
|
|
if strings.Contains(strings.Join(log, ","), "boot") || strings.Contains(strings.Join(log, ","), "register") {
|
|
t.Fatalf("lifecycle ran despite cycle: %v", log)
|
|
}
|
|
}
|
|
|
|
func TestActivateDuplicateIDRejected(t *testing.T) {
|
|
var log []string
|
|
a := &tracePlugin{id: "golem15.hello", log: &log}
|
|
dup := &tracePlugin{id: "golem15.hello", log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
_, err := activate([]Plugin{a, dup}, app, []string{"golem15.hello"})
|
|
if err == nil {
|
|
t.Fatal("expected duplicate id error")
|
|
}
|
|
if !strings.Contains(err.Error(), "golem15.hello") {
|
|
t.Fatalf("error %q should name the duplicate id", err)
|
|
}
|
|
}
|
|
|
|
type bootProbe struct {
|
|
id string
|
|
req []string
|
|
log *[]string
|
|
sawOpt *bool
|
|
booted *[]string
|
|
}
|
|
|
|
func (p *bootProbe) ID() string { return p.id }
|
|
func (p *bootProbe) Requires() []string { return p.req }
|
|
|
|
func (p *bootProbe) Register(*backpack.App) error {
|
|
*p.log = append(*p.log, p.id+":register")
|
|
return nil
|
|
}
|
|
|
|
func (p *bootProbe) Boot(app *backpack.App) error {
|
|
if p.sawOpt != nil && p.id == "golem15.hello" {
|
|
*p.sawOpt = app.HasPlugin("golem15.optional")
|
|
}
|
|
*p.log = append(*p.log, p.id+":boot")
|
|
if p.booted != nil {
|
|
*p.booted = append(*p.booted, p.id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestActivateHasPluginSeesNotYetBootedPlugins(t *testing.T) {
|
|
var log []string
|
|
var saw bool
|
|
hello := &bootProbe{id: "golem15.hello", log: &log, sawOpt: &saw}
|
|
opt := &bootProbe{id: "golem15.optional", log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
if _, err := activate([]Plugin{opt, hello}, app, []string{"golem15.hello", "golem15.optional"}); err != nil {
|
|
t.Fatalf("activate: %v", err)
|
|
}
|
|
if !saw {
|
|
t.Fatal("HasPlugin(golem15.optional) was false during hello Boot; complete set must be visible before Boot")
|
|
}
|
|
want := []string{
|
|
"golem15.hello:register",
|
|
"golem15.optional:register",
|
|
"golem15.hello:boot",
|
|
"golem15.optional:boot",
|
|
}
|
|
if strings.Join(log, ",") != strings.Join(want, ",") {
|
|
t.Fatalf("lifecycle = %v, want %v", log, want)
|
|
}
|
|
}
|
|
|
|
func TestActivateIndependentPluginsKeepManifestOrder(t *testing.T) {
|
|
var log []string
|
|
a := &tracePlugin{id: "golem15.a", log: &log}
|
|
b := &tracePlugin{id: "golem15.b", log: &log}
|
|
c := &tracePlugin{id: "golem15.c", log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
got, err := activate([]Plugin{c, a, b}, app, []string{"golem15.c", "golem15.a", "golem15.b"})
|
|
if err != nil {
|
|
t.Fatalf("activate: %v", err)
|
|
}
|
|
if len(got) != 3 || got[0].ID() != "golem15.c" || got[1].ID() != "golem15.a" || got[2].ID() != "golem15.b" {
|
|
t.Fatalf("order = %v", idsOf(got))
|
|
}
|
|
}
|
|
|
|
func idsOf(plugins []Plugin) []string {
|
|
out := make([]string, len(plugins))
|
|
for i, p := range plugins {
|
|
out[i] = p.ID()
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestActivateRunsAllRegisterBeforeAnyBoot(t *testing.T) {
|
|
var log []string
|
|
hello := &tracePlugin{id: "golem15.hello", log: &log}
|
|
greeter := &tracePlugin{id: "golem15.greeter", req: []string{"golem15.hello"}, log: &log}
|
|
app := backpack.New(&compass.Config{})
|
|
|
|
got, err := activate([]Plugin{greeter, hello}, app, []string{"golem15.greeter", "golem15.hello"})
|
|
if err != nil {
|
|
t.Fatalf("activate: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("got %d plugins, want 2", len(got))
|
|
}
|
|
if got[0].ID() != "golem15.hello" || got[1].ID() != "golem15.greeter" {
|
|
t.Fatalf("order = %s, %s", got[0].ID(), got[1].ID())
|
|
}
|
|
|
|
want := []string{
|
|
"golem15.hello:register",
|
|
"golem15.greeter:register",
|
|
"golem15.hello:boot",
|
|
"golem15.greeter:boot",
|
|
}
|
|
if strings.Join(log, ",") != strings.Join(want, ",") {
|
|
t.Fatalf("lifecycle = %v, want %v", log, want)
|
|
}
|
|
}
|