mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
fix: make event assertions in tests wait for async event consumption
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>
This commit is contained in:
parent
037765794d
commit
1cc3a31c33
7 changed files with 92 additions and 49 deletions
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue