283 lines
7.3 KiB
Go
283 lines
7.3 KiB
Go
package gateway
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/tech/sendico/gateway/tgsettle/storage"
|
|
storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
|
|
envelope "github.com/tech/sendico/pkg/messaging/envelope"
|
|
tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
|
|
mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mservice"
|
|
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
|
)
|
|
|
|
//
|
|
// FAKE STORES
|
|
//
|
|
|
|
type fakePaymentsStore struct {
|
|
mu sync.Mutex
|
|
records map[string]*storagemodel.PaymentRecord
|
|
}
|
|
|
|
func (f *fakePaymentsStore) FindByIdempotencyKey(_ context.Context, key string) (*storagemodel.PaymentRecord, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.records == nil {
|
|
return nil, nil
|
|
}
|
|
return f.records[key], nil
|
|
}
|
|
|
|
func (f *fakePaymentsStore) Upsert(_ context.Context, record *storagemodel.PaymentRecord) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.records == nil {
|
|
f.records = map[string]*storagemodel.PaymentRecord{}
|
|
}
|
|
f.records[record.IdempotencyKey] = record
|
|
return nil
|
|
}
|
|
|
|
type fakeTelegramStore struct {
|
|
mu sync.Mutex
|
|
records map[string]*storagemodel.TelegramConfirmation
|
|
}
|
|
|
|
func (f *fakeTelegramStore) Upsert(_ context.Context, record *storagemodel.TelegramConfirmation) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if f.records == nil {
|
|
f.records = map[string]*storagemodel.TelegramConfirmation{}
|
|
}
|
|
f.records[record.RequestID] = record
|
|
return nil
|
|
}
|
|
|
|
type fakeRepo struct {
|
|
payments *fakePaymentsStore
|
|
tg *fakeTelegramStore
|
|
}
|
|
|
|
func (f *fakeRepo) Payments() storage.PaymentsStore {
|
|
return f.payments
|
|
}
|
|
|
|
func (f *fakeRepo) TelegramConfirmations() storage.TelegramConfirmationsStore {
|
|
return f.tg
|
|
}
|
|
|
|
//
|
|
// FAKE BROKER (ОБЯЗАТЕЛЕН ДЛЯ СЕРВИСА)
|
|
//
|
|
|
|
type fakeBroker struct{}
|
|
|
|
func (f *fakeBroker) Publish(_ envelope.Envelope) error {
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeBroker) Subscribe(event model.NotificationEvent) (<-chan envelope.Envelope, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *fakeBroker) Unsubscribe(event model.NotificationEvent, subChan <-chan envelope.Envelope) error {
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// CAPTURE ONLY TELEGRAM REACTIONS
|
|
//
|
|
|
|
type captureProducer struct {
|
|
mu sync.Mutex
|
|
reactions []envelope.Envelope
|
|
sig string
|
|
}
|
|
|
|
func (c *captureProducer) SendMessage(env envelope.Envelope) error {
|
|
if env.GetSignature().ToString() != c.sig {
|
|
return nil
|
|
}
|
|
c.mu.Lock()
|
|
c.reactions = append(c.reactions, env)
|
|
c.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
//
|
|
// TESTS
|
|
//
|
|
|
|
func newTestService(_ *testing.T) (*Service, *fakeRepo, *captureProducer) {
|
|
logger := mloggerfactory.NewLogger(false)
|
|
|
|
repo := &fakeRepo{
|
|
payments: &fakePaymentsStore{},
|
|
tg: &fakeTelegramStore{},
|
|
}
|
|
|
|
sigEnv := tnotifications.TelegramReaction(string(mservice.PaymentGateway), &model.TelegramReactionRequest{
|
|
RequestID: "x",
|
|
ChatID: "1",
|
|
MessageID: "2",
|
|
Emoji: "ok",
|
|
})
|
|
|
|
prod := &captureProducer{
|
|
sig: sigEnv.GetSignature().ToString(),
|
|
}
|
|
|
|
svc := NewService(logger, repo, prod, &fakeBroker{}, Config{
|
|
Rail: "card",
|
|
SuccessReaction: "👍",
|
|
})
|
|
|
|
return svc, repo, prod
|
|
}
|
|
|
|
func TestConfirmed(t *testing.T) {
|
|
svc, repo, prod := newTestService(t)
|
|
|
|
_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
|
|
IdempotencyKey: "idem-1",
|
|
PaymentIntentID: "pi-1",
|
|
QuoteRef: "quote-1",
|
|
OutgoingLeg: "card",
|
|
RequestedMoney: &paymenttypes.Money{Amount: "5", Currency: "EUR"},
|
|
Status: storagemodel.PaymentStatusWaiting,
|
|
})
|
|
|
|
result := &model.ConfirmationResult{
|
|
RequestID: "idem-1",
|
|
Money: &paymenttypes.Money{Amount: "5", Currency: "EUR"},
|
|
Status: model.ConfirmationStatusConfirmed,
|
|
RawReply: &model.TelegramMessage{ChatID: "1", MessageID: "2"},
|
|
}
|
|
|
|
_ = svc.onConfirmationResult(context.Background(), result)
|
|
|
|
rec := repo.payments.records["idem-1"]
|
|
|
|
if rec.Status != storagemodel.PaymentStatusSuccess {
|
|
t.Fatalf("expected success, got %s", rec.Status)
|
|
}
|
|
if rec.RequestedMoney == nil {
|
|
t.Fatalf("requested money not set")
|
|
}
|
|
if rec.ExecutedAt.IsZero() {
|
|
t.Fatalf("executedAt not set")
|
|
}
|
|
if repo.tg.records["idem-1"] == nil {
|
|
t.Fatalf("telegram confirmation not stored")
|
|
}
|
|
if len(prod.reactions) != 1 {
|
|
t.Fatalf("reaction must be published")
|
|
}
|
|
}
|
|
|
|
func TestClarified(t *testing.T) {
|
|
svc, repo, prod := newTestService(t)
|
|
|
|
_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
|
|
IdempotencyKey: "idem-2",
|
|
Status: storagemodel.PaymentStatusWaiting,
|
|
})
|
|
|
|
result := &model.ConfirmationResult{
|
|
RequestID: "idem-2",
|
|
Status: model.ConfirmationStatusClarified,
|
|
RawReply: &model.TelegramMessage{ChatID: "1", MessageID: "2"},
|
|
}
|
|
|
|
_ = svc.onConfirmationResult(context.Background(), result)
|
|
|
|
rec := repo.payments.records["idem-2"]
|
|
|
|
if rec.Status != storagemodel.PaymentStatusWaiting {
|
|
t.Fatalf("clarified must not change status")
|
|
}
|
|
if repo.tg.records["idem-2"] == nil {
|
|
t.Fatalf("telegram confirmation must be stored")
|
|
}
|
|
if len(prod.reactions) != 0 {
|
|
t.Fatalf("clarified must not publish reaction")
|
|
}
|
|
}
|
|
|
|
func TestRejected(t *testing.T) {
|
|
svc, repo, prod := newTestService(t)
|
|
|
|
// ВАЖНО: чтобы текущий emitTransferStatusEvent не падал на nil,
|
|
// даем минимально ожидаемые поля + non-nil ExecutedMoney.
|
|
_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
|
|
IdempotencyKey: "idem-3",
|
|
PaymentIntentID: "pi-3",
|
|
QuoteRef: "quote-3",
|
|
OutgoingLeg: "card",
|
|
RequestedMoney: &paymenttypes.Money{Amount: "5", Currency: "EUR"},
|
|
ExecutedMoney: &paymenttypes.Money{Amount: "0", Currency: "EUR"},
|
|
Status: storagemodel.PaymentStatusWaiting,
|
|
})
|
|
|
|
result := &model.ConfirmationResult{
|
|
RequestID: "idem-3",
|
|
Status: model.ConfirmationStatusRejected,
|
|
RawReply: &model.TelegramMessage{ChatID: "1", MessageID: "2"},
|
|
}
|
|
|
|
_ = svc.onConfirmationResult(context.Background(), result)
|
|
|
|
rec := repo.payments.records["idem-3"]
|
|
|
|
if rec.Status != storagemodel.PaymentStatusFailed {
|
|
t.Fatalf("expected failed")
|
|
}
|
|
if repo.tg.records["idem-3"] == nil {
|
|
t.Fatalf("telegram confirmation must be stored")
|
|
}
|
|
if len(prod.reactions) != 0 {
|
|
t.Fatalf("rejected must not publish reaction")
|
|
}
|
|
}
|
|
|
|
func TestTimeout(t *testing.T) {
|
|
svc, repo, prod := newTestService(t)
|
|
|
|
// ВАЖНО: чтобы текущий emitTransferStatusEvent не падал на nil,
|
|
// даем минимально ожидаемые поля + non-nil ExecutedMoney.
|
|
_ = repo.payments.Upsert(context.Background(), &storagemodel.PaymentRecord{
|
|
IdempotencyKey: "idem-4",
|
|
PaymentIntentID: "pi-4",
|
|
QuoteRef: "quote-4",
|
|
OutgoingLeg: "card",
|
|
RequestedMoney: &paymenttypes.Money{Amount: "5", Currency: "EUR"},
|
|
ExecutedMoney: &paymenttypes.Money{Amount: "0", Currency: "EUR"},
|
|
Status: storagemodel.PaymentStatusWaiting,
|
|
})
|
|
|
|
result := &model.ConfirmationResult{
|
|
RequestID: "idem-4",
|
|
Status: model.ConfirmationStatusTimeout,
|
|
RawReply: &model.TelegramMessage{ChatID: "1", MessageID: "2"},
|
|
}
|
|
|
|
_ = svc.onConfirmationResult(context.Background(), result)
|
|
|
|
rec := repo.payments.records["idem-4"]
|
|
|
|
if rec.Status != storagemodel.PaymentStatusFailed {
|
|
t.Fatalf("timeout must be failed")
|
|
}
|
|
if repo.tg.records["idem-4"] == nil {
|
|
t.Fatalf("telegram confirmation must be stored")
|
|
}
|
|
if len(prod.reactions) != 0 {
|
|
t.Fatalf("timeout must not publish reaction")
|
|
}
|
|
}
|