纯库、零接线:不碰 gateway/frontend/Docs/.env*(本片刻意不接线,避免与在途切片争用文件面)。 - pkg/plugins/sdk:面板声明类型(JSON tag 与 design/09 §5.2 示例逐字同形); 字段类型与 section 类型都是**闭集**;When 三态 + **严格 UnmarshalJSON**(未知条件键报错, 不静默丢) - internal/capability/paneldecl:闭集校验(47 条拒绝用例,断言的是**分类哨兵 + JSON 路径**, 不是错误字符串)· catalog(并发安全、返回拷贝、按 (group, order, 登记序号) 的**全序**稳定排序)· when 求值(上下文由 Resolver 注入,本包不读环境变量/文件)· 角色过滤(viewer 只留 status/log/table 且摘掉 rowActions,纯写面板整体隐藏;未知角色 fail-closed) - 拒绝粒度:插件 id 非法 ⇒ 整插件拒绝;单个面板非法 ⇒ 只拒该面板 + 聚合显式报错(理由见报告) 【我的验收】隔离 worktree(19 文件)build/vet/linux/darwin 全过、69 包全绿; 我亲手打红变异:把同组同序的回退比较 反成 ⇒ TestCatalog_StableOrder FAIL。 (记录一条观察:比较器已靠 seq 构成全序,故 sort.SliceStable 的稳定性是冗余保护 —— 我第一次用「SliceStable→Slice」打不红,正是因为全序与算法无关;这不是测试虚护。)
175 lines
5.6 KiB
Go
175 lines
5.6 KiB
Go
package paneldecl
|
|
|
|
import (
|
|
"git.yeij.top/AskaEth/Cyrene_v2/pkg/plugins/sdk"
|
|
)
|
|
|
|
// 本文件是区块层的拒绝用例:类型闭集、id/标题必填、以及 §5.2.1 接入点表的形状矩阵
|
|
// (哪些键必须出现、哪些不许出现、哪些是只读区块上的写面)。
|
|
|
|
// rejectCasesSectionCommon 是区块的通用必填与唯一性。
|
|
func rejectCasesSectionCommon() []rejectCase {
|
|
return []rejectCase{
|
|
{
|
|
name: "区块类型不在闭集",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[0].Type = "chart" })
|
|
},
|
|
want: ErrUnknownSectionType, wantPath: "adminPanels[0].sections[0].type",
|
|
},
|
|
{
|
|
name: "区块 id 缺失",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[0].ID = "" })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[0].id",
|
|
},
|
|
{
|
|
name: "区块标题缺失",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[0].Title = "" })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[0].title",
|
|
},
|
|
{
|
|
name: "区块 id 在面板内重复",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[1].ID = "add" })
|
|
},
|
|
want: ErrDuplicateID, wantPath: "adminPanels[0].sections[1].id",
|
|
},
|
|
}
|
|
}
|
|
|
|
// rejectCasesFormShape 是 form 区块的形状(§5.2.1 section.form 只消费 fields[] + submit.action)。
|
|
func rejectCasesFormShape() []rejectCase {
|
|
return []rejectCase{
|
|
{
|
|
name: "form 缺 submit",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[0].Submit = nil })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[0].submit",
|
|
},
|
|
{
|
|
name: "form 缺 fields",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[0].Fields = nil })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[0].fields",
|
|
},
|
|
{
|
|
name: "form 声明 source(形状错误)",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].Source = &sdk.Source{Action: "plugin.listProviders"}
|
|
})
|
|
},
|
|
want: ErrSectionShape, wantPath: "adminPanels[0].sections[0].source",
|
|
},
|
|
{
|
|
name: "form 声明 columns(形状错误)",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].Columns = []sdk.Column{{Key: "k"}}
|
|
})
|
|
},
|
|
want: ErrSectionShape, wantPath: "adminPanels[0].sections[0].columns",
|
|
},
|
|
}
|
|
}
|
|
|
|
// rejectCasesTableShape 是 table 区块的形状(§5.2.1 section.table 消费 columns[]/rowActions[]/source)。
|
|
func rejectCasesTableShape() []rejectCase {
|
|
return []rejectCase{
|
|
{
|
|
name: "table 缺 columns",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[1].Columns = nil })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[1].columns",
|
|
},
|
|
{
|
|
name: "table 缺 source",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) { p.Sections[1].Source = nil })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[1].source",
|
|
},
|
|
{
|
|
name: "table 上出现写面 submit",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) {
|
|
p.Sections[1].Submit = &sdk.Submit{Action: "plugin.saveProvider"}
|
|
})
|
|
},
|
|
want: ErrReadOnlySection, wantPath: "adminPanels[0].sections[1].submit",
|
|
},
|
|
{
|
|
name: "table 声明字段(形状错误)",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(samplePanel(), func(p *sdk.Panel) {
|
|
p.Sections[1].Fields = []sdk.Field{{Key: "state", Type: sdk.FieldText}}
|
|
})
|
|
},
|
|
want: ErrSectionShape, wantPath: "adminPanels[0].sections[1].fields",
|
|
},
|
|
}
|
|
}
|
|
|
|
// rejectCasesReadOnlySections 是只读区块(status / log)的写面与形状。
|
|
func rejectCasesReadOnlySections() []rejectCase {
|
|
return []rejectCase{
|
|
{
|
|
name: "status 上出现写面 submit",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(statusPanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].Submit = &sdk.Submit{Action: "plugin.saveProvider"}
|
|
})
|
|
},
|
|
want: ErrReadOnlySection, wantPath: "adminPanels[0].sections[0].submit",
|
|
},
|
|
{
|
|
name: "status 上出现行内动作",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(statusPanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].RowActions = []sdk.RowAction{{ID: "test", Label: "测试", Action: "plugin.t"}}
|
|
})
|
|
},
|
|
want: ErrReadOnlySection, wantPath: "adminPanels[0].sections[0].rowActions",
|
|
},
|
|
{
|
|
name: "status 声明 source(形状错误)",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(statusPanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].Source = &sdk.Source{Action: "plugin.chain"}
|
|
})
|
|
},
|
|
want: ErrSectionShape, wantPath: "adminPanels[0].sections[0].source",
|
|
},
|
|
{
|
|
name: "log 缺 source",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(logPanel(), func(p *sdk.Panel) { p.Sections[0].Source = nil })
|
|
},
|
|
want: ErrMissingField, wantPath: "adminPanels[0].sections[0].source",
|
|
},
|
|
{
|
|
name: "log 的 limit 越界",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(logPanel(), func(p *sdk.Panel) { p.Sections[0].Limit = 5000 })
|
|
},
|
|
want: ErrInvalidLimit, wantPath: "adminPanels[0].sections[0].limit",
|
|
},
|
|
{
|
|
name: "log 声明 fields(形状错误)",
|
|
panels: func() []sdk.Panel {
|
|
return panelsOf(logPanel(), func(p *sdk.Panel) {
|
|
p.Sections[0].Fields = []sdk.Field{{Key: "line", Type: sdk.FieldText}}
|
|
})
|
|
},
|
|
want: ErrSectionShape, wantPath: "adminPanels[0].sections[0].fields",
|
|
},
|
|
}
|
|
}
|