package ucp import ( "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "git.k6n.net/mats/go-cart-actor/pkg/actor" "git.k6n.net/mats/go-cart-actor/pkg/cart" "git.k6n.net/mats/go-cart-actor/pkg/checkout" checkoutMessages "git.k6n.net/mats/go-cart-actor/proto/checkout" "google.golang.org/protobuf/proto" ) type testCheckoutApplier struct { grains map[uint64]*checkout.CheckoutGrain } func newTestCheckoutApplier() *testCheckoutApplier { return &testCheckoutApplier{ grains: make(map[uint64]*checkout.CheckoutGrain), } } func (a *testCheckoutApplier) Get(_ context.Context, id uint64) (*checkout.CheckoutGrain, error) { g, ok := a.grains[id] if !ok { g = checkout.NewCheckoutGrain(id, 0, 0, time.UnixMilli(1000000), nil) a.grains[id] = g } return g, nil } func (a *testCheckoutApplier) Apply(ctx context.Context, id uint64, msgs ...proto.Message) (*actor.MutationResult[checkout.CheckoutGrain], error) { g, err := a.Get(ctx, id) if err != nil { return nil, err } for _, msg := range msgs { switch m := msg.(type) { case *checkoutMessages.InitializeCheckout: g.CartId = cart.CartId(m.CartId) } } return &actor.MutationResult[checkout.CheckoutGrain]{ Result: *g, }, nil } func TestCheckoutHandler_CreateWithCartId(t *testing.T) { // Start a mock Cart server. mockCartServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.URL.Path, "/cart/byid/") { t.Errorf("expected path to contain /cart/byid/, got %q", r.URL.Path) } // Return dummy cart state bytes. w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) })) defer mockCartServer.Close() os.Setenv("CART_INTERNAL_URL", mockCartServer.URL) defer os.Unsetenv("CART_INTERNAL_URL") applier := newTestCheckoutApplier() handler := CheckoutHandler(applier) // Create a dummy cart ID. dummyCartId, _ := cart.NewCartId() reqBody := fmt.Sprintf(`{"cartId": %q}`, dummyCartId.String()) req := httptest.NewRequest("POST", "/", strings.NewReader(reqBody)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("expected 201 Created, got %d. Body: %s", rec.Code, rec.Body.String()) } var resp CheckoutResponse if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("failed to decode response: %v", err) } if resp.Id == "" { t.Fatal("expected non-empty checkout session id") } if resp.CartId != dummyCartId.String() { t.Fatalf("expected cartId %q, got %q", dummyCartId.String(), resp.CartId) } } func TestCheckoutHandler_CreateWithLineItemsFallback(t *testing.T) { // Start a mock Cart server that supports both creating a cart and getting it. var createdCartId cart.CartId mockCartServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" && strings.Contains(r.URL.Path, "/ucp/v1/carts/") { var err error createdCartId, err = cart.NewCartId() if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) w.Write([]byte(fmt.Sprintf(`{"id": %q, "status": "active"}`, createdCartId.String()))) return } if r.Method == "GET" && strings.Contains(r.URL.Path, "/cart/byid/") { w.WriteHeader(http.StatusOK) w.Write([]byte(`{}`)) return } w.WriteHeader(http.StatusNotFound) })) defer mockCartServer.Close() os.Setenv("CART_INTERNAL_URL", mockCartServer.URL) defer os.Unsetenv("CART_INTERNAL_URL") applier := newTestCheckoutApplier() handler := CheckoutHandler(applier) // POST with line_items and no cartId. reqBody := `{"line_items": [{"sku": "SKU123", "quantity": 2}]}` req := httptest.NewRequest("POST", "/", strings.NewReader(reqBody)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("expected 201 Created, got %d. Body: %s", rec.Code, rec.Body.String()) } var resp CheckoutResponse if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { t.Fatalf("failed to decode response: %v", err) } if resp.Id == "" { t.Fatal("expected non-empty checkout session id") } if resp.CartId != createdCartId.String() { t.Fatalf("expected cartId %q to match created implicit cart %q", resp.CartId, createdCartId.String()) } }