package lua import ( "encoding/json" "path/filepath" "strings" "sync" "testing" ) func bridgeVM(t *testing.T) (*VM, *Plugins) { t.Helper() dir := filepath.Join(t.TempDir(), "adapters") vm := NewVM(dir) if err := vm.Start(); err != nil { t.Fatal(err) } t.Cleanup(vm.Stop) ps := NewPlugins(vm, filepath.Join(t.TempDir(), "plugins")) return vm, ps } const tokenAdapterV1 = ` local adapter = {} adapter.name = "myauth" adapter.version = "1.0" adapter.endpoint = "/chat/completions" function adapter.transform_request(body) return '{"token":"TOKEN_V1"}' end return adapter ` const tokenAdapterV2 = ` local adapter = {} adapter.name = "myauth" adapter.version = "2.0" adapter.endpoint = "/chat/completions" function adapter.transform_request(body) return '{"token":"TOKEN_V2"}' end return adapter ` // TestPluginInstallsAdapter 是基础能力:插件装的适配器立即可用。 func TestPluginInstallsAdapter(t *testing.T) { vm, ps := bridgeVM(t) if _, err := ps.InstallAdapter("authtoken", "myauth", tokenAdapterV1); err != nil { t.Fatalf("install: %v", err) } if !vm.HasFn("myauth", "transform_request") { t.Fatal("安装后适配器未注册") } out, err := vm.Transform("myauth", "transform_request", "{}") if err != nil { t.Fatal(err) } if !strings.Contains(out, "TOKEN_V1") { t.Errorf("安装的适配器未被使用: %s", out) } } // TestPluginUpdateAdapterRotatesToken 是核心用例:**token 轮换**。 // 插件长期运行,在运行中改写适配器,后续请求用新 token —— 适配器自身 // 不跑任何循环。 func TestPluginUpdateAdapterRotatesToken(t *testing.T) { vm, ps := bridgeVM(t) if _, err := ps.InstallAdapter("authtoken", "myauth", tokenAdapterV1); err != nil { t.Fatal(err) } first, _ := vm.Transform("myauth", "transform_request", "{}") if !strings.Contains(first, "TOKEN_V1") { t.Fatalf("初次应带 V1: %s", first) } // 轮换:插件改写适配器 if err := ps.UpdateAdapter("authtoken", "myauth", tokenAdapterV2); err != nil { t.Fatalf("update: %v", err) } second, err := vm.Transform("myauth", "transform_request", "{}") if err != nil { t.Fatalf("改写后适配器不可用: %v", err) } if !strings.Contains(second, "TOKEN_V2") { t.Errorf("轮换后应带 V2: %s", second) } if strings.Contains(second, "TOKEN_V1") { t.Errorf("轮换后仍带旧 V1(池未重建?): %s", second) } // 版本号也应更新,便于运维从 /api/status 看出发生过轮换 if v := vm.AdapterVersion("myauth"); v != "2.0" { t.Errorf("适配器版本 = %q, want 2.0", v) } } // TestPluginCannotShadowBuiltin:插件不能篡改内核自带的适配器。 func TestPluginCannotShadowBuiltin(t *testing.T) { _, ps := bridgeVM(t) evil := `local a={} a.name="openai" a.version="EVIL" a.endpoint="/x" return a` if _, err := ps.InstallAdapter("sneaky", "openai", evil); err == nil { t.Fatal("插件装与内置同名的适配器应被拒") } } // TestPluginCannotStealAnotherPluginsAdapter:归属隔离。 func TestPluginCannotStealAnotherPluginsAdapter(t *testing.T) { _, ps := bridgeVM(t) if _, err := ps.InstallAdapter("alpha", "shared_auth", tokenAdapterV1); err != nil { t.Fatal(err) } if _, err := ps.InstallAdapter("beta", "shared_auth", tokenAdapterV2); err == nil { t.Error("插件 B 应无法覆盖插件 A 装的适配器") } if err := ps.UpdateAdapter("beta", "shared_auth", tokenAdapterV2); err == nil { t.Error("插件 B 应无法更新插件 A 的适配器") } if err := ps.RemoveAdapter("beta", "shared_auth"); err == nil { t.Error("插件 B 应无法删除插件 A 的适配器") } // 属主自己可以 if err := ps.UpdateAdapter("alpha", "shared_auth", tokenAdapterV2); err != nil { t.Errorf("属主应能更新自己的适配器: %v", err) } } // TestPluginCannotTouchOperatorAdapter:运维的适配器不可被插件改写。 func TestPluginCannotTouchOperatorAdapter(t *testing.T) { _, ps := bridgeVM(t) if err := ps.UpdateAdapter("authtoken", "openai", tokenAdapterV1); err == nil { t.Error("插件更新运维适配器应被拒") } if err := ps.RemoveAdapter("authtoken", "openai"); err == nil { t.Error("插件删除运维适配器应被拒") } } // TestPluginAdapterNameTraversal:路径穿越名在桥接层同样被拒。 func TestPluginAdapterNameTraversal(t *testing.T) { _, ps := bridgeVM(t) for _, bad := range []string{"../evil", "/etc/passwd", "has space", ".."} { if _, err := ps.InstallAdapter("authtoken", bad, tokenAdapterV1); err == nil { t.Errorf("桥接层应拒绝路径穿越名 %q", bad) } } } // TestPluginRemoveAdapter 让插件能回收自己的适配器(token 失效/功能下线)。 func TestPluginRemoveAdapter(t *testing.T) { vm, ps := bridgeVM(t) if _, err := ps.InstallAdapter("authtoken", "myauth", tokenAdapterV1); err != nil { t.Fatal(err) } if err := ps.RemoveAdapter("authtoken", "myauth"); err != nil { t.Fatalf("remove: %v", err) } if vm.HasFn("myauth", "transform_request") { t.Error("删除后适配器仍在") } if owners := ps.PluginAdapters("authtoken"); len(owners) != 0 { t.Errorf("删除后归属表应清空,实得 %v", owners) } } // TestPluginAdapterOwnershipListing 供 UI/诊断用。 func TestPluginAdapterOwnershipListing(t *testing.T) { _, ps := bridgeVM(t) if _, err := ps.InstallAdapter("alpha", "a1", tokenAdapterV1); err != nil { t.Fatal(err) } if _, err := ps.InstallAdapter("alpha", "a2", tokenAdapterV1); err != nil { t.Fatal(err) } if _, err := ps.InstallAdapter("beta", "b1", tokenAdapterV1); err != nil { t.Fatal(err) } got := ps.PluginAdapters("alpha") if len(got) != 2 || got[0] != "a1" || got[1] != "a2" { t.Errorf("alpha 的适配器 = %v, want [a1 a2]", got) } if o := ps.AdapterOwnerOf("b1"); o != "beta" { t.Errorf("b1 属主 = %q, want beta", o) } if o := ps.AdapterOwnerOf("openai"); o != "" { t.Errorf("内置适配器不应有插件属主,实得 %q", o) } } // TestConcurrentPluginAdapterUpdates 是压力测试:多 goroutine 轮换同一适配器。 // 池重建与归属表都要在并发下自洽(不 panic、不串属主)。 func TestConcurrentPluginAdapterUpdates(t *testing.T) { vm, ps := bridgeVM(t) if _, err := ps.InstallAdapter("authtoken", "myauth", tokenAdapterV1); err != nil { t.Fatal(err) } const workers, iters = 8, 20 var wg sync.WaitGroup errs := make(chan string, workers*iters) for w := 0; w < workers; w++ { wg.Add(1) go func(w int) { defer wg.Done() for i := 0; i < iters; i++ { code := tokenAdapterV1 if i%2 == 1 { code = tokenAdapterV2 } if err := ps.UpdateAdapter("authtoken", "myauth", code); err != nil { errs <- "update: " + err.Error() return } // 更新后必须仍可用(池重建期间不能出现「找不到适配器」) if _, err := vm.Transform("myauth", "transform_request", "{}"); err != nil { errs <- "update 后适配器不可用: " + err.Error() return } } }(w) } wg.Wait() close(errs) for e := range errs { t.Error(e) } if o := ps.AdapterOwnerOf("myauth"); o != "authtoken" { t.Errorf("并发后属主应为 authtoken,实得 %q", o) } } // TestInstalledAdapterBodyIsValidJSON 确认插件提供的代码必须真能编译成 // 可用适配器 —— 语法错误在安装时就报,而不是等到第一个请求。 func TestInstalledAdapterBodyIsValidJSON(t *testing.T) { _, ps := bridgeVM(t) if _, err := ps.InstallAdapter("bad", "broken", "this is not lua ==="); err == nil { t.Error("语法错误的适配器应在安装时被拒") } if _, err := ps.InstallAdapter("bad", "notatable", "return 42"); err == nil { t.Error("未返回 table 的适配器应在安装时被拒") } // 失败的安装不应留下归属记录 if o := ps.AdapterOwnerOf("broken"); o != "" { t.Errorf("安装失败却留下了属主记录: %q", o) } if o := ps.AdapterOwnerOf("notatable"); o != "" { t.Errorf("安装失败却留下了属主记录: %q", o) } } // TestInstallAdapterReportsActualName 适配器脚本可声明与注册键不同的 name; // 调用方拿到实际名,便于更新配置。 func TestInstallAdapterReportsActualName(t *testing.T) { _, ps := bridgeVM(t) code := `local a={} a.name="declared_name" a.version="1" a.endpoint="/x" return a` got, err := ps.InstallAdapter("p", "registration_key", code) if err != nil { t.Fatal(err) } if got != "declared_name" { t.Errorf("返回名 = %q, want declared_name", got) } if o := ps.AdapterOwnerOf("declared_name"); o != "p" { t.Errorf("归属应记在声明名上,实得 %q", o) } var _ = json.Marshal }