mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
The mock event consumer waited a fixed 10ms before returning consumed events, which was not always enough on slow CI runners and caused flaky failures (e.g. TestMarkSettled_App_BudgetWarning missing its nwc_budget_warning event). It also appended to the events slice from concurrent goroutines without synchronization, a data race that could drop events. - guard the consumed events slice with a mutex and return copies - add WaitForConsumedEvents which polls until the expected number of events arrived (up to 5s) instead of relying on a fixed sleep - use it in tests that assert on consumed events; tests asserting that no event was published keep the short grace period - normalize event order in the keysend self-payment test, matching the existing approach in the self-payment test, since async publishing does not guarantee ordering Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
262 lines
8.6 KiB
Go
262 lines
8.6 KiB
Go
package transactions
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/getAlby/hub/constants"
|
|
"github.com/getAlby/hub/db"
|
|
"github.com/getAlby/hub/tests"
|
|
)
|
|
|
|
func TestSendPaymentSync_App_NoPermission(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "app does not have pay_invoice scope", err.Error())
|
|
assert.Nil(t, transaction)
|
|
}
|
|
func TestSendPaymentSync_App_WithPermission(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, "123preimage", *transaction.Preimage)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
}
|
|
|
|
func TestMarkSettled_App_BudgetWarning(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 100,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// settling this payment pushes the app over 80% of its 100 sat budget
|
|
dbTransaction := db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_PENDING,
|
|
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
|
PaymentHash: tests.MockLNClientTransaction.PaymentHash,
|
|
AmountMsat: 90000,
|
|
}
|
|
svc.DB.Create(&dbTransaction)
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
_, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
|
|
|
|
assert.NoError(t, err)
|
|
consumedEvents := mockEventConsumer.WaitForConsumedEvents(2)
|
|
assert.Equal(t, 2, len(consumedEvents))
|
|
eventNames := []string{}
|
|
for _, consumedEvent := range consumedEvents {
|
|
eventNames = append(eventNames, consumedEvent.Event)
|
|
}
|
|
assert.Contains(t, eventNames, "nwc_payment_sent")
|
|
assert.Contains(t, eventNames, "nwc_budget_warning")
|
|
}
|
|
|
|
func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 1,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, NewQuotaExceededError())
|
|
assert.Nil(t, transaction)
|
|
|
|
consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
|
|
assert.Equal(t, 1, len(consumedEvents))
|
|
assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
|
|
assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
|
|
assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, consumedEvents[0].Properties.(map[string]interface{})["code"])
|
|
expectedMessage := NewQuotaExceededError().Error() + " te" // invoice description is "te" in the mock invoice
|
|
assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"])
|
|
}
|
|
|
|
func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// 1 sat payment pushes app over the limit
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_SETTLED,
|
|
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
|
AmountMsat: 1000,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, NewQuotaExceededError())
|
|
assert.Nil(t, transaction)
|
|
}
|
|
func TestSendPaymentSync_App_BudgetExceeded_UnsettledPayment(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// 1 sat payment pushes app over the limit
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_PENDING,
|
|
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
|
AmountMsat: 1000,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.Error(t, err)
|
|
assert.ErrorIs(t, err, NewQuotaExceededError())
|
|
assert.Nil(t, transaction)
|
|
}
|
|
|
|
func TestSendPaymentSync_App_BudgetNotExceeded_FailedPayment(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// 1 sat payment would push app over the limit, but it failed so its not counted
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_FAILED,
|
|
Type: constants.TRANSACTION_TYPE_OUTGOING,
|
|
AmountMsat: 1000,
|
|
CreatedAt: time.Now(),
|
|
})
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, "123preimage", *transaction.Preimage)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
}
|