internal product index
Build and Publish / BuildAndDeployArm64 (push) Failing after 6s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 10s

This commit is contained in:
2026-06-28 18:40:58 +02:00
parent aa8b2bdedc
commit 63b0112cc7
4 changed files with 135 additions and 9 deletions
+76
View File
@@ -1,8 +1,11 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"git.k6n.net/mats/go-cart-actor/pkg/cart"
messages "git.k6n.net/mats/go-cart-actor/proto/cart"
"git.k6n.net/mats/platform/catalog"
"git.k6n.net/mats/platform/money"
@@ -125,6 +128,54 @@ func TestApplyProjectionOverlay_NilMsgSafe(t *testing.T) {
ApplyProjectionOverlay(nil, catalog.Projection{SKU: "X"})
}
// TestAddSkuToCartHandler_TombstoneReturns404 verifies the bus-deleted
// rejection path: HTTP 404 with the product-fetcher-shaped error string, no
// HTTP fetch attempted. Locks down the regression where the handler returned
// an error and the framework rendered it as 500.
//
// Why a real mux: `r.PathValue("sku")` only resolves when the request is
// dispatched through a mux that registered the path pattern (e.g.
// `GET /cart/add/{sku}` in pool-server.go's Serve()). Calling the handler
// directly with httptest.NewRequest produces an empty PathValue, which would
// bypass the tombstone branch and fall through to the HTTP fetch — failing
// the test for a wiring reason rather than the contract under test.
func TestAddSkuToCartHandler_TombstoneReturns404(t *testing.T) {
idx := newCatalogProjectionCache()
idx.Apply([]catalog.ProjectionUpdate{
{Projection: catalog.Projection{SKU: "DEL", PriceIncVat: money.Cents(10_00)}},
{Projection: catalog.Projection{SKU: "DEL"}, Deleted: true},
})
// Use a stub PoolServer with nil grain pool: the tombstone path returns
// BEFORE ApplyLocal, so the embedded actor.GrainPool is never touched. If a
// future edit adds an early s.IsHealthy() / pool-touch on this handler, the
// test will panic with a nil-pointer deref and the wiring fault will be
// obvious.
s := &PoolServer{pod_name: "test", idx: idx}
mux := http.NewServeMux()
mux.HandleFunc("GET /cart/add/{sku}", func(w http.ResponseWriter, r *http.Request) {
_ = s.AddSkuToCartHandler(w, r, cart.CartId(1))
})
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/cart/add/DEL", nil))
if rec.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d (bus-deleted SKU). body=%s",
rec.Code, http.StatusNotFound, rec.Body.String())
}
// http.Error-style exact shape: trailing newline, text/plain. Pinning the
// string catches regressions either way (body too loose AND format flips).
wantCT := "text/plain; charset=utf-8"
if got := rec.Header().Get("Content-Type"); got != wantCT {
t.Errorf("Content-Type = %q, want %q", got, wantCT)
}
wantBody := "product service returned 404 for sku DEL (bus-deleted)\n"
if got := rec.Body.String(); got != wantBody {
t.Errorf("body mismatch:\n got %q\n want %q", got, wantBody)
}
}
// TestIsDeletedVsGet asserts the two methods don't conflict: a tombstoned SKU
// is reported by both IsDeleted (true) and Get (miss).
func TestIsDeletedVsGet(t *testing.T) {
@@ -146,3 +197,28 @@ func TestIsDeletedVsGet(t *testing.T) {
t.Fatalf("Get on absent entry: ok=true")
}
}
// TestApplyProjectionOverlay_ItemIdPositiveOnly locks the contract that a
// positive bus ItemID overrides HTTP, but ItemID=0 must NOT clobber a real
// HTTP id (e.g. a producer that omits an integer id, or a tombstone's zero
// residual). This is the wire that future-unblocks the cache-only-skip-HTTP
// path; regression here would silently drop cart-line dedup info.
func TestApplyProjectionOverlay_ItemIdPositiveOnly(t *testing.T) {
base := func() *messages.AddItem {
return &messages.AddItem{Sku: "X", ItemId: 12345}
}
// Positive cache id wins.
m := base()
ApplyProjectionOverlay(m, catalog.Projection{SKU: "X", ItemID: 67890})
if m.ItemId != 67890 {
t.Errorf("positive cache ItemID: got %d, want 67890", m.ItemId)
}
// Zero cache id must NOT overwrite HTTP.
m = base()
ApplyProjectionOverlay(m, catalog.Projection{SKU: "X", ItemID: 0})
if m.ItemId != 12345 {
t.Errorf("zero cache ItemID wiped HTTP id: got %d, want 12345", m.ItemId)
}
}