Files
go-trustlog/internal/helpers/validate_test.go
ryan 4b72a37120 feat: 完善数据库持久化与存证功能
主要更新:

1. 数据库持久化功能
   - 支持三种策略:仅落库、既落库又存证、仅存证
   - 实现 Cursor Worker 异步扫描和存证机制
   - 实现 Retry Worker 失败重试机制
   - 支持 PostgreSQL、MySQL、SQLite 等多种数据库
   - 添加 ClientIP 和 ServerIP 字段(可空,仅落库)

2. 集群并发安全
   - 使用 SELECT FOR UPDATE SKIP LOCKED 防止重复处理
   - 实现 CAS (Compare-And-Set) 原子状态更新
   - 添加 updated_at 字段支持并发控制

3. Cursor 初始化优化
   - 自动基于历史数据初始化 cursor
   - 确保不遗漏任何历史记录
   - 修复 UPSERT 逻辑

4. 测试完善
   - 添加 E2E 集成测试(含 Pulsar 消费者验证)
   - 添加 PostgreSQL 集成测试
   - 添加 Pulsar 集成测试
   - 添加集群并发安全测试
   - 添加 Cursor 初始化验证测试
   - 补充大量单元测试,提升覆盖率

5. 工具脚本
   - 添加数据库迁移脚本
   - 添加 Cursor 状态检查工具
   - 添加 Cursor 初始化工具
   - 添加 Pulsar 消息验证工具

6. 文档清理
   - 删除冗余文档,只保留根目录 README

测试结果:
- 所有 E2E 测试通过(100%)
- 数据库持久化与异步存证流程验证通过
- 集群环境下的并发安全性验证通过
- Cursor 自动初始化和历史数据处理验证通过
2025-12-24 15:31:11 +08:00

187 lines
4.1 KiB
Go

package helpers_test
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yandata.net/iod/iod/go-trustlog/internal/helpers"
)
func TestGetValidator(t *testing.T) {
t.Run("返回有效的validator实例", func(t *testing.T) {
v := helpers.GetValidator()
require.NotNil(t, v, "Validator不应该为nil")
})
t.Run("单例模式:多次调用返回同一个实例", func(t *testing.T) {
v1 := helpers.GetValidator()
v2 := helpers.GetValidator()
v3 := helpers.GetValidator()
// 使用指针比较,确保是同一个实例
assert.Same(t, v1, v2, "第一次和第二次调用应该返回同一个实例")
assert.Same(t, v2, v3, "第二次和第三次调用应该返回同一个实例")
assert.Same(t, v1, v3, "第一次和第三次调用应该返回同一个实例")
})
t.Run("并发获取validator应该安全", func(t *testing.T) {
const concurrency = 100
validators := make([]interface{}, concurrency)
var wg sync.WaitGroup
wg.Add(concurrency)
for i := range concurrency {
go func(idx int) {
defer wg.Done()
v := helpers.GetValidator()
// 存储validator实例
validators[idx] = v
}(i)
}
wg.Wait()
// 验证所有goroutine获取的是同一个实例
firstValidator := validators[0]
for i := 1; i < concurrency; i++ {
assert.Same(t, firstValidator, validators[i],
"并发调用第%d次获取的validator应该与第一次相同", i)
}
})
t.Run("validator可以正常工作", func(t *testing.T) {
v := helpers.GetValidator()
// 测试一个简单的结构体验证
type TestStruct struct {
Name string `validate:"required,min=2,max=10"`
Email string `validate:"required,email"`
Age int `validate:"gte=0,lte=120"`
}
// 有效的结构体
validData := TestStruct{
Name: "John",
Email: "john@example.com",
Age: 30,
}
err := v.Struct(validData)
require.NoError(t, err, "有效的数据不应该产生验证错误")
// 无效的结构体 - 缺少必填字段
invalidData1 := TestStruct{
Name: "",
Age: 30,
}
err = v.Struct(invalidData1)
require.Error(t, err, "缺少必填字段应该产生验证错误")
// 无效的结构体 - 字段值超出范围
invalidData2 := TestStruct{
Name: "John",
Email: "john@example.com",
Age: 150,
}
err = v.Struct(invalidData2)
require.Error(t, err, "年龄超出范围应该产生验证错误")
// 无效的结构体 - 邮箱格式错误
invalidData3 := TestStruct{
Name: "John",
Email: "invalid-email",
Age: 30,
}
err = v.Struct(invalidData3)
assert.Error(t, err, "无效的邮箱格式应该产生验证错误")
})
}
func TestGetValidator_InitializationOnce(t *testing.T) {
// 这个测试验证 sync.Once 确保初始化只执行一次
const calls = 1000
var wg sync.WaitGroup
wg.Add(calls)
results := make([]interface{}, calls)
for i := range calls {
go func(idx int) {
defer wg.Done()
v := helpers.GetValidator()
results[idx] = v
}(i)
}
wg.Wait()
// 所有结果应该指向同一个实例
first := results[0]
for i := 1; i < calls; i++ {
assert.Same(t, first, results[i],
"所有调用应该返回完全相同的validator实例")
}
}
func TestGetValidator_ValidatorFunctionality(t *testing.T) {
v := helpers.GetValidator()
tests := []struct {
name string
data interface{}
wantErr bool
}{
{
name: "结构体字段验证-成功",
data: struct {
Field string `validate:"required"`
}{
Field: "value",
},
wantErr: false,
},
{
name: "结构体字段验证-失败",
data: struct {
Field string `validate:"required"`
}{
Field: "",
},
wantErr: true,
},
{
name: "数字范围验证-成功",
data: struct {
Count int `validate:"min=1,max=100"`
}{
Count: 50,
},
wantErr: false,
},
{
name: "数字范围验证-失败",
data: struct {
Count int `validate:"min=1,max=100"`
}{
Count: 200,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := v.Struct(tt.data)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}