package DB import ( "path/filepath" "strings" "testing" ) // TestUseTableRejectsUnexportedField 回归测试: 含未导出字段的模型必须在建模阶段 // (UseTable → ParseModel) 就返回带字段名的明确错误 —— 而不是拖到首次 Insert 才在 // structToFieldValues 里对 reflect 拒绝访问的未导出字段 panic。 // 被拒时库里不应留下半成品表。 func TestUseTableRejectsUnexportedField(t *testing.T) { type badRow struct { BaseModel Name string age int // 未导出: reflect 无法读写, 旧实现到首次写入才 panic } db, err := Open(filepath.Join(t.TempDir(), "model_unexported.db")) if err != nil { t.Fatalf("打开: %v", err) } defer func() { _ = db.Close() }() tbl, err := UseTable[badRow](db, "badunexp") if err == nil { t.Fatalf("UseTable 应拒绝未导出字段, 却建表成功: %+v", tbl) } if !strings.Contains(err.Error(), "age") { t.Fatalf("错误应包含字段名 age, 得到: %v", err) } if _, ok := db.CoreDB().FindTable("badunexp"); ok { t.Fatal("被拒的模型不应在库里留下表") } // ParseModel 直调同样报错 (UseTable 之外的建模入口) if _, err := ParseModel(&badRow{}); err == nil { t.Fatal("ParseModel 应拒绝未导出字段") } else if !strings.Contains(err.Error(), "age") { t.Fatalf("ParseModel 错误应包含字段名 age, 得到: %v", err) } } // TestUseTableAcceptsEmbeddedBase 对照: 常见嵌入写法 (内嵌 DB.BaseModel, // 以及内嵌一个导出字段的普通结构体) 不受影响, 建模照常成功。 func TestUseTableAcceptsEmbeddedBase(t *testing.T) { type auditFields struct { CreatedBy string `vrtx:"maxlen:32"` } type goodRow struct { BaseModel auditFields // 匿名嵌入: 递归展开, 不是未导出普通字段 Name string } db, err := Open(filepath.Join(t.TempDir(), "model_embedded.db")) if err != nil { t.Fatalf("打开: %v", err) } defer func() { _ = db.Close() }() tbl, err := UseTable[goodRow](db, "goodembed") if err != nil { t.Fatalf("内嵌写法应建模成功: %v", err) } obj := &goodRow{Name: "x", auditFields: auditFields{CreatedBy: "u1"}} if err := tbl.Insert(obj); err != nil { t.Fatalf("Insert: %v", err) } got, err := tbl.Find(obj.ID) if err != nil { t.Fatalf("Find: %v", err) } if got.CreatedBy != "u1" || got.Name != "x" { t.Fatalf("嵌入字段读写异常: %+v", got) } }