notifications_test.go raw

   1  package transactions
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"testing"
   7  	"time"
   8  
   9  	"github.com/stretchr/testify/assert"
  10  	"github.com/stretchr/testify/require"
  11  
  12  	"github.com/getAlby/hub/constants"
  13  	"github.com/getAlby/hub/db"
  14  	"github.com/getAlby/hub/events"
  15  	"github.com/getAlby/hub/lnclient"
  16  	"github.com/getAlby/hub/tests"
  17  )
  18  
  19  func TestNotifications_ReceivedKnownPayment(t *testing.T) {
  20  	ctx := context.TODO()
  21  
  22  	svc, err := tests.CreateTestService(t)
  23  	require.NoError(t, err)
  24  	defer svc.Remove()
  25  
  26  	mockPreimage := tests.MockLNClientTransaction.Preimage
  27  	svc.DB.Create(&db.Transaction{
  28  		State:          constants.TRANSACTION_STATE_PENDING,
  29  		Type:           constants.TRANSACTION_TYPE_INCOMING,
  30  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
  31  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
  32  		Preimage:       &mockPreimage,
  33  		AmountMsat:     123000,
  34  	})
  35  
  36  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  37  
  38  	transactionsService.ConsumeEvent(ctx, &events.Event{
  39  		Event:      "nwc_lnclient_payment_received",
  40  		Properties: tests.MockLNClientTransaction,
  41  	}, map[string]interface{}{})
  42  
  43  	incomingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, nil, svc.LNClient, nil)
  44  	assert.NoError(t, err)
  45  	assert.Equal(t, uint64(123000), incomingTransaction.AmountMsat)
  46  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
  47  	assert.Equal(t, tests.MockLNClientTransaction.Preimage, *incomingTransaction.Preimage)
  48  	assert.Zero(t, incomingTransaction.FeeReserveMsat)
  49  
  50  	transactions := []db.Transaction{}
  51  	result := svc.DB.Find(&transactions)
  52  	assert.Equal(t, int64(1), result.RowsAffected)
  53  }
  54  
  55  func TestNotifications_ReceivedUnknownPayment(t *testing.T) {
  56  	ctx := context.TODO()
  57  
  58  	svc, err := tests.CreateTestService(t)
  59  	require.NoError(t, err)
  60  	defer svc.Remove()
  61  
  62  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  63  
  64  	transactionsService.ConsumeEvent(ctx, &events.Event{
  65  		Event:      "nwc_lnclient_payment_received",
  66  		Properties: tests.MockLNClientTransaction,
  67  	}, map[string]interface{}{})
  68  
  69  	transactionType := constants.TRANSACTION_TYPE_INCOMING
  70  	incomingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
  71  	assert.NoError(t, err)
  72  	assert.Equal(t, uint64(tests.MockLNClientTransaction.AmountMsat), incomingTransaction.AmountMsat)
  73  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
  74  	assert.Equal(t, tests.MockLNClientTransaction.Preimage, *incomingTransaction.Preimage)
  75  	assert.Zero(t, incomingTransaction.FeeReserveMsat)
  76  
  77  	transactions := []db.Transaction{}
  78  	result := svc.DB.Find(&transactions)
  79  	assert.Equal(t, int64(1), result.RowsAffected)
  80  }
  81  
  82  func TestNotifications_ReceivedKeysend(t *testing.T) {
  83  	ctx := context.TODO()
  84  
  85  	svc, err := tests.CreateTestService(t)
  86  	require.NoError(t, err)
  87  	defer svc.Remove()
  88  
  89  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
  90  
  91  	metadata := map[string]interface{}{}
  92  
  93  	metadata["tlv_records"] = []lnclient.TLVRecord{
  94  		{
  95  			Type:  7629169,
  96  			Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
  97  		},
  98  	}
  99  
 100  	transaction := &lnclient.Transaction{
 101  		Type:            "incoming",
 102  		Invoice:         tests.MockInvoice,
 103  		Description:     "",
 104  		DescriptionHash: "",
 105  		Preimage:        tests.MockLNClientTransaction.Preimage,
 106  		PaymentHash:     tests.MockLNClientTransaction.PaymentHash,
 107  		AmountMsat:      2000,
 108  		FeesPaidMsat:    75,
 109  		SettledAt:       &tests.MockTimeUnix,
 110  		Metadata:        metadata,
 111  	}
 112  
 113  	transactionsService.ConsumeEvent(ctx, &events.Event{
 114  		Event:      "nwc_lnclient_payment_received",
 115  		Properties: transaction,
 116  	}, map[string]interface{}{})
 117  
 118  	transactionType := constants.TRANSACTION_TYPE_INCOMING
 119  	incomingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
 120  	assert.NoError(t, err)
 121  	assert.Equal(t, uint64(2000), incomingTransaction.AmountMsat)
 122  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
 123  	assert.Equal(t, tests.MockLNClientTransaction.Preimage, *incomingTransaction.Preimage)
 124  	assert.Zero(t, incomingTransaction.FeeReserveMsat)
 125  
 126  	var boostagram Boostagram
 127  	err = json.Unmarshal(incomingTransaction.Boostagram, &boostagram)
 128  	assert.NoError(t, err)
 129  
 130  	assert.Equal(t, "⚡ WebLN Demo", boostagram.AppName)
 131  	assert.Equal(t, "⚡ WebLN Demo", boostagram.Name)
 132  	assert.Equal(t, "Podcasting 2.0", boostagram.Podcast)
 133  	assert.Equal(t, "Episode 104: A New Dump", boostagram.Episode.String())
 134  	assert.Equal(t, "https://feeds.podcastindex.org/pc20.xml", boostagram.FeedId.String())
 135  	assert.Equal(t, int64(21), boostagram.Timestamp)
 136  	assert.Equal(t, "Go podcasting!", boostagram.Message)
 137  	assert.Equal(t, "Satoshi Nakamoto", boostagram.SenderName)
 138  	assert.Equal(t, "boost", boostagram.Action)
 139  	assert.Equal(t, int64(1000), boostagram.ValueMsatTotal)
 140  
 141  	assert.Equal(t, "Go podcasting!", incomingTransaction.Description)
 142  
 143  	transactions := []db.Transaction{}
 144  	result := svc.DB.Find(&transactions)
 145  	assert.Equal(t, int64(1), result.RowsAffected)
 146  }
 147  
 148  func TestNotifications_SentKnownPayment(t *testing.T) {
 149  	ctx := context.TODO()
 150  
 151  	svc, err := tests.CreateTestService(t)
 152  	require.NoError(t, err)
 153  	defer svc.Remove()
 154  
 155  	svc.DB.Create(&db.Transaction{
 156  		State:          constants.TRANSACTION_STATE_PENDING,
 157  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 158  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 159  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 160  		AmountMsat:     123000,
 161  		FeeReserveMsat: uint64(10000),
 162  	})
 163  
 164  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 165  
 166  	transactionsService.ConsumeEvent(ctx, &events.Event{
 167  		Event:      "nwc_lnclient_payment_sent",
 168  		Properties: tests.MockLNClientTransaction,
 169  	}, map[string]interface{}{})
 170  
 171  	transactionType := constants.TRANSACTION_TYPE_OUTGOING
 172  	outgoingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
 173  	assert.NoError(t, err)
 174  	assert.Equal(t, uint64(123000), outgoingTransaction.AmountMsat)
 175  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, outgoingTransaction.State)
 176  	assert.Equal(t, tests.MockLNClientTransaction.Preimage, *outgoingTransaction.Preimage)
 177  	assert.Zero(t, outgoingTransaction.FeeReserveMsat)
 178  
 179  	transactions := []db.Transaction{}
 180  	result := svc.DB.Find(&transactions)
 181  	assert.Equal(t, int64(1), result.RowsAffected)
 182  }
 183  
 184  func TestNotifications_SentUnknownPayment(t *testing.T) {
 185  	ctx := context.TODO()
 186  
 187  	svc, err := tests.CreateTestService(t)
 188  	require.NoError(t, err)
 189  	defer svc.Remove()
 190  
 191  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 192  
 193  	transactions := []db.Transaction{}
 194  	result := svc.DB.Find(&transactions)
 195  	assert.Equal(t, int64(0), result.RowsAffected)
 196  
 197  	transactionsService.ConsumeEvent(ctx, &events.Event{
 198  		Event:      "nwc_lnclient_payment_sent",
 199  		Properties: tests.MockLNClientTransaction,
 200  	}, map[string]interface{}{})
 201  
 202  	transactionType := constants.TRANSACTION_TYPE_OUTGOING
 203  	outgoingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
 204  	assert.NoError(t, err)
 205  	assert.Equal(t, uint64(tests.MockLNClientTransaction.AmountMsat), outgoingTransaction.AmountMsat)
 206  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, outgoingTransaction.State)
 207  	assert.Equal(t, tests.MockLNClientTransaction.Preimage, *outgoingTransaction.Preimage)
 208  	assert.Zero(t, outgoingTransaction.FeeReserveMsat)
 209  	assert.Nil(t, outgoingTransaction.AppId)
 210  
 211  	transactions = []db.Transaction{}
 212  	result = svc.DB.Find(&transactions)
 213  	assert.Equal(t, int64(1), result.RowsAffected)
 214  }
 215  
 216  func TestNotifications_SentUnknownPaymentIdempotent(t *testing.T) {
 217  	ctx := context.TODO()
 218  
 219  	svc, err := tests.CreateTestService(t)
 220  	require.NoError(t, err)
 221  	defer svc.Remove()
 222  
 223  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 224  
 225  	for range 2 {
 226  		transactionsService.ConsumeEvent(ctx, &events.Event{
 227  			Event:      "nwc_lnclient_payment_sent",
 228  			Properties: tests.MockLNClientTransaction,
 229  		}, map[string]interface{}{})
 230  	}
 231  
 232  	transactions := []db.Transaction{}
 233  	result := svc.DB.Find(&transactions, &db.Transaction{
 234  		Type:        constants.TRANSACTION_TYPE_OUTGOING,
 235  		PaymentHash: tests.MockLNClientTransaction.PaymentHash,
 236  	})
 237  	assert.Equal(t, int64(1), result.RowsAffected)
 238  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transactions[0].State)
 239  }
 240  
 241  func TestNotifications_FailedKnownPendingPayment(t *testing.T) {
 242  	ctx := context.TODO()
 243  
 244  	svc, err := tests.CreateTestService(t)
 245  	require.NoError(t, err)
 246  	defer svc.Remove()
 247  
 248  	svc.DB.Create(&db.Transaction{
 249  		State:          constants.TRANSACTION_STATE_PENDING,
 250  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 251  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 252  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 253  		AmountMsat:     123000,
 254  		FeeReserveMsat: uint64(10000),
 255  	})
 256  
 257  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 258  
 259  	transactionsService.ConsumeEvent(ctx, &events.Event{
 260  		Event: "nwc_lnclient_payment_failed",
 261  		Properties: &lnclient.PaymentFailedEventProperties{
 262  			Transaction: tests.MockLNClientTransaction,
 263  			Reason:      "Some failure reason",
 264  		},
 265  	}, map[string]interface{}{})
 266  
 267  	transactionType := constants.TRANSACTION_TYPE_OUTGOING
 268  	outgoingTransaction, err := transactionsService.LookupTransaction(ctx, tests.MockLNClientTransaction.PaymentHash, &transactionType, svc.LNClient, nil)
 269  	assert.NoError(t, err)
 270  	assert.Equal(t, constants.TRANSACTION_STATE_FAILED, outgoingTransaction.State)
 271  	assert.Nil(t, outgoingTransaction.Preimage)
 272  	assert.Zero(t, outgoingTransaction.FeeReserveMsat)
 273  
 274  	transactions := []db.Transaction{}
 275  	result := svc.DB.Find(&transactions)
 276  	assert.Equal(t, int64(1), result.RowsAffected)
 277  }
 278  
 279  func TestNotifications_FailedKnownPendingAndExistingFailedPayment(t *testing.T) {
 280  	ctx := context.TODO()
 281  
 282  	svc, err := tests.CreateTestService(t)
 283  	require.NoError(t, err)
 284  	defer svc.Remove()
 285  
 286  	// in this test, a user tries to pay again, and the payment fails again.
 287  	// The second (pending) payment should be marked as failed.
 288  
 289  	svc.DB.Create(&db.Transaction{
 290  		State:          constants.TRANSACTION_STATE_FAILED,
 291  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 292  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 293  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 294  		AmountMsat:     123000,
 295  		FeeReserveMsat: uint64(10000),
 296  	})
 297  
 298  	svc.DB.Create(&db.Transaction{
 299  		State:          constants.TRANSACTION_STATE_PENDING,
 300  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 301  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 302  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 303  		AmountMsat:     123000,
 304  		FeeReserveMsat: uint64(10000),
 305  	})
 306  
 307  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 308  
 309  	transactionsService.ConsumeEvent(ctx, &events.Event{
 310  		Event: "nwc_lnclient_payment_failed",
 311  		Properties: &lnclient.PaymentFailedEventProperties{
 312  			Transaction: tests.MockLNClientTransaction,
 313  			Reason:      "Some failure reason",
 314  		},
 315  	}, map[string]interface{}{})
 316  
 317  	transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, svc.LNClient, nil, false, nil)
 318  	assert.NoError(t, err)
 319  	assert.Equal(t, uint64(2), totalCount)
 320  	for _, transaction := range transactions {
 321  		assert.Equal(t, constants.TRANSACTION_STATE_FAILED, transaction.State)
 322  	}
 323  }
 324  
 325  func TestNotifications_SentAfterMarkedPaymentFailed(t *testing.T) {
 326  	ctx := context.TODO()
 327  
 328  	svc, err := tests.CreateTestService(t)
 329  	require.NoError(t, err)
 330  	defer svc.Remove()
 331  
 332  	// in this test, we marked a payment as failed (for whatever reason) but then later received a payment successful event.
 333  	// The second (pending) payment should be marked as failed.
 334  
 335  	svc.DB.Create(&db.Transaction{
 336  		State:          constants.TRANSACTION_STATE_FAILED,
 337  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 338  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 339  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 340  		AmountMsat:     123000,
 341  		FeeReserveMsat: uint64(10000),
 342  	})
 343  
 344  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 345  
 346  	transactionsService.ConsumeEvent(ctx, &events.Event{
 347  		Event:      "nwc_lnclient_payment_sent",
 348  		Properties: tests.MockLNClientTransaction,
 349  	}, map[string]interface{}{})
 350  
 351  	transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, svc.LNClient, nil, false, nil)
 352  	assert.NoError(t, err)
 353  	assert.Equal(t, uint64(1), totalCount)
 354  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transactions[0].State)
 355  }
 356  
 357  func TestNotifications_SentAfterMarkedTwoPaymentsFailed(t *testing.T) {
 358  	ctx := context.TODO()
 359  
 360  	svc, err := tests.CreateTestService(t)
 361  	require.NoError(t, err)
 362  	defer svc.Remove()
 363  
 364  	// in this test, we failed to pay twice but then later received a payment successful event.
 365  	// The second (latest) failed payment should be marked as settled.
 366  
 367  	svc.DB.Create(&db.Transaction{
 368  		State:          constants.TRANSACTION_STATE_FAILED,
 369  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 370  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 371  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 372  		AmountMsat:     123000,
 373  		FeeReserveMsat: uint64(10000),
 374  		UpdatedAt:      time.Now().Add(-1 * time.Second),
 375  	})
 376  
 377  	latestFailedTransaction := &db.Transaction{
 378  		State:          constants.TRANSACTION_STATE_FAILED,
 379  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 380  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 381  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 382  		AmountMsat:     123000,
 383  		FeeReserveMsat: uint64(10000),
 384  		UpdatedAt:      time.Now(),
 385  	}
 386  	svc.DB.Create(latestFailedTransaction)
 387  
 388  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 389  
 390  	transactionsService.ConsumeEvent(ctx, &events.Event{
 391  		Event:      "nwc_lnclient_payment_sent",
 392  		Properties: tests.MockLNClientTransaction,
 393  	}, map[string]interface{}{})
 394  
 395  	transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, svc.LNClient, nil, false, nil)
 396  	assert.NoError(t, err)
 397  	assert.Equal(t, uint64(2), totalCount)
 398  	assert.Equal(t, latestFailedTransaction.ID, transactions[0].ID)
 399  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transactions[0].State)
 400  	assert.Equal(t, constants.TRANSACTION_STATE_FAILED, transactions[1].State)
 401  }
 402  
 403  func TestNotifications_SentWithFailedAndPendingPayment(t *testing.T) {
 404  	ctx := context.TODO()
 405  
 406  	svc, err := tests.CreateTestService(t)
 407  	require.NoError(t, err)
 408  	defer svc.Remove()
 409  
 410  	// in this test, we failed to pay once and retried (second attempt pending) then later received a payment successful event.
 411  	// The pending payment should be marked as settled.
 412  
 413  	svc.DB.Create(&db.Transaction{
 414  		State:          constants.TRANSACTION_STATE_FAILED,
 415  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 416  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 417  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 418  		AmountMsat:     123000,
 419  		FeeReserveMsat: uint64(10000),
 420  	})
 421  
 422  	pendingTransaction := &db.Transaction{
 423  		State:          constants.TRANSACTION_STATE_PENDING,
 424  		Type:           constants.TRANSACTION_TYPE_OUTGOING,
 425  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
 426  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
 427  		AmountMsat:     123000,
 428  		FeeReserveMsat: uint64(10000),
 429  	}
 430  	svc.DB.Create(pendingTransaction)
 431  
 432  	transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
 433  
 434  	transactionsService.ConsumeEvent(ctx, &events.Event{
 435  		Event:      "nwc_lnclient_payment_sent",
 436  		Properties: tests.MockLNClientTransaction,
 437  	}, map[string]interface{}{})
 438  
 439  	transactions, totalCount, err := transactionsService.ListTransactions(ctx, uint64(0), uint64(0), uint64(0), uint64(0), true, false, svc.LNClient, nil, false, nil)
 440  	assert.NoError(t, err)
 441  	assert.Equal(t, uint64(2), totalCount)
 442  	assert.Equal(t, pendingTransaction.ID, transactions[0].ID)
 443  	assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transactions[0].State)
 444  	assert.Equal(t, constants.TRANSACTION_STATE_FAILED, transactions[1].State)
 445  }
 446