internal/domain/item_prices_test.go
Rama main · réplica de lectura del repositorio original.
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])
}
}