orca.ai/pkg/plugin/registry_test.go
大森 6b94476347 Initial commit: Orca Agent Framework
Core features:
- Microkernel architecture with Actor model
- Session management with JSONL persistence
- Tool system (5 built-in tools)
- Skill system with SKILL.md parsing
- Sandbox security execution
- Ollama integration with gemma4:e4b
- Prompt-based tool calling (compatible with native function calling)
- REPL interface

11 packages, all tests passing
2026-05-08 00:55:48 +08:00

257 lines
5.4 KiB
Go

package plugin
import (
"errors"
"testing"
"github.com/orca/orca/pkg/bus"
)
// mockPlugin implements Plugin for testing.
type mockPlugin struct {
name string
version string
initFn func(host PluginHost) error
closeFn func() error
}
func (m *mockPlugin) Name() string { return m.name }
func (m *mockPlugin) Version() string { return m.version }
func (m *mockPlugin) Init(host PluginHost) error {
if m.initFn != nil {
return m.initFn(host)
}
return nil
}
func (m *mockPlugin) Shutdown() error {
if m.closeFn != nil {
return m.closeFn()
}
return nil
}
func TestRegistryNew(t *testing.T) {
r := NewRegistry()
if r == nil {
t.Fatal("NewRegistry() returned nil")
}
if n := r.Count(); n != 0 {
t.Errorf("expected empty registry, got %d plugins", n)
}
}
func TestRegistryRegister(t *testing.T) {
r := NewRegistry()
p := &mockPlugin{name: "test", version: "1.0.0"}
err := r.Register(p)
if err != nil {
t.Fatalf("Register failed: %v", err)
}
if n := r.Count(); n != 1 {
t.Errorf("expected 1 plugin, got %d", n)
}
}
func TestRegistryRegisterDuplicate(t *testing.T) {
r := NewRegistry()
p1 := &mockPlugin{name: "test", version: "1.0.0"}
p2 := &mockPlugin{name: "test", version: "2.0.0"}
r.Register(p1)
err := r.Register(p2)
if err == nil {
t.Error("expected error registering duplicate plugin")
}
}
func TestRegistryGet(t *testing.T) {
r := NewRegistry()
p := &mockPlugin{name: "test", version: "1.0.0"}
r.Register(p)
got, ok := r.Get("test")
if !ok {
t.Fatal("Get returned not found")
}
if got.Name() != "test" {
t.Errorf("expected name 'test', got %q", got.Name())
}
}
func TestRegistryGetNotFound(t *testing.T) {
r := NewRegistry()
_, ok := r.Get("nonexistent")
if ok {
t.Error("expected false for nonexistent plugin")
}
}
func TestRegistryUnregister(t *testing.T) {
r := NewRegistry()
p := &mockPlugin{name: "test", version: "1.0.0"}
r.Register(p)
err := r.Unregister("test")
if err != nil {
t.Fatalf("Unregister failed: %v", err)
}
if n := r.Count(); n != 0 {
t.Errorf("expected 0 plugins, got %d", n)
}
}
func TestRegistryUnregisterNotFound(t *testing.T) {
r := NewRegistry()
err := r.Unregister("nonexistent")
if err == nil {
t.Error("expected error unregistering nonexistent plugin")
}
}
func TestRegistryList(t *testing.T) {
r := NewRegistry()
r.Register(&mockPlugin{name: "a", version: "1.0.0"})
r.Register(&mockPlugin{name: "b", version: "1.0.0"})
r.Register(&mockPlugin{name: "c", version: "1.0.0"})
plugins := r.List()
if len(plugins) != 3 {
t.Errorf("expected 3 plugins, got %d", len(plugins))
}
names := make(map[string]bool)
for _, p := range plugins {
names[p.Name()] = true
}
for _, n := range []string{"a", "b", "c"} {
if !names[n] {
t.Errorf("missing plugin %q in list", n)
}
}
}
func TestRegistryState(t *testing.T) {
r := NewRegistry()
p := &mockPlugin{name: "test", version: "1.0.0"}
r.Register(p)
if s := r.State("test"); s != StateRegistered {
t.Errorf("expected StateRegistered, got %s", s)
}
r.SetState("test", StateRunning)
if s := r.State("test"); s != StateRunning {
t.Errorf("expected StateRunning, got %s", s)
}
}
func TestRegistryStateUnknown(t *testing.T) {
r := NewRegistry()
if s := r.State("nonexistent"); s != StateUnknown {
t.Errorf("expected StateUnknown for nonexistent, got %s", s)
}
}
func TestRegistrySetStateNoOp(t *testing.T) {
r := NewRegistry()
r.SetState("nonexistent", StateRunning)
if n := r.Count(); n != 0 {
t.Errorf("SetState should not add plugins")
}
}
func TestPluginStateString(t *testing.T) {
tests := []struct {
state PluginState
want string
}{
{StateUnknown, "unknown"},
{StateRegistered, "registered"},
{StateInitialized, "initialized"},
{StateRunning, "running"},
{StateStopped, "stopped"},
{StateError, "error"},
{PluginState(99), "unknown"},
}
for _, tt := range tests {
if got := tt.state.String(); got != tt.want {
t.Errorf("PluginState(%d).String() = %q, want %q", tt.state, got, tt.want)
}
}
}
func TestRegistryConcurrent(t *testing.T) {
r := NewRegistry()
done := make(chan struct{}, 2)
go func() {
for i := 0; i < 100; i++ {
r.Register(&mockPlugin{name: "a", version: "1.0.0"})
r.Get("a")
r.Unregister("a")
}
done <- struct{}{}
}()
go func() {
for i := 0; i < 100; i++ {
r.Register(&mockPlugin{name: "b", version: "1.0.0"})
r.List()
r.State("b")
r.Unregister("b")
}
done <- struct{}{}
}()
<-done
<-done
}
// mockPluginHost implements PluginHost for testing kernel-level plugin init.
type mockPluginHost struct{}
func (m *mockPluginHost) Bus() bus.MessageBus { return nil }
func (m *mockPluginHost) GetPlugin(name string) (Plugin, bool) { return nil, false }
func (m *mockPluginHost) ListPlugins() []Plugin { return nil }
func TestPluginInitAndShutdown(t *testing.T) {
var initCalled, shutdownCalled bool
p := &mockPlugin{
name: "test",
version: "1.0.0",
initFn: func(host PluginHost) error {
initCalled = true
if host == nil {
return errors.New("host is nil")
}
return nil
},
closeFn: func() error {
shutdownCalled = true
return nil
},
}
host := &mockPluginHost{}
if err := p.Init(host); err != nil {
t.Fatalf("Init failed: %v", err)
}
if !initCalled {
t.Error("Init function was not called")
}
if err := p.Shutdown(); err != nil {
t.Fatalf("Shutdown failed: %v", err)
}
if !shutdownCalled {
t.Error("Shutdown function was not called")
}
}