Files
lijiaoqiao/supply-api/internal/audit/service/audit_service_test.go
Your Name 89104bd0db 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规范
2026-04-02 23:35:53 +08:00

403 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"testing"
"time"
"lijiaoqiao/supply-api/internal/audit/model"
"github.com/stretchr/testify/assert"
)
// ==================== 写入API测试 ====================
func TestAuditService_CreateEvent_Success(t *testing.T) {
// 201 首次成功
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
event := &model.AuditEvent{
EventID: "test-event-1",
EventName: "CRED-EXPOSE-RESPONSE",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "create",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "SEC_CRED_EXPOSED",
IdempotencyKey: "idem-key-001",
}
result, err := svc.CreateEvent(ctx, event)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, 201, result.StatusCode)
assert.NotEmpty(t, result.EventID)
assert.Equal(t, "created", result.Status)
}
func TestAuditService_CreateEvent_IdempotentReplay(t *testing.T) {
// 200 重放同参
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
event := &model.AuditEvent{
EventID: "test-event-2",
EventName: "CRED-INGRESS-PLATFORM",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "query",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "CRED_INGRESS_OK",
IdempotencyKey: "idem-key-002",
}
// 首次创建
result1, err1 := svc.CreateEvent(ctx, event)
assert.NoError(t, err1)
assert.Equal(t, 201, result1.StatusCode)
// 重放同参
result2, err2 := svc.CreateEvent(ctx, event)
assert.NoError(t, err2)
assert.Equal(t, 200, result2.StatusCode)
assert.Equal(t, result1.EventID, result2.EventID)
assert.Equal(t, "duplicate", result2.Status)
}
func TestAuditService_CreateEvent_PayloadMismatch(t *testing.T) {
// 409 重放异参
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
// 第一次事件
event1 := &model.AuditEvent{
EventName: "CRED-INGRESS-PLATFORM",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "query",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "CRED_INGRESS_OK",
IdempotencyKey: "idem-key-003",
}
// 第二次同幂等键但不同payload
event2 := &model.AuditEvent{
EventName: "CRED-INGRESS-PLATFORM",
EventCategory: "CRED",
OperatorID: 1002, // 不同的operator
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "query",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "CRED_INGRESS_OK",
IdempotencyKey: "idem-key-003", // 同幂等键
}
// 首次创建
result1, err1 := svc.CreateEvent(ctx, event1)
assert.NoError(t, err1)
assert.Equal(t, 201, result1.StatusCode)
// 重放异参
result2, err2 := svc.CreateEvent(ctx, event2)
assert.NoError(t, err2)
assert.Equal(t, 409, result2.StatusCode)
assert.Equal(t, "IDEMPOTENCY_PAYLOAD_MISMATCH", result2.ErrorCode)
}
func TestAuditService_CreateEvent_InProgress(t *testing.T) {
// 202 处理中(模拟异步场景)
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
// 启用处理中模拟
svc.SetProcessingDelay(100 * time.Millisecond)
event := &model.AuditEvent{
EventName: "CRED-DIRECT-SUPPLIER",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "api",
ObjectID: 12345,
Action: "call",
CredentialType: "none",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: false,
ResultCode: "SEC_DIRECT_BYPASS",
IdempotencyKey: "idem-key-004",
}
// 由于是异步处理这里返回202
// 注意:在实际实现中,可能需要处理并发场景
result, err := svc.CreateEvent(ctx, event)
assert.NoError(t, err)
// 同步处理场景下可能是201或202
assert.True(t, result.StatusCode == 201 || result.StatusCode == 202)
}
func TestAuditService_CreateEvent_WithoutIdempotencyKey(t *testing.T) {
// 无幂等键时每次都创建新事件
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
event := &model.AuditEvent{
EventName: "AUTH-TOKEN-OK",
EventCategory: "AUTH",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "token",
ObjectID: 12345,
Action: "verify",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "AUTH_TOKEN_OK",
// 无 IdempotencyKey
}
result1, err1 := svc.CreateEvent(ctx, event)
assert.NoError(t, err1)
assert.Equal(t, 201, result1.StatusCode)
// 再次创建,由于没有幂等键,应该创建新事件
// 注意需要重置event.EventID否则会认为是同一个事件
event.EventID = ""
result2, err2 := svc.CreateEvent(ctx, event)
assert.NoError(t, err2)
assert.Equal(t, 201, result2.StatusCode)
assert.NotEqual(t, result1.EventID, result2.EventID)
}
func TestAuditService_CreateEvent_InvalidInput(t *testing.T) {
// 测试无效输入
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
// 空事件
result, err := svc.CreateEvent(ctx, nil)
assert.Error(t, err)
assert.Nil(t, result)
// 缺少必填字段
invalidEvent := &model.AuditEvent{
EventName: "", // 缺少事件名
}
result, err = svc.CreateEvent(ctx, invalidEvent)
assert.Error(t, err)
assert.Nil(t, result)
}
// ==================== 查询API测试 ====================
func TestAuditService_ListEvents_Pagination(t *testing.T) {
// 分页测试
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
// 创建10个事件
for i := 0; i < 10; i++ {
event := &model.AuditEvent{
EventName: "AUTH-TOKEN-OK",
EventCategory: "AUTH",
OperatorID: int64(1001 + i),
TenantID: 2001,
ObjectType: "token",
ObjectID: int64(i),
Action: "verify",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "AUTH_TOKEN_OK",
}
svc.CreateEvent(ctx, event)
}
// 第一页
events1, total1, err1 := svc.ListEvents(ctx, 2001, 0, 5)
assert.NoError(t, err1)
assert.Len(t, events1, 5)
assert.Equal(t, int64(10), total1)
// 第二页
events2, total2, err2 := svc.ListEvents(ctx, 2001, 5, 5)
assert.NoError(t, err2)
assert.Len(t, events2, 5)
assert.Equal(t, int64(10), total2)
}
func TestAuditService_ListEvents_FilterByCategory(t *testing.T) {
// 按类别过滤
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
// 创建不同类别的事件
categories := []string{"AUTH", "CRED", "DATA", "CONFIG"}
for i, cat := range categories {
event := &model.AuditEvent{
EventName: cat + "-TEST",
EventCategory: cat,
OperatorID: 1001,
TenantID: 2001,
ObjectType: "test",
ObjectID: int64(i),
Action: "test",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "TEST_OK",
}
svc.CreateEvent(ctx, event)
}
// 只查询AUTH类别
filter := &EventFilter{
TenantID: 2001,
Category: "AUTH",
}
events, total, err := svc.ListEventsWithFilter(ctx, filter)
assert.NoError(t, err)
assert.Len(t, events, 1)
assert.Equal(t, int64(1), total)
assert.Equal(t, "AUTH", events[0].EventCategory)
}
func TestAuditService_ListEvents_FilterByTimeRange(t *testing.T) {
// 按时间范围过滤
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
now := time.Now()
event := &model.AuditEvent{
EventName: "AUTH-TOKEN-OK",
EventCategory: "AUTH",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "token",
ObjectID: 12345,
Action: "verify",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "AUTH_TOKEN_OK",
}
svc.CreateEvent(ctx, event)
// 在时间范围内
filter := &EventFilter{
TenantID: 2001,
StartTime: now.Add(-1 * time.Hour),
EndTime: now.Add(1 * time.Hour),
}
events, total, err := svc.ListEventsWithFilter(ctx, filter)
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(events), 1)
assert.GreaterOrEqual(t, total, int64(len(events)))
// 在时间范围外
filter2 := &EventFilter{
TenantID: 2001,
StartTime: now.Add(1 * time.Hour),
EndTime: now.Add(2 * time.Hour),
}
events2, total2, err2 := svc.ListEventsWithFilter(ctx, filter2)
assert.NoError(t, err2)
assert.Equal(t, 0, len(events2))
assert.Equal(t, int64(0), total2)
}
func TestAuditService_ListEvents_FilterByEventName(t *testing.T) {
// 按事件名称过滤
ctx := context.Background()
svc := NewAuditService(NewInMemoryAuditStore())
event1 := &model.AuditEvent{
EventName: "CRED-EXPOSE-RESPONSE",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "create",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "SEC_CRED_EXPOSED",
}
event2 := &model.AuditEvent{
EventName: "CRED-INGRESS-PLATFORM",
EventCategory: "CRED",
OperatorID: 1001,
TenantID: 2001,
ObjectType: "account",
ObjectID: 12345,
Action: "query",
CredentialType: "platform_token",
SourceType: "api",
SourceIP: "192.168.1.1",
Success: true,
ResultCode: "CRED_INGRESS_OK",
}
svc.CreateEvent(ctx, event1)
svc.CreateEvent(ctx, event2)
// 按事件名称过滤
filter := &EventFilter{
TenantID: 2001,
EventName: "CRED-EXPOSE-RESPONSE",
}
events, total, err := svc.ListEventsWithFilter(ctx, filter)
assert.NoError(t, err)
assert.Len(t, events, 1)
assert.Equal(t, "CRED-EXPOSE-RESPONSE", events[0].EventName)
assert.Equal(t, int64(1), total)
}
// ==================== 辅助函数测试 ====================
func TestAuditService_HashIdempotencyKey(t *testing.T) {
// 测试幂等键哈希
svc := NewAuditService(NewInMemoryAuditStore())
key := "test-idempotency-key"
hash1 := svc.HashIdempotencyKey(key)
hash2 := svc.HashIdempotencyKey(key)
// 相同键应产生相同哈希
assert.Equal(t, hash1, hash2)
// 不同键应产生不同哈希
hash3 := svc.HashIdempotencyKey("different-key")
assert.NotEqual(t, hash1, hash3)
}