Files
go-cart-actor/cmd/cart/projection_store_integration_test.go
T
matsandClaude Opus 4.8 d711348863
Build and Publish / BuildAndDeployArm64 (push) Failing after 5s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 3s
cart: back projection cache with platform/catalog.ProjectionStore
Consumer side of the catalog-projection seam (C1):
- catalogProjectionCache now wraps ProjectionStore[catalog.Projection] (identity
  transform — the cart's overlay uses ~all fields); delivery handler routes bus
  frames via HandleFrame (snapshot begin|chunk|end + epoch-stamped deltas).
- Per-pod cold-start-ready .bin under CART_PROJECTION_DIR (pod-local, never
  shared) + IsDeleted tombstone passthrough; HTTP fetch demoted to last-resort.
- Tests: test-only Apply shim + mustCache helper keep existing cases green;
  new snapshot-framing integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:05:55 +02:00

73 lines
2.4 KiB
Go

package main
import (
"strconv"
"testing"
"git.k6n.net/mats/platform/catalog"
"git.k6n.net/mats/platform/money"
)
// frameMeta builds the event.Meta a producer stamps for a snapshot frame / delta.
func frameMeta(phase string, epoch int64) map[string]string {
m := map[string]string{catalog.MetaEpoch: strconv.FormatInt(epoch, 10), catalog.MetaTenant: "se"}
if phase != "" {
m[catalog.MetaSnapshotPhase] = phase
}
return m
}
func enc(t *testing.T, ups []catalog.ProjectionUpdate) []byte {
t.Helper()
b, err := catalog.EncodeUpdates(ups)
if err != nil {
t.Fatalf("encode: %v", err)
}
return b
}
// TestCartCache_SnapshotThenDelta exercises the cart's cache through the real bus
// framing: a full begin→chunk→end snapshot makes SKUs Get-able cold (no Apply),
// then an epoch-matched delta overrides one and tombstones another.
func TestCartCache_SnapshotThenDelta(t *testing.T) {
c := mustCache(t)
const epoch = int64(1000)
if err := c.HandleFrame(frameMeta(catalog.SnapshotBegin, epoch), enc(t, nil)); err != nil {
t.Fatal(err)
}
if err := c.HandleFrame(frameMeta(catalog.SnapshotChunk, epoch), enc(t, []catalog.ProjectionUpdate{
{Projection: catalog.Projection{ID: "sku:A", SKU: "A", PriceIncVat: money.Cents(100_00), TaxClass: "standard", ItemID: 1}},
{Projection: catalog.Projection{ID: "sku:B", SKU: "B", PriceIncVat: money.Cents(50_00), TaxClass: "reduced", ItemID: 2}},
})); err != nil {
t.Fatal(err)
}
// end carries the total count for the completeness guard.
endMeta := frameMeta(catalog.SnapshotEnd, epoch)
endMeta[catalog.MetaCount] = "2"
if err := c.HandleFrame(endMeta, enc(t, nil)); err != nil {
t.Fatal(err)
}
if got, ok := c.Get("A"); !ok || got.PriceIncVat != 100_00 {
t.Fatalf("A from snapshot = %+v ok=%v, want 10000", got, ok)
}
// Epoch-matched delta: bump A's price, delete B.
if err := c.HandleFrame(frameMeta("", epoch), enc(t, []catalog.ProjectionUpdate{
{Projection: catalog.Projection{SKU: "A", PriceIncVat: money.Cents(90_00), TaxClass: "standard", ItemID: 1}},
{Projection: catalog.Projection{SKU: "B"}, Deleted: true},
})); err != nil {
t.Fatal(err)
}
if got, _ := c.Get("A"); got.PriceIncVat != 90_00 {
t.Fatalf("A after delta = %d, want 9000", got.PriceIncVat)
}
if _, ok := c.Get("B"); ok {
t.Fatalf("B after delete should miss")
}
if !c.IsDeleted("B") {
t.Fatalf("B should be tombstoned (skip HTTP fetch)")
}
}