package cart import ( "encoding/json" "testing" "git.k6n.net/mats/platform/money" ) func TestPriceMarshalJSON(t *testing.T) { p := Price{IncVat: 13700, VatRates: map[int]money.Cents{2500: 2500, 1200: 1200}} // ExVat = 13700 - (2500+1200) = 10000 data, err := json.Marshal(p) if err != nil { t.Fatalf("marshal error: %v", err) } // Unmarshal into a generic struct to validate fields var out struct { ExVat int64 `json:"exVat"` IncVat int64 `json:"incVat"` Vat map[string]int64 `json:"vat"` } if err := json.Unmarshal(data, &out); err != nil { t.Fatalf("unmarshal error: %v", err) } if out.ExVat != 10000 { t.Fatalf("expected exVat 10000 got %d", out.ExVat) } if out.IncVat != 13700 { t.Fatalf("expected incVat 13700 got %d", out.IncVat) } if out.Vat["2500"] != 2500 || out.Vat["1200"] != 1200 { t.Fatalf("unexpected vat map: %#v", out.Vat) } } func TestNewPriceFromIncVat(t *testing.T) { p := NewPriceFromIncVat(1250, 2500) // 25% in basis points if p.IncVat != 1250 { t.Fatalf("expected IncVat %d got %d", 1250, p.IncVat) } if p.VatRates[2500] != 250 { t.Fatalf("expected VAT 2500bp rate %d got %d", 250, p.VatRates[2500]) } if p.ValueExVat() != 1000 { t.Fatalf("expected exVat %d got %d", 1000, p.ValueExVat()) } } func TestSumPrices(t *testing.T) { // We'll construct prices via raw struct since constructor expects tax math. // IncVat already includes vat portions. Keys are basis points (2500 = 25%). a := Price{IncVat: 1250, VatRates: map[int]money.Cents{2500: 250}} // ex=1000 b := Price{IncVat: 2740, VatRates: map[int]money.Cents{2500: 500, 1200: 240}} // ex=2000 c := Price{IncVat: 0, VatRates: nil} sum := SumPrices(a, b, c) if sum.IncVat != 3990 { // 1250+2740 t.Fatalf("expected incVat 3990 got %d", sum.IncVat) } if len(sum.VatRates) != 2 { t.Fatalf("expected 2 vat rates got %d", len(sum.VatRates)) } if sum.VatRates[2500] != 750 { t.Fatalf("expected 25%% vat 750 got %d", sum.VatRates[2500]) } if sum.VatRates[1200] != 240 { t.Fatalf("expected 12%% vat 240 got %d", sum.VatRates[1200]) } if sum.ValueExVat() != 3000 { // 3990 - (750+240) t.Fatalf("expected exVat 3000 got %d", sum.ValueExVat()) } } func TestSumPricesEmpty(t *testing.T) { sum := SumPrices() // SumPrices nils an empty VatRates map (cleaner JSON); a nil map still reads // as zero, so only the totals need to be zero here. if sum.IncVat != 0 || sum.TotalVat() != 0 { t.Fatalf("expected zero price got %#v", sum) } } func TestMultiplyPriceFunction(t *testing.T) { base := Price{IncVat: 1250, VatRates: map[int]money.Cents{2500: 250}} multiplied := MultiplyPrice(base, 3) if multiplied.IncVat != 1250*3 { t.Fatalf("expected IncVat %d got %d", 1250*3, multiplied.IncVat) } if multiplied.VatRates[2500] != 250*3 { t.Fatalf("expected VAT 2500bp rate %d got %d", 250*3, multiplied.VatRates[2500]) } if multiplied.ValueExVat() != (1250-250)*3 { t.Fatalf("expected exVat %d got %d", (1250-250)*3, multiplied.ValueExVat()) } } func TestPriceAddSubtract(t *testing.T) { a := Price{IncVat: 1000, VatRates: map[int]money.Cents{2500: 200}} b := Price{IncVat: 500, VatRates: map[int]money.Cents{2500: 100, 1200: 54}} acc := NewPrice() acc.Add(a) acc.Add(b) if acc.IncVat != 1500 { t.Fatalf("expected IncVat 1500 got %d", acc.IncVat) } if acc.VatRates[2500] != 300 || acc.VatRates[1200] != 54 { t.Fatalf("unexpected VAT map: %#v", acc.VatRates) } // Subtract b then a returns to zero acc.Subtract(b) acc.Subtract(a) if acc.IncVat != 0 { t.Fatalf("expected IncVat 0 got %d", acc.IncVat) } if len(acc.VatRates) != 2 || acc.VatRates[2500] != 0 || acc.VatRates[1200] != 0 { t.Fatalf("expected zeroed vat rates got %#v", acc.VatRates) } } func TestPriceMultiplyMethod(t *testing.T) { p := Price{IncVat: 2000, VatRates: map[int]money.Cents{2500: 400}} // Value before multiply exBefore := p.ValueExVat() p.Multiply(2) if p.IncVat != 4000 { t.Fatalf("expected IncVat 4000 got %d", p.IncVat) } if p.VatRates[2500] != 800 { t.Fatalf("expected VAT 800 got %d", p.VatRates[2500]) } if p.ValueExVat() != exBefore*2 { t.Fatalf("expected exVat %d got %d", exBefore*2, p.ValueExVat()) } } func TestComputeTax(t *testing.T) { tests := []struct { total int64 rateBp int expected int64 desc string }{ // ex-VAT-first integer math: tax = total - total*10000/(10000+rateBp). {1250, 2500, 250, "25% VAT"}, // exVat 1000, tax 250 {1000, 2000, 167, "20% VAT"}, // exVat 1000*10000/12000=833, tax 167 (Klarna convention) {1200, 2500, 240, "25% VAT on 1200"}, {0, 2500, 0, "zero total"}, {100, 1000, 10, "10% VAT"}, // exVat 100*10000/11000=90, tax 10 {100, 10000, 50, "100% VAT"}, // exVat 50, tax 50 } for _, tt := range tests { result := ComputeTax(money.Cents(tt.total), tt.rateBp).Int64() if result != tt.expected { t.Errorf("ComputeTax(%d, %d) [%s] = %d; expected %d", tt.total, tt.rateBp, tt.desc, result, tt.expected) } } } func TestNewPriceFromIncVatEdgeCases(t *testing.T) { // Zero VAT rate p := NewPriceFromIncVat(1000, 0) if p.IncVat != 1000 { t.Errorf("expected IncVat 1000, got %d", p.IncVat) } if len(p.VatRates) != 1 || p.VatRates[0] != 0 { t.Errorf("expected VAT 0 for rate 0, got %v", p.VatRates) } if p.ValueExVat() != 1000 { t.Errorf("expected exVat 1000, got %d", p.ValueExVat()) } // High VAT rate, e.g., 50% = 5000 basis points p = NewPriceFromIncVat(1500, 5000) expectedVat := money.Cents(500) // exVat 1500*10000/15000=1000, tax 500 if p.VatRates[5000] != expectedVat { t.Errorf("expected VAT %d for 50%%, got %d", expectedVat, p.VatRates[5000]) } if p.ValueExVat() != 1500-expectedVat { t.Errorf("expected exVat %d, got %d", 1500-expectedVat, p.ValueExVat()) } } func TestPriceValueExVatAndTotalVat(t *testing.T) { p := Price{IncVat: 13700, VatRates: map[int]money.Cents{2500: 2500, 1200: 1200}} exVat := p.ValueExVat() totalVat := p.TotalVat() if exVat != 10000 { t.Errorf("expected exVat 10000, got %d", exVat) } if totalVat != 3700 { t.Errorf("expected totalVat 3700, got %d", totalVat) } if exVat+totalVat != p.IncVat { t.Errorf("exVat + totalVat should equal IncVat: %d + %d != %d", exVat, totalVat, p.IncVat) } // Empty VAT rates p2 := Price{IncVat: 500, VatRates: nil} if p2.ValueExVat() != 500 { t.Errorf("expected exVat 500 for no VAT, got %d", p2.ValueExVat()) } if p2.TotalVat() != 0 { t.Errorf("expected totalVat 0, got %d", p2.TotalVat()) } } func TestMultiplyPriceWithZeroQty(t *testing.T) { base := Price{IncVat: 1250, VatRates: map[int]money.Cents{2500: 250}} multiplied := MultiplyPrice(base, 0) if multiplied.IncVat != 0 { t.Errorf("expected IncVat 0, got %d", multiplied.IncVat) } if len(multiplied.VatRates) != 1 || multiplied.VatRates[2500] != 0 { t.Errorf("expected VAT 0, got %v", multiplied.VatRates) } } func TestPriceAddSubtractEdgeCases(t *testing.T) { a := Price{IncVat: 1000, VatRates: map[int]money.Cents{2500: 200}} b := Price{IncVat: 500, VatRates: map[int]money.Cents{1200: 54}} // Different rate acc := NewPrice() acc.Add(a) acc.Add(b) if acc.VatRates[2500] != 200 || acc.VatRates[1200] != 54 { t.Errorf("expected VAT 2500:200, 1200:54, got %v", acc.VatRates) } // Subtract more than added (negative VAT) acc.Subtract(a) acc.Subtract(b) acc.Subtract(a) // Subtract extra a if acc.VatRates[2500] != -200 || acc.VatRates[1200] != 0 { t.Errorf("expected negative VAT for 2500 after over-subtract, got %v", acc.VatRates) } }