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) }