Ctrl K
ComunidadGuías…

internal/domain/item_prices_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package domain

import (
	"os"
	"regexp"
	"strconv"
	"strings"
	"testing"
)

// The shop's product list lives in the bot's catalog.js, not here. The Go side
// keeps a copy so the admin panel can show and edit every product before any
// override row exists -- and a copy is exactly the kind of thing that goes
// stale quietly. It already had: num_short, num_long and uname_10 were priced
// differently from the bot, two of the three username tiers were missing
// entirely (so they could never be priced at all), and three rows were listed
// for products the bot does not sell.
//
// These tests read catalog.js directly so the two cannot drift again.

// botCatalogProduct is one entry of the `fixed` array in catalog.js.
type botCatalogProduct struct {
	code       string
	starsPrice int
	bid        int64
}

var catalogEntry = regexp.MustCompile(`\{ kind: KINDS\.(\w+), code: "([^"]+)"([^\n]*)\}`)

func parseBotCatalog(t *testing.T) []botCatalogProduct {
	t.Helper()
	raw, err := os.ReadFile("../../cmd/bots/grammystore/src/catalog.js")
	if err != nil {
		t.Fatalf("read bot catalog: %v", err)
	}
	src := string(raw)
	fixed := src[strings.Index(src, "const fixed"):]
	fixed = fixed[:strings.Index(fixed, "];")]

	var out []botCatalogProduct
	for _, m := range catalogEntry.FindAllStringSubmatch(fixed, -1) {
		price := regexp.MustCompile(`starsPrice: (\d+)`).FindStringSubmatch(m[3])
		if price == nil {
			t.Fatalf("catalog entry %q has no starsPrice; update this test", m[2])
		}
		starsPrice, err := strconv.Atoi(price[1])
		if err != nil {
			t.Fatalf("catalog entry %q: %v", m[2], err)
		}
		var bid int64
		if b := regexp.MustCompile(`bid: (\d+)`).FindStringSubmatch(m[3]); b != nil {
			// The catalog's bid is a TON amount that fits an int by
			// inspection today; a jump past that is worth failing on
			// rather than silently truncating.
			v, err := strconv.ParseInt(b[1], 10, 64)
			if err != nil {
				t.Fatalf("catalog entry %q bid %q: %v", m[2], b[1], err)
			}
			bid = v
		}
		out = append(out, botCatalogProduct{code: m[2], starsPrice: starsPrice, bid: bid})
	}
	if len(out) == 0 {
		t.Fatal("no products parsed out of catalog.js; the format changed and this test needs updating")
	}
	return out
}

func TestDefaultItemPricesMirrorBotCatalog(t *testing.T) {
	bot := parseBotCatalog(t)

	defaults := make(map[string]ItemPrice, len(DefaultItemPrices))
	for _, p := range DefaultItemPrices {
		defaults[p.ProductCode] = p
	}

	for _, want := range bot {
		got, ok := defaults[want.code]
		if !ok {
			t.Errorf("DefaultItemPrices is missing %q, which the bot sells at %d stars -- it cannot be priced from the admin panel", want.code, want.starsPrice)
			continue
		}
		if got.StarsPrice != want.starsPrice {
			t.Errorf("%q: DefaultItemPrices says %d stars, the bot charges %d", want.code, got.StarsPrice, want.starsPrice)
		}
		if got.Bid != want.bid {
			t.Errorf("%q: DefaultItemPrices says bid %d TON, the bot uses %d", want.code, got.Bid, want.bid)
		}
	}

	for _, p := range DefaultItemPrices {
		found := false
		for _, want := range bot {
			if want.code == p.ProductCode {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("DefaultItemPrices lists %q, which the bot's catalog() does not sell -- it shows up in the admin shop-prices page as a product that cannot exist", p.ProductCode)
		}
	}
}

// TestBotStarPackagesAreNotPriced makes the other half of the mirror explicit:
// the bot's stars_N packages are computed from the rate rather than listed in
// `fixed`, so they must not appear as editable products. They change with
// stars_rate and an operator price on a rate-derived row would be meaningless.
func TestBotStarPackagesAreNotPriced(t *testing.T) {
	raw, err := os.ReadFile("../../cmd/bots/grammystore/src/catalog.js")
	if err != nil {
		t.Fatalf("read bot catalog: %v", err)
	}
	pkg := regexp.MustCompile(`const starPackages = \[([\d, ]+)\]`)
	m := pkg.FindStringSubmatch(string(raw))
	if m == nil {
		t.Skip("catalog.js no longer defines starPackages; re-check whether stars_N rows belong here")
	}
	for _, size := range strings.Split(m[1], ",") {
		size = strings.TrimSpace(size)
		if size == "" {
			continue
		}
		code := "stars_" + size
		for _, p := range DefaultItemPrices {
			if p.ProductCode == code {
				t.Errorf("DefaultItemPrices lists %q, but the bot derives it from stars_rate rather than reading a per-product price", code)
			}
		}
	}
}

// MergeItemPrices has to keep serving an override for a code the defaults do
// not know: a product added on the bot side first is still editable here
// without a backend release. That is the escape hatch that made the stale copy
// above survive so long, so it is worth pinning.
func TestMergeItemPricesAppendsUnknownOverride(t *testing.T) {
	defaults := []ItemPrice{{ProductCode: "premium_1m", StarsPrice: 20, Enabled: true}}
	overrides := []ItemPrice{
		{ProductCode: "premium_1m", StarsPrice: 25, Enabled: true, UpdatedBy: "ops"},
		{ProductCode: "brand_new", StarsPrice: 7, Enabled: true},
	}
	got := MergeItemPrices(defaults, overrides, false)
	if len(got) != 2 {
		t.Fatalf("got %d rows, want 2: %+v", len(got), got)
	}
	if got[0].StarsPrice != 25 || got[0].UpdatedBy != "ops" {
		t.Errorf("known default did not take the override: %+v", got[0])
	}
	if got[1].ProductCode != "brand_new" || got[1].StarsPrice != 7 {
		t.Errorf("unknown override was not appended: %+v", got[1])
	}
}