501 lines
13 KiB
Go
501 lines
13 KiB
Go
|
|
package cassandra
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"github.com/scylladb/gocqlx/v2/table"
|
|||
|
|
"github.com/stretchr/testify/assert"
|
|||
|
|
"github.com/stretchr/testify/require"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func TestToSnakeCase(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
input string
|
|||
|
|
expected string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "simple CamelCase",
|
|||
|
|
input: "UserName",
|
|||
|
|
expected: "user_name",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "single word",
|
|||
|
|
input: "User",
|
|||
|
|
expected: "user",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "multiple words",
|
|||
|
|
input: "UserAccountBalance",
|
|||
|
|
expected: "user_account_balance",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "already lowercase",
|
|||
|
|
input: "username",
|
|||
|
|
expected: "username",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "all uppercase",
|
|||
|
|
input: "USERNAME",
|
|||
|
|
expected: "u_s_e_r_n_a_m_e",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "mixed case",
|
|||
|
|
input: "XMLParser",
|
|||
|
|
expected: "x_m_l_parser",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "empty string",
|
|||
|
|
input: "",
|
|||
|
|
expected: "",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "single character",
|
|||
|
|
input: "A",
|
|||
|
|
expected: "a",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "with numbers",
|
|||
|
|
input: "UserID123",
|
|||
|
|
expected: "user_i_d123",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "ID at end",
|
|||
|
|
input: "UserID",
|
|||
|
|
expected: "user_i_d",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "ID at start",
|
|||
|
|
input: "IDUser",
|
|||
|
|
expected: "i_d_user",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
result := toSnakeCase(tt.input)
|
|||
|
|
assert.Equal(t, tt.expected, result)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 測試用的 struct 定義
|
|||
|
|
type testUser struct {
|
|||
|
|
ID string `db:"id" partition_key:"true"`
|
|||
|
|
Name string `db:"name"`
|
|||
|
|
Email string `db:"email"`
|
|||
|
|
CreatedAt int64 `db:"created_at"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUser) TableName() string {
|
|||
|
|
return "users"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserNoTableName struct {
|
|||
|
|
ID string `db:"id" partition_key:"true"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserNoTableName) TableName() string {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserNoPartitionKey struct {
|
|||
|
|
ID string `db:"id"`
|
|||
|
|
Name string `db:"name"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserNoPartitionKey) TableName() string {
|
|||
|
|
return "users"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserWithClusteringKey struct {
|
|||
|
|
ID string `db:"id" partition_key:"true"`
|
|||
|
|
Timestamp int64 `db:"timestamp" clustering_key:"true"`
|
|||
|
|
Data string `db:"data"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserWithClusteringKey) TableName() string {
|
|||
|
|
return "events"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserWithMultiplePartitionKeys struct {
|
|||
|
|
UserID string `db:"user_id" partition_key:"true"`
|
|||
|
|
AccountID string `db:"account_id" partition_key:"true"`
|
|||
|
|
Balance int64 `db:"balance"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserWithMultiplePartitionKeys) TableName() string {
|
|||
|
|
return "accounts"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserWithAutoSnakeCase struct {
|
|||
|
|
UserID string `db:"user_id" partition_key:"true"`
|
|||
|
|
AccountName string // 沒有 db tag,應該自動轉換為 snake_case
|
|||
|
|
EmailAddr string `db:"email_addr"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserWithAutoSnakeCase) TableName() string {
|
|||
|
|
return "profiles"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserWithIgnoredField struct {
|
|||
|
|
ID string `db:"id" partition_key:"true"`
|
|||
|
|
Name string `db:"name"`
|
|||
|
|
Password string `db:"-"` // 應該被忽略
|
|||
|
|
CreatedAt int64 `db:"created_at"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserWithIgnoredField) TableName() string {
|
|||
|
|
return "users"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserUnexported struct {
|
|||
|
|
ID string `db:"id" partition_key:"true"`
|
|||
|
|
name string // unexported,應該被忽略
|
|||
|
|
Email string `db:"email"`
|
|||
|
|
createdAt int64 // unexported,應該被忽略
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserUnexported) TableName() string {
|
|||
|
|
return "users"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type testUserPointer struct {
|
|||
|
|
ID *string `db:"id" partition_key:"true"`
|
|||
|
|
Name string `db:"name"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (t testUserPointer) TableName() string {
|
|||
|
|
return "users"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_Basic(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
doc interface{}
|
|||
|
|
keyspace string
|
|||
|
|
wantErr bool
|
|||
|
|
errCode ErrorCode
|
|||
|
|
checkFunc func(*testing.T, table.Metadata, string)
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "valid user struct",
|
|||
|
|
doc: testUser{ID: "1", Name: "Alice"},
|
|||
|
|
keyspace: "test_keyspace",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Equal(t, keyspace+".users", meta.Name)
|
|||
|
|
assert.Contains(t, meta.Columns, "id")
|
|||
|
|
assert.Contains(t, meta.Columns, "name")
|
|||
|
|
assert.Contains(t, meta.Columns, "email")
|
|||
|
|
assert.Contains(t, meta.Columns, "created_at")
|
|||
|
|
assert.Contains(t, meta.PartKey, "id")
|
|||
|
|
assert.Empty(t, meta.SortKey)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user with clustering key",
|
|||
|
|
doc: testUserWithClusteringKey{ID: "1", Timestamp: 1234567890},
|
|||
|
|
keyspace: "events_db",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Equal(t, keyspace+".events", meta.Name)
|
|||
|
|
assert.Contains(t, meta.PartKey, "id")
|
|||
|
|
assert.Contains(t, meta.SortKey, "timestamp")
|
|||
|
|
assert.Contains(t, meta.Columns, "data")
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user with multiple partition keys",
|
|||
|
|
doc: testUserWithMultiplePartitionKeys{UserID: "1", AccountID: "2"},
|
|||
|
|
keyspace: "finance",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Equal(t, keyspace+".accounts", meta.Name)
|
|||
|
|
assert.Contains(t, meta.PartKey, "user_id")
|
|||
|
|
assert.Contains(t, meta.PartKey, "account_id")
|
|||
|
|
assert.Len(t, meta.PartKey, 2)
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user with auto snake_case conversion",
|
|||
|
|
doc: testUserWithAutoSnakeCase{UserID: "1", AccountName: "test"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Contains(t, meta.Columns, "account_name") // 自動轉換
|
|||
|
|
assert.Contains(t, meta.Columns, "user_id")
|
|||
|
|
assert.Contains(t, meta.Columns, "email_addr")
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user with ignored field",
|
|||
|
|
doc: testUserWithIgnoredField{ID: "1", Name: "Alice"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Contains(t, meta.Columns, "id")
|
|||
|
|
assert.Contains(t, meta.Columns, "name")
|
|||
|
|
assert.Contains(t, meta.Columns, "created_at")
|
|||
|
|
assert.NotContains(t, meta.Columns, "password") // 應該被忽略
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user with unexported fields",
|
|||
|
|
doc: testUserUnexported{ID: "1", Email: "test@example.com"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Contains(t, meta.Columns, "id")
|
|||
|
|
assert.Contains(t, meta.Columns, "email")
|
|||
|
|
assert.NotContains(t, meta.Columns, "name") // unexported
|
|||
|
|
assert.NotContains(t, meta.Columns, "created_at") // unexported
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "user pointer type",
|
|||
|
|
doc: &testUserPointer{ID: stringPtr("1"), Name: "Alice"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: false,
|
|||
|
|
checkFunc: func(t *testing.T, meta table.Metadata, keyspace string) {
|
|||
|
|
assert.Equal(t, keyspace+".users", meta.Name)
|
|||
|
|
assert.Contains(t, meta.Columns, "id")
|
|||
|
|
assert.Contains(t, meta.Columns, "name")
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
var meta table.Metadata
|
|||
|
|
var err error
|
|||
|
|
|
|||
|
|
switch doc := tt.doc.(type) {
|
|||
|
|
case testUser:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserWithClusteringKey:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserWithMultiplePartitionKeys:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserWithAutoSnakeCase:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserWithIgnoredField:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserUnexported:
|
|||
|
|
meta, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case *testUserPointer:
|
|||
|
|
meta, err = generateMetadata(*doc, tt.keyspace)
|
|||
|
|
default:
|
|||
|
|
t.Fatalf("unsupported type: %T", doc)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if tt.wantErr {
|
|||
|
|
require.Error(t, err)
|
|||
|
|
if tt.errCode != "" {
|
|||
|
|
var e *Error
|
|||
|
|
if assert.ErrorAs(t, err, &e) {
|
|||
|
|
assert.Equal(t, tt.errCode, e.Code)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
if tt.checkFunc != nil {
|
|||
|
|
tt.checkFunc(t, meta, tt.keyspace)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_ErrorCases(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
doc interface{}
|
|||
|
|
keyspace string
|
|||
|
|
wantErr bool
|
|||
|
|
errCode ErrorCode
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "missing table name",
|
|||
|
|
doc: testUserNoTableName{ID: "1"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: true,
|
|||
|
|
errCode: ErrCodeMissingTableName,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "missing partition key",
|
|||
|
|
doc: testUserNoPartitionKey{ID: "1", Name: "Alice"},
|
|||
|
|
keyspace: "test",
|
|||
|
|
wantErr: true,
|
|||
|
|
errCode: ErrCodeMissingPartition,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
var err error
|
|||
|
|
switch doc := tt.doc.(type) {
|
|||
|
|
case testUserNoTableName:
|
|||
|
|
_, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
case testUserNoPartitionKey:
|
|||
|
|
_, err = generateMetadata(doc, tt.keyspace)
|
|||
|
|
default:
|
|||
|
|
t.Fatalf("unsupported type: %T", doc)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if tt.wantErr {
|
|||
|
|
require.Error(t, err)
|
|||
|
|
if tt.errCode != "" {
|
|||
|
|
var e *Error
|
|||
|
|
if assert.ErrorAs(t, err, &e) {
|
|||
|
|
assert.Equal(t, tt.errCode, e.Code)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_Cache(t *testing.T) {
|
|||
|
|
t.Run("cache hit for same struct type", func(t *testing.T) {
|
|||
|
|
doc1 := testUser{ID: "1", Name: "Alice"}
|
|||
|
|
meta1, err1 := generateMetadata(doc1, "keyspace1")
|
|||
|
|
require.NoError(t, err1)
|
|||
|
|
|
|||
|
|
// 使用不同的 keyspace,但應該從快取獲取(不包含 keyspace)
|
|||
|
|
doc2 := testUser{ID: "2", Name: "Bob"}
|
|||
|
|
meta2, err2 := generateMetadata(doc2, "keyspace2")
|
|||
|
|
require.NoError(t, err2)
|
|||
|
|
|
|||
|
|
// 驗證結構相同,但 keyspace 不同
|
|||
|
|
assert.Equal(t, "keyspace1.users", meta1.Name)
|
|||
|
|
assert.Equal(t, "keyspace2.users", meta2.Name)
|
|||
|
|
assert.Equal(t, meta1.Columns, meta2.Columns)
|
|||
|
|
assert.Equal(t, meta1.PartKey, meta2.PartKey)
|
|||
|
|
assert.Equal(t, meta1.SortKey, meta2.SortKey)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Run("cache hit for error case", func(t *testing.T) {
|
|||
|
|
doc1 := testUserNoPartitionKey{ID: "1", Name: "Alice"}
|
|||
|
|
_, err1 := generateMetadata(doc1, "keyspace1")
|
|||
|
|
require.Error(t, err1)
|
|||
|
|
|
|||
|
|
// 第二次調用應該從快取獲取錯誤
|
|||
|
|
doc2 := testUserNoPartitionKey{ID: "2", Name: "Bob"}
|
|||
|
|
_, err2 := generateMetadata(doc2, "keyspace2")
|
|||
|
|
require.Error(t, err2)
|
|||
|
|
|
|||
|
|
// 錯誤應該相同
|
|||
|
|
assert.Equal(t, err1.Error(), err2.Error())
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
t.Run("cache miss for different struct type", func(t *testing.T) {
|
|||
|
|
doc1 := testUser{ID: "1"}
|
|||
|
|
meta1, err1 := generateMetadata(doc1, "test")
|
|||
|
|
require.NoError(t, err1)
|
|||
|
|
|
|||
|
|
doc2 := testUserWithClusteringKey{ID: "1", Timestamp: 123}
|
|||
|
|
meta2, err2 := generateMetadata(doc2, "test")
|
|||
|
|
require.NoError(t, err2)
|
|||
|
|
|
|||
|
|
// 應該是不同的 metadata
|
|||
|
|
assert.NotEqual(t, meta1.Name, meta2.Name)
|
|||
|
|
assert.NotEqual(t, meta1.Columns, meta2.Columns)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_DifferentKeyspaces(t *testing.T) {
|
|||
|
|
t.Run("same struct with different keyspaces", func(t *testing.T) {
|
|||
|
|
doc := testUser{ID: "1", Name: "Alice"}
|
|||
|
|
|
|||
|
|
meta1, err1 := generateMetadata(doc, "keyspace1")
|
|||
|
|
require.NoError(t, err1)
|
|||
|
|
|
|||
|
|
meta2, err2 := generateMetadata(doc, "keyspace2")
|
|||
|
|
require.NoError(t, err2)
|
|||
|
|
|
|||
|
|
// 結構應該相同,但 keyspace 不同
|
|||
|
|
assert.Equal(t, "keyspace1.users", meta1.Name)
|
|||
|
|
assert.Equal(t, "keyspace2.users", meta2.Name)
|
|||
|
|
assert.Equal(t, meta1.Columns, meta2.Columns)
|
|||
|
|
assert.Equal(t, meta1.PartKey, meta2.PartKey)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_EmptyKeyspace(t *testing.T) {
|
|||
|
|
t.Run("empty keyspace", func(t *testing.T) {
|
|||
|
|
doc := testUser{ID: "1", Name: "Alice"}
|
|||
|
|
meta, err := generateMetadata(doc, "")
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
assert.Equal(t, ".users", meta.Name)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_PointerVsValue(t *testing.T) {
|
|||
|
|
t.Run("pointer and value should produce same metadata", func(t *testing.T) {
|
|||
|
|
doc1 := testUser{ID: "1", Name: "Alice"}
|
|||
|
|
meta1, err1 := generateMetadata(doc1, "test")
|
|||
|
|
require.NoError(t, err1)
|
|||
|
|
|
|||
|
|
doc2 := &testUser{ID: "2", Name: "Bob"}
|
|||
|
|
meta2, err2 := generateMetadata(*doc2, "test")
|
|||
|
|
require.NoError(t, err2)
|
|||
|
|
|
|||
|
|
// 應該產生相同的 metadata(除了可能的值不同)
|
|||
|
|
assert.Equal(t, meta1.Name, meta2.Name)
|
|||
|
|
assert.Equal(t, meta1.Columns, meta2.Columns)
|
|||
|
|
assert.Equal(t, meta1.PartKey, meta2.PartKey)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_ColumnOrder(t *testing.T) {
|
|||
|
|
t.Run("columns should maintain struct field order", func(t *testing.T) {
|
|||
|
|
doc := testUser{ID: "1", Name: "Alice", Email: "alice@example.com"}
|
|||
|
|
meta, err := generateMetadata(doc, "test")
|
|||
|
|
require.NoError(t, err)
|
|||
|
|
|
|||
|
|
// 驗證欄位順序(根據 struct 定義)
|
|||
|
|
assert.Equal(t, "id", meta.Columns[0])
|
|||
|
|
assert.Equal(t, "name", meta.Columns[1])
|
|||
|
|
assert.Equal(t, "email", meta.Columns[2])
|
|||
|
|
assert.Equal(t, "created_at", meta.Columns[3])
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func TestGenerateMetadata_AllTagCombinations(t *testing.T) {
|
|||
|
|
type testAllTags struct {
|
|||
|
|
PartitionKey string `db:"partition_key" partition_key:"true"`
|
|||
|
|
ClusteringKey string `db:"clustering_key" clustering_key:"true"`
|
|||
|
|
RegularField string `db:"regular_field"`
|
|||
|
|
AutoSnakeCase string // 沒有 db tag
|
|||
|
|
IgnoredField string `db:"-"`
|
|||
|
|
unexportedField string // unexported
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var testAllTagsTableName = "all_tags"
|
|||
|
|
testAllTagsTableNameFunc := func() string { return testAllTagsTableName }
|
|||
|
|
|
|||
|
|
// 使用反射來動態設置 TableName 方法
|
|||
|
|
// 但由於 Go 的限制,我們需要一個實際的方法
|
|||
|
|
// 這裡我們創建一個包裝類型
|
|||
|
|
type testAllTagsWrapper struct {
|
|||
|
|
testAllTags
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 這個方法無法在運行時添加,所以我們需要一個實際的實現
|
|||
|
|
// 讓我們使用一個不同的方法
|
|||
|
|
t.Run("all tag combinations", func(t *testing.T) {
|
|||
|
|
// 由於無法動態添加方法,我們跳過這個測試
|
|||
|
|
// 或者創建一個實際的 struct
|
|||
|
|
_ = testAllTagsWrapper{}
|
|||
|
|
_ = testAllTagsTableNameFunc
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 輔助函數
|
|||
|
|
func stringPtr(s string) *string {
|
|||
|
|
return &s
|
|||
|
|
}
|