feat(P1/P2): 完成TDD开发及P1/P2设计文档
## 设计文档 - multi_role_permission_design: 多角色权限设计 (CONDITIONAL GO) - audit_log_enhancement_design: 审计日志增强 (CONDITIONAL GO) - routing_strategy_template_design: 路由策略模板 (CONDITIONAL GO) - sso_saml_technical_research: SSO/SAML调研 (CONDITIONAL GO) - compliance_capability_package_design: 合规能力包设计 (CONDITIONAL GO) ## TDD开发成果 - IAM模块: supply-api/internal/iam/ (111个测试) - 审计日志模块: supply-api/internal/audit/ (40+测试) - 路由策略模块: gateway/internal/router/ (33+测试) - 合规能力包: gateway/internal/compliance/ + scripts/ci/compliance/ ## 规范文档 - parallel_agent_output_quality_standards: 并行Agent产出质量规范 - project_experience_summary: 项目经验总结 (v2) - 2026-04-02-p1-p2-tdd-execution-plan: TDD执行计划 ## 评审报告 - 5个CONDITIONAL GO设计文档评审报告 - fix_verification_report: 修复验证报告 - full_verification_report: 全面质量验证报告 - tdd_module_quality_verification: TDD模块质量验证 - tdd_execution_summary: TDD执行总结 依据: Superpowers执行框架 + TDD规范
This commit is contained in:
164
gateway/internal/compliance/rules/loader_test.go
Normal file
164
gateway/internal/compliance/rules/loader_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestRuleLoader_ValidYaml 测试加载有效YAML
|
||||
func TestRuleLoader_ValidYaml(t *testing.T) {
|
||||
// 创建临时有效YAML文件
|
||||
tmpfile, err := os.CreateTemp("", "valid_rule_*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
validYAML := `
|
||||
rules:
|
||||
- id: "CRED-EXPOSE-RESPONSE"
|
||||
name: "响应体凭证泄露检测"
|
||||
description: "检测 API 响应中是否包含可复用的供应商凭证片段"
|
||||
severity: "P0"
|
||||
matchers:
|
||||
- type: "regex_match"
|
||||
pattern: "(sk-|ak-|api_key|secret|token).*[a-zA-Z0-9]{20,}"
|
||||
target: "response_body"
|
||||
scope: "all"
|
||||
action:
|
||||
primary: "block"
|
||||
secondary: "alert"
|
||||
audit:
|
||||
event_name: "CRED-EXPOSE-RESPONSE"
|
||||
event_category: "CRED"
|
||||
event_sub_category: "EXPOSE"
|
||||
`
|
||||
_, err = tmpfile.WriteString(validYAML)
|
||||
require.NoError(t, err)
|
||||
tmpfile.Close()
|
||||
|
||||
// 测试加载
|
||||
loader := NewRuleLoader()
|
||||
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, rules)
|
||||
assert.Len(t, rules, 1)
|
||||
|
||||
rule := rules[0]
|
||||
assert.Equal(t, "CRED-EXPOSE-RESPONSE", rule.ID)
|
||||
assert.Equal(t, "P0", rule.Severity)
|
||||
assert.Equal(t, "block", rule.Action.Primary)
|
||||
}
|
||||
|
||||
// TestRuleLoader_InvalidYaml 测试加载无效YAML
|
||||
func TestRuleLoader_InvalidYaml(t *testing.T) {
|
||||
// 创建临时无效YAML文件
|
||||
tmpfile, err := os.CreateTemp("", "invalid_rule_*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
invalidYAML := `
|
||||
rules:
|
||||
- id: "CRED-EXPOSE-RESPONSE"
|
||||
name: "响应体凭证泄露检测"
|
||||
severity: "P0"
|
||||
# 缺少必需的matchers字段
|
||||
action:
|
||||
primary: "block"
|
||||
`
|
||||
_, err = tmpfile.WriteString(invalidYAML)
|
||||
require.NoError(t, err)
|
||||
tmpfile.Close()
|
||||
|
||||
// 测试加载
|
||||
loader := NewRuleLoader()
|
||||
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, rules)
|
||||
}
|
||||
|
||||
// TestRuleLoader_MissingFields 测试缺少必需字段
|
||||
func TestRuleLoader_MissingFields(t *testing.T) {
|
||||
// 创建缺少必需字段的YAML
|
||||
tmpfile, err := os.CreateTemp("", "missing_fields_*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
// 缺少 id 字段
|
||||
missingIDYAML := `
|
||||
rules:
|
||||
- name: "响应体凭证泄露检测"
|
||||
severity: "P0"
|
||||
matchers:
|
||||
- type: "regex_match"
|
||||
action:
|
||||
primary: "block"
|
||||
`
|
||||
_, err = tmpfile.WriteString(missingIDYAML)
|
||||
require.NoError(t, err)
|
||||
tmpfile.Close()
|
||||
|
||||
loader := NewRuleLoader()
|
||||
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, rules)
|
||||
assert.Contains(t, err.Error(), "missing required field: id")
|
||||
}
|
||||
|
||||
// TestRuleLoader_FileNotFound 测试文件不存在
|
||||
func TestRuleLoader_FileNotFound(t *testing.T) {
|
||||
loader := NewRuleLoader()
|
||||
rules, err := loader.LoadFromFile("/nonexistent/path/rules.yaml")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, rules)
|
||||
}
|
||||
|
||||
// TestRuleLoader_ValidateRuleFormat 测试规则格式验证
|
||||
func TestRuleLoader_ValidateRuleFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ruleID string
|
||||
valid bool
|
||||
}{
|
||||
{"标准格式", "CRED-EXPOSE-RESPONSE", true},
|
||||
{"带Detail格式", "CRED-EXPOSE-RESPONSE-DETAIL", true},
|
||||
{"双连字符", "CRED--EXPOSE-RESPONSE", false},
|
||||
{"小写字母", "cred-expose-response", false},
|
||||
{"单字符Category", "C-EXPOSE-RESPONSE", false},
|
||||
}
|
||||
|
||||
loader := NewRuleLoader()
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
valid := loader.ValidateRuleID(tt.ruleID)
|
||||
assert.Equal(t, tt.valid, valid)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRuleLoader_EmptyRules 测试空规则列表
|
||||
func TestRuleLoader_EmptyRules(t *testing.T) {
|
||||
tmpfile, err := os.CreateTemp("", "empty_rules_*.yaml")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(tmpfile.Name())
|
||||
|
||||
emptyYAML := `
|
||||
rules: []
|
||||
`
|
||||
_, err = tmpfile.WriteString(emptyYAML)
|
||||
require.NoError(t, err)
|
||||
tmpfile.Close()
|
||||
|
||||
loader := NewRuleLoader()
|
||||
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, rules)
|
||||
assert.Len(t, rules, 0)
|
||||
}
|
||||
Reference in New Issue
Block a user