package highclient_test import ( "errors" "fmt" "testing" "time" "github.com/ThreeDotsLabs/watermill/message" "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.yandata.net/iod/iod/trustlog-sdk/api/adapter" "go.yandata.net/iod/iod/trustlog-sdk/api/highclient" "go.yandata.net/iod/iod/trustlog-sdk/api/logger" "go.yandata.net/iod/iod/trustlog-sdk/api/model" ) // MockPublisher 模拟 message.Publisher. type MockPublisher struct { mock.Mock } func (m *MockPublisher) Publish(topic string, messages ...*message.Message) error { args := m.Called(topic, messages) return args.Error(0) } func (m *MockPublisher) Close() error { args := m.Called() return args.Error(0) } // generateTestKeys 生成测试用的SM2密钥对(DER格式). func generateTestKeys(t testing.TB) ([]byte, []byte) { keyPair, err := model.GenerateSM2KeyPair() if err != nil { if t != nil { require.NoError(t, err) } else { panic(err) } } // 私钥:DER编码 privateKeyDER, err := model.MarshalSM2PrivateDER(keyPair.Private) if err != nil { if t != nil { require.NoError(t, err) } else { panic(err) } } // 公钥:DER编码 publicKeyDER, err := model.MarshalSM2PublicDER(keyPair.Public) if err != nil { if t != nil { require.NoError(t, err) } else { panic(err) } } return privateKeyDER, publicKeyDER } func TestNewClient(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) client := highclient.NewClient(mockPublisher, testLogger, config) require.NotNil(t, client) assert.Equal(t, mockPublisher, client.GetLow()) } func TestClient_GetLow(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) client := highclient.NewClient(mockPublisher, testLogger, config) lowLevelPublisher := client.GetLow() assert.Equal(t, mockPublisher, lowLevelPublisher) } func TestClient_OperationPublish(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的 tests := []struct { name string operation *model.Operation setupMock func(*MockPublisher) wantErr bool errContains string }{ { name: "成功发布Operation", operation: createTestOperation(t), setupMock: func(mp *MockPublisher) { mp.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")).Return(nil).Once() }, wantErr: false, }, { name: "发布失败", operation: createTestOperation(t), setupMock: func(mp *MockPublisher) { mp.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")). Return(errors.New("publish failed")). Once() }, wantErr: true, errContains: "publish failed", }, { name: "nil Operation应该失败", operation: nil, setupMock: func(_ *MockPublisher) { // nil operation不会调用Publish,因为会在之前失败 }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) tt.setupMock(mockPublisher) client := highclient.NewClient(mockPublisher, testLogger, config) err := client.OperationPublish(tt.operation) if tt.wantErr { require.Error(t, err) if tt.errContains != "" { assert.Contains(t, err.Error(), tt.errContains) } } else { require.NoError(t, err) } mockPublisher.AssertExpectations(t) }) } } func TestClient_RecordPublish(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的 tests := []struct { name string record *model.Record setupMock func(*MockPublisher) wantErr bool errContains string }{ { name: "成功发布Record", record: createTestRecord(t), setupMock: func(mp *MockPublisher) { mp.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")).Return(nil).Once() }, wantErr: false, }, { name: "发布失败", record: createTestRecord(t), setupMock: func(mp *MockPublisher) { mp.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")). Return(errors.New("record publish failed")). Once() }, wantErr: true, errContains: "record publish failed", }, { name: "nil Record应该失败", record: nil, setupMock: func(_ *MockPublisher) { // nil record不会调用Publish,因为会在之前失败 }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) tt.setupMock(mockPublisher) client := highclient.NewClient(mockPublisher, testLogger, config) err := client.RecordPublish(tt.record) if tt.wantErr { require.Error(t, err) if tt.errContains != "" { assert.Contains(t, err.Error(), tt.errContains) } } else { require.NoError(t, err) } mockPublisher.AssertExpectations(t) }) } } func TestClient_Close(t *testing.T) { tests := []struct { name string setupMock func(*MockPublisher) wantErr bool errContains string }{ { name: "成功关闭", setupMock: func(mp *MockPublisher) { mp.On("Close").Return(nil).Once() }, wantErr: false, }, { name: "关闭失败", setupMock: func(mp *MockPublisher) { mp.On("Close").Return(errors.New("close failed")).Once() }, wantErr: true, errContains: "close failed", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) tt.setupMock(mockPublisher) client := highclient.NewClient(mockPublisher, testLogger, config) err := client.Close() if tt.wantErr { require.Error(t, err) if tt.errContains != "" { assert.Contains(t, err.Error(), tt.errContains) } } else { require.NoError(t, err) } mockPublisher.AssertExpectations(t) }) } } func TestClient_MessageContent(t *testing.T) { // 测试发布的消息内容是否正确 t.Run("Operation消息内容验证", func(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的 mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) operation := createTestOperation(t) // 捕获发布的消息 var capturedMessages []*message.Message mockPublisher.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")). Run(func(args mock.Arguments) { messages, ok := args.Get(1).([]*message.Message) if ok { capturedMessages = messages } }).Return(nil).Once() client := highclient.NewClient(mockPublisher, testLogger, config) err := client.OperationPublish(operation) require.NoError(t, err) // 验证消息内容 require.Len(t, capturedMessages, 1) msg := capturedMessages[0] assert.Equal(t, operation.Key(), msg.UUID) assert.NotEmpty(t, msg.Payload) // 验证是Envelope格式,可以反序列化 unmarshaledOp, err := model.UnmarshalOperation(msg.Payload) require.NoError(t, err) assert.Equal(t, operation.OpID, unmarshaledOp.OpID) // 验证签名 verifyConfig := model.NewSM2VerifyConfig(publicKey) verifiedEnv, err := model.VerifyEnvelopeWithConfig(msg.Payload, verifyConfig) require.NoError(t, err) assert.NotNil(t, verifiedEnv) }) t.Run("Record消息内容验证", func(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的 mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) record := createTestRecord(t) // 捕获发布的消息 var capturedMessages []*message.Message mockPublisher.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")). Run(func(args mock.Arguments) { messages, ok := args.Get(1).([]*message.Message) if ok { capturedMessages = messages } }).Return(nil).Once() client := highclient.NewClient(mockPublisher, testLogger, config) err := client.RecordPublish(record) require.NoError(t, err) // 验证消息内容 require.Len(t, capturedMessages, 1) msg := capturedMessages[0] assert.Equal(t, record.Key(), msg.UUID) assert.NotEmpty(t, msg.Payload) // 验证是Envelope格式,可以反序列化 unmarshaledRecord, err := model.UnmarshalRecord(msg.Payload) require.NoError(t, err) assert.Equal(t, record.ID, unmarshaledRecord.ID) // 验证签名 verifyConfig := model.NewSM2VerifyConfig(publicKey) verifiedEnv, err := model.VerifyEnvelopeWithConfig(msg.Payload, verifyConfig) require.NoError(t, err) assert.NotNil(t, verifiedEnv) }) } func TestClient_ConcurrentPublish(t *testing.T) { // 测试并发发布 mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) // 设置期望的调用次数 publishCount := 100 mockPublisher.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")). Return(nil).Times(publishCount) client := highclient.NewClient(mockPublisher, testLogger, config) // 并发发布 errChan := make(chan error, publishCount) for i := range publishCount { go func(id int) { //nolint:testifylint // 在goroutine中创建测试数据,使用panic处理错误 operation := createTestOperationWithID(nil, fmt.Sprintf("concurrent-test-%d", id)) errChan <- client.OperationPublish(operation) }(i) } // 收集结果 for range publishCount { err := <-errChan require.NoError(t, err) } mockPublisher.AssertExpectations(t) } func TestClient_EdgeCases(t *testing.T) { t.Run("发布大型Operation", func(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) mockPublisher.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")). Return(nil). Once() client := highclient.NewClient(mockPublisher, testLogger, config) // 创建包含大量数据的Operation operation := createTestOperation(t) operation.OpActor = string(make([]byte, 1000)) // 1KB数据 err := client.OperationPublish(operation) require.NoError(t, err) mockPublisher.AssertExpectations(t) }) t.Run("发布大型Record", func(t *testing.T) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) mockPublisher.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")).Return(nil).Once() client := highclient.NewClient(mockPublisher, testLogger, config) // 创建包含大量数据的Record record := createTestRecord(t) record.WithExtra(make([]byte, 500)) // 500字节的额外数据 err := client.RecordPublish(record) require.NoError(t, err) mockPublisher.AssertExpectations(t) }) } func TestClient_Integration(t *testing.T) { // 集成测试 - 测试完整的工作流程 mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(t) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) // 设置期望:发布Operation -> 发布Record -> 关闭 mockPublisher.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")).Return(nil).Once() mockPublisher.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")).Return(nil).Once() mockPublisher.On("Close").Return(nil).Once() client := highclient.NewClient(mockPublisher, testLogger, config) // 发布Operation operation := createTestOperation(t) err := client.OperationPublish(operation) require.NoError(t, err) // 发布Record record := createTestRecord(t) err = client.RecordPublish(record) require.NoError(t, err) // 关闭客户端 err = client.Close() require.NoError(t, err) mockPublisher.AssertExpectations(t) } // 性能基准测试. func BenchmarkClient_OperationPublish(b *testing.B) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(b) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) mockPublisher.On("Publish", adapter.OperationTopic, mock.AnythingOfType("[]*message.Message")).Return(nil) client := highclient.NewClient(mockPublisher, testLogger, config) operation := createTestOperation(b) b.ResetTimer() for range b.N { err := client.OperationPublish(operation) if err != nil { b.Fatal(err) } } } func BenchmarkClient_RecordPublish(b *testing.B) { mockPublisher := &MockPublisher{} testLogger := logger.NewLogger(logr.Discard()) privateKey, publicKey := generateTestKeys(b) config := model.NewSM2EnvelopeConfig(privateKey, publicKey) mockPublisher.On("Publish", adapter.RecordTopic, mock.AnythingOfType("[]*message.Message")).Return(nil) client := highclient.NewClient(mockPublisher, testLogger, config) record := createTestRecord(b) b.ResetTimer() for range b.N { err := client.RecordPublish(record) if err != nil { b.Fatal(err) } } } // 测试辅助函数. func createTestOperation(t testing.TB) *model.Operation { return createTestOperationWithID(t, "test-operation-001") } func createTestOperationWithID(t testing.TB, id string) *model.Operation { // 在并发测试中,t可能为nil,这是正常的 errorHandler := func(err error) { if t != nil { require.NoError(t, err) } else if err != nil { panic(err) } } operation, err := model.NewFullOperation( model.OpSourceDOIP, model.OpTypeRetrieve, "test-prefix", "test-repo", "test-prefix/test-repo/test-object", "test-producer-id", "test-actor", "test request body", "test response body", time.Now(), ) errorHandler(err) operation.OpID = id // 设置自定义ID return operation } func createTestRecord(t testing.TB) *model.Record { record, err := model.NewFullRecord( "test-prefix", "test-producer-id", time.Now(), "test-operator", []byte("test extra data"), "test-type", ) require.NoError(t, err) return record }