mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
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>
552 lines
21 KiB
Go
552 lines
21 KiB
Go
package transactions
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/getAlby/hub/constants"
|
|
"github.com/getAlby/hub/db"
|
|
"github.com/getAlby/hub/db/queries"
|
|
"github.com/getAlby/hub/lnclient"
|
|
"github.com/getAlby/hub/tests"
|
|
)
|
|
|
|
func TestSendKeysend(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, nil, nil)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.Nil(t, metadata["tlv_records"])
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, 64, len(*transaction.Preimage))
|
|
|
|
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) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
svc.LNClient.(*tests.MockLn).PayKeysendErrors = append(svc.LNClient.(*tests.MockLn).PayKeysendErrors, errors.New("Some error"))
|
|
svc.LNClient.(*tests.MockLn).PayKeysendResponses = append(svc.LNClient.(*tests.MockLn).PayKeysendResponses, nil)
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, nil, nil)
|
|
|
|
assert.Error(t, err)
|
|
assert.Nil(t, transaction)
|
|
|
|
failedTransaction := db.Transaction{}
|
|
require.NoError(t, svc.DB.Where("type = ? AND state = ?", constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_FAILED).First(&failedTransaction).Error)
|
|
|
|
assert.Equal(t, uint64(1000), failedTransaction.AmountMsat)
|
|
assert.Zero(t, failedTransaction.FeeReserveMsat)
|
|
assert.Equal(t, "Some error", failedTransaction.FailureReason)
|
|
|
|
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) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
customPreimage := "018465013e2337234a7e5530a21c4a8cf70d84231f4a8ff0b1e2cce3cb2bd03b"
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, customPreimage, svc.LNClient, nil, nil)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.Nil(t, metadata["tlv_records"])
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, customPreimage, *transaction.Preimage)
|
|
}
|
|
|
|
func TestSendKeysend_App_NoPermission(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "app does not have pay_invoice scope", err.Error())
|
|
assert.Nil(t, transaction)
|
|
}
|
|
|
|
func TestSendKeysend_App_WithPermission(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.Nil(t, metadata["tlv_records"])
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, 64, len(*transaction.Preimage))
|
|
}
|
|
|
|
func TestSendKeysend_App_BudgetExceeded(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 10, // not enough for the fee reserve
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.ErrorIs(t, err, NewQuotaExceededError())
|
|
assert.Nil(t, transaction)
|
|
|
|
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)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
MaxAmountSat: 11, // fee reserve (10) + keysend amount (1)
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.Nil(t, metadata["tlv_records"])
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, 64, len(*transaction.Preimage))
|
|
}
|
|
|
|
func TestSendKeysend_App_BalanceExceeded(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
app.Isolated = true
|
|
svc.DB.Save(&app)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_SETTLED,
|
|
Type: constants.TRANSACTION_TYPE_INCOMING,
|
|
AmountMsat: 10000, // invoice is 1000 msat, but we also calculate fee reserves max of(10 sats or 1%)
|
|
})
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.ErrorIs(t, err, NewInsufficientBalanceError())
|
|
assert.Nil(t, transaction)
|
|
}
|
|
|
|
func TestSendKeysend_App_BalanceSufficient(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
app.Isolated = true
|
|
svc.DB.Save(&app)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_SETTLED,
|
|
Type: constants.TRANSACTION_TYPE_INCOMING,
|
|
AmountMsat: 11000, // invoice is 1000 msat, but we also calculate fee reserves max of(10 sats or 1%)
|
|
})
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", nil, "", svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.Nil(t, metadata["tlv_records"])
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, 64, len(*transaction.Preimage))
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
}
|
|
|
|
func TestSendKeysend_TLVs(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(uint64(1000), "fake destination", []lnclient.TLVRecord{
|
|
{
|
|
Type: 7629169,
|
|
Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
|
|
},
|
|
}, "", svc.LNClient, nil, nil)
|
|
assert.NoError(t, err)
|
|
|
|
var metadata lnclient.Metadata
|
|
err = json.Unmarshal(transaction.Metadata, &metadata)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "fake destination", metadata["destination"])
|
|
assert.NotNil(t, metadata["tlv_records"])
|
|
|
|
var boostagram Boostagram
|
|
err = json.Unmarshal(transaction.Boostagram, &boostagram)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, "⚡ WebLN Demo", boostagram.AppName)
|
|
assert.Equal(t, "⚡ WebLN Demo", boostagram.Name)
|
|
assert.Equal(t, "Podcasting 2.0", boostagram.Podcast)
|
|
assert.Equal(t, "Episode 104: A New Dump", boostagram.Episode.String())
|
|
assert.Equal(t, "https://feeds.podcastindex.org/pc20.xml", boostagram.FeedId.String())
|
|
assert.Equal(t, int64(21), boostagram.Timestamp)
|
|
assert.Equal(t, "Go podcasting!", boostagram.Message)
|
|
assert.Equal(t, "Satoshi Nakamoto", boostagram.SenderName)
|
|
assert.Equal(t, "boost", boostagram.Action)
|
|
assert.Equal(t, int64(1000), boostagram.ValueMsatTotal)
|
|
|
|
assert.Equal(t, "Go podcasting!", transaction.Description)
|
|
|
|
assert.Equal(t, uint64(1000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.NotNil(t, transaction.Preimage)
|
|
assert.Equal(t, 64, len(*transaction.Preimage))
|
|
assert.Zero(t, transaction.FeeReserveMsat)
|
|
}
|
|
|
|
func TestSendKeysend_IsolatedAppToNoApp(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
// setup for self payment
|
|
svc.LNClient.(*tests.MockLn).Pubkey = "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c"
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
app.Isolated = true
|
|
err = svc.DB.Save(&app).Error
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// give the isolated app 133 sats
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_SETTLED,
|
|
Type: constants.TRANSACTION_TYPE_INCOMING,
|
|
AmountMsat: 133000, // payment is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
|
|
})
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
mockPreimage := "c8aeb44ae8eb269c8dbfb7ec5c263f0bfa3d755bc0ca641b8ee118673afda657"
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(123000, "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c", []lnclient.TLVRecord{}, mockPreimage, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, transaction)
|
|
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, mockPreimage, *transaction.Preimage)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
assert.True(t, transaction.SelfPayment)
|
|
|
|
transactionType := constants.TRANSACTION_TYPE_INCOMING
|
|
incomingTransaction, err := transactionsService.LookupTransaction(context.TODO(), transaction.PaymentHash, &transactionType, svc.LNClient, nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, uint64(123000), incomingTransaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
|
|
assert.Equal(t, mockPreimage, *incomingTransaction.Preimage)
|
|
assert.True(t, incomingTransaction.SelfPayment)
|
|
|
|
transactions := []db.Transaction{}
|
|
result := svc.DB.Find(&transactions)
|
|
assert.Equal(t, int64(3), result.RowsAffected)
|
|
// expect balance to be decreased
|
|
balanceMsat, err := queries.GetIsolatedBalanceMsat(svc.DB, app.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(10000), balanceMsat)
|
|
}
|
|
|
|
func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
// setup for self payment
|
|
svc.LNClient.(*tests.MockLn).Pubkey = "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c"
|
|
|
|
app, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
app.Isolated = true
|
|
err = svc.DB.Save(&app).Error
|
|
assert.NoError(t, err)
|
|
|
|
app2, _, err := tests.CreateApp(svc)
|
|
assert.NoError(t, err)
|
|
app2.Isolated = true
|
|
err = svc.DB.Save(&app2).Error
|
|
assert.NoError(t, err)
|
|
|
|
appPermission := &db.AppPermission{
|
|
AppId: app.ID,
|
|
App: *app,
|
|
Scope: constants.PAY_INVOICE_SCOPE,
|
|
}
|
|
err = svc.DB.Create(appPermission).Error
|
|
assert.NoError(t, err)
|
|
|
|
// give the isolated app 133 sats
|
|
svc.DB.Create(&db.Transaction{
|
|
AppId: &app.ID,
|
|
State: constants.TRANSACTION_STATE_SETTLED,
|
|
Type: constants.TRANSACTION_TYPE_INCOMING,
|
|
AmountMsat: 133000, // payment is 123000 msat, but we also calculate fee reserves max of(10 sats or 1%)
|
|
})
|
|
|
|
dbRequestEvent := &db.RequestEvent{}
|
|
err = svc.DB.Create(&dbRequestEvent).Error
|
|
assert.NoError(t, err)
|
|
|
|
mockPreimage := "c8aeb44ae8eb269c8dbfb7ec5c263f0bfa3d755bc0ca641b8ee118673afda657"
|
|
|
|
// Keysend from app 1 to app 2
|
|
tlvRecords := []lnclient.TLVRecord{
|
|
{
|
|
Type: 696969,
|
|
Value: hex.EncodeToString([]byte(strconv.FormatUint(uint64(app2.ID), 10))),
|
|
},
|
|
{
|
|
Type: 7629169,
|
|
Value: "7b22616374696f6e223a22626f6f7374222c2276616c75655f6d736174223a313030302c2276616c75655f6d7361745f746f74616c223a313030302c226170705f6e616d65223a22e29aa1205765624c4e2044656d6f222c226170705f76657273696f6e223a22312e30222c22666565644944223a2268747470733a2f2f66656564732e706f6463617374696e6465782e6f72672f706332302e786d6c222c22706f6463617374223a22506f6463617374696e6720322e30222c22657069736f6465223a22457069736f6465203130343a2041204e65772044756d70222c227473223a32312c226e616d65223a22e29aa1205765624c4e2044656d6f222c2273656e6465725f6e616d65223a225361746f736869204e616b616d6f746f222c226d657373616765223a22476f20706f6463617374696e6721227d",
|
|
},
|
|
}
|
|
|
|
mockEventConsumer := tests.NewMockEventConsumer()
|
|
svc.EventPublisher.RegisterSubscriber(mockEventConsumer)
|
|
|
|
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)
|
|
transaction, err := transactionsService.SendKeysend(123000, "03cbd788f5b22bd56e2714bff756372d2293504c064e03250ed16a4dd80ad70e2c", tlvRecords, mockPreimage, svc.LNClient, &app.ID, &dbRequestEvent.ID)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, transaction)
|
|
assert.Equal(t, uint64(123000), transaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
|
|
assert.Equal(t, mockPreimage, *transaction.Preimage)
|
|
assert.Equal(t, app.ID, *transaction.AppId)
|
|
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
|
|
assert.True(t, transaction.SelfPayment)
|
|
|
|
transactionType := constants.TRANSACTION_TYPE_INCOMING
|
|
incomingTransaction, err := transactionsService.LookupTransaction(context.TODO(), transaction.PaymentHash, &transactionType, svc.LNClient, &app2.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, uint64(123000), incomingTransaction.AmountMsat)
|
|
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransaction.State)
|
|
assert.Equal(t, mockPreimage, *incomingTransaction.Preimage)
|
|
assert.Equal(t, app2.ID, *incomingTransaction.AppId)
|
|
assert.True(t, incomingTransaction.SelfPayment)
|
|
|
|
// receiving app should have the same data as what was sent
|
|
assert.Equal(t, transaction.Description, incomingTransaction.Description)
|
|
assert.Equal(t, transaction.Metadata, incomingTransaction.Metadata)
|
|
assert.Equal(t, transaction.Boostagram, incomingTransaction.Boostagram)
|
|
|
|
transactions := []db.Transaction{}
|
|
result := svc.DB.Find(&transactions)
|
|
assert.Equal(t, int64(3), result.RowsAffected)
|
|
// expect balance to be decreased
|
|
balanceMsat, err := queries.GetIsolatedBalanceMsat(svc.DB, app.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(10000), balanceMsat)
|
|
|
|
// expect app2 to receive the payment
|
|
balanceMsat, err = queries.GetIsolatedBalanceMsat(svc.DB, app2.ID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, int64(123000), balanceMsat)
|
|
|
|
// check notifications
|
|
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
|
|
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", consumedEvents[0].Event)
|
|
receivedTransaction := consumedEvents[0].Properties.(*db.Transaction)
|
|
assert.Equal(t, incomingTransaction.ID, receivedTransaction.ID)
|
|
}
|