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