internal/app/bots/premiumbot_test.go
Rama main · réplica de lectura del repositorio original.
package bots
import (
"context"
"strings"
"testing"
"telesrv/internal/domain"
"telesrv/internal/store/memory"
)
type fakePremiumStorefront struct {
plans []domain.PremiumPlan
active []domain.PremiumEntitlement
history []domain.PremiumEntitlement
balance int64
}
func (f *fakePremiumStorefront) BotUserID() int64 { return domain.PremiumBotConfiguredUserID() }
func (f *fakePremiumStorefront) BotUsername() string { return "premiumbot" }
func (f *fakePremiumStorefront) Plans(context.Context) ([]domain.PremiumPlan, error) {
return append([]domain.PremiumPlan(nil), f.plans...), nil
}
func (f *fakePremiumStorefront) Plan(_ context.Context, months int) (domain.PremiumPlan, error) {
for _, plan := range f.plans {
if plan.Months == months {
return plan, nil
}
}
return domain.PremiumPlan{}, domain.ErrPremiumPlanUnavailable
}
func (f *fakePremiumStorefront) Balance(context.Context, int64) (domain.StarsBalance, error) {
return domain.StarsBalance{Balance: f.balance}, nil
}
func (f *fakePremiumStorefront) ActiveEntitlements(context.Context, int64, int) ([]domain.PremiumEntitlement, error) {
return append([]domain.PremiumEntitlement(nil), f.active...), nil
}
func (f *fakePremiumStorefront) PurchaseHistory(context.Context, int64, int) ([]domain.PremiumEntitlement, error) {
return append([]domain.PremiumEntitlement(nil), f.history...), nil
}
func premiumBotTestPlan() domain.PremiumPlan {
return domain.PremiumPlan{
Months: 3, DurationDays: 90, AmountStars: 750, Enabled: true,
SortOrder: 10, Label: "3 months", Version: 4,
}
}
func TestPremiumBotStartHintsNeverSelectPrice(t *testing.T) {
svc, _, _, _ := newTestService(t)
svc.SetPremium(&fakePremiumStorefront{plans: []domain.PremiumPlan{premiumBotTestPlan()}})
for _, body := range []string{
"/start settings", "/start profile", "/start business",
"/start deeplink_referrer", "/start doubled_limits",
} {
reply := svc.handlePremium(context.Background(), 1001, body)
if reply.Media != nil {
t.Fatalf("%q produced invoice media from a navigation hint", body)
}
if reply.ReplyMarkup == nil || len(reply.ReplyMarkup.Inline) < 3 {
t.Fatalf("%q did not produce the safe main menu: %+v", body, reply)
}
}
}
func TestPremiumBotInvoiceUsesCatalogSnapshotAndBuyButton(t *testing.T) {
svc, _, _, _ := newTestService(t)
plan := premiumBotTestPlan()
svc.SetPremium(&fakePremiumStorefront{plans: []domain.PremiumPlan{plan}, balance: 1250})
reply := svc.handlePremium(context.Background(), 1001, "/start buy_3")
if reply.Media == nil || reply.Media.Kind != domain.MessageMediaKindInvoice ||
reply.Media.Invoice == nil {
t.Fatalf("invoice reply media = %+v", reply.Media)
}
invoice := reply.Media.Invoice
if invoice.Kind != domain.PremiumPurchaseSelf || invoice.Months != plan.Months ||
invoice.DurationDays != plan.DurationDays || invoice.AmountStars != plan.AmountStars ||
invoice.PlanVersion != plan.Version {
t.Fatalf("invoice = %+v, want catalog snapshot %+v", invoice, plan)
}
if !strings.Contains(reply.Text, "Текущий баланс: 1250 Stars") {
t.Fatalf("invoice text does not show Stars balance: %q", reply.Text)
}
if reply.ReplyMarkup == nil || len(reply.ReplyMarkup.Inline) != 1 ||
len(reply.ReplyMarkup.Inline[0]) != 1 ||
reply.ReplyMarkup.Inline[0][0].Type != domain.MarkupButtonBuy {
t.Fatalf("invoice markup = %+v, want one buy button", reply.ReplyMarkup)
}
if err := domain.ValidateReplyMarkup(reply.ReplyMarkup); err != nil {
t.Fatalf("invoice markup validation: %v", err)
}
}
func TestPremiumBotGiftPickerAndCatalogInvoice(t *testing.T) {
svc, users, _, _ := newTestService(t)
buyer := newOwner(t, users, "+15550001")
recipient := newOwner(t, users, "+15550002")
plan := premiumBotTestPlan()
svc.SetPremium(&fakePremiumStorefront{plans: []domain.PremiumPlan{plan}, balance: 2200})
picker := svc.handlePremium(context.Background(), buyer.ID, "/gift")
if picker.ReplyMarkup == nil || picker.ReplyMarkup.Type != domain.MessageReplyMarkupKeyboard ||
len(picker.ReplyMarkup.Keyboard) != 1 ||
picker.ReplyMarkup.Keyboard[0][0].Type != domain.MarkupButtonRequestPeer {
t.Fatalf("gift picker = %+v", picker.ReplyMarkup)
}
if picker.ReplyMarkup.Selective {
t.Fatal("gift picker must be visible in a private bot chat")
}
if err := domain.ValidateReplyMarkup(picker.ReplyMarkup); err != nil {
t.Fatalf("gift picker markup: %v", err)
}
selected := svc.handlePremiumMessage(context.Background(), buyer.ID, domain.Message{
Media: &domain.MessageMedia{Kind: domain.MessageMediaKindService,
ServiceAction: &domain.MessageServiceAction{
Kind: domain.MessageServiceActionRequestedPeer,
RequestedPeer: &domain.MessageRequestedPeerAction{
ButtonID: premiumGiftPeerButtonID,
Peers: []domain.Peer{{Type: domain.PeerTypeUser, ID: recipient.ID}},
},
}},
})
if selected.ReplyMarkup == nil || selected.ReplyMarkup.Type != domain.MessageReplyMarkupInline ||
len(selected.ReplyMarkup.Inline) != 1 ||
!strings.HasPrefix(string(selected.ReplyMarkup.Inline[0][0].Data), premiumCallbackGiftBuy) {
t.Fatalf("selected recipient plans = %+v", selected)
}
if err := domain.ValidateReplyMarkup(selected.ReplyMarkup); err != nil {
t.Fatalf("gift plans markup: %v", err)
}
invoiceReply := svc.premiumBotGiftInvoice(
context.Background(), buyer.ID, recipient.ID, plan.Months, "Happy birthday",
)
if invoiceReply.Media == nil || invoiceReply.Media.Invoice == nil {
t.Fatalf("gift invoice reply = %+v", invoiceReply)
}
invoice := invoiceReply.Media.Invoice
if invoice.Kind != domain.PremiumPurchaseGift || invoice.RecipientUserID != recipient.ID ||
invoice.Months != plan.Months || invoice.AmountStars != plan.AmountStars ||
invoice.PlanVersion != plan.Version || invoice.Message.Text != "Happy birthday" {
t.Fatalf("gift invoice = %+v", invoice)
}
if !containsAll(invoiceReply.Text, "Цена: 750 Stars", "Текущий баланс: 2200 Stars") {
t.Fatalf("gift invoice text does not show price and balance: %q", invoiceReply.Text)
}
if err := domain.ValidateReplyMarkup(invoiceReply.ReplyMarkup); err != nil {
t.Fatalf("gift invoice markup: %v", err)
}
}
func TestPremiumBotStatusAndHistory(t *testing.T) {
svc, _, _, _ := newTestService(t)
now := int(svc.now().Unix())
svc.SetPremium(&fakePremiumStorefront{
plans: []domain.PremiumPlan{premiumBotTestPlan()},
active: []domain.PremiumEntitlement{{Status: domain.PremiumEntitlementActive, ExpiresAt: now + 86400}},
history: []domain.PremiumEntitlement{{
UserID: 2002, SourceUserID: 1001, Source: domain.PremiumEntitlementGift,
Months: 3, Status: domain.PremiumEntitlementActive, CreatedAt: now,
}},
})
if reply := svc.handlePremium(context.Background(), 1001, "/status"); !containsAll(reply.Text, "активен до", "UTC") {
t.Fatalf("/status = %q", reply.Text)
}
if reply := svc.handlePremium(context.Background(), 1001, "/history"); !containsAll(reply.Text, "3 мес.", "подарок пользователю 2002") {
t.Fatalf("/history = %q", reply.Text)
}
if !svc.HandlesBot(domain.PremiumBotConfiguredUserID()) {
t.Fatal("Premium bot is not registered as a built-in responder")
}
}
func TestPremiumBotUsesClientSessionLanguageForMessagesAndCallbacks(t *testing.T) {
svc, users, _, messages := newTestService(t)
owner := newOwner(t, users, "+15550888")
svc.SetPremium(&fakePremiumStorefront{
plans: []domain.PremiumPlan{premiumBotTestPlan()}, balance: 900,
})
locale := premiumLocaleFromSession(domain.ClientSessionMetadata{
SessionID: 77, LangPack: "tdesktop", LangCode: "en-US",
})
home := svc.handlePremium(context.Background(), owner.ID, "/start", locale)
if !strings.Contains(home.Text, "Welcome to the Premium store") ||
home.ReplyMarkup == nil || home.ReplyMarkup.Inline[0][0].Text != "Buy Premium" {
t.Fatalf("English Premium home = %+v", home)
}
_, handled, err := svc.OnCallbackQuery(context.Background(), domain.BotCallbackQuery{
ID: 8, BotUserID: svc.premium.BotUserID(), UserID: owner.ID,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
MessageID: 1, Data: []byte(premiumCallbackPlans),
ClientSession: domain.ClientSessionMetadata{SessionID: 77, LangCode: "en"},
})
if err != nil || !handled {
t.Fatalf("English callback handled=%v err=%v", handled, err)
}
reply := latestPremiumBotReply(t, messages, owner.ID, svc.premium.BotUserID())
if !strings.Contains(reply.Body, "Choose a Premium plan") {
t.Fatalf("English Premium callback reply = %q", reply.Body)
}
}
func TestPremiumBotMenuCallbacksExecuteInsideLocalServer(t *testing.T) {
svc, users, _, messages := newTestService(t)
owner := newOwner(t, users, "+15550999")
svc.SetPremium(&fakePremiumStorefront{
plans: []domain.PremiumPlan{premiumBotTestPlan()}, balance: 900,
})
home := svc.handlePremium(context.Background(), owner.ID, "/start")
if home.ReplyMarkup == nil || len(home.ReplyMarkup.Inline) != 3 {
t.Fatalf("home markup = %+v", home.ReplyMarkup)
}
for _, row := range home.ReplyMarkup.Inline {
for _, button := range row {
if button.Type != domain.MarkupButtonCallback || button.URL != "" ||
!strings.HasPrefix(string(button.Data), premiumCallbackPrefix) {
t.Fatalf("home button is not a local callback: %+v", button)
}
}
}
answer, handled, err := svc.OnCallbackQuery(context.Background(), domain.BotCallbackQuery{
ID: 1, BotUserID: svc.premium.BotUserID(), UserID: owner.ID,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
MessageID: 1, Data: []byte(premiumCallbackPlans),
})
if err != nil || !handled || answer.Alert {
t.Fatalf("plans callback answer = %+v handled=%v err=%v", answer, handled, err)
}
reply := latestPremiumBotReply(t, messages, owner.ID, svc.premium.BotUserID())
if !strings.Contains(reply.Body, "Выберите тариф Premium") ||
reply.ReplyMarkup == nil || len(reply.ReplyMarkup.Inline) != 1 ||
string(reply.ReplyMarkup.Inline[0][0].Data) != premiumCallbackBuy+"3" {
t.Fatalf("plans callback reply = %+v", reply)
}
_, handled, err = svc.OnCallbackQuery(context.Background(), domain.BotCallbackQuery{
ID: 2, BotUserID: svc.premium.BotUserID(), UserID: owner.ID,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
MessageID: reply.ID, Data: []byte(premiumCallbackBuy + "3"),
})
if err != nil || !handled {
t.Fatalf("buy callback handled=%v err=%v", handled, err)
}
invoice := latestPremiumBotReply(t, messages, owner.ID, svc.premium.BotUserID())
if invoice.Media == nil || invoice.Media.Invoice == nil ||
invoice.Media.Invoice.AmountStars != 750 {
t.Fatalf("buy callback did not create catalog invoice: %+v", invoice)
}
}
func latestPremiumBotReply(
t *testing.T,
messages *memory.MessageStore,
userID, botID int64,
) domain.Message {
t.Helper()
list, err := messages.ListByUser(context.Background(), userID, domain.MessageFilter{
HasPeer: true,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: botID},
Limit: 100,
})
if err != nil {
t.Fatalf("list premium bot replies: %v", err)
}
var latest domain.Message
for _, message := range list.Messages {
if message.From.ID == botID && message.ID > latest.ID {
latest = message
}
}
if latest.ID == 0 {
t.Fatal("no Premium bot reply")
}
return latest
}
func containsAll(text string, parts ...string) bool {
for _, part := range parts {
if !strings.Contains(text, part) {
return false
}
}
return true
}