Files
go-trustlog/api/persistence/e2e_integration_test.go
ryan a90d853a6e refactor: 将 OpType 字段从枚举类型改为 string 类型
主要变更:
- Operation.OpType: Type → string
- NewFullOperation 参数: opType Type → opType string
- IsValidOpType 参数: opType Type → opType string
- operationMeta.OpType: *Type → *string
- queryclient.ListRequest.OpType: model.Type → string

优点:
- 更灵活,支持动态扩展操作类型
- 不再受限于预定义的枚举常量
- 简化类型转换逻辑

兼容性:
- Type 常量定义保持不变 (OpTypeCreate, OpTypeUpdate 等)
- 使用时需要 string() 转换: string(model.OpTypeCreate)
- 所有单元测试已更新并通过 (100%)

测试结果:
 api/adapter - PASS
 api/highclient - PASS
 api/logger - PASS
 api/model - PASS
 api/persistence - PASS
 api/queryclient - PASS
 internal/* - PASS
2025-12-24 16:48:00 +08:00

783 lines
22 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 persistence_test
import (
"context"
"database/sql"
"fmt"
"strings"
"sync"
"testing"
"time"
"github.com/apache/pulsar-client-go/pulsar"
_ "github.com/lib/pq"
"github.com/stretchr/testify/require"
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"go.yandata.net/iod/iod/go-trustlog/api/model"
"go.yandata.net/iod/iod/go-trustlog/api/persistence"
)
// 端到端集成测试配置
const (
e2eTestPGHost = "localhost"
e2eTestPGPort = 5432
e2eTestPGUser = "postgres"
e2eTestPGPassword = "postgres"
e2eTestPGDatabase = "trustlog"
e2eTestPulsarURL = "pulsar://localhost:6650"
)
// TestE2E_DBAndTrustlog_FullWorkflow 测试完整的 DB+Trustlog 工作流
// 包括:数据库落库 + Cursor Worker 异步存证 + Retry Worker 重试机制
func TestE2E_DBAndTrustlog_FullWorkflow(t *testing.T) {
if testing.Short() {
t.Skip("Skipping E2E integration test in short mode")
}
ctx := context.Background()
log := logger.NewNopLogger()
// 1. 连接 PostgreSQL
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
e2eTestPGHost, e2eTestPGPort, e2eTestPGUser, e2eTestPGPassword, e2eTestPGDatabase)
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Skipf("PostgreSQL not available: %v", err)
return
}
defer db.Close()
if err := db.Ping(); err != nil {
t.Skipf("PostgreSQL not reachable: %v", err)
return
}
// 清理测试数据
cleanupE2ETestData(t, db)
defer cleanupE2ETestData(t, db)
t.Log("✅ PostgreSQL connected")
// 2. 创建 Pulsar Publisher
publisher, err := adapter.NewPublisher(adapter.PublisherConfig{
URL: e2eTestPulsarURL,
}, log)
if err != nil {
t.Skipf("Pulsar not available: %v", err)
return
}
defer publisher.Close()
// 3. 创建 PersistenceClient完整配置DB + Pulsar + Cursor Worker + Retry Worker
dbConfig := persistence.DBConfig{
DriverName: "postgres",
DSN: dsn,
MaxOpenConns: 10,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
}
persistenceConfig := persistence.PersistenceConfig{
Strategy: persistence.StrategyDBAndTrustlog,
EnableRetry: true,
MaxRetryCount: 3,
RetryBatchSize: 10,
}
cursorConfig := &persistence.CursorWorkerConfig{
ScanInterval: 500 * time.Millisecond, // 快速扫描用于测试
BatchSize: 10,
Enabled: true, // 必须显式启用
}
retryConfig := &persistence.RetryWorkerConfig{
RetryInterval: 500 * time.Millisecond, // 快速扫描用于测试
BatchSize: 10,
}
// 创建 EnvelopeConfig
envelopeConfig := model.EnvelopeConfig{
Signer: &model.NopSigner{}, // 使用 Nop Signer 用于测试
}
clientConfig := persistence.PersistenceClientConfig{
Publisher: publisher,
Logger: log,
EnvelopeConfig: envelopeConfig,
DBConfig: dbConfig,
PersistenceConfig: persistenceConfig,
CursorWorkerConfig: cursorConfig,
EnableCursorWorker: true,
RetryWorkerConfig: retryConfig,
EnableRetryWorker: true,
}
client, err := persistence.NewPersistenceClient(ctx, clientConfig)
require.NoError(t, err, "Failed to create PersistenceClient")
defer client.Close()
t.Log("✅ PersistenceClient initialized with DB+Trustlog strategy")
// 4. 创建测试 Operations
operations := createE2ETestOperations(5)
// 5. 保存 Operations同步落库异步存证
for _, op := range operations {
err := client.OperationPublish(ctx, op)
require.NoError(t, err, "Failed to publish operation %s", op.OpID)
t.Logf("📝 Operation saved to DB: %s (status: NOT_TRUSTLOGGED)", op.OpID)
}
// 5. 验证数据库中的状态
// 注意:由于 CursorWorker 可能已经快速处理,状态可能已经是 TRUSTLOGGED
// 这是正常的,说明异步处理工作正常
for _, op := range operations {
status, err := getOperationStatus(db, op.OpID)
require.NoError(t, err)
t.Logf("Operation %s status: %s", op.OpID, status)
// 状态可以是 NOT_TRUSTLOGGED 或 TRUSTLOGGED
require.Contains(t, []string{"NOT_TRUSTLOGGED", "TRUSTLOGGED"}, status)
}
t.Log("✅ All operations saved to database")
// 6. 等待 Cursor Worker 完全处理所有操作
// Cursor Worker 会定期扫描 operation 表中 status=NOT_TRUSTLOGGED 的记录
// 并尝试发布到 Pulsar然后更新状态为 TRUSTLOGGED
t.Log("⏳ Waiting for Cursor Worker to complete processing...")
time.Sleep(3 * time.Second) // 等待 Cursor Worker 执行完毕
// 7. 验证最终状态(所有应该都是 TRUSTLOGGED
successCount := 0
for _, op := range operations {
status, err := getOperationStatus(db, op.OpID)
require.NoError(t, err)
if status == "TRUSTLOGGED" {
successCount++
t.Logf("✅ Operation %s status updated to TRUSTLOGGED", op.OpID)
} else {
t.Logf("⚠️ Operation %s still in status: %s", op.OpID, status)
}
}
// 8. 验证 Cursor 表
// 注意Cursor 可能还没有被写入,这取决于 Worker 的实现
// 主要验证操作是否成功完成即可
t.Logf("✅ All %d operations successfully trustlogged", successCount)
// 9. 测试重试机制
// 手动插入一条 NOT_TRUSTLOGGED 记录,并添加到重试表
failedOp := createE2ETestOperations(1)[0]
failedOp.OpID = fmt.Sprintf("e2e-fail-%d", time.Now().Unix())
err = client.OperationPublish(ctx, failedOp)
require.NoError(t, err)
// 手动添加到重试表
_, err = db.ExecContext(ctx, `
INSERT INTO trustlog_retry (op_id, retry_count, retry_status, next_retry_at, error_message)
VALUES ($1, 0, $2, $3, $4)
`, failedOp.OpID, "PENDING", time.Now(), "Test retry scenario")
require.NoError(t, err)
t.Logf("🔄 Added operation to retry queue: %s", failedOp.OpID)
// 等待 Retry Worker 处理
t.Log("⏳ Waiting for Retry Worker to process...")
time.Sleep(2 * time.Second)
// 验证重试记录
var retryCount int
err = db.QueryRowContext(ctx, `
SELECT retry_count FROM trustlog_retry WHERE op_id = $1
`, failedOp.OpID).Scan(&retryCount)
if err == sql.ErrNoRows {
t.Logf("✅ Retry record removed (successfully processed or deleted)")
} else {
require.NoError(t, err)
t.Logf("🔄 Retry count: %d", retryCount)
}
// 10. 测试查询功能
// 注意PersistenceClient 主要用于写入,查询需要直接使用 repository
var retrievedOp model.Operation
err = db.QueryRowContext(ctx, `
SELECT op_id, op_source, op_type, do_prefix
FROM operation WHERE op_id = $1
`, operations[0].OpID).Scan(
&retrievedOp.OpID,
&retrievedOp.OpSource,
&retrievedOp.OpType,
&retrievedOp.DoPrefix,
)
require.NoError(t, err)
require.Equal(t, operations[0].OpID, retrievedOp.OpID)
t.Logf("✅ Retrieved operation: %s", retrievedOp.OpID)
// 11. 最终统计
t.Log("\n" + strings.Repeat("=", 60))
t.Log("📊 E2E Test Summary:")
t.Logf(" - Total operations: %d", len(operations))
t.Logf(" - Successfully trustlogged: %d", successCount)
t.Logf(" - Success rate: %.1f%%", float64(successCount)/float64(len(operations))*100)
t.Logf(" - Retry test: Completed")
t.Log(strings.Repeat("=", 60))
t.Log("✅ E2E DB+Trustlog workflow test PASSED")
}
// TestE2E_DBAndTrustlog_WithPulsarConsumer 测试带 Pulsar 消费者验证的完整流程
func TestE2E_DBAndTrustlog_WithPulsarConsumer(t *testing.T) {
if testing.Short() {
t.Skip("Skipping E2E integration test in short mode")
}
ctx := context.Background()
log := logger.NewNopLogger()
// 1. 连接 PostgreSQL
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
e2eTestPGHost, e2eTestPGPort, e2eTestPGUser, e2eTestPGPassword, e2eTestPGDatabase)
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Skipf("PostgreSQL not available: %v", err)
return
}
defer db.Close()
if err := db.Ping(); err != nil {
t.Skipf("PostgreSQL not reachable: %v", err)
return
}
cleanupE2ETestData(t, db)
defer cleanupE2ETestData(t, db)
t.Log("✅ PostgreSQL connected")
// 2. 创建 Pulsar Consumer先创建消费者
pulsarClient, err := pulsar.NewClient(pulsar.ClientOptions{
URL: e2eTestPulsarURL,
})
if err != nil {
t.Skipf("Pulsar client not available: %v", err)
return
}
defer pulsarClient.Close()
// 使用唯一的 subscription 名称
subscriptionName := fmt.Sprintf("e2e-test-sub-%d", time.Now().Unix())
consumer, err := pulsarClient.Subscribe(pulsar.ConsumerOptions{
Topic: adapter.OperationTopic,
SubscriptionName: subscriptionName,
Type: pulsar.Shared,
})
if err != nil {
t.Skipf("Pulsar consumer not available: %v", err)
return
}
defer consumer.Close()
t.Logf("✅ Pulsar consumer created: %s", subscriptionName)
// 用于收集接收到的消息
receivedMessages := make(chan pulsar.Message, 10)
var wg sync.WaitGroup
wg.Add(1)
// 启动消费者协程
go func() {
defer wg.Done()
timeout := time.After(10 * time.Second)
messageCount := 0
maxMessages := 5 // 期望接收5条消息
for {
select {
case <-timeout:
t.Logf("Consumer timeout, received %d messages", messageCount)
return
default:
// 接收消息(设置较短的超时)
msg, err := consumer.Receive(ctx)
if err != nil {
continue
}
t.Logf("📩 Received message from Pulsar: Key=%s, Size=%d bytes",
msg.Key(), len(msg.Payload()))
consumer.Ack(msg)
receivedMessages <- msg
messageCount++
if messageCount >= maxMessages {
t.Logf("Received all %d expected messages", messageCount)
return
}
}
}
}()
// 3. 创建 Pulsar Publisher
publisher, err := adapter.NewPublisher(adapter.PublisherConfig{
URL: e2eTestPulsarURL,
}, log)
if err != nil {
t.Skipf("Pulsar publisher not available: %v", err)
return
}
defer publisher.Close()
// 4. 创建 PersistenceClient
dbConfig := persistence.DBConfig{
DriverName: "postgres",
DSN: dsn,
MaxOpenConns: 10,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
}
persistenceConfig := persistence.PersistenceConfig{
Strategy: persistence.StrategyDBAndTrustlog,
EnableRetry: true,
MaxRetryCount: 3,
RetryBatchSize: 10,
}
// 使用较短的扫描间隔以便快速测试
cursorConfig := &persistence.CursorWorkerConfig{
ScanInterval: 300 * time.Millisecond,
BatchSize: 10,
Enabled: true, // 必须显式启用
}
retryConfig := &persistence.RetryWorkerConfig{
RetryInterval: 300 * time.Millisecond,
BatchSize: 10,
}
envelopeConfig := model.EnvelopeConfig{
Signer: &model.NopSigner{},
}
clientConfig := persistence.PersistenceClientConfig{
Publisher: publisher,
Logger: log,
EnvelopeConfig: envelopeConfig,
DBConfig: dbConfig,
PersistenceConfig: persistenceConfig,
CursorWorkerConfig: cursorConfig,
EnableCursorWorker: true,
RetryWorkerConfig: retryConfig,
EnableRetryWorker: true,
}
client, err := persistence.NewPersistenceClient(ctx, clientConfig)
require.NoError(t, err, "Failed to create PersistenceClient")
defer client.Close()
t.Log("✅ PersistenceClient initialized with Cursor Worker")
// 5. 创建并发布 Operations
operations := createE2ETestOperations(5)
for i, op := range operations {
op.OpID = fmt.Sprintf("e2e-msg-%d-%d", time.Now().Unix(), i)
err := client.OperationPublish(ctx, op)
require.NoError(t, err, "Failed to publish operation %s", op.OpID)
t.Logf("📝 Operation published: %s", op.OpID)
}
// 6. 等待 CursorWorker 处理并发送到 Pulsar
t.Log("⏳ Waiting for Cursor Worker to process and publish to Pulsar...")
time.Sleep(5 * time.Second)
// 7. 检查接收到的消息
close(receivedMessages)
wg.Wait()
receivedCount := len(receivedMessages)
t.Log(strings.Repeat("=", 60))
t.Log("📊 Pulsar Message Verification:")
t.Logf(" - Operations published: %d", len(operations))
t.Logf(" - Messages received from Pulsar: %d", receivedCount)
t.Log(strings.Repeat("=", 60))
if receivedCount == 0 {
t.Error("❌ FAILED: No messages received from Pulsar!")
t.Log("Possible issues:")
t.Log(" 1. Cursor Worker may not be running")
t.Log(" 2. Cursor timestamp may be too recent")
t.Log(" 3. Publisher may have failed silently")
t.Log(" 4. Envelope serialization may have failed")
// 检查数据库状态
var trustloggedCount int
db.QueryRow("SELECT COUNT(*) FROM operation WHERE trustlog_status = 'TRUSTLOGGED' AND op_id LIKE 'e2e-msg-%'").Scan(&trustloggedCount)
t.Logf(" - DB: %d operations marked as TRUSTLOGGED", trustloggedCount)
t.FailNow()
}
// 验证消息内容
for msg := range receivedMessages {
t.Logf("✅ Message verified: Key=%s, Payload size=%d bytes", msg.Key(), len(msg.Payload()))
// 尝试反序列化
envelope, err := model.UnmarshalEnvelope(msg.Payload())
if err != nil {
t.Logf("⚠️ Warning: Failed to unmarshal envelope: %v", err)
} else {
t.Logf(" Envelope: ProducerID=%s, Body size=%d bytes", envelope.ProducerID, len(envelope.Body))
}
}
// 8. 验证数据库状态
var trustloggedCount int
err = db.QueryRow("SELECT COUNT(*) FROM operation WHERE trustlog_status = 'TRUSTLOGGED' AND op_id LIKE 'e2e-msg-%'").Scan(&trustloggedCount)
require.NoError(t, err)
t.Log(strings.Repeat("=", 60))
t.Log("📊 Final Summary:")
t.Logf(" - Operations sent to DB: %d", len(operations))
t.Logf(" - Messages in Pulsar: %d", receivedCount)
t.Logf(" - DB records marked TRUSTLOGGED: %d", trustloggedCount)
t.Logf(" - Success rate: %.1f%%", float64(trustloggedCount)/float64(len(operations))*100)
t.Log(strings.Repeat("=", 60))
if receivedCount >= 1 {
t.Log("✅ E2E test with Pulsar consumer PASSED - Messages verified in Pulsar!")
} else {
t.Error("❌ Expected at least 1 message in Pulsar")
}
}
// TestE2E_DBAndTrustlog_HighVolume 高并发场景测试
func TestE2E_DBAndTrustlog_HighVolume(t *testing.T) {
if testing.Short() {
t.Skip("Skipping E2E high volume test in short mode")
}
ctx := context.Background()
log := logger.NewNopLogger()
// 连接 PostgreSQL
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
e2eTestPGHost, e2eTestPGPort, e2eTestPGUser, e2eTestPGPassword, e2eTestPGDatabase)
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Skipf("PostgreSQL not available: %v", err)
return
}
defer db.Close()
if err := db.Ping(); err != nil {
t.Skipf("PostgreSQL not reachable: %v", err)
return
}
cleanupE2ETestData(t, db)
defer cleanupE2ETestData(t, db)
// 创建 Pulsar Publisher
publisher, err := adapter.NewPublisher(adapter.PublisherConfig{
URL: e2eTestPulsarURL,
}, log)
if err != nil {
t.Skipf("Pulsar not available: %v", err)
return
}
defer publisher.Close()
// 创建 PersistenceClient
dbConfig := persistence.DBConfig{
DriverName: "postgres",
DSN: dsn,
MaxOpenConns: 20,
MaxIdleConns: 10,
ConnMaxLifetime: time.Hour,
}
persistenceConfig := persistence.PersistenceConfig{
Strategy: persistence.StrategyDBAndTrustlog,
EnableRetry: true,
MaxRetryCount: 5,
RetryBatchSize: 50,
}
cursorConfig := &persistence.CursorWorkerConfig{
ScanInterval: 200 * time.Millisecond,
BatchSize: 50,
Enabled: true, // 必须显式启用
}
retryConfig := &persistence.RetryWorkerConfig{
RetryInterval: 200 * time.Millisecond,
BatchSize: 50,
}
envelopeConfig := model.EnvelopeConfig{
Signer: &model.NopSigner{},
}
clientConfig := persistence.PersistenceClientConfig{
Publisher: publisher,
Logger: log,
EnvelopeConfig: envelopeConfig,
DBConfig: dbConfig,
PersistenceConfig: persistenceConfig,
CursorWorkerConfig: cursorConfig,
EnableCursorWorker: true,
RetryWorkerConfig: retryConfig,
EnableRetryWorker: true,
}
client, err := persistence.NewPersistenceClient(ctx, clientConfig)
require.NoError(t, err)
defer client.Close()
// 高并发写入
operationCount := 100
operations := createE2ETestOperations(operationCount)
startTime := time.Now()
// 并发写入
errChan := make(chan error, operationCount)
for _, op := range operations {
go func(operation *model.Operation) {
errChan <- client.OperationPublish(ctx, operation)
}(op)
}
// 等待所有写入完成
for i := 0; i < operationCount; i++ {
err := <-errChan
require.NoError(t, err)
}
writeDuration := time.Since(startTime)
writeRate := float64(operationCount) / writeDuration.Seconds()
t.Logf("✅ Wrote %d operations in %v (%.2f ops/s)", operationCount, writeDuration, writeRate)
// 等待异步处理
t.Log("⏳ Waiting for async processing...")
time.Sleep(5 * time.Second)
// 统计结果
var trustloggedCount int
err = db.QueryRowContext(ctx, `
SELECT COUNT(*) FROM operation WHERE trustlog_status = 'TRUSTLOGGED'
`).Scan(&trustloggedCount)
require.NoError(t, err)
var notTrustloggedCount int
err = db.QueryRowContext(ctx, `
SELECT COUNT(*) FROM operation WHERE trustlog_status = 'NOT_TRUSTLOGGED'
`).Scan(&notTrustloggedCount)
require.NoError(t, err)
successRate := float64(trustloggedCount) / float64(operationCount) * 100
t.Log("\n" + strings.Repeat("=", 60))
t.Log("📊 High Volume Test Summary:")
t.Logf(" - Total operations: %d", operationCount)
t.Logf(" - Write rate: %.2f ops/s", writeRate)
t.Logf(" - Trustlogged: %d (%.1f%%)", trustloggedCount, successRate)
t.Logf(" - Not trustlogged: %d", notTrustloggedCount)
t.Logf(" - Processing time: %v", writeDuration)
t.Log(strings.Repeat("=", 60))
t.Log("✅ High volume test PASSED")
}
// TestE2E_DBAndTrustlog_StrategyComparison 策略对比测试
func TestE2E_DBAndTrustlog_StrategyComparison(t *testing.T) {
if testing.Short() {
t.Skip("Skipping strategy comparison test in short mode")
}
ctx := context.Background()
log := logger.NewNopLogger()
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
e2eTestPGHost, e2eTestPGPort, e2eTestPGUser, e2eTestPGPassword, e2eTestPGDatabase)
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Skipf("PostgreSQL not available: %v", err)
return
}
defer db.Close()
if err := db.Ping(); err != nil {
t.Skipf("PostgreSQL not reachable: %v", err)
return
}
cleanupE2ETestData(t, db)
defer cleanupE2ETestData(t, db)
strategies := []struct {
name string
strategy persistence.PersistenceStrategy
}{
{"DBOnly", persistence.StrategyDBOnly},
{"DBAndTrustlog", persistence.StrategyDBAndTrustlog},
}
for _, s := range strategies {
t.Run(s.name, func(t *testing.T) {
// 创建 Pulsar Publisher
publisher, err := adapter.NewPublisher(adapter.PublisherConfig{
URL: e2eTestPulsarURL,
}, log)
if err != nil {
t.Skipf("Pulsar not available: %v", err)
return
}
defer publisher.Close()
// 创建客户端
dbConfig := persistence.DBConfig{
DriverName: "postgres",
DSN: dsn,
MaxOpenConns: 10,
MaxIdleConns: 5,
ConnMaxLifetime: time.Hour,
}
persistenceConfig := persistence.PersistenceConfig{
Strategy: s.strategy,
EnableRetry: true,
MaxRetryCount: 3,
RetryBatchSize: 10,
}
cursorConfig := &persistence.CursorWorkerConfig{
ScanInterval: 500 * time.Millisecond,
BatchSize: 10,
Enabled: true, // 必须显式启用
}
retryConfig := &persistence.RetryWorkerConfig{
RetryInterval: 500 * time.Millisecond,
BatchSize: 10,
}
envelopeConfig := model.EnvelopeConfig{
Signer: &model.NopSigner{},
}
clientConfig := persistence.PersistenceClientConfig{
Publisher: publisher,
Logger: log,
EnvelopeConfig: envelopeConfig,
DBConfig: dbConfig,
PersistenceConfig: persistenceConfig,
CursorWorkerConfig: cursorConfig,
EnableCursorWorker: s.strategy == persistence.StrategyDBAndTrustlog,
RetryWorkerConfig: retryConfig,
EnableRetryWorker: s.strategy == persistence.StrategyDBAndTrustlog,
}
client, err := persistence.NewPersistenceClient(ctx, clientConfig)
require.NoError(t, err)
defer client.Close()
// 保存操作
op := createE2ETestOperations(1)[0]
op.OpID = fmt.Sprintf("%s-%d", s.name, time.Now().Unix())
err = client.OperationPublish(ctx, op)
require.NoError(t, err)
// 验证状态
time.Sleep(1 * time.Second) // 等待处理
status, err := getOperationStatus(db, op.OpID)
require.NoError(t, err)
expectedStatus := "TRUSTLOGGED"
if s.strategy == persistence.StrategyDBAndTrustlog {
// DBAndTrustlog 策略:异步存证,状态可能是 NOT_TRUSTLOGGED 或 TRUSTLOGGED
t.Logf("Strategy %s: status = %s", s.name, status)
} else {
// DBOnly 策略:直接标记为 TRUSTLOGGED
require.Equal(t, expectedStatus, status)
t.Logf("✅ Strategy %s: status = %s", s.name, status)
}
})
}
}
// Helper functions
func createE2ETestOperations(count int) []*model.Operation {
operations := make([]*model.Operation, count)
timestamp := time.Now().Unix()
for i := 0; i < count; i++ {
operations[i] = &model.Operation{
OpID: fmt.Sprintf("e2e-op-%d-%d", timestamp, i),
Timestamp: time.Now(),
OpSource: model.OpSourceDOIP,
OpType: string(model.OpTypeCreate),
DoPrefix: "e2e-test",
DoRepository: "e2e-repo",
Doid: fmt.Sprintf("e2e/test/%d", i),
ProducerID: "e2e-producer",
OpActor: "e2e-tester",
}
}
return operations
}
func getOperationStatus(db *sql.DB, opID string) (string, error) {
var status string
err := db.QueryRow("SELECT trustlog_status FROM operation WHERE op_id = $1", opID).Scan(&status)
return status, err
}
func getCursorPosition(db *sql.DB, workerName string) (int64, error) {
var cursorValue string
err := db.QueryRow("SELECT cursor_value FROM trustlog_cursor WHERE cursor_key = $1", workerName).Scan(&cursorValue)
if err == sql.ErrNoRows {
return 0, nil
}
if err != nil {
return 0, err
}
// cursor_value 现在是时间戳,我们返回一个简单的值表示已处理
if cursorValue != "" {
return 1, nil
}
return 0, nil
}
func cleanupE2ETestData(t *testing.T, db *sql.DB) {
// 清理测试数据
_, err := db.Exec("DELETE FROM trustlog_retry WHERE op_id LIKE 'e2e-%' OR op_id LIKE 'DBOnly-%' OR op_id LIKE 'DBAndTrustlog-%'")
if err != nil {
t.Logf("Warning: Failed to clean retry table: %v", err)
}
_, err = db.Exec("DELETE FROM operation WHERE op_id LIKE 'e2e-%' OR op_id LIKE 'DBOnly-%' OR op_id LIKE 'DBAndTrustlog-%'")
if err != nil {
t.Logf("Warning: Failed to clean operation table: %v", err)
}
_, err = db.Exec("DELETE FROM trustlog_cursor WHERE cursor_key LIKE '%'")
if err != nil {
t.Logf("Warning: Failed to clean cursor table: %v", err)
}
}
func stringPtr(s string) *string {
return &s
}