app_payments_test.go raw

   1  package transactions
   2  
   3  import (
   4  	"testing"
   5  	"time"
   6  
   7  	"github.com/stretchr/testify/assert"
   8  	"github.com/stretchr/testify/require"
   9  
  10  	"github.com/getAlby/hub/constants"
  11  	"github.com/getAlby/hub/db"
  12  	"github.com/getAlby/hub/tests"
  13  )
  14  
  15  func TestSendPaymentSync_App_NoPermission(t *testing.T) {
  16  	svc, err := tests.CreateTestService(t)
  17  	require.NoError(t, err)
  18  	defer svc.Remove()
  19  
  20  	app, _, err := tests.CreateApp(svc)
  21  	assert.NoError(t, err)
  22  
  23  	dbRequestEvent := &db.RequestEvent{}
  24  	err = svc.DB.Create(&dbRequestEvent).Error
  25  	assert.NoError(t, err)
  26  
  27  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  28  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
  29  
  30  	assert.Error(t, err)
  31  	assert.Equal(t, "app does not have pay_invoice scope", err.Error())
  32  	assert.Nil(t, transaction)
  33  }
  34  func TestSendPaymentSync_App_WithPermission(t *testing.T) {
  35  	svc, err := tests.CreateTestService(t)
  36  	require.NoError(t, err)
  37  	defer svc.Remove()
  38  
  39  	app, _, err := tests.CreateApp(svc)
  40  	assert.NoError(t, err)
  41  
  42  	appPermission := &db.AppPermission{
  43  		AppId: app.ID,
  44  		App:   *app,
  45  		Scope: constants.PAY_INVOICE_SCOPE,
  46  	}
  47  	err = svc.DB.Create(appPermission).Error
  48  	assert.NoError(t, err)
  49  
  50  	dbRequestEvent := &db.RequestEvent{}
  51  	err = svc.DB.Create(&dbRequestEvent).Error
  52  	assert.NoError(t, err)
  53  
  54  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  55  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
  56  
  57  	assert.NoError(t, err)
  58  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
  59  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
  60  	assert.Equal(t, "123preimage", *transaction.Preimage)
  61  	assert.Equal(t, app.ID, *transaction.AppId)
  62  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
  63  }
  64  
  65  func TestMarkSettled_App_BudgetWarning(t *testing.T) {
  66  	svc, err := tests.CreateTestService(t)
  67  	require.NoError(t, err)
  68  	defer svc.Remove()
  69  
  70  	app, _, err := tests.CreateApp(svc)
  71  	assert.NoError(t, err)
  72  
  73  	appPermission := &db.AppPermission{
  74  		AppId:        app.ID,
  75  		App:          *app,
  76  		Scope:        constants.PAY_INVOICE_SCOPE,
  77  		MaxAmountSat: 100,
  78  	}
  79  	err = svc.DB.Create(appPermission).Error
  80  	assert.NoError(t, err)
  81  
  82  	// settling this payment pushes the app over 80% of its 100 sat budget
  83  	dbTransaction := db.Transaction{
  84  		AppId:       &app.ID,
  85  		State:       constants.TRANSACTION_STATE_PENDING,
  86  		Type:        constants.TRANSACTION_TYPE_OUTGOING,
  87  		PaymentHash: tests.MockLNClientTransaction.PaymentHash,
  88  		AmountMsat:  90000,
  89  	}
  90  	svc.DB.Create(&dbTransaction)
  91  
  92  	mockEventConsumer := tests.NewMockEventConsumer()
  93  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
  94  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  95  	_, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
  96  
  97  	assert.NoError(t, err)
  98  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(2)
  99  	assert.Equal(t, 2, len(consumedEvents))
 100  	eventNames := []string{}
 101  	for _, consumedEvent := range consumedEvents {
 102  		eventNames = append(eventNames, consumedEvent.Event)
 103  	}
 104  	assert.Contains(t, eventNames, "nwc_payment_sent")
 105  	assert.Contains(t, eventNames, "nwc_budget_warning")
 106  }
 107  
 108  func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) {
 109  	svc, err := tests.CreateTestService(t)
 110  	require.NoError(t, err)
 111  	defer svc.Remove()
 112  
 113  	app, _, err := tests.CreateApp(svc)
 114  	assert.NoError(t, err)
 115  
 116  	appPermission := &db.AppPermission{
 117  		AppId:        app.ID,
 118  		App:          *app,
 119  		Scope:        constants.PAY_INVOICE_SCOPE,
 120  		MaxAmountSat: 1,
 121  	}
 122  	err = svc.DB.Create(appPermission).Error
 123  	assert.NoError(t, err)
 124  
 125  	dbRequestEvent := &db.RequestEvent{}
 126  	err = svc.DB.Create(&dbRequestEvent).Error
 127  	assert.NoError(t, err)
 128  
 129  	mockEventConsumer := tests.NewMockEventConsumer()
 130  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
 131  
 132  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 133  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 134  
 135  	assert.Error(t, err)
 136  	assert.ErrorIs(t, err, NewQuotaExceededError())
 137  	assert.Nil(t, transaction)
 138  
 139  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
 140  	assert.Equal(t, 1, len(consumedEvents))
 141  	assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
 142  	assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
 143  	assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, consumedEvents[0].Properties.(map[string]interface{})["code"])
 144  	expectedMessage := NewQuotaExceededError().Error() + " te" // invoice description is "te" in the mock invoice
 145  	assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"])
 146  }
 147  
 148  func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) {
 149  	svc, err := tests.CreateTestService(t)
 150  	require.NoError(t, err)
 151  	defer svc.Remove()
 152  
 153  	app, _, err := tests.CreateApp(svc)
 154  	assert.NoError(t, err)
 155  
 156  	appPermission := &db.AppPermission{
 157  		AppId:        app.ID,
 158  		App:          *app,
 159  		Scope:        constants.PAY_INVOICE_SCOPE,
 160  		MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
 161  	}
 162  	err = svc.DB.Create(appPermission).Error
 163  	assert.NoError(t, err)
 164  
 165  	// 1 sat payment pushes app over the limit
 166  	svc.DB.Create(&db.Transaction{
 167  		AppId:      &app.ID,
 168  		State:      constants.TRANSACTION_STATE_SETTLED,
 169  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 170  		AmountMsat: 1000,
 171  		CreatedAt:  time.Now(),
 172  	})
 173  
 174  	dbRequestEvent := &db.RequestEvent{}
 175  	err = svc.DB.Create(&dbRequestEvent).Error
 176  	assert.NoError(t, err)
 177  
 178  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 179  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 180  
 181  	assert.Error(t, err)
 182  	assert.ErrorIs(t, err, NewQuotaExceededError())
 183  	assert.Nil(t, transaction)
 184  }
 185  func TestSendPaymentSync_App_BudgetExceeded_UnsettledPayment(t *testing.T) {
 186  	svc, err := tests.CreateTestService(t)
 187  	require.NoError(t, err)
 188  	defer svc.Remove()
 189  
 190  	app, _, err := tests.CreateApp(svc)
 191  	assert.NoError(t, err)
 192  
 193  	appPermission := &db.AppPermission{
 194  		AppId:        app.ID,
 195  		App:          *app,
 196  		Scope:        constants.PAY_INVOICE_SCOPE,
 197  		MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
 198  	}
 199  	err = svc.DB.Create(appPermission).Error
 200  	assert.NoError(t, err)
 201  
 202  	// 1 sat payment pushes app over the limit
 203  	svc.DB.Create(&db.Transaction{
 204  		AppId:      &app.ID,
 205  		State:      constants.TRANSACTION_STATE_PENDING,
 206  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 207  		AmountMsat: 1000,
 208  		CreatedAt:  time.Now(),
 209  	})
 210  
 211  	dbRequestEvent := &db.RequestEvent{}
 212  	err = svc.DB.Create(&dbRequestEvent).Error
 213  	assert.NoError(t, err)
 214  
 215  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 216  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 217  
 218  	assert.Error(t, err)
 219  	assert.ErrorIs(t, err, NewQuotaExceededError())
 220  	assert.Nil(t, transaction)
 221  }
 222  
 223  func TestSendPaymentSync_App_BudgetNotExceeded_FailedPayment(t *testing.T) {
 224  	svc, err := tests.CreateTestService(t)
 225  	require.NoError(t, err)
 226  	defer svc.Remove()
 227  
 228  	app, _, err := tests.CreateApp(svc)
 229  	assert.NoError(t, err)
 230  
 231  	appPermission := &db.AppPermission{
 232  		AppId:        app.ID,
 233  		App:          *app,
 234  		Scope:        constants.PAY_INVOICE_SCOPE,
 235  		MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
 236  	}
 237  	err = svc.DB.Create(appPermission).Error
 238  	assert.NoError(t, err)
 239  
 240  	// 1 sat payment would push app over the limit, but it failed so its not counted
 241  	svc.DB.Create(&db.Transaction{
 242  		AppId:      &app.ID,
 243  		State:      constants.TRANSACTION_STATE_FAILED,
 244  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 245  		AmountMsat: 1000,
 246  		CreatedAt:  time.Now(),
 247  	})
 248  
 249  	dbRequestEvent := &db.RequestEvent{}
 250  	err = svc.DB.Create(&dbRequestEvent).Error
 251  	assert.NoError(t, err)
 252  
 253  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 254  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 255  
 256  	assert.NoError(t, err)
 257  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 258  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 259  	assert.Equal(t, "123preimage", *transaction.Preimage)
 260  	assert.Equal(t, app.ID, *transaction.AppId)
 261  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 262  }
 263