Ctrl K
ComunidadGuías…

internal/app/bots/premiumbot_deposit_test.go

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

Archivo original ↗Preguntar a KiBot
package bots

import (
	"context"
	"strings"
	"testing"

	"telesrv/internal/domain"
)

// stubDonationsSource is a minimal donationsSource double: no real wallet,
// no real chain, just canned answers so premiumBotDepositReply's branches
// (unconfigured, no watchable chain, address assignment failure, happy
// path) can each be exercised without a database or a blockchain.
type stubDonationsSource struct {
	ready      bool
	chains     []domain.DonationChain
	chainsErr  error
	address    string
	addressErr error
	deposits   []domain.DonationDeposit
}

func (s *stubDonationsSource) Ready() bool { return s.ready }
func (s *stubDonationsSource) AddressForUser(context.Context, int64) (string, error) {
	return s.address, s.addressErr
}
func (s *stubDonationsSource) EnabledChains(context.Context) ([]domain.DonationChain, error) {
	return s.chains, s.chainsErr
}
func (s *stubDonationsSource) UserDeposits(context.Context, int64, int) ([]domain.DonationDeposit, error) {
	return s.deposits, nil
}

func TestPremiumBotDepositTextUnconfigured(t *testing.T) {
	svc, _, _, _ := newTestService(t)
	// No SetDonationsSource call at all -- the zero value (nil) must answer
	// gracefully, not panic, exactly like an operator who hasn't set
	// TELESRV_DONATION_WALLET_KEY.
	got := svc.premiumBotDepositReply(context.Background(), 42).Text
	if !strings.Contains(got, "not available") {
		t.Fatalf("unconfigured text = %q, want it to say deposits are not available", got)
	}
}

func TestPremiumBotDepositTextNotReady(t *testing.T) {
	svc, _, _, _ := newTestService(t)
	svc.SetDonationsSource(&stubDonationsSource{ready: false})
	got := svc.premiumBotDepositReply(context.Background(), 42).Text
	if !strings.Contains(got, "not available") {
		t.Fatalf("not-ready text = %q, want it to say deposits are not available", got)
	}
}

func TestPremiumBotDepositTextNoWatchableChain(t *testing.T) {
	svc, _, _, _ := newTestService(t)
	svc.SetDonationsSource(&stubDonationsSource{
		ready: true,
		chains: []domain.DonationChain{
			{Key: "ethereum", Name: "Ethereum", ChainID: 1, NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 12, Enabled: true}, // no RPCURL -> not watchable
		},
	})
	got := svc.premiumBotDepositReply(context.Background(), 42).Text
	if !strings.Contains(got, "not available") {
		t.Fatalf("no-watchable-chain text = %q, want it to say deposits are not available", got)
	}
}

func TestPremiumBotDepositTextAddressFailure(t *testing.T) {
	svc, _, _, _ := newTestService(t)
	svc.SetDonationsSource(&stubDonationsSource{
		ready: true,
		chains: []domain.DonationChain{
			{Key: "ganache", Name: "Ganache (local)", ChainID: 1337, RPCURL: "http://127.0.0.1:7545", NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 1, Enabled: true},
		},
		addressErr: domain.ErrDonationWalletNotConfigured,
	})
	got := svc.premiumBotDepositReply(context.Background(), 42).Text
	if !strings.Contains(got, "try again") {
		t.Fatalf("address-failure text = %q, want a retry message", got)
	}
}

func TestPremiumBotDepositTextHappyPath(t *testing.T) {
	svc, _, _, _ := newTestService(t)
	const address = "0xabc0000000000000000000000000000000dead"
	svc.SetDonationsSource(&stubDonationsSource{
		ready: true,
		chains: []domain.DonationChain{
			{Key: "ganache", Name: "Ganache (local)", ChainID: 1337, RPCURL: "http://127.0.0.1:7545", NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 1, Enabled: true},
			{Key: "sepolia", Name: "Sepolia (testnet)", ChainID: 11155111, RPCURL: "https://sepolia.example", NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 10, Enabled: true},
			{Key: "ethereum", Name: "Ethereum", ChainID: 1, NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 12, Enabled: true}, // disabled by empty RPCURL, must be left out
		},
		address: address,
	})
	reply := svc.premiumBotDepositReply(context.Background(), 42)
	got := reply.Text
	if !strings.Contains(got, address) {
		t.Fatalf("happy-path text = %q, want the assigned address", got)
	}
	if strings.Contains(got, "`") {
		t.Fatalf("happy-path text = %q, want no literal backticks -- the server has no markdown parser, the address must be a Code entity instead", got)
	}
	if !strings.Contains(got, "Ganache (local)") || !strings.Contains(got, "Sepolia (testnet)") {
		t.Fatalf("happy-path text = %q, want both watchable chains listed", got)
	}
	if strings.Contains(got, "Ethereum (ETH)") {
		t.Fatalf("happy-path text = %q, want the unwatchable Ethereum row left out", got)
	}
	if len(reply.Entities) != 1 || reply.Entities[0].Type != domain.MessageEntityCode {
		t.Fatalf("entities = %+v, want exactly one Code entity for the address", reply.Entities)
	}
	entity := reply.Entities[0]
	if got[entity.Offset:entity.Offset+entity.Length] != address {
		t.Fatalf("Code entity covers %q, want the address %q", got[entity.Offset:entity.Offset+entity.Length], address)
	}
}

// TestPremiumBotRespondsToDepositCommand pins that /deposit is wired into
// the command dispatcher at all, not just that premiumBotDepositReply itself
// behaves -- the two were separate bugs to make in the same change.
func TestPremiumBotRespondsToDepositCommand(t *testing.T) {
	svc, users, _, messages := newTestService(t)
	owner := newOwner(t, users, "15550001234")
	svc.SetDonationsSource(&stubDonationsSource{
		ready: true,
		chains: []domain.DonationChain{
			{Key: "ganache", Name: "Ganache (local)", ChainID: 1337, RPCURL: "http://127.0.0.1:7545", NativeSymbol: "ETH", NativeDecimals: 18, ConfirmationsRequired: 1, Enabled: true},
		},
		address: "0xabc0000000000000000000000000000000dead",
	})
	svc.respondAsPremium(domain.PremiumBotUserID, owner.ID, domain.Message{
		From: domain.Peer{Type: domain.PeerTypeUser, ID: owner.ID},
		Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.PremiumBotUserID},
		Body: "/deposit",
	}, domain.ClientSessionMetadata{})
	list, err := messages.ListByUser(context.Background(), owner.ID, domain.MessageFilter{
		HasPeer: true, Peer: domain.Peer{Type: domain.PeerTypeUser, ID: domain.PremiumBotUserID}, Limit: 100,
	})
	if err != nil {
		t.Fatalf("list premiumbot history: %v", err)
	}
	var reply domain.Message
	for _, msg := range list.Messages {
		if msg.From.ID == domain.PremiumBotUserID && msg.ID > reply.ID {
			reply = msg
		}
	}
	if !strings.Contains(reply.Body, "0xabc0000000000000000000000000000000dead") {
		t.Fatalf("premiumbot /deposit reply = %q, want the assigned address", reply.Body)
	}
}