Files
shaw 8ba2fbd786 feat(plugin): Phase-1/1.5 进程内插件内核 + 开发套件后端
Caddy 式进程内模块系统(详见 docs/plugin-architecture/MODULE-AUTHOR-GUIDE.md):
- internal/plugin:命名空间注册表(init 注册/重复 panic)、Provision→Validate→Start→Stop
  生命周期(Start 半途逆序回滚、Stop 逆序+ctx deadline)、Host 四项 ports 能力面、
  modules: 配置子树(enabled 三态;viper 含点 key 手工提取)、Runtime 状态机
- internal/plugin/plugintest:模块测试夹具(NewHost Options + RunLifecycle)
- internal/modules:standard/imports.go 唯一插装清单 + job.hello 示例模块(默认 disabled)
- Wire/main 接入:Build+Start 失败 fail-fast(显式 Cleanup 防 leader 锁泄漏)、
  runtime.Stop 在 cleanup 并行组首位(先于 Redis/Ent)
- admin 可观测:GET /api/v1/admin/modules(只读,错误文本脱敏)
- 默认配置零行为变更(Phase-0 安全网 + bench allocs 零漂移验证)

含两轮对抗式审计修复(1×P1 启动失败泄漏 leader 锁 + 11×P2)与回归测试。
2026-06-11 22:58:51 +08:00

56 lines
1.3 KiB
Go

package plugin
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestModuleIDNamespaceAndName(t *testing.T) {
tests := []struct {
id ModuleID
namespace string
name string
}{
{id: "job.hello", namespace: "job", name: "hello"},
{id: "gateway.platform.anthropic", namespace: "gateway.platform", name: "anthropic"},
{id: "hello", namespace: "", name: "hello"},
{id: "payment.provider.stripe-checkout", namespace: "payment.provider", name: "stripe-checkout"},
}
for _, tt := range tests {
t.Run(string(tt.id), func(t *testing.T) {
require.Equal(t, tt.namespace, tt.id.Namespace())
require.Equal(t, tt.name, tt.id.Name())
})
}
}
func TestModuleIDValidate(t *testing.T) {
valid := []ModuleID{
"hello",
"job.hello",
"gateway.platform.anthropic",
"a.b-c.d_e",
"ns.mod123",
}
for _, id := range valid {
require.NoError(t, id.validate(), "id %q should be valid", id)
}
invalid := []ModuleID{
"",
".",
".hello",
"hello.",
"job..hello",
"Job.hello", // 大写
"job.hello!", // 非法字符
"job. hello", // 空格
"job.héllo", // 非 ASCII
"job.hello.World", // 末段大写
}
for _, id := range invalid {
require.Error(t, id.validate(), "id %q should be invalid", id)
}
}