isolated_app_payments_test.go raw
1 package transactions
2
3 import (
4 "testing"
5
6 "github.com/stretchr/testify/assert"
7 "github.com/stretchr/testify/require"
8
9 "github.com/getAlby/hub/constants"
10 "github.com/getAlby/hub/db"
11 "github.com/getAlby/hub/tests"
12 )
13
14 func TestSendPaymentSync_IsolatedApp_NoBalance(t *testing.T) {
15 svc, err := tests.CreateTestService(t)
16 require.NoError(t, err)
17 defer svc.Remove()
18
19 app, _, err := tests.CreateApp(svc)
20 assert.NoError(t, err)
21 app.Isolated = true
22 svc.DB.Save(&app)
23
24 appPermission := &db.AppPermission{
25 AppId: app.ID,
26 App: *app,
27 Scope: constants.PAY_INVOICE_SCOPE,
28 }
29 err = svc.DB.Create(appPermission).Error
30 assert.NoError(t, err)
31
32 dbRequestEvent := &db.RequestEvent{}
33 err = svc.DB.Create(&dbRequestEvent).Error
34 assert.NoError(t, err)
35
36 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
37 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
38
39 assert.Error(t, err)
40 assert.ErrorIs(t, err, NewInsufficientBalanceError())
41 assert.Nil(t, transaction)
42 }
43
44 func TestSendPaymentSync_IsolatedApp_BalanceInsufficient(t *testing.T) {
45 svc, err := tests.CreateTestService(t)
46 require.NoError(t, err)
47 defer svc.Remove()
48
49 app, _, err := tests.CreateApp(svc)
50 assert.NoError(t, err)
51 app.Isolated = true
52 svc.DB.Save(&app)
53
54 appPermission := &db.AppPermission{
55 AppId: app.ID,
56 App: *app,
57 Scope: constants.PAY_INVOICE_SCOPE,
58 }
59 err = svc.DB.Create(appPermission).Error
60 assert.NoError(t, err)
61
62 dbRequestEvent := &db.RequestEvent{}
63 err = svc.DB.Create(&dbRequestEvent).Error
64 assert.NoError(t, err)
65
66 svc.DB.Create(&db.Transaction{
67 AppId: &app.ID,
68 State: constants.TRANSACTION_STATE_SETTLED,
69 Type: constants.TRANSACTION_TYPE_INCOMING,
70 AmountMsat: 132000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
71 })
72
73 mockEventConsumer := tests.NewMockEventConsumer()
74 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
75
76 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
77 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
78
79 assert.Error(t, err)
80 assert.ErrorIs(t, err, NewInsufficientBalanceError())
81 assert.Nil(t, transaction)
82
83 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
84 assert.Equal(t, 1, len(consumedEvents))
85 assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
86 assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
87 assert.Equal(t, constants.ERROR_INSUFFICIENT_BALANCE, consumedEvents[0].Properties.(map[string]interface{})["code"])
88 expectedMessage := NewInsufficientBalanceError().Error() + " te" // invoice description is "te" in the mock invoice
89 assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"])
90 }
91
92 func TestSendPaymentSync_IsolatedApp_BalanceSufficient(t *testing.T) {
93 svc, err := tests.CreateTestService(t)
94 require.NoError(t, err)
95 defer svc.Remove()
96
97 app, _, err := tests.CreateApp(svc)
98 assert.NoError(t, err)
99 app.Isolated = true
100 svc.DB.Save(&app)
101
102 appPermission := &db.AppPermission{
103 AppId: app.ID,
104 App: *app,
105 Scope: constants.PAY_INVOICE_SCOPE,
106 }
107 err = svc.DB.Create(appPermission).Error
108 assert.NoError(t, err)
109
110 dbRequestEvent := &db.RequestEvent{}
111 err = svc.DB.Create(&dbRequestEvent).Error
112 assert.NoError(t, err)
113
114 svc.DB.Create(&db.Transaction{
115 AppId: &app.ID,
116 State: constants.TRANSACTION_STATE_SETTLED,
117 Type: constants.TRANSACTION_TYPE_INCOMING,
118 AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
119 })
120
121 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
122 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
123
124 assert.NoError(t, err)
125 assert.Equal(t, uint64(123000), transaction.AmountMsat)
126 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
127 assert.Equal(t, "123preimage", *transaction.Preimage)
128 assert.Equal(t, app.ID, *transaction.AppId)
129 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
130 }
131
132 func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_OutstandingPayment(t *testing.T) {
133 svc, err := tests.CreateTestService(t)
134 require.NoError(t, err)
135 defer svc.Remove()
136
137 app, _, err := tests.CreateApp(svc)
138 assert.NoError(t, err)
139 app.Isolated = true
140 svc.DB.Save(&app)
141
142 appPermission := &db.AppPermission{
143 AppId: app.ID,
144 App: *app,
145 Scope: constants.PAY_INVOICE_SCOPE,
146 }
147 err = svc.DB.Create(appPermission).Error
148 assert.NoError(t, err)
149
150 dbRequestEvent := &db.RequestEvent{}
151 err = svc.DB.Create(&dbRequestEvent).Error
152 assert.NoError(t, err)
153
154 svc.DB.Create(&db.Transaction{
155 AppId: &app.ID,
156 State: constants.TRANSACTION_STATE_SETTLED,
157 Type: constants.TRANSACTION_TYPE_INCOMING,
158 AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
159 })
160
161 svc.DB.Create(&db.Transaction{
162 AppId: &app.ID,
163 State: constants.TRANSACTION_STATE_PENDING,
164 Type: constants.TRANSACTION_TYPE_OUTGOING,
165 AmountMsat: 1000,
166 })
167
168 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
169 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
170
171 assert.Error(t, err)
172 assert.ErrorIs(t, err, NewInsufficientBalanceError())
173 assert.Nil(t, transaction)
174 }
175
176 func TestSendPaymentSync_IsolatedApp_BalanceInsufficient_SettledPayment(t *testing.T) {
177 svc, err := tests.CreateTestService(t)
178 require.NoError(t, err)
179 defer svc.Remove()
180
181 app, _, err := tests.CreateApp(svc)
182 assert.NoError(t, err)
183 app.Isolated = true
184 svc.DB.Save(&app)
185
186 appPermission := &db.AppPermission{
187 AppId: app.ID,
188 App: *app,
189 Scope: constants.PAY_INVOICE_SCOPE,
190 }
191 err = svc.DB.Create(appPermission).Error
192 assert.NoError(t, err)
193
194 dbRequestEvent := &db.RequestEvent{}
195 err = svc.DB.Create(&dbRequestEvent).Error
196 assert.NoError(t, err)
197
198 svc.DB.Create(&db.Transaction{
199 AppId: &app.ID,
200 State: constants.TRANSACTION_STATE_SETTLED,
201 Type: constants.TRANSACTION_TYPE_INCOMING,
202 AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
203 })
204
205 svc.DB.Create(&db.Transaction{
206 AppId: &app.ID,
207 State: constants.TRANSACTION_STATE_SETTLED,
208 Type: constants.TRANSACTION_TYPE_OUTGOING,
209 AmountMsat: 1000,
210 })
211
212 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
213 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
214
215 assert.Error(t, err)
216 assert.ErrorIs(t, err, NewInsufficientBalanceError())
217 assert.Nil(t, transaction)
218 }
219
220 func TestSendPaymentSync_IsolatedApp_BalanceSufficient_UnrelatedPayment(t *testing.T) {
221 svc, err := tests.CreateTestService(t)
222 require.NoError(t, err)
223 defer svc.Remove()
224
225 app, _, err := tests.CreateApp(svc)
226 assert.NoError(t, err)
227 app.Isolated = true
228 svc.DB.Save(&app)
229
230 appPermission := &db.AppPermission{
231 AppId: app.ID,
232 App: *app,
233 Scope: constants.PAY_INVOICE_SCOPE,
234 }
235 err = svc.DB.Create(appPermission).Error
236 assert.NoError(t, err)
237
238 dbRequestEvent := &db.RequestEvent{}
239 err = svc.DB.Create(&dbRequestEvent).Error
240 assert.NoError(t, err)
241
242 svc.DB.Create(&db.Transaction{
243 AppId: &app.ID,
244 State: constants.TRANSACTION_STATE_SETTLED,
245 Type: constants.TRANSACTION_TYPE_INCOMING,
246 AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
247 })
248 svc.DB.Create(&db.Transaction{
249 AppId: nil, // unrelated to this app
250 State: constants.TRANSACTION_STATE_SETTLED,
251 Type: constants.TRANSACTION_TYPE_OUTGOING,
252 AmountMsat: 1000,
253 })
254
255 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
256 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
257
258 assert.NoError(t, err)
259 assert.Equal(t, uint64(123000), transaction.AmountMsat)
260 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
261 assert.Equal(t, "123preimage", *transaction.Preimage)
262 assert.Equal(t, app.ID, *transaction.AppId)
263 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
264 }
265
266 func TestSendPaymentSync_IsolatedApp_BalanceSufficient_FailedPayment(t *testing.T) {
267 svc, err := tests.CreateTestService(t)
268 require.NoError(t, err)
269 defer svc.Remove()
270
271 app, _, err := tests.CreateApp(svc)
272 assert.NoError(t, err)
273 app.Isolated = true
274 svc.DB.Save(&app)
275
276 appPermission := &db.AppPermission{
277 AppId: app.ID,
278 App: *app,
279 Scope: constants.PAY_INVOICE_SCOPE,
280 }
281 err = svc.DB.Create(appPermission).Error
282 assert.NoError(t, err)
283
284 dbRequestEvent := &db.RequestEvent{}
285 err = svc.DB.Create(&dbRequestEvent).Error
286 assert.NoError(t, err)
287
288 svc.DB.Create(&db.Transaction{
289 AppId: &app.ID,
290 State: constants.TRANSACTION_STATE_SETTLED,
291 Type: constants.TRANSACTION_TYPE_INCOMING,
292 AmountMsat: 133000, // invoice is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
293 })
294 svc.DB.Create(&db.Transaction{
295 AppId: &app.ID,
296 State: constants.TRANSACTION_STATE_FAILED,
297 Type: constants.TRANSACTION_TYPE_OUTGOING,
298 AmountMsat: 1000,
299 })
300
301 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
302 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
303
304 assert.NoError(t, err)
305 assert.Equal(t, uint64(123000), transaction.AmountMsat)
306 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
307 assert.Equal(t, "123preimage", *transaction.Preimage)
308 assert.Equal(t, app.ID, *transaction.AppId)
309 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
310 }
311
312 func TestSendPaymentSync_IsolatedApp_BalanceInsufficientThenSufficient(t *testing.T) {
313 svc, err := tests.CreateTestService(t)
314 require.NoError(t, err)
315 defer svc.Remove()
316
317 app, _, err := tests.CreateApp(svc)
318 assert.NoError(t, err)
319 app.Isolated = true
320 svc.DB.Save(&app)
321
322 appPermission := &db.AppPermission{
323 AppId: app.ID,
324 App: *app,
325 Scope: constants.PAY_INVOICE_SCOPE,
326 }
327 err = svc.DB.Create(appPermission).Error
328 assert.NoError(t, err)
329
330 dbRequestEvent := &db.RequestEvent{}
331 err = svc.DB.Create(&dbRequestEvent).Error
332 assert.NoError(t, err)
333
334 svc.DB.Create(&db.Transaction{
335 AppId: &app.ID,
336 State: constants.TRANSACTION_STATE_SETTLED,
337 Type: constants.TRANSACTION_TYPE_INCOMING,
338 AmountMsat: 123000, // invoice is 123000 msat, but we also need a fee reserves max of(10 sats or 1%)
339 })
340
341 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
342 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
343
344 assert.Error(t, err)
345 assert.ErrorIs(t, err, NewInsufficientBalanceError())
346 assert.Nil(t, transaction)
347
348 svc.DB.Create(&db.Transaction{
349 AppId: &app.ID,
350 State: constants.TRANSACTION_STATE_SETTLED,
351 Type: constants.TRANSACTION_TYPE_INCOMING,
352 AmountMsat: 10000, // add extra to cover fee reserves max of(10 sats or 1%)
353 })
354
355 transaction, err = transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
356
357 assert.NoError(t, err)
358 assert.Equal(t, uint64(123000), transaction.AmountMsat)
359 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
360 assert.Equal(t, "123preimage", *transaction.Preimage)
361 assert.Equal(t, app.ID, *transaction.AppId)
362 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
363 }
364
365 func TestCalculateFeeReserve(t *testing.T) {
366 assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(0))
367 assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(10_000))
368 assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(100_000))
369 assert.Equal(t, uint64(10_000), CalculateFeeReserveMsat(1000_000))
370 assert.Equal(t, uint64(20_000), CalculateFeeReserveMsat(2000_000))
371 }
372