isolated_app_payments_test.go raw

   1  package transactions
   2  
   3  import (
   4  	"testing"
   5  
   6  	"github.com/stretchr/testify/assert"
   7  	"github.com/stretchr/testify/require"
   8  
   9  	"github.com/getAlby/hub/constants"
  10  	"github.com/getAlby/hub/db"
  11  	"github.com/getAlby/hub/tests"
  12  )
  13  
  14  func TestSendPaymentSync_IsolatedApp_NoBalance(t *testing.T) {
  15  	svc, err := tests.CreateTestService(t)
  16  	require.NoError(t, err)
  17  	defer svc.Remove()
  18  
  19  	app, _, err := tests.CreateApp(svc)
  20  	assert.NoError(t, err)
  21  	app.Isolated = true
  22  	svc.DB.Save(&app)
  23  
  24  	appPermission := &db.AppPermission{
  25  		AppId: app.ID,
  26  		App:   *app,
  27  		Scope: constants.PAY_INVOICE_SCOPE,
  28  	}
  29  	err = svc.DB.Create(appPermission).Error
  30  	assert.NoError(t, err)
  31  
  32  	dbRequestEvent := &db.RequestEvent{}
  33  	err = svc.DB.Create(&dbRequestEvent).Error
  34  	assert.NoError(t, err)
  35  
  36  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  37  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
  38  
  39  	assert.Error(t, err)
  40  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
  41  	assert.Nil(t, transaction)
  42  }
  43  
  44  func TestSendPaymentSync_IsolatedApp_BalanceInsufficient(t *testing.T) {
  45  	svc, err := tests.CreateTestService(t)
  46  	require.NoError(t, err)
  47  	defer svc.Remove()
  48  
  49  	app, _, err := tests.CreateApp(svc)
  50  	assert.NoError(t, err)
  51  	app.Isolated = true
  52  	svc.DB.Save(&app)
  53  
  54  	appPermission := &db.AppPermission{
  55  		AppId: app.ID,
  56  		App:   *app,
  57  		Scope: constants.PAY_INVOICE_SCOPE,
  58  	}
  59  	err = svc.DB.Create(appPermission).Error
  60  	assert.NoError(t, err)
  61  
  62  	dbRequestEvent := &db.RequestEvent{}
  63  	err = svc.DB.Create(&dbRequestEvent).Error
  64  	assert.NoError(t, err)
  65  
  66  	svc.DB.Create(&db.Transaction{
  67  		AppId:      &app.ID,
  68  		State:      constants.TRANSACTION_STATE_SETTLED,
  69  		Type:       constants.TRANSACTION_TYPE_INCOMING,
  70  		AmountMsat: 132000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
  71  	})
  72  
  73  	mockEventConsumer := tests.NewMockEventConsumer()
  74  	svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
  75  
  76  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  77  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
  78  
  79  	assert.Error(t, err)
  80  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
  81  	assert.Nil(t, transaction)
  82  
  83  	consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
  84  	assert.Equal(t, 1, len(consumedEvents))
  85  	assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
  86  	assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
  87  	assert.Equal(t, constants.ERROR_INSUFFICIENT_BALANCE, consumedEvents[0].Properties.(map[string]interface{})["code"])
  88  	expectedMessage := NewInsufficientBalanceError().Error() + " te" // invoice description is "te" in the mock invoice
  89  	assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"])
  90  }
  91  
  92  func TestSendPaymentSync_IsolatedApp_BalanceSufficient(t *testing.T) {
  93  	svc, err := tests.CreateTestService(t)
  94  	require.NoError(t, err)
  95  	defer svc.Remove()
  96  
  97  	app, _, err := tests.CreateApp(svc)
  98  	assert.NoError(t, err)
  99  	app.Isolated = true
 100  	svc.DB.Save(&app)
 101  
 102  	appPermission := &db.AppPermission{
 103  		AppId: app.ID,
 104  		App:   *app,
 105  		Scope: constants.PAY_INVOICE_SCOPE,
 106  	}
 107  	err = svc.DB.Create(appPermission).Error
 108  	assert.NoError(t, err)
 109  
 110  	dbRequestEvent := &db.RequestEvent{}
 111  	err = svc.DB.Create(&dbRequestEvent).Error
 112  	assert.NoError(t, err)
 113  
 114  	svc.DB.Create(&db.Transaction{
 115  		AppId:      &app.ID,
 116  		State:      constants.TRANSACTION_STATE_SETTLED,
 117  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 118  		AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 119  	})
 120  
 121  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 122  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 123  
 124  	assert.NoError(t, err)
 125  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 126  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 127  	assert.Equal(t, "123preimage", *transaction.Preimage)
 128  	assert.Equal(t, app.ID, *transaction.AppId)
 129  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 130  }
 131  
 132  func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_OutstandingPayment(t *testing.T) {
 133  	svc, err := tests.CreateTestService(t)
 134  	require.NoError(t, err)
 135  	defer svc.Remove()
 136  
 137  	app, _, err := tests.CreateApp(svc)
 138  	assert.NoError(t, err)
 139  	app.Isolated = true
 140  	svc.DB.Save(&app)
 141  
 142  	appPermission := &db.AppPermission{
 143  		AppId: app.ID,
 144  		App:   *app,
 145  		Scope: constants.PAY_INVOICE_SCOPE,
 146  	}
 147  	err = svc.DB.Create(appPermission).Error
 148  	assert.NoError(t, err)
 149  
 150  	dbRequestEvent := &db.RequestEvent{}
 151  	err = svc.DB.Create(&dbRequestEvent).Error
 152  	assert.NoError(t, err)
 153  
 154  	svc.DB.Create(&db.Transaction{
 155  		AppId:      &app.ID,
 156  		State:      constants.TRANSACTION_STATE_SETTLED,
 157  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 158  		AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 159  	})
 160  
 161  	svc.DB.Create(&db.Transaction{
 162  		AppId:      &app.ID,
 163  		State:      constants.TRANSACTION_STATE_PENDING,
 164  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 165  		AmountMsat: 1000,
 166  	})
 167  
 168  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 169  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 170  
 171  	assert.Error(t, err)
 172  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
 173  	assert.Nil(t, transaction)
 174  }
 175  
 176  func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_SettledPayment(t *testing.T) {
 177  	svc, err := tests.CreateTestService(t)
 178  	require.NoError(t, err)
 179  	defer svc.Remove()
 180  
 181  	app, _, err := tests.CreateApp(svc)
 182  	assert.NoError(t, err)
 183  	app.Isolated = true
 184  	svc.DB.Save(&app)
 185  
 186  	appPermission := &db.AppPermission{
 187  		AppId: app.ID,
 188  		App:   *app,
 189  		Scope: constants.PAY_INVOICE_SCOPE,
 190  	}
 191  	err = svc.DB.Create(appPermission).Error
 192  	assert.NoError(t, err)
 193  
 194  	dbRequestEvent := &db.RequestEvent{}
 195  	err = svc.DB.Create(&dbRequestEvent).Error
 196  	assert.NoError(t, err)
 197  
 198  	svc.DB.Create(&db.Transaction{
 199  		AppId:      &app.ID,
 200  		State:      constants.TRANSACTION_STATE_SETTLED,
 201  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 202  		AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 203  	})
 204  
 205  	svc.DB.Create(&db.Transaction{
 206  		AppId:      &app.ID,
 207  		State:      constants.TRANSACTION_STATE_SETTLED,
 208  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 209  		AmountMsat: 1000,
 210  	})
 211  
 212  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 213  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 214  
 215  	assert.Error(t, err)
 216  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
 217  	assert.Nil(t, transaction)
 218  }
 219  
 220  func TestSendPaymentSync_IsolatedApp_BalanceSufficient_UnrelatedPayment(t *testing.T) {
 221  	svc, err := tests.CreateTestService(t)
 222  	require.NoError(t, err)
 223  	defer svc.Remove()
 224  
 225  	app, _, err := tests.CreateApp(svc)
 226  	assert.NoError(t, err)
 227  	app.Isolated = true
 228  	svc.DB.Save(&app)
 229  
 230  	appPermission := &db.AppPermission{
 231  		AppId: app.ID,
 232  		App:   *app,
 233  		Scope: constants.PAY_INVOICE_SCOPE,
 234  	}
 235  	err = svc.DB.Create(appPermission).Error
 236  	assert.NoError(t, err)
 237  
 238  	dbRequestEvent := &db.RequestEvent{}
 239  	err = svc.DB.Create(&dbRequestEvent).Error
 240  	assert.NoError(t, err)
 241  
 242  	svc.DB.Create(&db.Transaction{
 243  		AppId:      &app.ID,
 244  		State:      constants.TRANSACTION_STATE_SETTLED,
 245  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 246  		AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 247  	})
 248  	svc.DB.Create(&db.Transaction{
 249  		AppId:      nil, // unrelated to this app
 250  		State:      constants.TRANSACTION_STATE_SETTLED,
 251  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 252  		AmountMsat: 1000,
 253  	})
 254  
 255  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 256  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 257  
 258  	assert.NoError(t, err)
 259  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 260  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 261  	assert.Equal(t, "123preimage", *transaction.Preimage)
 262  	assert.Equal(t, app.ID, *transaction.AppId)
 263  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 264  }
 265  
 266  func TestSendPaymentSync_IsolatedApp_BalanceSufficient_FailedPayment(t *testing.T) {
 267  	svc, err := tests.CreateTestService(t)
 268  	require.NoError(t, err)
 269  	defer svc.Remove()
 270  
 271  	app, _, err := tests.CreateApp(svc)
 272  	assert.NoError(t, err)
 273  	app.Isolated = true
 274  	svc.DB.Save(&app)
 275  
 276  	appPermission := &db.AppPermission{
 277  		AppId: app.ID,
 278  		App:   *app,
 279  		Scope: constants.PAY_INVOICE_SCOPE,
 280  	}
 281  	err = svc.DB.Create(appPermission).Error
 282  	assert.NoError(t, err)
 283  
 284  	dbRequestEvent := &db.RequestEvent{}
 285  	err = svc.DB.Create(&dbRequestEvent).Error
 286  	assert.NoError(t, err)
 287  
 288  	svc.DB.Create(&db.Transaction{
 289  		AppId:      &app.ID,
 290  		State:      constants.TRANSACTION_STATE_SETTLED,
 291  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 292  		AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
 293  	})
 294  	svc.DB.Create(&db.Transaction{
 295  		AppId:      &app.ID,
 296  		State:      constants.TRANSACTION_STATE_FAILED,
 297  		Type:       constants.TRANSACTION_TYPE_OUTGOING,
 298  		AmountMsat: 1000,
 299  	})
 300  
 301  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 302  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 303  
 304  	assert.NoError(t, err)
 305  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 306  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 307  	assert.Equal(t, "123preimage", *transaction.Preimage)
 308  	assert.Equal(t, app.ID, *transaction.AppId)
 309  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 310  }
 311  
 312  func TestSendPaymentSync_IsolatedApp_BalanceInsufficientThenSufficient(t *testing.T) {
 313  	svc, err := tests.CreateTestService(t)
 314  	require.NoError(t, err)
 315  	defer svc.Remove()
 316  
 317  	app, _, err := tests.CreateApp(svc)
 318  	assert.NoError(t, err)
 319  	app.Isolated = true
 320  	svc.DB.Save(&app)
 321  
 322  	appPermission := &db.AppPermission{
 323  		AppId: app.ID,
 324  		App:   *app,
 325  		Scope: constants.PAY_INVOICE_SCOPE,
 326  	}
 327  	err = svc.DB.Create(appPermission).Error
 328  	assert.NoError(t, err)
 329  
 330  	dbRequestEvent := &db.RequestEvent{}
 331  	err = svc.DB.Create(&dbRequestEvent).Error
 332  	assert.NoError(t, err)
 333  
 334  	svc.DB.Create(&db.Transaction{
 335  		AppId:      &app.ID,
 336  		State:      constants.TRANSACTION_STATE_SETTLED,
 337  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 338  		AmountMsat: 123000, // invoice is 123000 msat, but we also need a fee reserves max of(10 sats or 1%)
 339  	})
 340  
 341  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 342  	transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 343  
 344  	assert.Error(t, err)
 345  	assert.ErrorIs(t, err, NewInsufficientBalanceError())
 346  	assert.Nil(t, transaction)
 347  
 348  	svc.DB.Create(&db.Transaction{
 349  		AppId:      &app.ID,
 350  		State:      constants.TRANSACTION_STATE_SETTLED,
 351  		Type:       constants.TRANSACTION_TYPE_INCOMING,
 352  		AmountMsat: 10000, // add extra to cover fee reserves max of(10 sats or 1%)
 353  	})
 354  
 355  	transaction, err = transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
 356  
 357  	assert.NoError(t, err)
 358  	assert.Equal(t, uint64(123000), transaction.AmountMsat)
 359  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
 360  	assert.Equal(t, "123preimage", *transaction.Preimage)
 361  	assert.Equal(t, app.ID, *transaction.AppId)
 362  	assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
 363  }
 364  
 365  func TestCalculateFeeReserve(t *testing.T) {
 366  	assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(0))
 367  	assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(10_000))
 368  	assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(100_000))
 369  	assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(1000_000))
 370  	assert.Equal(t, uint64(20_000), CalculateFeeReserveMsat(2000_000))
 371  }
 372