Sale (1177→2321 LOC): - Quotation templates (apply to order, option lines) - Sales reports (by month, product, customer, salesperson, category) - Advance payment wizard (delivered/percentage/fixed modes) - SO cancel wizard, discount wizard - action_quotation_sent, action_lock/unlock, preview_quotation - Line computes: invoice_status, price_reduce, untaxed_amount - Partner extension: sale_order_total Purchase (478→1424 LOC): - Purchase reports (by month, category, bill status, receipt analysis) - Receipt creation from PO (action_create_picking) - 3-way matching: action_view_picking, action_view_invoice - button_approve, button_done, action_rfq_send - Line computes: price_subtotal/total with tax, product onchange - Partner extension: purchase_order_count/total Project (218→1161 LOC): - Project updates (status tracking: on_track/at_risk/off_track) - Milestones (deadline, reached tracking, task count) - Timesheet integration (account.analytic.line extension) - Timesheet reports (by project, employee, task, week) - Task recurrence model - Task: planned/effective/remaining hours, progress, subtask hours - Project: allocated/remaining hours, profitability actions ORM Tests (102 tests, 0→1257 LOC): - domain_test.go: 32 tests (compile, operators, AND/OR/NOT, null) - field_test.go: 15 tests (IsCopyable, SQLType, IsRelational, IsStored) - model_test.go: 21 tests (NewModel, AddFields, RegisterMethod, ExtendModel) - domain_parse_test.go: 21 tests (parse Python domain strings) - sanitize_test.go: 13 tests (false→nil, type conversions) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
278 lines
6.6 KiB
Go
278 lines
6.6 KiB
Go
package orm
|
|
|
|
import "testing"
|
|
|
|
func TestParseDomainStringSimple(t *testing.T) {
|
|
domain, err := ParseDomainString("[('name', '=', 'test')]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 1 {
|
|
t.Fatalf("expected 1 node, got %d", len(domain))
|
|
}
|
|
cond, ok := domain[0].(Condition)
|
|
if !ok {
|
|
t.Fatal("expected Condition")
|
|
}
|
|
if cond.Field != "name" {
|
|
t.Errorf("field: %s", cond.Field)
|
|
}
|
|
if cond.Operator != "=" {
|
|
t.Errorf("op: %s", cond.Operator)
|
|
}
|
|
if cond.Value != "test" {
|
|
t.Errorf("value: %v", cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringNumeric(t *testing.T) {
|
|
domain, err := ParseDomainString("[('age', '>', 18)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != int64(18) {
|
|
t.Errorf("expected int64(18), got %T %v", cond.Value, cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringFloat(t *testing.T) {
|
|
domain, err := ParseDomainString("[('amount', '>', 99.5)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != float64(99.5) {
|
|
t.Errorf("expected float64(99.5), got %T %v", cond.Value, cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringNegativeNumber(t *testing.T) {
|
|
domain, err := ParseDomainString("[('balance', '<', -100)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != int64(-100) {
|
|
t.Errorf("expected int64(-100), got %T %v", cond.Value, cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringBoolean(t *testing.T) {
|
|
domain, err := ParseDomainString("[('active', '=', True)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != true {
|
|
t.Errorf("expected true, got %v", cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringBooleanFalse(t *testing.T) {
|
|
domain, err := ParseDomainString("[('active', '=', False)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != false {
|
|
t.Errorf("expected false, got %v", cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringList(t *testing.T) {
|
|
domain, err := ParseDomainString("[('id', 'in', [1, 2, 3])]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
vals, ok := cond.Value.([]int64)
|
|
if !ok {
|
|
t.Fatalf("expected []int64, got %T", cond.Value)
|
|
}
|
|
if len(vals) != 3 {
|
|
t.Errorf("expected 3, got %d", len(vals))
|
|
}
|
|
if vals[0] != 1 || vals[1] != 2 || vals[2] != 3 {
|
|
t.Errorf("values: %v", vals)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringStringList(t *testing.T) {
|
|
domain, err := ParseDomainString("[('state', 'in', ['draft', 'sent'])]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
vals, ok := cond.Value.([]string)
|
|
if !ok {
|
|
t.Fatalf("expected []string, got %T", cond.Value)
|
|
}
|
|
if len(vals) != 2 {
|
|
t.Errorf("expected 2, got %d", len(vals))
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringEmptyList(t *testing.T) {
|
|
domain, err := ParseDomainString("[('id', 'in', [])]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
vals, ok := cond.Value.([]int64)
|
|
if !ok {
|
|
t.Fatalf("expected []int64, got %T", cond.Value)
|
|
}
|
|
if len(vals) != 0 {
|
|
t.Errorf("expected 0, got %d", len(vals))
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringOperators(t *testing.T) {
|
|
domain, err := ParseDomainString("['&', ('a', '=', 1), ('b', '=', 2)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 3 {
|
|
t.Fatalf("expected 3 nodes, got %d", len(domain))
|
|
}
|
|
if domain[0] != OpAnd {
|
|
t.Error("expected & operator")
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringOrOperator(t *testing.T) {
|
|
domain, err := ParseDomainString("['|', ('a', '=', 1), ('b', '=', 2)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 3 {
|
|
t.Fatalf("expected 3 nodes, got %d", len(domain))
|
|
}
|
|
if domain[0] != OpOr {
|
|
t.Error("expected | operator")
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringNotOperator(t *testing.T) {
|
|
domain, err := ParseDomainString("['!', ('active', '=', True)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 2 {
|
|
t.Fatalf("expected 2 nodes, got %d", len(domain))
|
|
}
|
|
if domain[0] != OpNot {
|
|
t.Error("expected ! operator")
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringEmpty(t *testing.T) {
|
|
domain, err := ParseDomainString("[]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 0 {
|
|
t.Errorf("expected 0 nodes, got %d", len(domain))
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringEmptyString(t *testing.T) {
|
|
domain, err := ParseDomainString("", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 0 {
|
|
t.Errorf("expected 0 nodes, got %d", len(domain))
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringNone(t *testing.T) {
|
|
domain, err := ParseDomainString("[('field', '=', None)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != nil {
|
|
t.Errorf("expected nil, got %v", cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringImplicitAnd(t *testing.T) {
|
|
// Multiple leaves without explicit operator should be implicitly ANDed
|
|
domain, err := ParseDomainString("[('a', '=', 1), ('b', '=', 2)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// normalizeDomainNodes wraps with And() → [&, leaf, leaf] = 3 nodes
|
|
if len(domain) != 3 {
|
|
t.Fatalf("expected 3 nodes (implicit AND), got %d", len(domain))
|
|
}
|
|
if domain[0] != OpAnd {
|
|
t.Error("expected implicit & operator")
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringDoubleQuotes(t *testing.T) {
|
|
domain, err := ParseDomainString(`[("name", "=", "test")]`, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(domain) != 1 {
|
|
t.Fatalf("expected 1 node, got %d", len(domain))
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Field != "name" {
|
|
t.Errorf("field: %s", cond.Field)
|
|
}
|
|
if cond.Value != "test" {
|
|
t.Errorf("value: %v", cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringContextVar(t *testing.T) {
|
|
// Without env, context vars should resolve to int64(0)
|
|
domain, err := ParseDomainString("[('user_id', '=', user.id)]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != int64(0) {
|
|
t.Errorf("expected int64(0), got %T %v", cond.Value, cond.Value)
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringInvalidSyntax(t *testing.T) {
|
|
_, err := ParseDomainString("not a domain", nil)
|
|
if err == nil {
|
|
t.Error("expected error for invalid syntax")
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringTupleAsList(t *testing.T) {
|
|
// Some domain_force uses tuple syntax for list values
|
|
domain, err := ParseDomainString("[('id', 'in', (1, 2, 3))]", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
vals, ok := cond.Value.([]int64)
|
|
if !ok {
|
|
t.Fatalf("expected []int64, got %T", cond.Value)
|
|
}
|
|
if len(vals) != 3 {
|
|
t.Errorf("expected 3, got %d", len(vals))
|
|
}
|
|
}
|
|
|
|
func TestParseDomainStringEscapedQuote(t *testing.T) {
|
|
domain, err := ParseDomainString(`[('name', '=', 'it\'s')]`, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cond := domain[0].(Condition)
|
|
if cond.Value != "it's" {
|
|
t.Errorf("expected it's, got %v", cond.Value)
|
|
}
|
|
}
|