diff --git a/admin/admin_test.go b/admin/admin_test.go index ad324864..4cd350d2 100644 --- a/admin/admin_test.go +++ b/admin/admin_test.go @@ -188,29 +188,6 @@ func TestMergeInto_WithRealAdminConfig(t *testing.T) { } } -func TestLoadConfig_Parses(t *testing.T) { - cfg, err := LoadConfig() - if err != nil { - t.Fatalf("LoadConfig: %v", err) - } - if cfg == nil { - t.Fatal("expected non-nil config") - } - if len(cfg.Modules) == 0 { - t.Error("expected at least one module in admin config") - } -} - -func TestLoadConfigRaw_NonEmpty(t *testing.T) { - raw, err := LoadConfigRaw() - if err != nil { - t.Fatalf("LoadConfigRaw: %v", err) - } - if len(raw) == 0 { - t.Error("expected non-empty raw config data") - } -} - func TestLoadConfig_HasExpectedModules(t *testing.T) { cfg, err := LoadConfig() if err != nil { diff --git a/engine.go b/engine.go index 3fc9d9ed..7182d970 100644 --- a/engine.go +++ b/engine.go @@ -218,7 +218,9 @@ func (e *StdEngine) LoadPlugin(p plugin.EnginePlugin) error { for triggerType, factory := range p.TriggerFactories() { // Delegate to the bridge helper; triggers are interfaces.Trigger values // (module.Trigger is a type alias for interfaces.Trigger). - e.registerPluginTrigger(triggerType, factory) + if err := e.registerPluginTrigger(triggerType, factory); err != nil { + return fmt.Errorf("load plugin: %w", err) + } } // Register pipeline trigger config wrappers from plugin (optional interface). diff --git a/engine_module_bridge.go b/engine_module_bridge.go index 7bf2cab1..c1b2ca7e 100644 --- a/engine_module_bridge.go +++ b/engine_module_bridge.go @@ -84,15 +84,17 @@ func (e *StdEngine) registerPluginSteps(typeName string, stepFactory func(name s // Lives here to avoid a direct module.Trigger type assertion in engine.go. // Since module.Trigger is now an alias for interfaces.Trigger, the assertion // uses the canonical interface type. -func (e *StdEngine) registerPluginTrigger(triggerType string, factory func() any) { +// Returns an error when the factory returns a value that does not satisfy +// interfaces.Trigger, so LoadPlugin can fail deterministically instead of +// silently skipping the trigger and surfacing a confusing "no handler found" +// error later at runtime. +func (e *StdEngine) registerPluginTrigger(triggerType string, factory func() any) error { result := factory() trigger, ok := result.(interfaces.Trigger) if !ok { - // Fail fast with a clear warning when a plugin misconfigures its trigger factory. - // This avoids silent failures that later surface as "no handler found" errors. - e.logger.Error(fmt.Sprintf("workflow: plugin trigger factory for %q returned non-Trigger type %T; trigger not registered", triggerType, result)) - return + return fmt.Errorf("workflow: plugin trigger factory for %q returned non-Trigger type %T", triggerType, result) } e.triggerTypeMap[triggerType] = trigger.Name() e.RegisterTrigger(trigger) + return nil } diff --git a/interfaces/events.go b/interfaces/events.go index 75db49b7..74f52057 100644 --- a/interfaces/events.go +++ b/interfaces/events.go @@ -5,7 +5,7 @@ import ( "time" ) -// EventEmitter publishes workflow and step lifecycle events. +// EventEmitter publishes workflow lifecycle events. // *module.WorkflowEventEmitter satisfies this interface. // All methods must be safe to call when no event bus is configured (no-ops). type EventEmitter interface {