app_payments_test.go raw
1 package transactions
2
3 import (
4 "testing"
5 "time"
6
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9
10 "github.com/getAlby/hub/constants"
11 "github.com/getAlby/hub/db"
12 "github.com/getAlby/hub/tests"
13 )
14
15 func TestSendPaymentSync_App_NoPermission(t *testing.T) {
16 svc, err := tests.CreateTestService(t)
17 require.NoError(t, err)
18 defer svc.Remove()
19
20 app, _, err := tests.CreateApp(svc)
21 assert.NoError(t, err)
22
23 dbRequestEvent := &db.RequestEvent{}
24 err = svc.DB.Create(&dbRequestEvent).Error
25 assert.NoError(t, err)
26
27 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
28 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
29
30 assert.Error(t, err)
31 assert.Equal(t, "app does not have pay_invoice scope", err.Error())
32 assert.Nil(t, transaction)
33 }
34 func TestSendPaymentSync_App_WithPermission(t *testing.T) {
35 svc, err := tests.CreateTestService(t)
36 require.NoError(t, err)
37 defer svc.Remove()
38
39 app, _, err := tests.CreateApp(svc)
40 assert.NoError(t, err)
41
42 appPermission := &db.AppPermission{
43 AppId: app.ID,
44 App: *app,
45 Scope: constants.PAY_INVOICE_SCOPE,
46 }
47 err = svc.DB.Create(appPermission).Error
48 assert.NoError(t, err)
49
50 dbRequestEvent := &db.RequestEvent{}
51 err = svc.DB.Create(&dbRequestEvent).Error
52 assert.NoError(t, err)
53
54 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
55 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
56
57 assert.NoError(t, err)
58 assert.Equal(t, uint64(123000), transaction.AmountMsat)
59 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
60 assert.Equal(t, "123preimage", *transaction.Preimage)
61 assert.Equal(t, app.ID, *transaction.AppId)
62 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
63 }
64
65 func TestMarkSettled_App_BudgetWarning(t *testing.T) {
66 svc, err := tests.CreateTestService(t)
67 require.NoError(t, err)
68 defer svc.Remove()
69
70 app, _, err := tests.CreateApp(svc)
71 assert.NoError(t, err)
72
73 appPermission := &db.AppPermission{
74 AppId: app.ID,
75 App: *app,
76 Scope: constants.PAY_INVOICE_SCOPE,
77 MaxAmountSat: 100,
78 }
79 err = svc.DB.Create(appPermission).Error
80 assert.NoError(t, err)
81
82 // settling this payment pushes the app over 80% of its 100 sat budget
83 dbTransaction := db.Transaction{
84 AppId: &app.ID,
85 State: constants.TRANSACTION_STATE_PENDING,
86 Type: constants.TRANSACTION_TYPE_OUTGOING,
87 PaymentHash: tests.MockLNClientTransaction.PaymentHash,
88 AmountMsat: 90000,
89 }
90 svc.DB.Create(&dbTransaction)
91
92 mockEventConsumer := tests.NewMockEventConsumer()
93 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
94 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
95 _, err = transactionsService.markTransactionSettled(&dbTransaction, "test", 0, false)
96
97 assert.NoError(t, err)
98 consumedEvents := mockEventConsumer.WaitForConsumedEvents(2)
99 assert.Equal(t, 2, len(consumedEvents))
100 eventNames := []string{}
101 for _, consumedEvent := range consumedEvents {
102 eventNames = append(eventNames, consumedEvent.Event)
103 }
104 assert.Contains(t, eventNames, "nwc_payment_sent")
105 assert.Contains(t, eventNames, "nwc_budget_warning")
106 }
107
108 func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) {
109 svc, err := tests.CreateTestService(t)
110 require.NoError(t, err)
111 defer svc.Remove()
112
113 app, _, err := tests.CreateApp(svc)
114 assert.NoError(t, err)
115
116 appPermission := &db.AppPermission{
117 AppId: app.ID,
118 App: *app,
119 Scope: constants.PAY_INVOICE_SCOPE,
120 MaxAmountSat: 1,
121 }
122 err = svc.DB.Create(appPermission).Error
123 assert.NoError(t, err)
124
125 dbRequestEvent := &db.RequestEvent{}
126 err = svc.DB.Create(&dbRequestEvent).Error
127 assert.NoError(t, err)
128
129 mockEventConsumer := tests.NewMockEventConsumer()
130 svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
131
132 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
133 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
134
135 assert.Error(t, err)
136 assert.ErrorIs(t, err, NewQuotaExceededError())
137 assert.Nil(t, transaction)
138
139 consumedEvents := mockEventConsumer.WaitForConsumedEvents(1)
140 assert.Equal(t, 1, len(consumedEvents))
141 assert.Equal(t, "nwc_permission_denied", consumedEvents[0].Event)
142 assert.Equal(t, app.Name, consumedEvents[0].Properties.(map[string]interface{})["app_name"])
143 assert.Equal(t, constants.ERROR_QUOTA_EXCEEDED, consumedEvents[0].Properties.(map[string]interface{})["code"])
144 expectedMessage := NewQuotaExceededError().Error() + " te" // invoice description is "te" in the mock invoice
145 assert.Equal(t, expectedMessage, consumedEvents[0].Properties.(map[string]interface{})["message"])
146 }
147
148 func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) {
149 svc, err := tests.CreateTestService(t)
150 require.NoError(t, err)
151 defer svc.Remove()
152
153 app, _, err := tests.CreateApp(svc)
154 assert.NoError(t, err)
155
156 appPermission := &db.AppPermission{
157 AppId: app.ID,
158 App: *app,
159 Scope: constants.PAY_INVOICE_SCOPE,
160 MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
161 }
162 err = svc.DB.Create(appPermission).Error
163 assert.NoError(t, err)
164
165 // 1 sat payment pushes app over the limit
166 svc.DB.Create(&db.Transaction{
167 AppId: &app.ID,
168 State: constants.TRANSACTION_STATE_SETTLED,
169 Type: constants.TRANSACTION_TYPE_OUTGOING,
170 AmountMsat: 1000,
171 CreatedAt: time.Now(),
172 })
173
174 dbRequestEvent := &db.RequestEvent{}
175 err = svc.DB.Create(&dbRequestEvent).Error
176 assert.NoError(t, err)
177
178 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
179 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
180
181 assert.Error(t, err)
182 assert.ErrorIs(t, err, NewQuotaExceededError())
183 assert.Nil(t, transaction)
184 }
185 func TestSendPaymentSync_App_BudgetExceeded_UnsettledPayment(t *testing.T) {
186 svc, err := tests.CreateTestService(t)
187 require.NoError(t, err)
188 defer svc.Remove()
189
190 app, _, err := tests.CreateApp(svc)
191 assert.NoError(t, err)
192
193 appPermission := &db.AppPermission{
194 AppId: app.ID,
195 App: *app,
196 Scope: constants.PAY_INVOICE_SCOPE,
197 MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
198 }
199 err = svc.DB.Create(appPermission).Error
200 assert.NoError(t, err)
201
202 // 1 sat payment pushes app over the limit
203 svc.DB.Create(&db.Transaction{
204 AppId: &app.ID,
205 State: constants.TRANSACTION_STATE_PENDING,
206 Type: constants.TRANSACTION_TYPE_OUTGOING,
207 AmountMsat: 1000,
208 CreatedAt: time.Now(),
209 })
210
211 dbRequestEvent := &db.RequestEvent{}
212 err = svc.DB.Create(&dbRequestEvent).Error
213 assert.NoError(t, err)
214
215 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
216 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
217
218 assert.Error(t, err)
219 assert.ErrorIs(t, err, NewQuotaExceededError())
220 assert.Nil(t, transaction)
221 }
222
223 func TestSendPaymentSync_App_BudgetNotExceeded_FailedPayment(t *testing.T) {
224 svc, err := tests.CreateTestService(t)
225 require.NoError(t, err)
226 defer svc.Remove()
227
228 app, _, err := tests.CreateApp(svc)
229 assert.NoError(t, err)
230
231 appPermission := &db.AppPermission{
232 AppId: app.ID,
233 App: *app,
234 Scope: constants.PAY_INVOICE_SCOPE,
235 MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
236 }
237 err = svc.DB.Create(appPermission).Error
238 assert.NoError(t, err)
239
240 // 1 sat payment would push app over the limit, but it failed so its not counted
241 svc.DB.Create(&db.Transaction{
242 AppId: &app.ID,
243 State: constants.TRANSACTION_STATE_FAILED,
244 Type: constants.TRANSACTION_TYPE_OUTGOING,
245 AmountMsat: 1000,
246 CreatedAt: time.Now(),
247 })
248
249 dbRequestEvent := &db.RequestEvent{}
250 err = svc.DB.Create(&dbRequestEvent).Error
251 assert.NoError(t, err)
252
253 transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
254 transaction, err := transactionsService.SendPaymentSync(tests.MockLNClientTransaction.Invoice, nil, nil, svc.LNClient, &app.ID, &dbRequestEvent.ID)
255
256 assert.NoError(t, err)
257 assert.Equal(t, uint64(123000), transaction.AmountMsat)
258 assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
259 assert.Equal(t, "123preimage", *transaction.Preimage)
260 assert.Equal(t, app.ID, *transaction.AppId)
261 assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
262 }
263