//go:build integration package inttest import ( "context" "testing" "github.com/google/uuid" "scrabble/backend/internal/payments" ) // methodPrice is one per-method money price for a seeded chip pack. type methodPrice struct { method string currency string amount int64 } // seedPackProduct creates an active chip pack: a product carrying the chips atom and a money price // per method. It returns the product id. func seedPackProduct(t *testing.T, chips int, prices ...methodPrice) uuid.UUID { t.Helper() ctx := context.Background() prod := uuid.New() if _, err := testDB.ExecContext(ctx, `INSERT INTO payments.product (product_id, title, active) VALUES ($1,'test pack',true)`, prod); err != nil { t.Fatalf("seed pack product: %v", err) } if _, err := testDB.ExecContext(ctx, `INSERT INTO payments.product_item (product_id, atom_type, quantity) VALUES ($1,'chips',$2)`, prod, chips); err != nil { t.Fatalf("seed chips item: %v", err) } for _, p := range prices { if _, err := testDB.ExecContext(ctx, `INSERT INTO payments.product_price (product_id, method, currency, amount) VALUES ($1,$2,$3,$4)`, prod, p.method, p.currency, p.amount); err != nil { t.Fatalf("seed pack price %s: %v", p.method, err) } } return prod } // findCatalogProduct returns the projected storefront product for id in the context (the catalog // is global, so tests assert by id rather than count — testDB is shared). func findCatalogProduct(t *testing.T, svc *payments.Service, cxt payments.Context, id uuid.UUID) (payments.CatalogProduct, bool) { t.Helper() view, err := svc.Catalog(context.Background(), cxt) if err != nil { t.Fatalf("catalog: %v", err) } for _, p := range view.Products { if p.ProductID == id.String() { return p, true } } return payments.CatalogProduct{}, false } // TestPaymentsCatalogByContext verifies the storefront projects a value at its chip price in every // context and a chip pack at the context method's money price, over Postgres. func TestPaymentsCatalogByContext(t *testing.T) { svc := newPaymentsService() value := seedValueProduct(t, 500, 250, 0) pack := seedPackProduct(t, 100, methodPrice{"vk", "VOTE", 20}, methodPrice{"telegram", "XTR", 25}, methodPrice{"direct", "RUB", 14900}, ) for _, kind := range []string{"vk", "telegram", "direct"} { v, ok := findCatalogProduct(t, svc, payments.NewContext(kind, "web"), value) if !ok { t.Fatalf("value missing in %s context", kind) } if v.Kind != "value" || v.Chips != 500 || v.MoneyCurrency != "" { t.Errorf("value in %s = %+v, want kind=value chips=500 no money", kind, v) } } cases := []struct { kind string amount int64 currency string }{ {"vk", 20, "VOTE"}, {"telegram", 25, "XTR"}, {"direct", 14900, "RUB"}, } for _, tc := range cases { p, ok := findCatalogProduct(t, svc, payments.NewContext(tc.kind, "web"), pack) if !ok { t.Fatalf("pack missing in %s context", tc.kind) } if p.Kind != "pack" || p.Chips != 0 || p.MoneyAmount != tc.amount || p.MoneyCurrency != tc.currency { t.Errorf("pack in %s = %+v, want kind=pack amount=%d currency=%s", tc.kind, p, tc.amount, tc.currency) } } } // TestPaymentsCatalogExcludesDeactivated verifies a soft-deleted product drops out of the storefront. func TestPaymentsCatalogExcludesDeactivated(t *testing.T) { svc := newPaymentsService() prod := seedValueProduct(t, 100, 10, 0) if _, err := testDB.ExecContext(context.Background(), `UPDATE payments.product SET active=false WHERE product_id=$1`, prod); err != nil { t.Fatalf("deactivate: %v", err) } if _, ok := findCatalogProduct(t, svc, payments.NewContext("direct", "web"), prod); ok { t.Error("deactivated product must not appear in the storefront") } }