主要功能: - 新增 OperationQueryRequest/OperationQueryResult 结构体 - 实现 Repository.Query() - 支持多条件筛选、分页、排序 - 实现 Repository.Count() - 统计记录数 - 新增 PersistenceClient.QueryOperations/CountOperations/GetOperationByID 查询功能: - 支持按 OpID、OpSource、OpType、Doid 等字段筛选 - 支持模糊查询(Doid、DoPrefix) - 支持时间范围查询(TimeFrom/TimeTo) - 支持 IP 地址筛选(ClientIP、ServerIP) - 支持按 TrustlogStatus 筛选 - 支持组合查询 - 支持分页(PageSize、PageNumber) - 支持排序(OrderBy、OrderDesc) 测试覆盖: - ✅ query_test.go - 查询功能单元测试 - ✅ pg_query_integration_test.go - PostgreSQL 集成测试(16个测试用例) * Query all records * Filter by OpSource/OpType/Status/Actor/Producer/IP * DOID 模糊查询 * 时间范围查询 * 分页测试 * 排序测试(升序/降序) * 组合查询 * Count 统计 * PersistenceClient 接口测试 修复: - 修复 TestClusterSafety_MultipleCursorWorkers - 添加缺失字段 - 修复 TestCursorInitialization - 确保 schema 最新 - 添加自动 schema 更新(ALTER TABLE IF NOT EXISTS) 测试结果: - ✅ 所有单元测试通过(100%) - ✅ 所有集成测试通过(PostgreSQL、Pulsar、E2E) - ✅ Query 功能测试通过(16个测试用例)
291 lines
6.9 KiB
Go
291 lines
6.9 KiB
Go
package persistence
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"go.yandata.net/iod/iod/go-trustlog/api/logger"
|
|
"go.yandata.net/iod/iod/go-trustlog/api/model"
|
|
)
|
|
|
|
func TestOperationRepository_Query(t *testing.T) {
|
|
ctx := context.Background()
|
|
log := logger.NewNopLogger()
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewOperationRepository(db, log)
|
|
|
|
// 准备测试数据
|
|
now := time.Now()
|
|
testOps := []struct {
|
|
opID string
|
|
opSource string
|
|
opType string
|
|
status TrustlogStatus
|
|
time time.Time
|
|
}{
|
|
{"op-001", "DOIP", "Create", StatusNotTrustlogged, now.Add(-3 * time.Hour)},
|
|
{"op-002", "DOIP", "Update", StatusTrustlogged, now.Add(-2 * time.Hour)},
|
|
{"op-003", "IRP", "Create", StatusNotTrustlogged, now.Add(-1 * time.Hour)},
|
|
{"op-004", "IRP", "Delete", StatusTrustlogged, now},
|
|
}
|
|
|
|
for _, testOp := range testOps {
|
|
op := createTestOperation(t, testOp.opID)
|
|
op.OpSource = model.Source(testOp.opSource)
|
|
op.OpType = testOp.opType
|
|
op.Timestamp = testOp.time
|
|
|
|
err := repo.Save(ctx, op, testOp.status)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("Query all operations", func(t *testing.T) {
|
|
req := &OperationQueryRequest{
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.Equal(t, int64(4), result.Total)
|
|
assert.Len(t, result.Operations, 4)
|
|
assert.Len(t, result.Statuses, 4)
|
|
})
|
|
|
|
t.Run("Query by OpSource", func(t *testing.T) {
|
|
opSource := "DOIP"
|
|
req := &OperationQueryRequest{
|
|
OpSource: &opSource,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), result.Total)
|
|
assert.Len(t, result.Operations, 2)
|
|
|
|
for _, op := range result.Operations {
|
|
assert.Equal(t, "DOIP", string(op.OpSource))
|
|
}
|
|
})
|
|
|
|
t.Run("Query by OpType", func(t *testing.T) {
|
|
opType := "Create"
|
|
req := &OperationQueryRequest{
|
|
OpType: &opType,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), result.Total)
|
|
})
|
|
|
|
t.Run("Query by TrustlogStatus", func(t *testing.T) {
|
|
status := StatusNotTrustlogged
|
|
req := &OperationQueryRequest{
|
|
TrustlogStatus: &status,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), result.Total)
|
|
|
|
for _, s := range result.Statuses {
|
|
assert.Equal(t, StatusNotTrustlogged, s)
|
|
}
|
|
})
|
|
|
|
t.Run("Query with time range", func(t *testing.T) {
|
|
timeFrom := now.Add(-2*time.Hour - 30*time.Minute)
|
|
timeTo := now.Add(-30 * time.Minute)
|
|
|
|
req := &OperationQueryRequest{
|
|
TimeFrom: &timeFrom,
|
|
TimeTo: &timeTo,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Total >= 2) // 应该包含 op-002 和 op-003
|
|
})
|
|
|
|
t.Run("Query with pagination", func(t *testing.T) {
|
|
req := &OperationQueryRequest{
|
|
PageSize: 2,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(4), result.Total)
|
|
assert.Len(t, result.Operations, 2)
|
|
assert.Equal(t, 2, result.TotalPages)
|
|
|
|
// 查询第二页
|
|
req.PageNumber = 2
|
|
result, err = repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Len(t, result.Operations, 2)
|
|
})
|
|
|
|
t.Run("Query with ordering DESC", func(t *testing.T) {
|
|
req := &OperationQueryRequest{
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
OrderBy: "timestamp",
|
|
OrderDesc: true,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Len(t, result.Operations, 4)
|
|
|
|
// 验证降序排列
|
|
for i := 1; i < len(result.Operations); i++ {
|
|
// 后面的时间应该早于或等于前面的时间
|
|
assert.True(t, result.Operations[i].Timestamp.Before(result.Operations[i-1].Timestamp) ||
|
|
result.Operations[i].Timestamp.Equal(result.Operations[i-1].Timestamp))
|
|
}
|
|
})
|
|
|
|
t.Run("Query by OpID", func(t *testing.T) {
|
|
opID := "op-001"
|
|
req := &OperationQueryRequest{
|
|
OpID: &opID,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), result.Total)
|
|
assert.Len(t, result.Operations, 1)
|
|
assert.Equal(t, "op-001", result.Operations[0].OpID)
|
|
})
|
|
|
|
t.Run("Query with Doid LIKE", func(t *testing.T) {
|
|
doid := "test-repo"
|
|
req := &OperationQueryRequest{
|
|
Doid: &doid,
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := repo.Query(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Total >= 4) // 所有记录的 doid 都包含 "test-repo"
|
|
})
|
|
}
|
|
|
|
func TestOperationRepository_Count(t *testing.T) {
|
|
ctx := context.Background()
|
|
log := logger.NewNopLogger()
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
repo := NewOperationRepository(db, log)
|
|
|
|
// 准备测试数据
|
|
for i := 0; i < 5; i++ {
|
|
op := createTestOperation(t, fmt.Sprintf("count-op-%d", i))
|
|
status := StatusNotTrustlogged
|
|
if i%2 == 0 {
|
|
status = StatusTrustlogged
|
|
}
|
|
err := repo.Save(ctx, op, status)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("Count all", func(t *testing.T) {
|
|
req := &OperationQueryRequest{}
|
|
count, err := repo.Count(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, count >= 5)
|
|
})
|
|
|
|
t.Run("Count by status", func(t *testing.T) {
|
|
status := StatusTrustlogged
|
|
req := &OperationQueryRequest{
|
|
TrustlogStatus: &status,
|
|
}
|
|
count, err := repo.Count(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, count >= 3) // i=0,2,4 三条记录
|
|
})
|
|
}
|
|
|
|
func TestPersistenceClient_QueryOperations(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping integration test in short mode")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
log := logger.NewNopLogger()
|
|
db := setupTestDB(t)
|
|
defer db.Close()
|
|
|
|
// 初始化 PersistenceManager
|
|
config := PersistenceConfig{
|
|
Strategy: StrategyDBOnly,
|
|
}
|
|
|
|
manager := NewPersistenceManager(db, config, log)
|
|
defer manager.Close()
|
|
|
|
// 创建 PersistenceClient
|
|
client := &PersistenceClient{
|
|
manager: manager,
|
|
logger: log,
|
|
}
|
|
|
|
// 准备测试数据
|
|
for i := 0; i < 3; i++ {
|
|
op := createTestOperation(t, fmt.Sprintf("client-op-%d", i))
|
|
err := manager.GetOperationRepo().Save(ctx, op, StatusNotTrustlogged)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
t.Run("QueryOperations", func(t *testing.T) {
|
|
req := &OperationQueryRequest{
|
|
PageSize: 10,
|
|
PageNumber: 1,
|
|
}
|
|
|
|
result, err := client.QueryOperations(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
assert.True(t, result.Total >= 3)
|
|
})
|
|
|
|
t.Run("CountOperations", func(t *testing.T) {
|
|
req := &OperationQueryRequest{}
|
|
count, err := client.CountOperations(ctx, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, count >= 3)
|
|
})
|
|
|
|
t.Run("GetOperationByID", func(t *testing.T) {
|
|
op, status, err := client.GetOperationByID(ctx, "client-op-0")
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, op)
|
|
assert.Equal(t, "client-op-0", op.OpID)
|
|
assert.Equal(t, StatusNotTrustlogged, status)
|
|
})
|
|
}
|
|
|