diff --git a/tests/mock_event_consumer.go b/tests/mock_event_consumer.go index a23cd53b..1eceb1c4 100644 --- a/tests/mock_event_consumer.go +++ b/tests/mock_event_consumer.go @@ -2,12 +2,14 @@ package tests import ( "context" + "sync" "time" "github.com/getAlby/hub/events" ) type mockEventConsumer struct { + mtx sync.Mutex consumedEvents []*events.Event } @@ -18,11 +20,33 @@ func NewMockEventConsumer() *mockEventConsumer { } func (e *mockEventConsumer) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) { + e.mtx.Lock() + defer e.mtx.Unlock() e.consumedEvents = append(e.consumedEvents, event) } func (e *mockEventConsumer) GetConsumedEvents() []*events.Event { // events are consumed async - give it a bit of time for tests time.Sleep(10 * time.Millisecond) - return e.consumedEvents + return e.snapshotConsumedEvents() +} + +// WaitForConsumedEvents waits until at least count events have been consumed +// (events are consumed async) and returns them. On timeout it returns the +// events consumed so far, so the caller's assertions fail with a useful message. +func (e *mockEventConsumer) WaitForConsumedEvents(count int) []*events.Event { + deadline := time.Now().Add(5 * time.Second) + for { + consumedEvents := e.snapshotConsumedEvents() + if len(consumedEvents) >= count || time.Now().After(deadline) { + return consumedEvents + } + time.Sleep(10 * time.Millisecond) + } +} + +func (e *mockEventConsumer) snapshotConsumedEvents() []*events.Event { + e.mtx.Lock() + defer e.mtx.Unlock() + return append([]*events.Event{}, e.consumedEvents...) } diff --git a/transactions/app_payments_test.go b/transactions/app_payments_test.go index b989407a..bc3da8bc 100644 --- a/transactions/app_payments_test.go +++ b/transactions/app_payments_test.go @@ -95,7 +95,7 @@ func TestMarkSettled_App_BudgetWarning(t *testing.T) { _, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false) assert.NoError(t, err) - consumedEvents := mockEventConsumer.GetConsumedEvents() + consumedEvents := mockEventConsumer.WaitForConsumedEvents(2) assert.Equal(t, 2, len(consumedEvents)) eventNames := []string{} for _, consumedEvent := range consumedEvents { @@ -136,12 +136,13 @@ func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) { assert.ErrorIs(t, err, NewQuotaExceededError()) assert.Nil(t, transaction) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_permission_denied", mockEventConsumer.GetConsumedEvents()[0].Event) - assert.Equal(t, app.Name, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["app_name"]) - assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["code"]) + 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, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["message"]) + assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"]) } func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) { diff --git a/transactions/check_unsettled_transaction_test.go b/transactions/check_unsettled_transaction_test.go index 1cdff0e9..db736b22 100644 --- a/transactions/check_unsettled_transaction_test.go +++ b/transactions/check_unsettled_transaction_test.go @@ -45,9 +45,10 @@ func TestCheckUnsettledTransaction(t *testing.T) { assert.NoError(t, err) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, &dbTransaction, settledTransaction) } @@ -91,8 +92,9 @@ func TestCheckUnsettledTransactions(t *testing.T) { }) assert.NoError(t, err) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, dbTransaction.ID, settledTransaction.ID) } diff --git a/transactions/isolated_app_payments_test.go b/transactions/isolated_app_payments_test.go index e9a3e8e2..bb680b5a 100644 --- a/transactions/isolated_app_payments_test.go +++ b/transactions/isolated_app_payments_test.go @@ -80,12 +80,13 @@ func TestSendPaymentSync_IsolatedApp_BalanceInsufficient(t *testing.T) { assert.ErrorIs(t, err, NewInsufficientBalanceError()) assert.Nil(t, transaction) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_permission_denied", mockEventConsumer.GetConsumedEvents()[0].Event) - assert.Equal(t, app.Name, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["app_name"]) - assert.Equal(t, constants.ERROR_INSUFFICIENT_BALANCE, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["code"]) + 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_INSUFFICIENT_BALANCE, consumedEvents[0].Properties.(map[string]interface{})["code"]) expectedMessage := NewInsufficientBalanceError().Error() + " te" // invoice description is "te" in the mock invoice - assert.Equal(t, expectedMessage, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["message"]) + assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"]) } func TestSendPaymentSync_IsolatedApp_BalanceSufficient(t *testing.T) { diff --git a/transactions/keysend_test.go b/transactions/keysend_test.go index ba50db49..6fe1f27e 100644 --- a/transactions/keysend_test.go +++ b/transactions/keysend_test.go @@ -43,9 +43,10 @@ func TestSendKeysend(t *testing.T) { assert.NotNil(t, transaction.Preimage) assert.Equal(t, 64, len(*transaction.Preimage)) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, transaction, settledTransaction) } func TestSendKeysend_FailedRemovesFeeReserve(t *testing.T) { @@ -72,8 +73,9 @@ func TestSendKeysend_FailedRemovesFeeReserve(t *testing.T) { assert.Zero(t, failedTransaction.FeeReserveMsat) assert.Equal(t, "Some error", failedTransaction.FailureReason) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_failed", mockEventConsumer.GetConsumedEvents()[0].Event) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event) } func TestSendKeysend_CustomPreimage(t *testing.T) { @@ -190,11 +192,12 @@ func TestSendKeysend_App_BudgetExceeded(t *testing.T) { assert.ErrorIs(t, err, NewQuotaExceededError()) assert.Nil(t, transaction) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_permission_denied", mockEventConsumer.GetConsumedEvents()[0].Event) - assert.Equal(t, app.Name, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["app_name"]) - assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["code"]) - assert.Equal(t, NewQuotaExceededError().Error(), mockEventConsumer.GetConsumedEvents()[0].Properties.(map[string]interface{})["message"]) + 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"]) + assert.Equal(t, NewQuotaExceededError().Error(), consumedEvents[0].Properties.(map[string]interface{})["message"]) } func TestSendKeysend_App_BudgetNotExceeded(t *testing.T) { svc, err := tests.CreateTestService(t) @@ -530,13 +533,20 @@ func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) { assert.Equal(t, int64(123000), balanceMsat) // check notifications - assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents())) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(2) + assert.Equal(t, 2, len(consumedEvents)) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[1].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[1].Properties.(*db.Transaction) + // we can't guarantee which notification was processed first because events are published async + // so swap them if they are back to front + if consumedEvents[1].Event == "nwc_payment_received" { + consumedEvents[0], consumedEvents[1] = consumedEvents[1], consumedEvents[0] + } + + assert.Equal(t, "nwc_payment_sent", consumedEvents[1].Event) + settledTransaction := consumedEvents[1].Properties.(*db.Transaction) assert.Equal(t, transaction.ID, settledTransaction.ID) - assert.Equal(t, "nwc_payment_received", mockEventConsumer.GetConsumedEvents()[0].Event) - receivedTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + assert.Equal(t, "nwc_payment_received", consumedEvents[0].Event) + receivedTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, incomingTransaction.ID, receivedTransaction.ID) } diff --git a/transactions/payments_test.go b/transactions/payments_test.go index ef8a5037..5fa8fe71 100644 --- a/transactions/payments_test.go +++ b/transactions/payments_test.go @@ -184,9 +184,10 @@ func TestMarkSettled_Sent(t *testing.T) { assert.NoError(t, err) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, &dbTransaction, settledTransaction) } @@ -233,9 +234,10 @@ func TestMarkSettled_Twice(t *testing.T) { var reloadedTransaction db.Transaction require.NoError(t, svc.DB.First(&reloadedTransaction, dbTransaction.ID).Error) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, reloadedTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_sent", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_sent", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, settledTransaction.State) assert.Equal(t, dbTransaction.PaymentHash, settledTransaction.PaymentHash) } @@ -260,9 +262,10 @@ func TestMarkSettled_Received(t *testing.T) { assert.NoError(t, err) assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, dbTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_received", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_received", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, &dbTransaction, settledTransaction) } @@ -311,9 +314,10 @@ func TestMarkFailed(t *testing.T) { assert.NoError(t, err) assert.True(t, markedFailed) assert.Equal(t, constants.TRANSACTION_STATE_FAILED, dbTransaction.State) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_failed", mockEventConsumer.GetConsumedEvents()[0].Event) - settledTransaction := mockEventConsumer.GetConsumedEvents()[0].Properties.(*db.Transaction) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event) + settledTransaction := consumedEvents[0].Properties.(*db.Transaction) assert.Equal(t, &dbTransaction, settledTransaction) assert.Equal(t, "some routing error", settledTransaction.FailureReason) } @@ -399,8 +403,9 @@ func TestSendPaymentSync_FailedRemovesFeeReserve(t *testing.T) { assert.Zero(t, transaction.FeeReserveMsat) assert.Nil(t, transaction.Preimage) - assert.Equal(t, 1, len(mockEventConsumer.GetConsumedEvents())) - assert.Equal(t, "nwc_payment_failed", mockEventConsumer.GetConsumedEvents()[0].Event) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(1) + assert.Equal(t, 1, len(consumedEvents)) + assert.Equal(t, "nwc_payment_failed", consumedEvents[0].Event) } func TestSendPaymentSync_PendingHasFeeReserve(t *testing.T) { diff --git a/transactions/self_payments_test.go b/transactions/self_payments_test.go index 0754bea5..97102f0c 100644 --- a/transactions/self_payments_test.go +++ b/transactions/self_payments_test.go @@ -399,11 +399,11 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) { assert.Equal(t, int64(0), balanceMsat) // check notifications - assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents())) + consumedEvents := mockEventConsumer.WaitForConsumedEvents(2) + assert.Equal(t, 2, len(consumedEvents)) // we can't guarantee which notification was processed first because events are published async // so swap them if they are back to front - consumedEvents := mockEventConsumer.GetConsumedEvents() if consumedEvents[1].Event == "nwc_payment_received" { consumedEvents[0], consumedEvents[1] = consumedEvents[1], consumedEvents[0] }