459 lines
11 KiB
Go
459 lines
11 KiB
Go
|
|
package model
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"github.com/stretchr/testify/assert"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// ==================== M-013 凭证暴露事件详情 ====================
|
|||
|
|
|
|||
|
|
func TestCredentialExposureDetail_New(t *testing.T) {
|
|||
|
|
// M-013: 凭证暴露事件专用
|
|||
|
|
detail := NewCredentialExposureDetail(
|
|||
|
|
"exposed_in_response",
|
|||
|
|
"response_body",
|
|||
|
|
"sk-[a-zA-Z0-9]{20,}",
|
|||
|
|
"sk-xxxxxx****xxxx",
|
|||
|
|
"SCAN-001",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.Equal(t, "exposed_in_response", detail.ExposureType)
|
|||
|
|
assert.Equal(t, "response_body", detail.ExposureLocation)
|
|||
|
|
assert.Equal(t, "sk-[a-zA-Z0-9]{20,}", detail.ExposurePattern)
|
|||
|
|
assert.Equal(t, "sk-xxxxxx****xxxx", detail.ExposedFragment)
|
|||
|
|
assert.Equal(t, "SCAN-001", detail.ScanRuleID)
|
|||
|
|
assert.False(t, detail.Resolved)
|
|||
|
|
assert.Nil(t, detail.ResolvedAt)
|
|||
|
|
assert.Nil(t, detail.ResolvedBy)
|
|||
|
|
assert.Empty(t, detail.ResolutionNotes)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialExposureDetail_Resolve(t *testing.T) {
|
|||
|
|
detail := NewCredentialExposureDetail(
|
|||
|
|
"exposed_in_response",
|
|||
|
|
"response_body",
|
|||
|
|
"sk-[a-zA-Z0-9]{20,}",
|
|||
|
|
"sk-xxxxxx****xxxx",
|
|||
|
|
"SCAN-001",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
detail.Resolve(1001, "Fixed by adding masking")
|
|||
|
|
|
|||
|
|
assert.True(t, detail.Resolved)
|
|||
|
|
assert.NotNil(t, detail.ResolvedAt)
|
|||
|
|
assert.Equal(t, int64(1001), *detail.ResolvedBy)
|
|||
|
|
assert.Equal(t, "Fixed by adding masking", detail.ResolutionNotes)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialExposureDetail_ExposureTypes(t *testing.T) {
|
|||
|
|
// 验证暴露类型常量
|
|||
|
|
validTypes := []string{
|
|||
|
|
"exposed_in_response",
|
|||
|
|
"exposed_in_log",
|
|||
|
|
"exposed_in_export",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, exposureType := range validTypes {
|
|||
|
|
detail := NewCredentialExposureDetail(
|
|||
|
|
exposureType,
|
|||
|
|
"response_body",
|
|||
|
|
"pattern",
|
|||
|
|
"fragment",
|
|||
|
|
"SCAN-001",
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, exposureType, detail.ExposureType)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialExposureDetail_ExposureLocations(t *testing.T) {
|
|||
|
|
// 验证暴露位置常量
|
|||
|
|
validLocations := []string{
|
|||
|
|
"response_body",
|
|||
|
|
"response_header",
|
|||
|
|
"log_file",
|
|||
|
|
"export_file",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, location := range validLocations {
|
|||
|
|
detail := NewCredentialExposureDetail(
|
|||
|
|
"exposed_in_response",
|
|||
|
|
location,
|
|||
|
|
"pattern",
|
|||
|
|
"fragment",
|
|||
|
|
"SCAN-001",
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, location, detail.ExposureLocation)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== M-014 凭证入站事件详情 ====================
|
|||
|
|
|
|||
|
|
func TestCredentialIngressDetail_New(t *testing.T) {
|
|||
|
|
// M-014: 凭证入站类型专用
|
|||
|
|
detail := NewCredentialIngressDetail(
|
|||
|
|
"platform_token",
|
|||
|
|
"platform_token",
|
|||
|
|
true,
|
|||
|
|
true,
|
|||
|
|
false,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.Equal(t, "platform_token", detail.RequestCredentialType)
|
|||
|
|
assert.Equal(t, "platform_token", detail.ExpectedCredentialType)
|
|||
|
|
assert.True(t, detail.CoverageCompliant)
|
|||
|
|
assert.True(t, detail.PlatformTokenPresent)
|
|||
|
|
assert.False(t, detail.UpstreamKeyPresent)
|
|||
|
|
assert.False(t, detail.Reviewed)
|
|||
|
|
assert.Nil(t, detail.ReviewedAt)
|
|||
|
|
assert.Nil(t, detail.ReviewedBy)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialIngressDetail_NonCompliant(t *testing.T) {
|
|||
|
|
// M-014 非合规场景:使用 query_key 而不是 platform_token
|
|||
|
|
detail := NewCredentialIngressDetail(
|
|||
|
|
"query_key",
|
|||
|
|
"platform_token",
|
|||
|
|
false,
|
|||
|
|
false,
|
|||
|
|
true,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.Equal(t, "query_key", detail.RequestCredentialType)
|
|||
|
|
assert.Equal(t, "platform_token", detail.ExpectedCredentialType)
|
|||
|
|
assert.False(t, detail.CoverageCompliant)
|
|||
|
|
assert.False(t, detail.PlatformTokenPresent)
|
|||
|
|
assert.True(t, detail.UpstreamKeyPresent)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialIngressDetail_Review(t *testing.T) {
|
|||
|
|
detail := NewCredentialIngressDetail(
|
|||
|
|
"platform_token",
|
|||
|
|
"platform_token",
|
|||
|
|
true,
|
|||
|
|
true,
|
|||
|
|
false,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
detail.Review(1001)
|
|||
|
|
|
|||
|
|
assert.True(t, detail.Reviewed)
|
|||
|
|
assert.NotNil(t, detail.ReviewedAt)
|
|||
|
|
assert.Equal(t, int64(1001), *detail.ReviewedBy)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCredentialIngressDetail_CredentialTypes(t *testing.T) {
|
|||
|
|
// 验证凭证类型
|
|||
|
|
testCases := []struct {
|
|||
|
|
credType string
|
|||
|
|
platformToken bool
|
|||
|
|
upstreamKey bool
|
|||
|
|
compliant bool
|
|||
|
|
}{
|
|||
|
|
{"platform_token", true, false, true},
|
|||
|
|
{"query_key", false, false, false},
|
|||
|
|
{"upstream_api_key", false, true, false},
|
|||
|
|
{"none", false, false, false},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tc := range testCases {
|
|||
|
|
detail := NewCredentialIngressDetail(
|
|||
|
|
tc.credType,
|
|||
|
|
"platform_token",
|
|||
|
|
tc.compliant,
|
|||
|
|
tc.platformToken,
|
|||
|
|
tc.upstreamKey,
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, tc.compliant, detail.CoverageCompliant, "Compliance mismatch for %s", tc.credType)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== M-015 直连绕过事件详情 ====================
|
|||
|
|
|
|||
|
|
func TestDirectCallDetail_New(t *testing.T) {
|
|||
|
|
// M-015: 直连绕过专用
|
|||
|
|
detail := NewDirectCallDetail(
|
|||
|
|
1001, // consumerID
|
|||
|
|
2001, // supplierID
|
|||
|
|
"https://supplier.example.com/v1/chat/completions",
|
|||
|
|
false, // viaPlatform
|
|||
|
|
"ip_bypass",
|
|||
|
|
"upstream_api_pattern_match",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.Equal(t, int64(1001), detail.ConsumerID)
|
|||
|
|
assert.Equal(t, int64(2001), detail.SupplierID)
|
|||
|
|
assert.Equal(t, "https://supplier.example.com/v1/chat/completions", detail.DirectEndpoint)
|
|||
|
|
assert.False(t, detail.ViaPlatform)
|
|||
|
|
assert.Equal(t, "ip_bypass", detail.BypassType)
|
|||
|
|
assert.Equal(t, "upstream_api_pattern_match", detail.DetectionMethod)
|
|||
|
|
assert.False(t, detail.Blocked)
|
|||
|
|
assert.Nil(t, detail.BlockedAt)
|
|||
|
|
assert.Empty(t, detail.BlockReason)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestDirectCallDetail_Block(t *testing.T) {
|
|||
|
|
detail := NewDirectCallDetail(
|
|||
|
|
1001,
|
|||
|
|
2001,
|
|||
|
|
"https://supplier.example.com/v1/chat/completions",
|
|||
|
|
false,
|
|||
|
|
"ip_bypass",
|
|||
|
|
"upstream_api_pattern_match",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
detail.Block("P0 event - immediate block")
|
|||
|
|
|
|||
|
|
assert.True(t, detail.Blocked)
|
|||
|
|
assert.NotNil(t, detail.BlockedAt)
|
|||
|
|
assert.Equal(t, "P0 event - immediate block", detail.BlockReason)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestDirectCallDetail_BypassTypes(t *testing.T) {
|
|||
|
|
// 验证绕过类型常量
|
|||
|
|
validBypassTypes := []string{
|
|||
|
|
"ip_bypass",
|
|||
|
|
"proxy_bypass",
|
|||
|
|
"config_bypass",
|
|||
|
|
"dns_bypass",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, bypassType := range validBypassTypes {
|
|||
|
|
detail := NewDirectCallDetail(
|
|||
|
|
1001,
|
|||
|
|
2001,
|
|||
|
|
"https://example.com",
|
|||
|
|
false,
|
|||
|
|
bypassType,
|
|||
|
|
"detection_method",
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, bypassType, detail.BypassType)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestDirectCallDetail_DetectionMethods(t *testing.T) {
|
|||
|
|
// 验证检测方法常量
|
|||
|
|
validMethods := []string{
|
|||
|
|
"upstream_api_pattern_match",
|
|||
|
|
"dns_resolution_check",
|
|||
|
|
"connection_source_check",
|
|||
|
|
"ip_whitelist_check",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, method := range validMethods {
|
|||
|
|
detail := NewDirectCallDetail(
|
|||
|
|
1001,
|
|||
|
|
2001,
|
|||
|
|
"https://example.com",
|
|||
|
|
false,
|
|||
|
|
"ip_bypass",
|
|||
|
|
method,
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, method, detail.DetectionMethod)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestDirectCallDetail_ViaPlatform(t *testing.T) {
|
|||
|
|
// 通过平台的调用不应该标记为直连
|
|||
|
|
detail := NewDirectCallDetail(
|
|||
|
|
1001,
|
|||
|
|
2001,
|
|||
|
|
"https://platform.example.com/v1/chat/completions",
|
|||
|
|
true, // viaPlatform = true
|
|||
|
|
"",
|
|||
|
|
"platform_proxy",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.True(t, detail.ViaPlatform)
|
|||
|
|
assert.False(t, detail.Blocked)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== M-016 Query Key 拒绝事件详情 ====================
|
|||
|
|
|
|||
|
|
func TestQueryKeyRejectDetail_New(t *testing.T) {
|
|||
|
|
// M-016: query key 拒绝专用
|
|||
|
|
detail := NewQueryKeyRejectDetail(
|
|||
|
|
"qk-12345",
|
|||
|
|
"/v1/chat/completions",
|
|||
|
|
"not_allowed",
|
|||
|
|
"QUERY_KEY_NOT_ALLOWED",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
assert.Equal(t, "qk-12345", detail.QueryKeyID)
|
|||
|
|
assert.Equal(t, "/v1/chat/completions", detail.RequestedEndpoint)
|
|||
|
|
assert.Equal(t, "not_allowed", detail.RejectReason)
|
|||
|
|
assert.Equal(t, "QUERY_KEY_NOT_ALLOWED", detail.RejectCode)
|
|||
|
|
assert.True(t, detail.FirstOccurrence)
|
|||
|
|
assert.Equal(t, 1, detail.OccurrenceCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestQueryKeyRejectDetail_RecordOccurrence(t *testing.T) {
|
|||
|
|
detail := NewQueryKeyRejectDetail(
|
|||
|
|
"qk-12345",
|
|||
|
|
"/v1/chat/completions",
|
|||
|
|
"not_allowed",
|
|||
|
|
"QUERY_KEY_NOT_ALLOWED",
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 第二次发生
|
|||
|
|
detail.RecordOccurrence(false)
|
|||
|
|
assert.Equal(t, 2, detail.OccurrenceCount)
|
|||
|
|
assert.False(t, detail.FirstOccurrence)
|
|||
|
|
|
|||
|
|
// 第三次发生
|
|||
|
|
detail.RecordOccurrence(false)
|
|||
|
|
assert.Equal(t, 3, detail.OccurrenceCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestQueryKeyRejectDetail_RejectReasons(t *testing.T) {
|
|||
|
|
// 验证拒绝原因常量
|
|||
|
|
validReasons := []string{
|
|||
|
|
"not_allowed",
|
|||
|
|
"expired",
|
|||
|
|
"malformed",
|
|||
|
|
"revoked",
|
|||
|
|
"rate_limited",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, reason := range validReasons {
|
|||
|
|
detail := NewQueryKeyRejectDetail(
|
|||
|
|
"qk-12345",
|
|||
|
|
"/v1/chat/completions",
|
|||
|
|
reason,
|
|||
|
|
"QUERY_KEY_REJECT",
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, reason, detail.RejectReason)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestQueryKeyRejectDetail_RejectCodes(t *testing.T) {
|
|||
|
|
// 验证拒绝码常量
|
|||
|
|
validCodes := []string{
|
|||
|
|
"QUERY_KEY_NOT_ALLOWED",
|
|||
|
|
"QUERY_KEY_EXPIRED",
|
|||
|
|
"QUERY_KEY_MALFORMED",
|
|||
|
|
"QUERY_KEY_REVOKED",
|
|||
|
|
"QUERY_KEY_RATE_LIMITED",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, code := range validCodes {
|
|||
|
|
detail := NewQueryKeyRejectDetail(
|
|||
|
|
"qk-12345",
|
|||
|
|
"/v1/chat/completions",
|
|||
|
|
"not_allowed",
|
|||
|
|
code,
|
|||
|
|
)
|
|||
|
|
assert.Equal(t, code, detail.RejectCode)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ==================== 指标计算辅助函数 ====================
|
|||
|
|
|
|||
|
|
func TestCalculateM013(t *testing.T) {
|
|||
|
|
// M-013: 凭证泄露事件数 = 0
|
|||
|
|
events := []struct {
|
|||
|
|
eventName string
|
|||
|
|
resolved bool
|
|||
|
|
}{
|
|||
|
|
{"CRED-EXPOSE-RESPONSE", true},
|
|||
|
|
{"CRED-EXPOSE-RESPONSE", true},
|
|||
|
|
{"CRED-EXPOSE-LOG", false},
|
|||
|
|
{"AUTH-TOKEN-OK", true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var unresolvedCount int
|
|||
|
|
for _, e := range events {
|
|||
|
|
if IsM013Event(e.eventName) && !e.resolved {
|
|||
|
|
unresolvedCount++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert.Equal(t, 1, unresolvedCount, "M-013 should have 1 unresolved event")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCalculateM014(t *testing.T) {
|
|||
|
|
// M-014: 平台凭证入站覆盖率 = 100%
|
|||
|
|
events := []struct {
|
|||
|
|
credentialType string
|
|||
|
|
compliant bool
|
|||
|
|
}{
|
|||
|
|
{"platform_token", true},
|
|||
|
|
{"platform_token", true},
|
|||
|
|
{"query_key", false},
|
|||
|
|
{"upstream_api_key", false},
|
|||
|
|
{"platform_token", true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var platformCount, totalCount int
|
|||
|
|
for _, e := range events {
|
|||
|
|
if IsM014Compliant(e.credentialType) {
|
|||
|
|
platformCount++
|
|||
|
|
}
|
|||
|
|
totalCount++
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
coverage := float64(platformCount) / float64(totalCount) * 100
|
|||
|
|
assert.Equal(t, 60.0, coverage, "M-014 coverage should be 60%%")
|
|||
|
|
assert.Equal(t, 3, platformCount)
|
|||
|
|
assert.Equal(t, 5, totalCount)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCalculateM015(t *testing.T) {
|
|||
|
|
// M-015: 直连事件数 = 0
|
|||
|
|
events := []struct {
|
|||
|
|
targetDirect bool
|
|||
|
|
blocked bool
|
|||
|
|
}{
|
|||
|
|
{targetDirect: true, blocked: false},
|
|||
|
|
{targetDirect: true, blocked: true},
|
|||
|
|
{targetDirect: false, blocked: false},
|
|||
|
|
{targetDirect: true, blocked: false},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var directCallCount, blockedCount int
|
|||
|
|
for _, e := range events {
|
|||
|
|
if e.targetDirect {
|
|||
|
|
directCallCount++
|
|||
|
|
if e.blocked {
|
|||
|
|
blockedCount++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
assert.Equal(t, 3, directCallCount, "M-015 should have 3 direct call events")
|
|||
|
|
assert.Equal(t, 1, blockedCount, "M-015 should have 1 blocked event")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestCalculateM016(t *testing.T) {
|
|||
|
|
// M-016: query key 拒绝率 = 100%
|
|||
|
|
// 分母:所有query key请求(不含被拒绝的无效请求)
|
|||
|
|
events := []struct {
|
|||
|
|
eventName string
|
|||
|
|
}{
|
|||
|
|
{"AUTH-QUERY-KEY"},
|
|||
|
|
{"AUTH-QUERY-REJECT"},
|
|||
|
|
{"AUTH-QUERY-KEY"},
|
|||
|
|
{"AUTH-QUERY-REJECT"},
|
|||
|
|
{"AUTH-TOKEN-OK"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var totalQueryKey, rejectedCount int
|
|||
|
|
for _, e := range events {
|
|||
|
|
if IsM016Event(e.eventName) {
|
|||
|
|
totalQueryKey++
|
|||
|
|
if e.eventName == "AUTH-QUERY-REJECT" {
|
|||
|
|
rejectedCount++
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
rejectRate := float64(rejectedCount) / float64(totalQueryKey) * 100
|
|||
|
|
assert.Equal(t, 4, totalQueryKey, "M-016 should have 4 query key events")
|
|||
|
|
assert.Equal(t, 2, rejectedCount, "M-016 should have 2 rejected events")
|
|||
|
|
assert.Equal(t, 50.0, rejectRate, "M-016 reject rate should be 50%%")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// IsM014Compliant 检查凭证类型是否为M-014合规
|
|||
|
|
func IsM014Compliant(credentialType string) bool {
|
|||
|
|
return credentialType == CredentialTypePlatformToken
|
|||
|
|
}
|