180 lines
4.6 KiB
Go
180 lines
4.6 KiB
Go
package voucher
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseRules_SimpleSku(t *testing.T) {
|
|
rs, err := ParseRules("sku=ABC123|XYZ999|def456")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(rs.Conditions) != 1 {
|
|
t.Fatalf("expected 1 condition got %d", len(rs.Conditions))
|
|
}
|
|
c := rs.Conditions[0]
|
|
if c.Kind != RuleSku {
|
|
t.Fatalf("expected kind sku got %s", c.Kind)
|
|
}
|
|
if len(c.StringVals) != 3 {
|
|
t.Fatalf("expected 3 sku values got %d", len(c.StringVals))
|
|
}
|
|
want := []string{"ABC123", "XYZ999", "def456"}
|
|
for i, v := range want {
|
|
if c.StringVals[i] != v {
|
|
t.Fatalf("expected sku[%d]=%s got %s", i, v, c.StringVals[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseRules_CategoryAndSkuMixedSeparators(t *testing.T) {
|
|
rs, err := ParseRules(" category=Shoes|Bags ; sku= A | B , min_total>=1000\nmin_item_price>=500")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(rs.Conditions) != 4 {
|
|
t.Fatalf("expected 4 conditions got %d", len(rs.Conditions))
|
|
}
|
|
|
|
kinds := []RuleKind{RuleCategory, RuleSku, RuleMinTotal, RuleMinItemPrice}
|
|
for i, k := range kinds {
|
|
if rs.Conditions[i].Kind != k {
|
|
t.Fatalf("expected condition[%d] kind %s got %s", i, k, rs.Conditions[i].Kind)
|
|
}
|
|
}
|
|
|
|
// Validate numeric thresholds
|
|
if rs.Conditions[2].MinValue == nil || *rs.Conditions[2].MinValue != 1000 {
|
|
t.Fatalf("expected min_total>=1000 got %+v", rs.Conditions[2])
|
|
}
|
|
if rs.Conditions[3].MinValue == nil || *rs.Conditions[3].MinValue != 500 {
|
|
t.Fatalf("expected min_item_price>=500 got %+v", rs.Conditions[3])
|
|
}
|
|
}
|
|
|
|
func TestParseRules_Empty(t *testing.T) {
|
|
_, err := ParseRules(" \n ")
|
|
if !errors.Is(err, ErrEmptyExpression) {
|
|
t.Fatalf("expected ErrEmptyExpression got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseRules_Invalid(t *testing.T) {
|
|
_, err := ParseRules("unknown=foo")
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown key")
|
|
}
|
|
_, err = ParseRules("min_total>100") // wrong operator
|
|
if err == nil {
|
|
t.Fatal("expected error for wrong operator")
|
|
}
|
|
_, err = ParseRules("min_total>=") // missing value
|
|
if err == nil {
|
|
t.Fatal("expected error for missing numeric value")
|
|
}
|
|
}
|
|
|
|
func TestRuleSet_Applies(t *testing.T) {
|
|
rs := MustParseRules("sku=ABC123|XYZ999; category=Shoes|min_total>=10000; min_item_price>=3000")
|
|
|
|
ctx := EvalContext{
|
|
Items: []Item{
|
|
{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
|
|
{Sku: "FFF000", Category: "Accessories", UnitPrice: 3200},
|
|
},
|
|
CartTotalInc: 12000,
|
|
}
|
|
|
|
if !rs.Applies(ctx) {
|
|
t.Fatalf("expected rules to apply")
|
|
}
|
|
|
|
// Fail due to missing sku/category
|
|
ctx2 := EvalContext{
|
|
Items: []Item{
|
|
{Sku: "NOPE", Category: "Different", UnitPrice: 4000},
|
|
},
|
|
CartTotalInc: 20000,
|
|
}
|
|
if rs.Applies(ctx2) {
|
|
t.Fatalf("expected rules NOT to apply (sku/category mismatch)")
|
|
}
|
|
|
|
// Fail due to min_total
|
|
ctx3 := EvalContext{
|
|
Items: []Item{
|
|
{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
|
|
{Sku: "FFF000", Category: "Accessories", UnitPrice: 3200},
|
|
},
|
|
CartTotalInc: 9000,
|
|
}
|
|
if rs.Applies(ctx3) {
|
|
t.Fatalf("expected rules NOT to apply (min_total not reached)")
|
|
}
|
|
|
|
// Fail due to min_item_price (no item >=3000)
|
|
ctx4 := EvalContext{
|
|
Items: []Item{
|
|
{Sku: "ABC123", Category: "Shoes", UnitPrice: 2500},
|
|
{Sku: "FFF000", Category: "Accessories", UnitPrice: 2800},
|
|
},
|
|
CartTotalInc: 15000,
|
|
}
|
|
if rs.Applies(ctx4) {
|
|
t.Fatalf("expected rules NOT to apply (min_item_price not satisfied)")
|
|
}
|
|
}
|
|
|
|
func TestRuleSet_Applies_CaseInsensitive(t *testing.T) {
|
|
rs := MustParseRules("SKU=abc123|xyz999; CATEGORY=Shoes")
|
|
ctx := EvalContext{
|
|
Items: []Item{
|
|
{Sku: "AbC123", Category: "shoes", UnitPrice: 1000},
|
|
},
|
|
CartTotalInc: 1000,
|
|
}
|
|
if !rs.Applies(ctx) {
|
|
t.Fatalf("expected rules to apply (case-insensitive match)")
|
|
}
|
|
}
|
|
|
|
func TestDescribe(t *testing.T) {
|
|
rs := MustParseRules("sku=A|B|min_total>=500")
|
|
desc := rs.Describe()
|
|
// Loose assertions to avoid over-specification
|
|
if desc == "" {
|
|
t.Fatalf("expected non-empty description")
|
|
}
|
|
if !(contains(desc, "sku") && contains(desc, "min_total")) {
|
|
t.Fatalf("description missing expected parts: %s", desc)
|
|
}
|
|
}
|
|
|
|
func contains(haystack, needle string) bool {
|
|
return len(haystack) >= len(needle) && indexOf(haystack, needle) >= 0
|
|
}
|
|
|
|
// Simple substring search (avoid importing strings to show intent explicitly here)
|
|
func indexOf(s, sub string) int {
|
|
outer:
|
|
for i := 0; i+len(sub) <= len(s); i++ {
|
|
for j := 0; j < len(sub); j++ {
|
|
if s[i+j] != sub[j] {
|
|
continue outer
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func TestMustParseRules_Panics(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Fatalf("expected panic for invalid expression")
|
|
}
|
|
}()
|
|
MustParseRules("~~ totally invalid ~~")
|
|
}
|