package payments
import (
"strings"
"testing"
"github.com/google/uuid"
)
// TestProjectOfferPricing checks the happy path: a chip pack priced on every rail and a chip-priced
// value render into the two tables, pack table first, money formatted through Money.
func TestProjectOfferPricing(t *testing.T) {
entries := []catalogEntry{
{
id: uuid.New(),
title: "50 «Фишек»",
atoms: []atomQty{{atomType: atomChips, quantity: 50}},
prices: []priceRow{
{method: string(SourceDirect), currency: CurrencyRUB, amount: 20000},
{method: string(SourceVK), currency: CurrencyVote, amount: 30},
{method: string(SourceTelegram), currency: CurrencyStar, amount: 100},
},
},
{
id: uuid.New(),
title: "200 подсказок",
atoms: []atomQty{{atomType: "hints", quantity: 200}},
prices: []priceRow{{method: "", currency: CurrencyChip, amount: 50}},
},
}
md := projectOfferPricing(entries)
for _, want := range []string{
"| Наименование | Рубли | Голоса в VK | Stars в Telegram |",
"| 50 «Фишек» | 200.00 | 30 | 100 |",
"| Наименование | «Фишки» |",
"| 200 подсказок | 50 |",
} {
if !strings.Contains(md, want) {
t.Errorf("projection missing %q\n---\n%s", want, md)
}
}
if strings.Index(md, "Приобретение") > strings.Index(md, "Использование") {
t.Errorf("the pack table must precede the values table:\n%s", md)
}
}
// TestProjectOfferPricingMissingRailAndEscaping checks a pack missing a rail shows an em dash and a
// title carrying a pipe is escaped so the table layout survives.
func TestProjectOfferPricingMissingRailAndEscaping(t *testing.T) {
entries := []catalogEntry{{
id: uuid.New(),
title: "Bonus | pack",
atoms: []atomQty{{atomType: atomChips, quantity: 10}},
// A roubles price only — no VK, no Telegram.
prices: []priceRow{{method: string(SourceDirect), currency: CurrencyRUB, amount: 9900}},
}}
md := projectOfferPricing(entries)
if want := `| Bonus \| pack | 99.00 | — | — |`; !strings.Contains(md, want) {
t.Errorf("want row %q in:\n%s", want, md)
}
}
// TestProjectOfferPricingEscapesHTMLAndLinks checks an admin title carrying HTML or a markdown link
// is neutralised so it cannot inject markup into the public offer: the tag becomes entities and the
// link brackets are escaped (so no "javascript:" anchor forms). The raw metacharacters must not
// survive into the projected markdown.
func TestProjectOfferPricingEscapesHTMLAndLinks(t *testing.T) {
entries := []catalogEntry{{
id: uuid.New(),
title: ` [x](javascript:alert(2)) & "q"`,
atoms: []atomQty{{atomType: "hints", quantity: 1}},
prices: []priceRow{{method: "", currency: CurrencyChip, amount: 5}},
}}
md := projectOfferPricing(entries)
for _, bad := range []string{"", "[x]", `& "q"`} {
if strings.Contains(md, bad) {
t.Errorf("unescaped %q survived into the projection:\n%s", bad, md)
}
}
for _, want := range []string{"<script>", `\[x\]`, "&", ""q""} {
if !strings.Contains(md, want) {
t.Errorf("want escaped %q in:\n%s", want, md)
}
}
}
// TestProjectOfferPricingEmpty checks an empty catalog projects to the empty string (no stray table
// headers), so the offer's pricing marker is replaced with nothing.
func TestProjectOfferPricingEmpty(t *testing.T) {
if got := projectOfferPricing(nil); got != "" {
t.Errorf("empty catalog must project to empty string, got %q", got)
}
}