refactor: 重构trustlog-sdk目录结构到trustlog/go-trustlog

- 将所有trustlog-sdk文件移动到trustlog/go-trustlog/目录
- 更新README中所有import路径从trustlog-sdk改为go-trustlog
- 更新cookiecutter配置文件中的项目名称
- 更新根目录.lefthook.yml以引用新位置的配置
- 添加go.sum文件到版本控制
- 删除过时的示例文件

这次重构与trustlog-server保持一致的目录结构,
为未来支持多语言SDK(Python、Java等)预留空间。
This commit is contained in:
ryan
2025-12-22 13:37:57 +08:00
commit d313449c5c
87 changed files with 20622 additions and 0 deletions

View File

@@ -0,0 +1,627 @@
package queryclient_test
import (
"context"
"testing"
"time"
"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
"google.golang.org/protobuf/types/known/timestamppb"
"go.yandata.net/iod/iod/trustlog-sdk/api/grpc/pb"
"go.yandata.net/iod/iod/trustlog-sdk/api/logger"
"go.yandata.net/iod/iod/trustlog-sdk/api/model"
"go.yandata.net/iod/iod/trustlog-sdk/api/queryclient"
)
const bufSize = 1024 * 1024
//nolint:gochecknoglobals // 测试文件中的全局变量是可接受的
var testLogger = logger.NewLogger(logr.Discard())
// mockOperationServer 模拟操作验证服务.
type mockOperationServer struct {
pb.UnimplementedOperationValidationServiceServer
}
func (s *mockOperationServer) ListOperations(
_ context.Context,
_ *pb.ListOperationReq,
) (*pb.ListOperationRes, error) {
return &pb.ListOperationRes{
Count: 2,
Data: []*pb.OperationData{
{
OpId: "op-1",
Timestamp: timestamppb.Now(),
OpSource: "test",
OpType: "create",
DoPrefix: "test",
DoRepository: "repo",
Doid: "test/repo/123",
ProducerId: "producer-1",
OpActor: "tester",
},
{
OpId: "op-2",
Timestamp: timestamppb.Now(),
OpSource: "test",
OpType: "update",
DoPrefix: "test",
DoRepository: "repo",
Doid: "test/repo/456",
ProducerId: "producer-1",
OpActor: "tester",
},
},
}, nil
}
func (s *mockOperationServer) ValidateOperation(
req *pb.ValidationReq,
stream pb.OperationValidationService_ValidateOperationServer,
) error {
// 发送进度消息
_ = stream.Send(&pb.ValidationStreamRes{
Code: 100,
Msg: "Processing",
Progress: "50%",
})
// 发送完成消息
_ = stream.Send(&pb.ValidationStreamRes{
Code: 200,
Msg: "Completed",
Progress: "100%",
Data: &pb.OperationData{
OpId: req.GetOpId(),
Timestamp: req.GetTime(),
OpSource: "test",
OpType: req.GetOpType(),
DoPrefix: "test",
DoRepository: req.GetDoRepository(),
Doid: "test/repo/123",
ProducerId: "producer-1",
OpActor: "tester",
},
Proof: &pb.Proof{
ColItems: []*pb.MerkleTreeProofItem{
{Floor: 1, Hash: "hash1", Left: true},
},
},
})
return nil
}
// mockRecordServer 模拟记录验证服务.
type mockRecordServer struct {
pb.UnimplementedRecordValidationServiceServer
}
func (s *mockRecordServer) ListRecords(
_ context.Context,
_ *pb.ListRecordReq,
) (*pb.ListRecordRes, error) {
return &pb.ListRecordRes{
Count: 2,
Data: []*pb.RecordData{
{
Id: "rec-1",
DoPrefix: "test",
ProducerId: "producer-1",
Timestamp: timestamppb.Now(),
Operator: "tester",
RcType: "log",
},
{
Id: "rec-2",
DoPrefix: "test",
ProducerId: "producer-1",
Timestamp: timestamppb.Now(),
Operator: "tester",
RcType: "log",
},
},
}, nil
}
func (s *mockRecordServer) ValidateRecord(
req *pb.RecordValidationReq,
stream pb.RecordValidationService_ValidateRecordServer,
) error {
// 发送进度消息
_ = stream.Send(&pb.RecordValidationStreamRes{
Code: 100,
Msg: "Processing",
Progress: "50%",
})
// 发送完成消息
_ = stream.Send(&pb.RecordValidationStreamRes{
Code: 200,
Msg: "Completed",
Progress: "100%",
Result: &pb.RecordData{
Id: req.GetRecordId(),
DoPrefix: req.GetDoPrefix(),
ProducerId: "producer-1",
Timestamp: req.GetTimestamp(),
Operator: "tester",
RcType: req.GetRcType(),
},
Proof: &pb.Proof{
ColItems: []*pb.MerkleTreeProofItem{
{Floor: 1, Hash: "hash1", Left: true},
},
},
})
return nil
}
// setupTestServer 创建测试用的 gRPC server.
func setupTestServer(t *testing.T) (*grpc.Server, *bufconn.Listener) {
lis := bufconn.Listen(bufSize)
s := grpc.NewServer()
pb.RegisterOperationValidationServiceServer(s, &mockOperationServer{})
pb.RegisterRecordValidationServiceServer(s, &mockRecordServer{})
go func() {
if err := s.Serve(lis); err != nil {
t.Logf("Server exited with error: %v", err)
}
}()
return s, lis
}
// createTestClient 创建用于测试的客户端.
//
//nolint:unparam // 集成测试暂时跳过,返回值始终为 nil
func createTestClient(t *testing.T, _ *bufconn.Listener) *queryclient.Client {
// 使用 bufconn 的特殊方式创建客户端
// 由于我们不能直接注入连接,需要通过地址的方式
// 这里我们使用一个变通的方法:直接构建客户端结构(不推荐生产使用)
// 更好的方法是提供一个可注入连接的构造函数
// 暂时使用真实的地址测试配置验证
client, err := queryclient.NewClient(queryclient.ClientConfig{
ServerAddr: "bufnet",
}, testLogger)
// 对于这个测试,我们关闭它并使用 mock 方式
if client != nil {
_ = client.Close()
}
// 检查 err 避免未使用的警告
_ = err
// 返回 nil让调用者知道需要用其他方式测试
t.Skip("Skipping integration test - requires real gRPC server setup")
return nil
}
func TestNewClient(t *testing.T) {
tests := []struct {
name string
config queryclient.ClientConfig
wantErr bool
errMsg string
}{
{
name: "使用ServerAddr成功创建客户端",
config: queryclient.ClientConfig{
ServerAddr: "localhost:9090",
},
wantErr: false,
},
{
name: "使用ServerAddrs成功创建客户端",
config: queryclient.ClientConfig{
ServerAddrs: []string{"localhost:9090", "localhost:9091"},
},
wantErr: false,
},
{
name: "没有提供地址应该失败",
config: queryclient.ClientConfig{},
wantErr: true,
errMsg: "at least one server address is required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client, err := queryclient.NewClient(tt.config, testLogger)
if tt.wantErr {
require.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
assert.Nil(t, client)
} else {
require.NoError(t, err)
require.NotNil(t, client)
// 清理
if client != nil {
_ = client.Close()
}
}
})
}
}
func TestClientConfig_GetAddrs(t *testing.T) {
tests := []struct {
name string
config queryclient.ClientConfig
wantAddrs []string
wantErr bool
}{
{
name: "ServerAddrs优先",
config: queryclient.ClientConfig{
ServerAddrs: []string{"addr1:9090", "addr2:9090"},
ServerAddr: "addr3:9090",
},
wantAddrs: []string{"addr1:9090", "addr2:9090"},
wantErr: false,
},
{
name: "使用ServerAddr作为后备",
config: queryclient.ClientConfig{
ServerAddr: "addr1:9090",
},
wantAddrs: []string{"addr1:9090"},
wantErr: false,
},
{
name: "没有地址应该返回错误",
config: queryclient.ClientConfig{},
wantAddrs: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
addrs, err := tt.config.GetAddrs()
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantAddrs, addrs)
}
})
}
}
func TestListOperationsRequest(t *testing.T) {
// 测试请求结构的创建
now := time.Now()
req := queryclient.ListOperationsRequest{
PageSize: 10,
PreTime: now,
Timestamp: &now,
OpSource: model.Source("test"),
OpType: model.Type("create"),
}
assert.Equal(t, uint64(10), req.PageSize)
assert.Equal(t, now, req.PreTime)
assert.NotNil(t, req.Timestamp)
assert.Equal(t, "test", string(req.OpSource))
assert.Equal(t, "create", string(req.OpType))
}
func TestValidationRequest(t *testing.T) {
// 测试验证请求结构
now := time.Now()
req := queryclient.ValidationRequest{
Time: now,
OpID: "op-123",
OpType: "create",
DoRepository: "repo",
}
assert.Equal(t, now, req.Time)
assert.Equal(t, "op-123", req.OpID)
assert.Equal(t, "create", req.OpType)
assert.Equal(t, "repo", req.DoRepository)
}
func TestListRecordsRequest(t *testing.T) {
// 测试记录列表请求结构
now := time.Now()
req := queryclient.ListRecordsRequest{
PageSize: 20,
PreTime: now,
DoPrefix: "test",
RCType: "log",
}
assert.Equal(t, uint64(20), req.PageSize)
assert.Equal(t, now, req.PreTime)
assert.Equal(t, "test", req.DoPrefix)
assert.Equal(t, "log", req.RCType)
}
func TestRecordValidationRequest(t *testing.T) {
// 测试记录验证请求结构
now := time.Now()
req := queryclient.RecordValidationRequest{
Timestamp: now,
RecordID: "rec-123",
DoPrefix: "test",
RCType: "log",
}
assert.Equal(t, now, req.Timestamp)
assert.Equal(t, "rec-123", req.RecordID)
assert.Equal(t, "test", req.DoPrefix)
assert.Equal(t, "log", req.RCType)
}
// 集成测试部分(需要真实的 gRPC server.
func TestIntegration_ListOperations(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.ListOperations(ctx, queryclient.ListOperationsRequest{
PageSize: 10,
})
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, int64(2), resp.Count)
assert.Len(t, resp.Data, 2)
assert.Equal(t, "op-1", resp.Data[0].OpID)
}
func TestIntegration_ValidateOperation(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resultChan, err := client.ValidateOperation(ctx, queryclient.ValidationRequest{
Time: time.Now(),
OpID: "op-test",
OpType: "create",
DoRepository: "repo",
})
require.NoError(t, err)
require.NotNil(t, resultChan)
results := []int32{}
for result := range resultChan {
results = append(results, result.Code)
if result.IsCompleted() {
break
}
}
assert.Contains(t, results, int32(100)) // Processing
assert.Contains(t, results, int32(200)) // Completed
}
func TestIntegration_ValidateOperationSync(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
progressCount := 0
result, err := client.ValidateOperationSync(
ctx,
queryclient.ValidationRequest{
Time: time.Now(),
OpID: "op-test",
OpType: "create",
DoRepository: "repo",
},
func(r *model.ValidationResult) {
progressCount++
assert.Equal(t, int32(100), r.Code)
},
)
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, int32(200), result.Code)
assert.True(t, result.IsCompleted())
assert.Positive(t, progressCount)
}
func TestIntegration_ListRecords(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.ListRecords(ctx, queryclient.ListRecordsRequest{
PageSize: 10,
})
require.NoError(t, err)
require.NotNil(t, resp)
assert.Equal(t, int64(2), resp.Count)
assert.Len(t, resp.Data, 2)
assert.Equal(t, "rec-1", resp.Data[0].ID)
}
func TestIntegration_ValidateRecord(t *testing.T) { //nolint:dupl // 测试代码中的重复模式是合理的
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resultChan, err := client.ValidateRecord(ctx, queryclient.RecordValidationRequest{
Timestamp: time.Now(),
RecordID: "rec-test",
DoPrefix: "test",
RCType: "log",
})
require.NoError(t, err)
require.NotNil(t, resultChan)
results := []int32{}
for result := range resultChan {
results = append(results, result.Code)
if result.IsCompleted() {
break
}
}
assert.Contains(t, results, int32(100)) // Processing
assert.Contains(t, results, int32(200)) // Completed
}
func TestIntegration_ValidateRecordSync(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
progressCount := 0
result, err := client.ValidateRecordSync(
ctx,
queryclient.RecordValidationRequest{
Timestamp: time.Now(),
RecordID: "rec-test",
DoPrefix: "test",
RCType: "log",
},
func(r *model.RecordValidationResult) {
progressCount++
assert.Equal(t, int32(100), r.Code)
},
)
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, int32(200), result.Code)
assert.True(t, result.IsCompleted())
assert.Positive(t, progressCount)
}
func TestClient_GetLowLevelClients(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
defer client.Close()
opClient := client.GetLowLevelOperationClient()
assert.NotNil(t, opClient)
recClient := client.GetLowLevelRecordClient()
assert.NotNil(t, recClient)
}
func TestClient_Close(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
server, lis := setupTestServer(t)
defer server.Stop()
client := createTestClient(t, lis)
if client == nil {
return
}
err := client.Close()
require.NoError(t, err)
// 再次关闭应该不会报错
err = client.Close()
require.NoError(t, err)
}