mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 23:53:33 +08:00
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)与回归测试。
60 lines
2.4 KiB
Go
60 lines
2.4 KiB
Go
package plugin
|
||
|
||
// Phase-1.5 对抗式审计(2026-06-11)发现的 P2 问题回归测试:
|
||
// A-1: 失败的 Build 不得被静默重跑(会对已 Provision 实例不 Stop 即覆盖)
|
||
// C-1: enabled 空值(YAML null)按"未显式配置"处理,走模块默认值
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// TestRuntimeBuildFailureBlocksRetry 固化审计 A-1 修复:
|
||
// Build 失败后,二次 Build 与 Start 均被拒绝,且不重新驱动任何生命周期。
|
||
func TestRuntimeBuildFailureBlocksRetry(t *testing.T) {
|
||
log := &lifecycleLog{}
|
||
okMod := &fakeModule{id: "job.a", enabledByDefault: true, log: log}
|
||
badMod := &fakeModule{id: "job.b", enabledByDefault: true, log: log, provisionErr: errors.New("boom")}
|
||
rt := newTestRuntime(t, Config{}, okMod, badMod)
|
||
|
||
require.Error(t, rt.Build(), "首次 Build 应因 job.b Provision 失败而报错")
|
||
firstLog := log.snapshot()
|
||
|
||
err := rt.Build()
|
||
require.Error(t, err)
|
||
require.Contains(t, err.Error(), "cannot be rebuilt", "失败后的二次 Build 应被明确拒绝")
|
||
require.Equal(t, firstLog, log.snapshot(), "二次 Build 不得重新 Provision 任何模块")
|
||
|
||
require.Error(t, rt.Start(context.Background()), "Build 未成功时 Start 应被拒绝")
|
||
}
|
||
|
||
// TestRuntimeBuildSuccessThenRebuildStillRejected 确认成功路径的原有 guard 语义不变。
|
||
func TestRuntimeBuildSuccessThenRebuildStillRejected(t *testing.T) {
|
||
rt := newTestRuntime(t, Config{}, &plainModule{id: "job.ok"})
|
||
require.NoError(t, rt.Build())
|
||
err := rt.Build()
|
||
require.Error(t, err)
|
||
require.Contains(t, err.Error(), "already built")
|
||
}
|
||
|
||
// TestParseConfigEnabledNilTreatedAsUnset 固化审计 C-1 修复:
|
||
// `enabled:`(YAML 空值 → nil)等价于未配置,三态语义回落到模块默认值,
|
||
// 且不影响同条目下私有配置的解析。
|
||
func TestParseConfigEnabledNilTreatedAsUnset(t *testing.T) {
|
||
cfg, err := ParseConfig(map[string]map[string]any{
|
||
"job.hello": {"enabled": nil, "greeting": "hi"},
|
||
})
|
||
require.NoError(t, err)
|
||
|
||
mc, ok := cfg["job.hello"]
|
||
require.True(t, ok)
|
||
require.Nil(t, mc.Enabled, "enabled 空值应视为未显式配置")
|
||
require.Equal(t, "hi", mc.Raw["greeting"], "私有配置不受 enabled 空值影响")
|
||
|
||
require.True(t, cfg.enabledFor(ModuleInfo{ID: "job.hello", EnabledByDefault: true}))
|
||
require.False(t, cfg.enabledFor(ModuleInfo{ID: "job.hello", EnabledByDefault: false}))
|
||
}
|