Files
go-cart-actor/pkg/cart/mutation_change_quantity_test.go
mats 9f1eca07e9
Build and Publish / BuildAndDeployAmd64 (push) Failing after 5s
Build and Publish / BuildAndDeployArm64 (push) Failing after 7s
parent item mutations
2026-07-08 11:39:42 +02:00

171 lines
6.5 KiB
Go

package cart
import (
"context"
"testing"
"time"
cart_messages "git.k6n.net/mats/go-cart-actor/proto/cart"
)
// Parent qty 1 → 2 resizes every direct child proportionally. Children all
// start at qty 1 so the proportional factor of 2 doubles them.
func TestChangeQuantity_ParentCascadesDoublingChildren(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 1, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 1, Price: 100, ParentId: &parentLine})
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 102, Sku: "C2", Quantity: 1, Price: 200, ParentId: &parentLine})
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: parentLine, Quantity: 2}); err != nil {
t.Fatalf("change qty: %v", err)
}
if g.Items[0].Quantity != 2 {
t.Errorf("parent qty = %d, want 2", g.Items[0].Quantity)
}
for _, it := range g.Items {
if it.Id == parentLine {
continue
}
if it.Quantity != 2 {
t.Errorf("child %s qty = %d, want 2 (cascaded from parent)", it.Sku, it.Quantity)
}
}
}
// Parent qty 2 → 3 with children at qty 2 → 3 (factor 3/2 = 1.5, integer math).
func TestChangeQuantity_ParentCascadesNonIntegerRatio(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 2, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 2, Price: 100, ParentId: &parentLine})
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: parentLine, Quantity: 3}); err != nil {
t.Fatalf("change qty: %v", err)
}
// parent: 2 -> 3 (direct set), child: 2 * 3 / 2 = 3
if g.Items[0].Quantity != 3 {
t.Errorf("parent qty = %d, want 3", g.Items[0].Quantity)
}
if g.Items[1].Quantity != 3 {
t.Errorf("child qty = %d, want 3 (proportional scale)", g.Items[1].Quantity)
}
}
// Scale-down that would round to 0 (parent 2 → 1, child qty 1 → (1*1)/2 = 0)
// must clamp to 1 so the child is never silently dropped by integer math.
func TestChangeQuantity_ParentScaleDownFloorsAtOne(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 2, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 1, Price: 100, ParentId: &parentLine})
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: parentLine, Quantity: 1}); err != nil {
t.Fatalf("change qty: %v", err)
}
if g.Items[0].Quantity != 1 {
t.Errorf("parent qty = %d, want 1", g.Items[0].Quantity)
}
if g.Items[1].Quantity != 1 {
t.Errorf("child qty = %d, want 1 (floor; integer math dropped to 0 without clamp)", g.Items[1].Quantity)
}
}
// Setting parent qty to 0 delegates to RemoveItem so the existing parent→
// child cascade takes over. Unrelated lines are left untouched.
func TestChangeQuantity_ZeroCascadesRemoval(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 1, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 1, Price: 100, ParentId: &parentLine})
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 200, Sku: "OTHER", Quantity: 1, Price: 500})
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: parentLine, Quantity: 0}); err != nil {
t.Fatalf("change qty 0: %v", err)
}
if len(g.Items) != 1 {
t.Fatalf("items = %d, want 1 (only OTHER survives)", len(g.Items))
}
if g.Items[0].Sku != "OTHER" {
t.Errorf("remaining sku = %q, want OTHER", g.Items[0].Sku)
}
}
// Changing a child's qty directly updates only that child — siblings and
// the parent are untouched. (The UI no longer exposes this control, but
// the HTTP surface still allows it for tools & direct API calls.)
func TestChangeQuantity_ChildDoesNotCascade(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 1, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 1, Price: 100, ParentId: &parentLine})
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 102, Sku: "C2", Quantity: 1, Price: 200, ParentId: &parentLine})
targetChild := g.Items[1].Id
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: targetChild, Quantity: 3}); err != nil {
t.Fatalf("change child qty: %v", err)
}
for _, it := range g.Items {
switch it.Id {
case parentLine:
if it.Quantity != 1 {
t.Errorf("parent qty = %d, want 1 (untouched on child edit)", it.Quantity)
}
case targetChild:
if it.Quantity != 3 {
t.Errorf("target child qty = %d, want 3", it.Quantity)
}
default:
// Sibling
if it.Quantity != 1 {
t.Errorf("sibling %s qty = %d, want 1 (untouched on child edit)", it.Sku, it.Quantity)
}
}
}
}
// Cascade descends into grandchildren so deeply nested accessory trees
// remain internally consistent with the top-level parent change.
func TestChangeQuantity_NestedChildren(t *testing.T) {
reg := NewCartMultationRegistry(NewCartMutationContext(nil))
g := NewCartGrain(1, time.Now())
ctx := context.Background()
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 100, Sku: "P", Quantity: 1, Price: 1000})
parentLine := g.Items[0].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 101, Sku: "C1", Quantity: 1, Price: 100, ParentId: &parentLine})
childLine := g.Items[1].Id
mustApply(t, reg, g, &cart_messages.AddItem{ItemId: 102, Sku: "G1", Quantity: 1, Price: 50, ParentId: &childLine})
if _, err := reg.Apply(ctx, g, &cart_messages.ChangeQuantity{Id: parentLine, Quantity: 2}); err != nil {
t.Fatalf("change qty: %v", err)
}
// parent 1 -> 2; child 1 -> 2 (parent -> child cascade); grandchild 1 -> 2 (child -> grandchild cascade)
for _, it := range g.Items {
if it.Quantity != 2 {
t.Errorf("line %s qty = %d, want 2 (nested cascade)", it.Sku, it.Quantity)
}
}
}