package orm import "testing" func TestFieldIsCopyable(t *testing.T) { tests := []struct { name string field Field want bool }{ {"regular char", Field{Name: "name", Type: TypeChar}, true}, {"id field", Field{Name: "id", Type: TypeInteger}, false}, {"create_uid", Field{Name: "create_uid", Type: TypeMany2one}, false}, {"write_uid", Field{Name: "write_uid", Type: TypeMany2one}, false}, {"create_date", Field{Name: "create_date", Type: TypeDatetime}, false}, {"write_date", Field{Name: "write_date", Type: TypeDatetime}, false}, {"password", Field{Name: "password", Type: TypeChar}, false}, {"computed non-stored", Field{Name: "total", Type: TypeFloat, Compute: "x"}, false}, {"computed stored", Field{Name: "total", Type: TypeFloat, Compute: "x", Store: true}, true}, {"o2m", Field{Name: "lines", Type: TypeOne2many}, false}, {"m2o", Field{Name: "partner_id", Type: TypeMany2one}, true}, {"boolean", Field{Name: "active", Type: TypeBoolean}, true}, {"explicit copy true", Field{Name: "ref", Type: TypeChar, Copy: true}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.field.IsCopyable() if got != tt.want { t.Errorf("IsCopyable() = %v, want %v", got, tt.want) } }) } } func TestFieldSQLType(t *testing.T) { tests := []struct { typ FieldType want string }{ {TypeChar, "varchar"}, {TypeText, "text"}, {TypeHTML, "text"}, {TypeInteger, "int4"}, {TypeFloat, "numeric"}, {TypeMonetary, "numeric"}, {TypeBoolean, "bool"}, {TypeDate, "date"}, {TypeDatetime, "timestamp without time zone"}, {TypeMany2one, "int4"}, {TypeOne2many, ""}, {TypeMany2many, ""}, {TypeJson, "jsonb"}, {TypeProperties, "jsonb"}, {TypeBinary, "bytea"}, {TypeSelection, "varchar"}, {TypeReference, "varchar"}, } for _, tt := range tests { t.Run(tt.typ.String(), func(t *testing.T) { got := tt.typ.SQLType() if got != tt.want { t.Errorf("SQLType() = %q, want %q", got, tt.want) } }) } } func TestFieldSQLTypeWithSize(t *testing.T) { f := &Field{Type: TypeChar, Size: 64} got := f.SQLType() if got != "varchar(64)" { t.Errorf("expected varchar(64), got %s", got) } } func TestFieldTypeString(t *testing.T) { if TypeChar.String() != "char" { t.Error("expected char") } if TypeMany2one.String() != "many2one" { t.Error("expected many2one") } if TypeBoolean.String() != "boolean" { t.Error("expected boolean") } if TypeText.String() != "text" { t.Error("expected text") } if TypeInteger.String() != "integer" { t.Error("expected integer") } if TypeFloat.String() != "float" { t.Error("expected float") } } func TestFieldTypeIsRelational(t *testing.T) { if !TypeMany2one.IsRelational() { t.Error("m2o should be relational") } if !TypeOne2many.IsRelational() { t.Error("o2m should be relational") } if !TypeMany2many.IsRelational() { t.Error("m2m should be relational") } if TypeChar.IsRelational() { t.Error("char should not be relational") } if TypeInteger.IsRelational() { t.Error("integer should not be relational") } if TypeBoolean.IsRelational() { t.Error("boolean should not be relational") } } func TestFieldTypeIsStored(t *testing.T) { if !TypeChar.IsStored() { t.Error("char should be stored") } if !TypeMany2one.IsStored() { t.Error("m2o should be stored") } if TypeOne2many.IsStored() { t.Error("o2m should not be stored") } if TypeMany2many.IsStored() { t.Error("m2m should not be stored") } if !TypeBoolean.IsStored() { t.Error("boolean should be stored") } if !TypeInteger.IsStored() { t.Error("integer should be stored") } } func TestFieldIsStored(t *testing.T) { tests := []struct { name string f Field want bool }{ {"plain char", Field{Type: TypeChar}, true}, {"computed not stored", Field{Type: TypeChar, Compute: "x"}, false}, {"computed stored", Field{Type: TypeChar, Compute: "x", Store: true}, true}, {"related not stored", Field{Type: TypeChar, Related: "partner_id.name"}, false}, {"related stored", Field{Type: TypeChar, Related: "partner_id.name", Store: true}, true}, {"o2m", Field{Type: TypeOne2many}, false}, {"m2m", Field{Type: TypeMany2many}, false}, {"m2o", Field{Type: TypeMany2one}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := tt.f.IsStored() if got != tt.want { t.Errorf("IsStored() = %v, want %v", got, tt.want) } }) } } func TestFieldColumn(t *testing.T) { f := &Field{Name: "partner_id", column: "partner_id"} if f.Column() != "partner_id" { t.Errorf("expected partner_id, got %s", f.Column()) } f2 := &Field{Name: "custom", column: "custom_col"} if f2.Column() != "custom_col" { t.Errorf("expected custom_col, got %s", f2.Column()) } // When column is empty, falls back to Name f3 := &Field{Name: "fallback"} if f3.Column() != "fallback" { t.Errorf("expected fallback, got %s", f3.Column()) } } func TestFieldConstructors(t *testing.T) { t.Run("Char", func(t *testing.T) { f := Char("name", FieldOpts{String: "Name", Required: true}) if f.Type != TypeChar { t.Errorf("type: %s", f.Type) } if f.Name != "name" { t.Errorf("name: %s", f.Name) } if !f.Required { t.Error("expected required") } if f.String != "Name" { t.Errorf("string: %s", f.String) } }) t.Run("Integer", func(t *testing.T) { f := Integer("count", FieldOpts{}) if f.Type != TypeInteger { t.Errorf("type: %s", f.Type) } }) t.Run("Boolean", func(t *testing.T) { f := Boolean("active", FieldOpts{Default: true}) if f.Type != TypeBoolean { t.Errorf("type: %s", f.Type) } if f.Default != true { t.Error("expected default true") } }) t.Run("Many2one", func(t *testing.T) { f := Many2one("partner_id", "res.partner", FieldOpts{String: "Partner"}) if f.Type != TypeMany2one { t.Errorf("type: %s", f.Type) } if f.Comodel != "res.partner" { t.Errorf("comodel: %s", f.Comodel) } if !f.Index { t.Error("M2O should be auto-indexed") } if f.OnDelete != OnDeleteSetNull { t.Errorf("expected set null, got %s", f.OnDelete) } }) t.Run("One2many", func(t *testing.T) { f := One2many("line_ids", "sale.order.line", "order_id", FieldOpts{}) if f.Type != TypeOne2many { t.Errorf("type: %s", f.Type) } if f.InverseField != "order_id" { t.Errorf("inverse: %s", f.InverseField) } }) t.Run("Many2many", func(t *testing.T) { f := Many2many("tag_ids", "res.partner.tag", FieldOpts{}) if f.Type != TypeMany2many { t.Errorf("type: %s", f.Type) } if f.Comodel != "res.partner.tag" { t.Errorf("comodel: %s", f.Comodel) } }) t.Run("Text", func(t *testing.T) { f := Text("description", FieldOpts{}) if f.Type != TypeText { t.Errorf("type: %s", f.Type) } }) t.Run("Float", func(t *testing.T) { f := Float("amount", FieldOpts{}) if f.Type != TypeFloat { t.Errorf("type: %s", f.Type) } }) t.Run("Date", func(t *testing.T) { f := Date("birthday", FieldOpts{}) if f.Type != TypeDate { t.Errorf("type: %s", f.Type) } }) t.Run("Datetime", func(t *testing.T) { f := Datetime("created", FieldOpts{}) if f.Type != TypeDatetime { t.Errorf("type: %s", f.Type) } }) t.Run("Binary", func(t *testing.T) { f := Binary("image", FieldOpts{}) if f.Type != TypeBinary { t.Errorf("type: %s", f.Type) } }) t.Run("Json", func(t *testing.T) { f := Json("data", FieldOpts{}) if f.Type != TypeJson { t.Errorf("type: %s", f.Type) } }) t.Run("default label from name", func(t *testing.T) { f := Char("my_field", FieldOpts{}) if f.String != "my_field" { t.Errorf("expected my_field as default label, got %s", f.String) } }) } func TestFieldResolveDefault(t *testing.T) { t.Run("nil default", func(t *testing.T) { f := &Field{Default: nil} if f.ResolveDefault() != nil { t.Error("expected nil") } }) t.Run("string default", func(t *testing.T) { f := &Field{Default: "hello"} if f.ResolveDefault() != "hello" { t.Error("expected hello") } }) t.Run("bool default", func(t *testing.T) { f := &Field{Default: true} if f.ResolveDefault() != true { t.Error("expected true") } }) t.Run("int default", func(t *testing.T) { f := &Field{Default: 42} if f.ResolveDefault() != 42 { t.Error("expected 42") } }) }