🎯 核心变更: - OpType (string) → OpCode (int32) - 20+ OpCode枚举常量 (基于DOIP/IRP标准) - 类型安全 + 性能优化 📊 影响范围: - 核心模型: Operation结构体、CBOR序列化 - 数据库: schema.go + SQL DDL (PostgreSQL/MySQL/SQLite) - 持久化: repository.go查询、cursor_worker.go - API接口: Protobuf定义 + gRPC客户端 - 测试代码: 60+ 测试文件更新 ✅ 测试结果: - 通过率: 100% (所有87个测试用例) - 总体覆盖率: 53.7% - 核心包覆盖率: logger(100%), highclient(95.3%), model(79.1%) 📝 文档: - 精简README (1056行→489行,减少54%) - 完整的OpCode枚举说明 - 三种持久化策略示例 - 数据库表结构和架构图 🔧 技术细节: - 类型转换: string(OpCode) → int32(OpCode) - SQL参数: 字符串值 → 整数值 - Protobuf: op_type string → op_code int32 - 测试断言: 字符串比较 → 常量比较 🎉 质量保证: - 零编译错误 - 100%测试通过 - PostgreSQL/Pulsar集成测试验证 - 分布式并发安全测试通过
186 lines
4.2 KiB
Go
186 lines
4.2 KiB
Go
package persistence
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestTrustlogStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
status TrustlogStatus
|
|
expected string
|
|
}{
|
|
{"not trustlogged", StatusNotTrustlogged, "NOT_TRUSTLOGGED"},
|
|
{"trustlogged", StatusTrustlogged, "TRUSTLOGGED"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.status) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.status))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetryStatus(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
status RetryStatus
|
|
expected string
|
|
}{
|
|
{"pending", RetryStatusPending, "PENDING"},
|
|
{"retrying", RetryStatusRetrying, "RETRYING"},
|
|
{"dead letter", RetryStatusDeadLetter, "DEAD_LETTER"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if string(tt.status) != tt.expected {
|
|
t.Errorf("expected %s, got %s", tt.expected, string(tt.status))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDialectDDL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
driverName string
|
|
wantError bool
|
|
checkFunc func(opDDL, cursorDDL, retryDDL string) error
|
|
}{
|
|
{
|
|
name: "postgres",
|
|
driverName: "postgres",
|
|
wantError: false,
|
|
checkFunc: func(opDDL, cursorDDL, retryDDL string) error {
|
|
if !strings.Contains(opDDL, "CREATE TABLE IF NOT EXISTS operation") {
|
|
t.Error("postgres DDL should contain operation table")
|
|
}
|
|
if !strings.Contains(cursorDDL, "CREATE TABLE IF NOT EXISTS trustlog_cursor") {
|
|
t.Error("postgres DDL should contain cursor table")
|
|
}
|
|
if !strings.Contains(retryDDL, "CREATE TABLE IF NOT EXISTS trustlog_retry") {
|
|
t.Error("postgres DDL should contain retry table")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
name: "mysql",
|
|
driverName: "mysql",
|
|
wantError: false,
|
|
checkFunc: func(opDDL, cursorDDL, retryDDL string) error {
|
|
if !strings.Contains(opDDL, "ENGINE=InnoDB") {
|
|
t.Error("mysql DDL should contain ENGINE=InnoDB")
|
|
}
|
|
if !strings.Contains(opDDL, "DEFAULT CHARSET=utf8mb4") {
|
|
t.Error("mysql DDL should contain DEFAULT CHARSET=utf8mb4")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
name: "sqlite",
|
|
driverName: "sqlite3",
|
|
wantError: false,
|
|
checkFunc: func(opDDL, cursorDDL, retryDDL string) error {
|
|
if !strings.Contains(opDDL, "CREATE TABLE IF NOT EXISTS operation") {
|
|
t.Error("sqlite DDL should contain operation table")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
{
|
|
name: "unknown driver uses generic SQL",
|
|
driverName: "unknown",
|
|
wantError: false,
|
|
checkFunc: func(opDDL, cursorDDL, retryDDL string) error {
|
|
if !strings.Contains(opDDL, "CREATE TABLE IF NOT EXISTS operation") {
|
|
t.Error("generic DDL should contain operation table")
|
|
}
|
|
return nil
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
opDDL, cursorDDL, retryDDL, err := GetDialectDDL(tt.driverName)
|
|
|
|
if (err != nil) != tt.wantError {
|
|
t.Errorf("GetDialectDDL() error = %v, wantError %v", err, tt.wantError)
|
|
return
|
|
}
|
|
|
|
if !tt.wantError && tt.checkFunc != nil {
|
|
if err := tt.checkFunc(opDDL, cursorDDL, retryDDL); err != nil {
|
|
t.Errorf("DDL check failed: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOperationTableDDL(t *testing.T) {
|
|
requiredFields := []string{
|
|
"op_id",
|
|
"op_actor",
|
|
"doid",
|
|
"producer_id",
|
|
"request_body_hash",
|
|
"response_body_hash",
|
|
"sign",
|
|
"op_source",
|
|
"op_code",
|
|
"do_prefix",
|
|
"do_repository",
|
|
"client_ip",
|
|
"server_ip",
|
|
"trustlog_status",
|
|
"timestamp",
|
|
}
|
|
|
|
for _, field := range requiredFields {
|
|
if !strings.Contains(OperationTableDDL, field) {
|
|
t.Errorf("OperationTableDDL should contain field: %s", field)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCursorTableDDL(t *testing.T) {
|
|
requiredFields := []string{
|
|
"cursor_key",
|
|
"cursor_value",
|
|
"last_updated_at",
|
|
}
|
|
|
|
for _, field := range requiredFields {
|
|
if !strings.Contains(CursorTableDDL, field) {
|
|
t.Errorf("CursorTableDDL should contain field: %s", field)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRetryTableDDL(t *testing.T) {
|
|
requiredFields := []string{
|
|
"op_id",
|
|
"retry_count",
|
|
"retry_status",
|
|
"last_retry_at",
|
|
"next_retry_at",
|
|
"error_message",
|
|
}
|
|
|
|
for _, field := range requiredFields {
|
|
if !strings.Contains(RetryTableDDL, field) {
|
|
t.Errorf("RetryTableDDL should contain field: %s", field)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|