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:
593
api/model/operation_test.go
Normal file
593
api/model/operation_test.go
Normal file
@@ -0,0 +1,593 @@
|
||||
package model_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/api/model"
|
||||
)
|
||||
|
||||
func TestOperation_Key(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{
|
||||
OpID: "test-op-id",
|
||||
}
|
||||
assert.Equal(t, "test-op-id", op.Key())
|
||||
}
|
||||
|
||||
func TestOperation_CheckAndInit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
op *model.Operation
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid operation",
|
||||
op: &model.Operation{
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "auto generate OpID",
|
||||
op: &model.Operation{
|
||||
OpID: "", // Will be auto-generated
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "auto set OpActor",
|
||||
op: &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
OpActor: "", // Will be set to "SYSTEM"
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid doid format",
|
||||
op: &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "invalid/123", // Doesn't start with "test/repo"
|
||||
ProducerID: "producer-1",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := tt.op.CheckAndInit()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
if tt.name == "auto generate OpID" {
|
||||
assert.NotEmpty(t, tt.op.OpID)
|
||||
}
|
||||
if tt.name == "auto set OpActor" {
|
||||
assert.Equal(t, "SYSTEM", tt.op.OpActor)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperation_RequestBodyFlexible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
input: "test",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bytes",
|
||||
input: []byte("test"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
input: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "empty bytes",
|
||||
input: []byte{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
input: 123,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
op := &model.Operation{}
|
||||
err := op.RequestBodyFlexible(tt.input)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperation_ResponseBodyFlexible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
input: "test",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bytes",
|
||||
input: []byte("test"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
input: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
op := &model.Operation{}
|
||||
err := op.ResponseBodyFlexible(tt.input)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOperation_WithRequestBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{}
|
||||
result := op.WithRequestBody([]byte("test"))
|
||||
assert.Equal(t, op, result)
|
||||
}
|
||||
|
||||
func TestOperation_WithResponseBody(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{}
|
||||
result := op.WithResponseBody([]byte("test"))
|
||||
assert.Equal(t, op, result)
|
||||
}
|
||||
|
||||
func TestOperation_MarshalUnmarshalBinary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
original := &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
OpActor: "actor-1",
|
||||
}
|
||||
|
||||
// Marshal
|
||||
data, err := original.MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, data)
|
||||
|
||||
// Unmarshal
|
||||
result := &model.Operation{}
|
||||
err = result.UnmarshalBinary(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, original.OpID, result.OpID)
|
||||
assert.Equal(t, original.OpSource, result.OpSource)
|
||||
assert.Equal(t, original.OpType, result.OpType)
|
||||
assert.Equal(t, original.DoPrefix, result.DoPrefix)
|
||||
assert.Equal(t, original.DoRepository, result.DoRepository)
|
||||
assert.Equal(t, original.Doid, result.Doid)
|
||||
assert.Equal(t, original.ProducerID, result.ProducerID)
|
||||
assert.Equal(t, original.OpActor, result.OpActor)
|
||||
// 验证纳秒精度被保留
|
||||
assert.Equal(t, original.Timestamp.UnixNano(), result.Timestamp.UnixNano(),
|
||||
"时间戳的纳秒精度应该被保留")
|
||||
}
|
||||
|
||||
func TestOperation_MarshalBinary_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
}
|
||||
// MarshalBinary should succeed even without CheckAndInit
|
||||
// It just serializes the data
|
||||
data, err := op.MarshalBinary()
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
}
|
||||
|
||||
func TestOperation_UnmarshalBinary_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{}
|
||||
err := op.UnmarshalBinary([]byte{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestOperation_GetProducerID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{
|
||||
ProducerID: "producer-123",
|
||||
}
|
||||
assert.Equal(t, "producer-123", op.GetProducerID())
|
||||
}
|
||||
|
||||
func TestOperation_DoHash(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
OpActor: "actor-1",
|
||||
}
|
||||
|
||||
err := op.CheckAndInit()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
hashData, err := op.DoHash(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, hashData)
|
||||
assert.Equal(t, op.OpID, hashData.Key())
|
||||
assert.NotEmpty(t, hashData.Hash())
|
||||
}
|
||||
|
||||
func TestOperationHashData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// OperationHashData is created through DoHash, test it indirectly
|
||||
op := &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
OpActor: "actor-1",
|
||||
}
|
||||
|
||||
err := op.CheckAndInit()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
hashData, err := op.DoHash(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, hashData)
|
||||
assert.Equal(t, "op-123", hashData.Key())
|
||||
assert.NotEmpty(t, hashData.Hash())
|
||||
assert.Equal(t, model.Sha256Simd, hashData.Type())
|
||||
}
|
||||
|
||||
func TestOperation_UnmarshalBinary_InvalidData(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{}
|
||||
err := op.UnmarshalBinary([]byte("invalid-cbor-data"))
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to unmarshal operation from CBOR")
|
||||
}
|
||||
|
||||
func TestOperation_MarshalTrustlog_EmptyProducerID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create an operation with empty ProducerID
|
||||
// MarshalBinary will fail validation, but MarshalTrustlog checks ProducerID first
|
||||
op := &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "", // Empty ProducerID
|
||||
OpActor: "actor-1",
|
||||
}
|
||||
|
||||
config := model.NewEnvelopeConfig(model.NewNopSigner())
|
||||
_, err := model.MarshalTrustlog(op, config)
|
||||
// MarshalTrustlog checks ProducerID before calling MarshalBinary
|
||||
require.Error(t, err)
|
||||
// Error could be from ProducerID check or MarshalBinary validation
|
||||
assert.True(t,
|
||||
err.Error() == "producerID cannot be empty" ||
|
||||
strings.Contains(err.Error(), "ProducerID") ||
|
||||
strings.Contains(err.Error(), "producerID"))
|
||||
}
|
||||
|
||||
func TestOperation_MarshalTrustlog_NilSigner(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
op := &model.Operation{
|
||||
OpID: "op-123",
|
||||
Timestamp: time.Now(),
|
||||
OpSource: model.OpSourceIRP,
|
||||
OpType: model.OpTypeOCCreateHandle,
|
||||
DoPrefix: "test",
|
||||
DoRepository: "repo",
|
||||
Doid: "test/repo/123",
|
||||
ProducerID: "producer-1",
|
||||
OpActor: "actor-1",
|
||||
}
|
||||
|
||||
err := op.CheckAndInit()
|
||||
require.NoError(t, err)
|
||||
|
||||
config := model.EnvelopeConfig{Signer: nil}
|
||||
_, err = model.MarshalTrustlog(op, config)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "signer is required")
|
||||
}
|
||||
|
||||
func TestGetOpTypesBySource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
source model.Source
|
||||
wantTypes []model.Type
|
||||
}{
|
||||
{
|
||||
name: "IRP操作类型",
|
||||
source: model.OpSourceIRP,
|
||||
wantTypes: []model.Type{
|
||||
model.OpTypeOCCreateHandle,
|
||||
model.OpTypeOCDeleteHandle,
|
||||
model.OpTypeOCAddValue,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DOIP操作类型",
|
||||
source: model.OpSourceDOIP,
|
||||
wantTypes: []model.Type{
|
||||
model.OpTypeHello,
|
||||
model.OpTypeCreate,
|
||||
model.OpTypeDelete,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
opTypes := model.GetOpTypesBySource(tt.source)
|
||||
assert.NotNil(t, opTypes)
|
||||
// Verify expected types are included
|
||||
for _, expectedType := range tt.wantTypes {
|
||||
assert.Contains(t, opTypes, expectedType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidOpType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
source model.Source
|
||||
opType model.Type
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "IRP有效操作类型",
|
||||
source: model.OpSourceIRP,
|
||||
opType: model.OpTypeOCCreateHandle,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "IRP无效操作类型",
|
||||
source: model.OpSourceIRP,
|
||||
opType: model.OpTypeHello,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "DOIP有效操作类型",
|
||||
source: model.OpSourceDOIP,
|
||||
opType: model.OpTypeHello,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "DOIP无效操作类型",
|
||||
source: model.OpSourceDOIP,
|
||||
opType: model.OpTypeOCCreateHandle,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "未知来源和类型",
|
||||
source: model.Source("unknown"),
|
||||
opType: model.Type("unknown"),
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := model.IsValidOpType(tt.source, tt.opType)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFullOperation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opSource model.Source
|
||||
opType model.Type
|
||||
doPrefix string
|
||||
doRepository string
|
||||
doid string
|
||||
producerID string
|
||||
opActor string
|
||||
requestBody interface{}
|
||||
responseBody interface{}
|
||||
timestamp time.Time
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "成功创建完整操作",
|
||||
opSource: model.OpSourceIRP,
|
||||
opType: model.OpTypeOCCreateHandle,
|
||||
doPrefix: "test",
|
||||
doRepository: "repo",
|
||||
doid: "test/repo/123",
|
||||
producerID: "producer-1",
|
||||
opActor: "actor-1",
|
||||
requestBody: []byte(`{"key": "value"}`),
|
||||
responseBody: []byte(`{"status": "ok"}`),
|
||||
timestamp: time.Now(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "空请求体和响应体",
|
||||
opSource: model.OpSourceIRP,
|
||||
opType: model.OpTypeOCCreateHandle,
|
||||
doPrefix: "test",
|
||||
doRepository: "repo",
|
||||
doid: "test/repo/123",
|
||||
producerID: "producer-1",
|
||||
opActor: "actor-1",
|
||||
requestBody: nil,
|
||||
responseBody: nil,
|
||||
timestamp: time.Now(),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "字符串类型的请求体",
|
||||
opSource: model.OpSourceIRP,
|
||||
opType: model.OpTypeOCCreateHandle,
|
||||
doPrefix: "test",
|
||||
doRepository: "repo",
|
||||
doid: "test/repo/123",
|
||||
producerID: "producer-1",
|
||||
opActor: "actor-1",
|
||||
requestBody: "string body",
|
||||
responseBody: "string response",
|
||||
timestamp: time.Now(),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
op, err := model.NewFullOperation(
|
||||
tt.opSource,
|
||||
tt.opType,
|
||||
tt.doPrefix,
|
||||
tt.doRepository,
|
||||
tt.doid,
|
||||
tt.producerID,
|
||||
tt.opActor,
|
||||
tt.requestBody,
|
||||
tt.responseBody,
|
||||
tt.timestamp,
|
||||
)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, op)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, op)
|
||||
assert.Equal(t, tt.opSource, op.OpSource)
|
||||
assert.Equal(t, tt.opType, op.OpType)
|
||||
assert.Equal(t, tt.doPrefix, op.DoPrefix)
|
||||
assert.Equal(t, tt.doRepository, op.DoRepository)
|
||||
assert.Equal(t, tt.doid, op.Doid)
|
||||
assert.Equal(t, tt.producerID, op.ProducerID)
|
||||
assert.Equal(t, tt.opActor, op.OpActor)
|
||||
assert.NotEmpty(t, op.OpID) // Should be auto-generated
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user