Files
srdb/schema_test.go
bourdon dd8a534931 前端:优化 Manifest 视图文件显示
- 文件名区域改为左右布局
- 左侧显示文件名(如 000001.sst)
- 右侧显示级别标签(如 L0、L1)
- 添加级别标签样式,使用主题色背景
2025-10-09 20:53:37 +08:00

723 lines
20 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package srdb
import (
"testing"
)
// UserSchema 用户表 Schema
var UserSchema = NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"},
{Name: "description", Type: FieldTypeString, Indexed: false, Comment: "描述"},
})
// LogSchema 日志表 Schema
var LogSchema = NewSchema("logs", []Field{
{Name: "level", Type: FieldTypeString, Indexed: true, Comment: "日志级别"},
{Name: "message", Type: FieldTypeString, Indexed: false, Comment: "日志消息"},
{Name: "source", Type: FieldTypeString, Indexed: true, Comment: "来源"},
{Name: "error_code", Type: FieldTypeInt64, Indexed: true, Comment: "错误码"},
})
// OrderSchema 订单表 Schema
var OrderSchema = NewSchema("orders", []Field{
{Name: "order_id", Type: FieldTypeString, Indexed: true, Comment: "订单ID"},
{Name: "user_id", Type: FieldTypeInt64, Indexed: true, Comment: "用户ID"},
{Name: "amount", Type: FieldTypeFloat, Indexed: true, Comment: "金额"},
{Name: "status", Type: FieldTypeString, Indexed: true, Comment: "状态"},
{Name: "paid", Type: FieldTypeBool, Indexed: true, Comment: "是否支付"},
})
func TestSchema(t *testing.T) {
// 创建 Schema
schema := NewSchema("test", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
{Name: "score", Type: FieldTypeFloat, Indexed: false, Comment: "分数"},
})
// 测试数据
data := map[string]any{
"name": "Alice",
"age": 25,
"score": 95.5,
}
// 验证
err := schema.Validate(data)
if err != nil {
t.Errorf("Validation failed: %v", err)
}
// 获取索引字段
indexedFields := schema.GetIndexedFields()
if len(indexedFields) != 2 {
t.Errorf("Expected 2 indexed fields, got %d", len(indexedFields))
}
t.Log("Schema test passed!")
}
func TestSchemaValidation(t *testing.T) {
schema := NewSchema("test", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
})
// 正确的数据
validData := map[string]any{
"name": "Bob",
"age": 30,
}
err := schema.Validate(validData)
if err != nil {
t.Errorf("Valid data failed validation: %v", err)
}
// 错误的数据类型
invalidData := map[string]any{
"name": "Charlie",
"age": "thirty", // 应该是 int64
}
err = schema.Validate(invalidData)
if err == nil {
t.Error("Invalid data should fail validation")
}
t.Log("Schema validation test passed!")
}
func TestExtractIndexValue(t *testing.T) {
schema := NewSchema("test", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "名称"},
{Name: "age", Type: FieldTypeInt64, Indexed: true, Comment: "年龄"},
})
data := map[string]any{
"name": "David",
"age": float64(35), // JSON 解析后是 float64
}
// 提取 name
name, err := schema.ExtractIndexValue("name", data)
if err != nil {
t.Errorf("Failed to extract name: %v", err)
}
if name != "David" {
t.Errorf("Expected 'David', got %v", name)
}
// 提取 age (float64 → int64)
age, err := schema.ExtractIndexValue("age", data)
if err != nil {
t.Errorf("Failed to extract age: %v", err)
}
if age != int64(35) {
t.Errorf("Expected 35, got %v", age)
}
t.Log("Extract index value test passed!")
}
func TestPredefinedSchemas(t *testing.T) {
// 测试 UserSchema
userData := map[string]any{
"name": "Alice",
"age": 25,
"email": "alice@example.com",
"description": "Test user",
}
err := UserSchema.Validate(userData)
if err != nil {
t.Errorf("UserSchema validation failed: %v", err)
}
// 测试 LogSchema
logData := map[string]any{
"level": "ERROR",
"message": "Something went wrong",
"source": "api",
"error_code": 500,
}
err = LogSchema.Validate(logData)
if err != nil {
t.Errorf("LogSchema validation failed: %v", err)
}
t.Log("Predefined schemas test passed!")
}
// TestChecksumDeterminism 测试 checksum 的确定性
func TestChecksumDeterminism(t *testing.T) {
// 创建相同的 Schema 多次
for i := range 10 {
s1 := NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
})
s2 := NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
})
checksum1, err := s1.ComputeChecksum()
if err != nil {
t.Fatal(err)
}
checksum2, err := s2.ComputeChecksum()
if err != nil {
t.Fatal(err)
}
if checksum1 != checksum2 {
t.Errorf("Iteration %d: checksums should be equal, got %s and %s", i, checksum1, checksum2)
}
}
t.Log("✅ Checksum is deterministic")
}
// TestChecksumFieldOrderIndependent 测试字段顺序不影响 checksum
func TestChecksumFieldOrderIndependent(t *testing.T) {
s1 := NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
})
s2 := NewSchema("users", []Field{
{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"},
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
})
checksum1, _ := s1.ComputeChecksum()
checksum2, _ := s2.ComputeChecksum()
if checksum1 != checksum2 {
t.Errorf("Checksums should be equal regardless of field order, got %s and %s", checksum1, checksum2)
} else {
t.Logf("✅ Field order does not affect checksum (expected behavior)")
t.Logf(" checksum: %s", checksum1)
}
}
// TestChecksumDifferentData 测试不同 Schema 的 checksum 应该不同
func TestChecksumDifferentData(t *testing.T) {
s1 := NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: true, Comment: "用户名"},
})
s2 := NewSchema("users", []Field{
{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "用户名"}, // Indexed 不同
})
checksum1, _ := s1.ComputeChecksum()
checksum2, _ := s2.ComputeChecksum()
if checksum1 == checksum2 {
t.Error("Different schemas should have different checksums")
} else {
t.Log("✅ Different schemas have different checksums")
}
}
// TestChecksumMultipleFieldOrders 测试多个字段的各种排列组合都产生相同 checksum
func TestChecksumMultipleFieldOrders(t *testing.T) {
// 定义 4 个字段
fieldA := Field{Name: "id", Type: FieldTypeInt64, Indexed: true, Comment: "ID"}
fieldB := Field{Name: "name", Type: FieldTypeString, Indexed: false, Comment: "名称"}
fieldC := Field{Name: "age", Type: FieldTypeInt64, Indexed: false, Comment: "年龄"}
fieldD := Field{Name: "email", Type: FieldTypeString, Indexed: true, Comment: "邮箱"}
// 创建不同顺序的 Schema
schemas := []*Schema{
NewSchema("test", []Field{fieldA, fieldB, fieldC, fieldD}), // 原始顺序
NewSchema("test", []Field{fieldD, fieldC, fieldB, fieldA}), // 完全反转
NewSchema("test", []Field{fieldB, fieldD, fieldA, fieldC}), // 随机顺序 1
NewSchema("test", []Field{fieldC, fieldA, fieldD, fieldB}), // 随机顺序 2
NewSchema("test", []Field{fieldD, fieldA, fieldC, fieldB}), // 随机顺序 3
}
// 计算所有 checksum
checksums := make([]string, len(schemas))
for i, s := range schemas {
checksum, err := s.ComputeChecksum()
if err != nil {
t.Fatalf("Failed to compute checksum for schema %d: %v", i, err)
}
checksums[i] = checksum
}
// 验证所有 checksum 都相同
expectedChecksum := checksums[0]
for i := 1; i < len(checksums); i++ {
if checksums[i] != expectedChecksum {
t.Errorf("Schema %d has different checksum: expected %s, got %s", i, expectedChecksum, checksums[i])
}
}
t.Logf("✅ All %d field permutations produce the same checksum", len(schemas))
t.Logf(" checksum: %s", expectedChecksum)
}
// TestStructToFields 测试从结构体生成 Field 列表
func TestStructToFields(t *testing.T) {
// 定义测试结构体
type User struct {
Name string `srdb:"name;indexed;comment:用户名"`
Age int64 `srdb:"age;comment:年龄"`
Email string `srdb:"email;indexed;comment:邮箱"`
Score float64 `srdb:"score;comment:分数"`
Active bool `srdb:"active;comment:是否激活"`
}
// 生成 Field 列表
fields, err := StructToFields(User{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
// 验证字段数量
if len(fields) != 5 {
t.Errorf("Expected 5 fields, got %d", len(fields))
}
// 验证每个字段
expectedFields := map[string]struct {
Type FieldType
Indexed bool
Comment string
}{
"name": {FieldTypeString, true, "用户名"},
"age": {FieldTypeInt64, false, "年龄"},
"email": {FieldTypeString, true, "邮箱"},
"score": {FieldTypeFloat, false, "分数"},
"active": {FieldTypeBool, false, "是否激活"},
}
for _, field := range fields {
expected, exists := expectedFields[field.Name]
if !exists {
t.Errorf("Unexpected field: %s", field.Name)
continue
}
if field.Type != expected.Type {
t.Errorf("Field %s: expected type %v, got %v", field.Name, expected.Type, field.Type)
}
if field.Indexed != expected.Indexed {
t.Errorf("Field %s: expected indexed=%v, got %v", field.Name, expected.Indexed, field.Indexed)
}
if field.Comment != expected.Comment {
t.Errorf("Field %s: expected comment=%s, got %s", field.Name, expected.Comment, field.Comment)
}
}
t.Log("✓ StructToFields basic test passed")
}
// TestStructToFieldsDefaultName 测试默认字段名 (snake_case)
func TestStructToFieldsDefaultName(t *testing.T) {
type Product struct {
ProductName string // 没有 tag应该使用 snake_case: product_name
Price int64 // 没有 tag应该使用 snake_case: price
}
fields, err := StructToFields(Product{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
if len(fields) != 2 {
t.Errorf("Expected 2 fields, got %d", len(fields))
}
// 验证默认字段名snake_case
if fields[0].Name != "product_name" {
t.Errorf("Expected field name 'product_name', got '%s'", fields[0].Name)
}
if fields[1].Name != "price" {
t.Errorf("Expected field name 'price', got '%s'", fields[1].Name)
}
t.Log("✓ Default field name (snake_case) test passed")
}
// TestStructToFieldsIgnore 测试忽略字段
func TestStructToFieldsIgnore(t *testing.T) {
type Order struct {
OrderID string `srdb:"order_id;comment:订单ID"`
Internal string `srdb:"-"` // 应该被忽略
CreatedAt int64 `srdb:"created_at;comment:创建时间"`
}
fields, err := StructToFields(Order{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
// 应该只有 2 个字段Internal 被忽略)
if len(fields) != 2 {
t.Errorf("Expected 2 fields (excluding ignored field), got %d", len(fields))
}
// 验证没有 Internal 字段
for _, field := range fields {
if field.Name == "internal" || field.Name == "Internal" {
t.Errorf("Field 'Internal' should have been ignored")
}
}
t.Log("✓ Ignore field test passed")
}
// TestStructToFieldsPointer 测试指针类型
func TestStructToFieldsPointer(t *testing.T) {
type Item struct {
Name string `srdb:"name;comment:名称"`
}
// 使用指针
fields, err := StructToFields(&Item{})
if err != nil {
t.Fatalf("StructToFields with pointer failed: %v", err)
}
if len(fields) != 1 {
t.Errorf("Expected 1 field, got %d", len(fields))
}
if fields[0].Name != "name" {
t.Errorf("Expected field name 'name', got '%s'", fields[0].Name)
}
t.Log("✓ Pointer type test passed")
}
// TestStructToFieldsAllTypes 测试所有支持的类型
func TestStructToFieldsAllTypes(t *testing.T) {
type AllTypes struct {
Int int `srdb:"int"`
Int64 int64 `srdb:"int64"`
Int32 int32 `srdb:"int32"`
Int16 int16 `srdb:"int16"`
Int8 int8 `srdb:"int8"`
Uint uint `srdb:"uint"`
Uint64 uint64 `srdb:"uint64"`
Uint32 uint32 `srdb:"uint32"`
Uint16 uint16 `srdb:"uint16"`
Uint8 uint8 `srdb:"uint8"`
String string `srdb:"string"`
Float64 float64 `srdb:"float64"`
Float32 float32 `srdb:"float32"`
Bool bool `srdb:"bool"`
}
fields, err := StructToFields(AllTypes{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
if len(fields) != 14 {
t.Errorf("Expected 14 fields, got %d", len(fields))
}
// 验证所有整数类型都映射到 FieldTypeInt64
intFields := []string{"int", "int64", "int32", "int16", "int8", "uint", "uint64", "uint32", "uint16", "uint8"}
for _, name := range intFields {
found := false
for _, field := range fields {
if field.Name == name {
found = true
if field.Type != FieldTypeInt64 {
t.Errorf("Field %s: expected FieldTypeInt64, got %v", name, field.Type)
}
break
}
}
if !found {
t.Errorf("Field %s not found", name)
}
}
// 验证浮点类型
for _, field := range fields {
if field.Name == "float64" || field.Name == "float32" {
if field.Type != FieldTypeFloat {
t.Errorf("Field %s: expected FieldTypeFloat, got %v", field.Name, field.Type)
}
}
}
t.Log("✓ All types test passed")
}
// TestStructToFieldsWithSchema 测试完整的使用流程
func TestStructToFieldsWithSchema(t *testing.T) {
// 定义结构体
type Customer struct {
CustomerID string `srdb:"customer_id;indexed;comment:客户ID"`
Name string `srdb:"name;comment:客户名称"`
Email string `srdb:"email;indexed;comment:邮箱"`
Balance int64 `srdb:"balance;comment:余额"`
}
// 生成 Field 列表
fields, err := StructToFields(Customer{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
// 创建 Schema
schema := NewSchema("customers", fields)
// 验证 Schema
if schema.Name != "customers" {
t.Errorf("Expected schema name 'customers', got '%s'", schema.Name)
}
if len(schema.Fields) != 4 {
t.Errorf("Expected 4 fields in schema, got %d", len(schema.Fields))
}
// 验证索引字段
indexedFields := schema.GetIndexedFields()
if len(indexedFields) != 2 {
t.Errorf("Expected 2 indexed fields, got %d", len(indexedFields))
}
// 测试数据验证
validData := map[string]any{
"customer_id": "C001",
"name": "张三",
"email": "zhangsan@example.com",
"balance": int64(1000),
}
err = schema.Validate(validData)
if err != nil {
t.Errorf("Valid data should pass validation: %v", err)
}
// 测试无效数据
invalidData := map[string]any{
"customer_id": "C002",
"name": "李四",
"email": 123, // 错误类型
"balance": int64(2000),
}
err = schema.Validate(invalidData)
if err == nil {
t.Error("Invalid data should fail validation")
}
t.Log("✓ Complete workflow test passed")
}
// TestStructToFieldsTagVariations 测试各种 tag 组合
func TestStructToFieldsTagVariations(t *testing.T) {
type TestStruct struct {
// 只有字段名
Field1 string `srdb:"field1"`
// 字段名 + indexed
Field2 string `srdb:"field2;indexed"`
// 字段名 + comment
Field3 string `srdb:"field3;comment:字段3"`
// 完整格式
Field4 string `srdb:"field4;indexed;comment:字段4"`
// 只有 indexed使用默认字段名
Field5 string `srdb:";indexed"`
// 只有 comment使用默认字段名
Field6 string `srdb:";comment:字段6"`
// 空 tag使用默认字段名
Field7 string
// indexed + comment使用默认字段名
Field8 string `srdb:";indexed;comment:字段8"`
}
fields, err := StructToFields(TestStruct{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
if len(fields) != 8 {
t.Errorf("Expected 8 fields, got %d", len(fields))
}
// 验证各个字段
tests := []struct {
name string
indexed bool
comment string
}{
{"field1", false, ""},
{"field2", true, ""},
{"field3", false, "字段3"},
{"field4", true, "字段4"},
{"field5", true, ""},
{"field6", false, "字段6"},
{"field7", false, ""},
{"field8", true, "字段8"},
}
for i, test := range tests {
if fields[i].Name != test.name {
t.Errorf("Field %d: expected name %s, got %s", i+1, test.name, fields[i].Name)
}
if fields[i].Indexed != test.indexed {
t.Errorf("Field %s: expected indexed=%v, got %v", test.name, test.indexed, fields[i].Indexed)
}
if fields[i].Comment != test.comment {
t.Errorf("Field %s: expected comment=%s, got %s", test.name, test.comment, fields[i].Comment)
}
}
t.Log("✓ Tag variations test passed")
}
// TestStructToFieldsErrors 测试错误情况
func TestStructToFieldsErrors(t *testing.T) {
// 测试非结构体类型
_, err := StructToFields("not a struct")
if err == nil {
t.Error("Expected error for non-struct type")
}
// 测试 nil
_, err = StructToFields(nil)
if err == nil {
t.Error("Expected error for nil")
}
// 测试没有导出字段的结构体
type Empty struct {
private string // 未导出
}
_, err = StructToFields(Empty{})
if err == nil {
t.Error("Expected error for struct with no exported fields")
}
t.Log("✓ Error handling test passed")
}
// TestCamelToSnake 测试驼峰命名转 snake_case
func TestCamelToSnake(t *testing.T) {
tests := []struct {
input string
expected string
}{
// 基本测试
{"UserName", "user_name"},
{"EmailAddress", "email_address"},
{"IsActive", "is_active"},
// 单个单词
{"Name", "name"},
{"ID", "id"},
// 连续大写字母
{"HTTPServer", "http_server"},
{"XMLParser", "xml_parser"},
{"HTMLContent", "html_content"},
{"URLPath", "url_path"},
// 带数字
{"User2Name", "user2_name"},
{"Address1", "address1"},
// 全小写
{"username", "username"},
// 全大写
{"HTTP", "http"},
{"API", "api"},
// 混合情况
{"getUserByID", "get_user_by_id"},
{"HTTPSConnection", "https_connection"},
{"createHTMLFile", "create_html_file"},
// 边界情况
{"A", "a"},
{"AB", "ab"},
{"AbC", "ab_c"},
}
for _, test := range tests {
result := camelToSnake(test.input)
if result != test.expected {
t.Errorf("camelToSnake(%q) = %q, expected %q", test.input, result, test.expected)
}
}
t.Log("✓ camelToSnake test passed")
}
// TestStructToFieldsSnakeCase 测试默认使用 snake_case
func TestStructToFieldsSnakeCase(t *testing.T) {
type User struct {
UserName string // 应该转为 user_name
EmailAddress string // 应该转为 email_address
IsActive bool // 应该转为 is_active
HTTPEndpoint string // 应该转为 http_endpoint
ID int64 // 应该转为 id
}
fields, err := StructToFields(User{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
expected := []string{"user_name", "email_address", "is_active", "http_endpoint", "id"}
if len(fields) != len(expected) {
t.Fatalf("Expected %d fields, got %d", len(expected), len(fields))
}
for i, exp := range expected {
if fields[i].Name != exp {
t.Errorf("Field %d: expected name %s, got %s", i, exp, fields[i].Name)
}
}
t.Log("✓ Default snake_case test passed")
}
// TestStructToFieldsOverrideSnakeCase 测试可以覆盖默认 snake_case
func TestStructToFieldsOverrideSnakeCase(t *testing.T) {
type User struct {
UserName string `srdb:"username"` // 覆盖默认的 user_name
IsActive bool `srdb:"active;comment:激活"` // 覆盖默认的 is_active
}
fields, err := StructToFields(User{})
if err != nil {
t.Fatalf("StructToFields failed: %v", err)
}
if len(fields) != 2 {
t.Fatalf("Expected 2 fields, got %d", len(fields))
}
// 验证覆盖成功
if fields[0].Name != "username" {
t.Errorf("Expected field name 'username', got '%s'", fields[0].Name)
}
if fields[1].Name != "active" {
t.Errorf("Expected field name 'active', got '%s'", fields[1].Name)
}
t.Log("✓ Override snake_case test passed")
}