package lua import ( "testing" golua "github.com/aarzilli/golua/lua" ) // 本文件钉住"插件与内核之间只有一条公开接口"这条契约。 // // 背景:插件曾经直接调用 `__llmsproxy_*` 这类内核私有全局。它们是实现细节, // 却被当成 API 使用,于是内核无法在不破坏所有已安装插件的前提下改实现 // (重命名、加守卫、把闭包换成表都会直接炸插件)。现在插件只有一个入口 // `llmsproxy`,旧全局降级为兼容别名。 // // 这些断言咬的是"SDK 是唯一入口"本身,而不是某个能力恰好能用: // - 表存在、带版本号; // - 每个成员与内部桥是**同一个对象**(不是复制品,复制会让凭据保险库分叉); // - 通过 SDK 能真正完成一次内核交互; // - 未授予的能力是"缺失"而不是"存在但一调用就报错"。 const sdkProbe = ` local plugin = { name = "sdkprobe", version = "1.0" } plugin.exports = { "surface", "roundtrip" } function plugin.surface() local sdk = modelrouter if sdk == nil then return { has_sdk = false } end return { has_sdk = true, sdk_version = sdk.sdk_version, http_fn = type(sdk.http), secrets_t = type(sdk.secrets), kernel_t = type(sdk.kernel), adapters_t = type(sdk.adapters), plugin_dir_t = type(sdk.plugin_dir), -- 与旧别名是同一张表?是则 tostring 相同。 secrets_aliased = tostring(sdk.secrets) == tostring(__llmsproxy_secrets), kernel_aliased = tostring(sdk.kernel) == tostring(__llmsproxy_kernel), -- 产品名是 modelrouter;llmsproxy 只作为历史别名,且必须指向同一张表。 legacy_is_same = tostring(llmsproxy) == tostring(modelrouter), -- 插件目录必须是内核给出的真实路径,而不是让插件自己猜。 plugin_dir = sdk.plugin_dir, } end -- 只通过 SDK 完成一次写读,证明它不是一个空壳表。 function plugin.roundtrip() local ok, err = modelrouter.secrets.set("probe-key", "probe-value") if not ok then return { ok = false, err = err } end local got = modelrouter.secrets.get("probe-key") local actions = modelrouter.kernel.actions() local n = 0 for _ in ipairs(actions) do n = n + 1 end return { ok = got == "probe-value", got = got, actions = n } end return plugin ` func TestPluginSDKIsTheSingleEntryPoint(t *testing.T) { _, ps, _ := runtimePlugins(t) if err := ps.LoadSource("sdkprobe", sdkProbe); err != nil { t.Fatal(err) } out, err := ps.Call("sdkprobe", "surface", nil) if err != nil { t.Fatalf("SDK 探测失败: %v", err) } m, ok := out.(map[string]interface{}) if !ok { t.Fatalf("返回值不是 table: %#v", out) } if m["has_sdk"] != true { t.Fatal("插件 VM 里没有 modelrouter —— 插件将只能依赖内核私有全局") } if m["legacy_is_same"] != true { t.Error("llmsproxy 别名与 modelrouter 不是同一张表 —— 凭据/内核动作面会分叉") } if m["sdk_version"] != SDKVersion { t.Errorf("sdk_version = %v, 期望 %q(版本号是插件判断契约的依据)", m["sdk_version"], SDKVersion) } // http 是函数,其余是表:这个形状就是文档里的形状,写错会让插件 // 在第一次网络调用时才炸。 wantTypes := map[string]string{ "http_fn": "function", "secrets_t": "table", "kernel_t": "table", "adapters_t": "table", "plugin_dir_t": "string", } for k, want := range wantTypes { if got := m[k]; got != want { t.Errorf("%s = %v, 期望 %s", k, got, want) } } // 复制品会让 secrets 变成两张表:插件写进 A、内核从 B 读。 if m["secrets_aliased"] != true { t.Error("llmsproxy.secrets 与内部桥不是同一张表 —— 凭据会分叉") } if m["kernel_aliased"] != true { t.Error("llmsproxy.kernel 与内部桥不是同一张表") } if m["plugin_dir"] == "" { t.Error("llmsproxy.plugin_dir 为空 —— 插件会退回去猜路径(曾覆盖生产数据)") } // 端到端:SDK 不是声明,而是能真正驱动内核。 rout, err := ps.Call("sdkprobe", "roundtrip", nil) if err != nil { t.Fatalf("SDK 往返失败: %v", err) } rm, ok := rout.(map[string]interface{}) if !ok { t.Fatalf("返回值不是 table: %#v", rout) } if rm["ok"] != true { t.Errorf("经 SDK 写读凭据失败: %#v", rm) } if n, _ := rm["actions"].(float64); n == 0 { t.Error("llmsproxy.kernel.actions() 为空 —— SDK 没有暴露内核动作面") } } // TestAdapterVMHasNoPluginSDK 是安全边界:适配器与插件共用 setupGlobals, // 但 SDK 只装在插件 VM 上。适配器是运维提供的配置,不该拿到凭据库与内核动作面。 func TestAdapterVMHasNoPluginSDK(t *testing.T) { L := golua.NewState() defer L.Close() L.OpenLibs() setupGlobals(L) L.GetGlobal(sdkGlobal) if L.Type(-1) != golua.LUA_TNIL { t.Error("setupGlobals 安装了 llmsproxy —— 适配器 VM 也会拿到凭据库与内核动作面") } }