2
0
mirror of https://github.com/lightninglabs/loop synced 2024-11-09 19:10:47 +00:00
loop/instantout/reservation/manager_test.go
Boris Nagaev 1947b90842
multi: typed callback in ExecTx
Provide a wrapped store type, exposing ExecTx method with a subset
interface in the callback argument. BaseDB interfaces in instantout,
reservation and sweepbatcher use ExecTx with their subset Querier
instead of whole sqlc.Querier (*sqlc.Queries).

This is needed to make the packages more reusable, so they don't
depend on methods of *sqlc.Queries they don't use.
2024-06-19 10:38:55 -03:00

177 lines
4.9 KiB
Go

package reservation
import (
"context"
"encoding/hex"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swapserverrpc"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
var (
defaultReservationId = mustDecodeID("17cecc61ab4aafebdc0542dabdae0d0cb8907ec1c9c8ae387bc5a3309ca8b600")
)
func TestManager(t *testing.T) {
ctxb, cancel := context.WithCancel(context.Background())
defer cancel()
testContext := newManagerTestContext(t)
// Start the manager.
go func() {
err := testContext.manager.Run(ctxb, testContext.mockLnd.Height)
require.NoError(t, err)
}()
// Create a new reservation.
reservationFSM, err := testContext.manager.newReservation(
ctxb, uint32(testContext.mockLnd.Height),
&swapserverrpc.ServerReservationNotification{
ReservationId: defaultReservationId[:],
Value: uint64(defaultValue),
ServerKey: defaultPubkeyBytes,
Expiry: uint32(testContext.mockLnd.Height) + defaultExpiry,
},
)
require.NoError(t, err)
// We'll expect the spendConfirmation to be sent to the server.
pkScript, err := reservationFSM.reservation.GetPkScript()
require.NoError(t, err)
confReg := <-testContext.mockLnd.RegisterConfChannel
require.Equal(t, confReg.PkScript, pkScript)
confTx := &wire.MsgTx{
TxOut: []*wire.TxOut{
{
PkScript: pkScript,
},
},
}
// We'll now confirm the spend.
confReg.ConfChan <- &chainntnfs.TxConfirmation{
BlockHeight: uint32(testContext.mockLnd.Height),
Tx: confTx,
}
// We'll now expect the reservation to be confirmed.
err = reservationFSM.DefaultObserver.WaitForState(ctxb, 5*time.Second, Confirmed)
require.NoError(t, err)
// We'll now expect a spend registration.
spendReg := <-testContext.mockLnd.RegisterSpendChannel
require.Equal(t, spendReg.PkScript, pkScript)
go func() {
// We'll expect a second spend registration.
spendReg = <-testContext.mockLnd.RegisterSpendChannel
require.Equal(t, spendReg.PkScript, pkScript)
}()
// We'll now try to lock the reservation.
err = testContext.manager.LockReservation(ctxb, defaultReservationId)
require.NoError(t, err)
// We'll try to lock the reservation again, which should fail.
err = testContext.manager.LockReservation(ctxb, defaultReservationId)
require.Error(t, err)
testContext.mockLnd.SpendChannel <- &chainntnfs.SpendDetail{
SpentOutPoint: spendReg.Outpoint,
}
// We'll now expect the reservation to be expired.
err = reservationFSM.DefaultObserver.WaitForState(ctxb, 5*time.Second, Spent)
require.NoError(t, err)
}
// ManagerTestContext is a helper struct that contains all the necessary
// components to test the reservation manager.
type ManagerTestContext struct {
manager *Manager
context test.Context
mockLnd *test.LndMockServices
reservationNotificationChan chan *swapserverrpc.ServerReservationNotification
mockReservationClient *mockReservationClient
}
// newManagerTestContext creates a new test context for the reservation manager.
func newManagerTestContext(t *testing.T) *ManagerTestContext {
mockLnd := test.NewMockLnd()
lndContext := test.NewContext(t, mockLnd)
dbFixture := loopdb.NewTestDB(t)
store := NewSQLStore(loopdb.NewTypedStore[Querier](dbFixture))
mockReservationClient := new(mockReservationClient)
sendChan := make(chan *swapserverrpc.ServerReservationNotification)
mockReservationClient.On(
"ReservationNotificationStream", mock.Anything, mock.Anything,
mock.Anything,
).Return(
&dummyReservationNotificationServer{
SendChan: sendChan,
}, nil,
)
mockReservationClient.On(
"OpenReservation", mock.Anything, mock.Anything, mock.Anything,
).Return(
&swapserverrpc.ServerOpenReservationResponse{}, nil,
)
cfg := &Config{
Store: store,
Wallet: mockLnd.WalletKit,
ChainNotifier: mockLnd.ChainNotifier,
FetchL402: func(context.Context) error { return nil },
ReservationClient: mockReservationClient,
}
manager := NewManager(cfg)
return &ManagerTestContext{
manager: manager,
context: lndContext,
mockLnd: mockLnd,
mockReservationClient: mockReservationClient,
reservationNotificationChan: sendChan,
}
}
type dummyReservationNotificationServer struct {
grpc.ClientStream
// SendChan is the channel that is used to send notifications.
SendChan chan *swapserverrpc.ServerReservationNotification
}
func (d *dummyReservationNotificationServer) Recv() (
*swapserverrpc.ServerReservationNotification, error) {
return <-d.SendChan, nil
}
func mustDecodeID(id string) ID {
bytes, err := hex.DecodeString(id)
if err != nil {
panic(err)
}
var decoded ID
copy(decoded[:], bytes)
return decoded
}