test(01-02): add failing test for optional plugin services

- Cover typed Publish/Lookup, duplicate providers and HasPlugin
- Require missing/cycle errors to name plugin IDs before Boot
- Assert greeter works with and without an optional extra
This commit is contained in:
Jakub Zych
2026-09-16 13:20:53 +02:00
parent cc44a1fa49
commit 5f2291849d
4 changed files with 317 additions and 5 deletions

View File

@@ -76,6 +76,128 @@ func TestActivateMergesHasConfigBeforeRegister(t *testing.T) {
}
}
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}