package payments import ( "testing" "github.com/google/uuid" ) // catalog fixtures: a chip-priced value (hints), a multi-method chip pack, a VK-only chip pack, a // value with no CHIP price (misconfigured), and a pack whose only method is telegram. func fixtureCatalog() []catalogEntry { value := catalogEntry{ id: uuid.MustParse("00000000-0000-0000-0000-000000000001"), title: "250 hints", atoms: []atomQty{{atomType: "hints", quantity: 250}}, prices: []priceRow{ {method: "", currency: CurrencyChip, amount: 500}, }, } pack := catalogEntry{ id: uuid.MustParse("00000000-0000-0000-0000-000000000002"), title: "100 chips", atoms: []atomQty{{atomType: "chips", quantity: 100}}, prices: []priceRow{ {method: "vk", currency: CurrencyVote, amount: 20}, {method: "telegram", currency: CurrencyStar, amount: 25}, {method: "direct", currency: CurrencyRUB, amount: 14900}, }, } vkOnlyPack := catalogEntry{ id: uuid.MustParse("00000000-0000-0000-0000-000000000003"), title: "VK-only chips", atoms: []atomQty{{atomType: "chips", quantity: 50}}, prices: []priceRow{ {method: "vk", currency: CurrencyVote, amount: 10}, }, } brokenValue := catalogEntry{ id: uuid.MustParse("00000000-0000-0000-0000-000000000004"), title: "no chip price", atoms: []atomQty{{atomType: "noads_days", quantity: 30}}, // no CHIP price row — misconfigured, must be omitted } return []catalogEntry{value, pack, vkOnlyPack, brokenValue} } // byID indexes a projected storefront by product id for assertions. func byID(v CatalogView) map[string]CatalogProduct { m := make(map[string]CatalogProduct, len(v.Products)) for _, p := range v.Products { m[p.ProductID] = p } return m } func TestProjectCatalog_ContextMatrix(t *testing.T) { const ( valueID = "00000000-0000-0000-0000-000000000001" packID = "00000000-0000-0000-0000-000000000002" vkPackID = "00000000-0000-0000-0000-000000000003" brokenID = "00000000-0000-0000-0000-000000000004" ) entries := fixtureCatalog() tests := []struct { name string cxt Context wantPackAmt int64 wantPackCur Currency wantVKPack bool // the vk-only pack visible? }{ {"vk", Context{Kind: SourceVK}, 20, CurrencyVote, true}, {"vk-ios frozen still lists vk price", Context{Kind: SourceVK, Subtype: SubtypeIOS}, 20, CurrencyVote, true}, {"telegram", Context{Kind: SourceTelegram}, 25, CurrencyStar, false}, {"direct", Context{Kind: SourceDirect}, 14900, CurrencyRUB, false}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := byID(projectCatalog(entries, tc.cxt)) // The value shows everywhere at its uniform chip price, never a money price. v, ok := got[valueID] if !ok { t.Fatalf("value missing from %s storefront", tc.name) } if v.Kind != "value" || v.Chips != 500 || v.MoneyCurrency != "" || v.MoneyAmount != 0 { t.Errorf("value = %+v, want kind=value chips=500 no money", v) } if len(v.Atoms) != 1 || v.Atoms[0].AtomType != "hints" || v.Atoms[0].Quantity != 250 { t.Errorf("value atoms = %+v, want [hints:250]", v.Atoms) } // The multi-method pack shows the context method's price, in that currency, no chips. p, ok := got[packID] if !ok { t.Fatalf("pack missing from %s storefront", tc.name) } if p.Kind != "pack" || p.Chips != 0 || p.MoneyAmount != tc.wantPackAmt || p.MoneyCurrency != string(tc.wantPackCur) { t.Errorf("pack = %+v, want kind=pack amount=%d currency=%s", p, tc.wantPackAmt, tc.wantPackCur) } // The vk-only pack shows only where a vk price exists. if _, ok := got[vkPackID]; ok != tc.wantVKPack { t.Errorf("vk-only pack present=%v, want %v (%s)", ok, tc.wantVKPack, tc.name) } // The misconfigured value (no CHIP price) is never shown. if _, ok := got[brokenID]; ok { t.Errorf("misconfigured value must be omitted (%s)", tc.name) } }) } } // An untrusted context (empty Kind) has no payment method, so it shows values only — no packs. func TestProjectCatalog_UntrustedShowsValuesOnly(t *testing.T) { got := byID(projectCatalog(fixtureCatalog(), Context{})) if _, ok := got["00000000-0000-0000-0000-000000000001"]; !ok { t.Error("untrusted context should still list the chip-priced value") } for _, id := range []string{ "00000000-0000-0000-0000-000000000002", "00000000-0000-0000-0000-000000000003", } { if _, ok := got[id]; ok { t.Errorf("untrusted context must not list pack %s", id) } } } // An empty catalog projects to an empty storefront (no products seeded yet — the Release-1 state). func TestProjectCatalog_Empty(t *testing.T) { if got := projectCatalog(nil, Context{Kind: SourceDirect}); len(got.Products) != 0 { t.Errorf("empty catalog projected %d products, want 0", len(got.Products)) } }