Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(SPV-896): Regression tests v1 #252

Merged
merged 20 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5afd17f
feat(SPV-896): adds test func template, adds functions to retrive os …
jakubmkowalski Jul 19, 2024
79155d0
feat(SPV-896): adds function to get shared config form instances
jakubmkowalski Jul 19, 2024
c72582a
feat(SPV-896): adds user creation
jakubmkowalski Jul 19, 2024
63e05c6
feat(SPV-896): adds user deletion
jakubmkowalski Jul 19, 2024
c3faafd
feat(SPV-896): adds balance checking
jakubmkowalski Jul 19, 2024
aeffe98
feat(SPV-896): adds function to get transactions
jakubmkowalski Jul 19, 2024
083a573
feat(SPV-896): adds funds transfer
jakubmkowalski Jul 19, 2024
3852387
feat(SPV-896): adds checks after funds transfer
jakubmkowalski Jul 19, 2024
f2d8ed9
feat(SPV-896): adds build flags
jakubmkowalski Jul 19, 2024
6bfcd97
feat(SPV-896): adds timeout to http client request
jakubmkowalski Jul 19, 2024
319c095
feat(SPV-896): adds taskfile for regression tests
jakubmkowalski Jul 22, 2024
a5f5fe2
feat(SPV-896): moves context declarations to upper functions
jakubmkowalski Jul 22, 2024
a6bd4fc
fix(SPV-896): applies cr comment - move errors check to require NoError
wregulski Aug 11, 2024
0dbe370
fix(SPV-896): replace all asserts with require keeping same logic
wregulski Aug 11, 2024
2b19aa2
feat(SPV-896): split tests into smaller ones
wregulski Aug 11, 2024
4aad98d
fix(SPV-896): simplify utils methods to limit outer methods calls
wregulski Aug 11, 2024
bd63bec
Merge branch 'main' into feat-896-regression-tests-v1
wregulski Aug 11, 2024
6150f46
feat(SPV-896): switches to GetSharedConfig method to get paymail domain
jakubmkowalski Aug 12, 2024
ecab3fb
feat(SPV-896): changes key to xpriv when requesting shared config, ch…
jakubmkowalski Aug 14, 2024
62dd6ea
Merge branch 'main' into feat-896-regression-tests-v1
jakubmkowalski Aug 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions regression_tests/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: "3"

tasks:
default:
cmds:
- task -l

run_regression_tests:
desc: "running regression tests"
cmds:
- echo "running regression tests..."
- go test -tags=regression ./...
dir: .
env:
CLIENT_ONE_URL: "{{.CLIENT_ONE_URL}}"
CLIENT_TWO_URL: "{{.CLIENT_TWO_URL}}"
CLIENT_ONE_LEADER_XPRIV: "{{.CLIENT_ONE_LEADER_XPRIV}}"
CLIENT_TWO_LEADER_XPRIV: "{{.CLIENT_TWO_LEADER_XPRIV}}"
133 changes: 133 additions & 0 deletions regression_tests/regression_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//go:build regression
// +build regression

package regressiontests

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/require"
)

const (
minimalFundsPerTransaction = 2

adminXPriv = "xprv9s21ZrQH143K3CbJXirfrtpLvhT3Vgusdo8coBritQ3rcS7Jy7sxWhatuxG5h2y1Cqj8FKmPp69536gmjYRpfga2MJdsGyBsnB12E19CESK"
adminXPub = "xpub661MyMwAqRbcFgfmdkPgE2m5UjHXu9dj124DbaGLSjaqVESTWfCD4VuNmEbVPkbYLCkykwVZvmA8Pbf8884TQr1FgdG2nPoHR8aB36YdDQh"

errGettingEnvVariables = "failed to get environment variables: %s"
errGettingSharedConfig = "failed to get shared config: %s"
errCreatingUser = "failed to create user: %s"
errDeletingUserPaymail = "failed to delete user's paymail: %s"
errSendingFunds = "failed to send funds: %s"
errGettingBalance = "failed to get balance: %s"
errGettingTransactions = "failed to get transactions: %s"
)

func TestRegression(t *testing.T) {
ac4ch marked this conversation as resolved.
Show resolved Hide resolved
ctx := context.Background()
rtConfig, err := getEnvVariables()
require.NoError(t, err, fmt.Sprintf(errGettingEnvVariables, err))

var paymailDomainInstanceOne, paymailDomainInstanceTwo string
var userOne, userTwo *regressionTestUser

t.Run("Initialize Shared Configurations", func(t *testing.T) {
t.Run("Should get sharedConfig for instance one", func(t *testing.T) {
paymailDomainInstanceOne, err = getPaymailDomain(ctx, adminXPriv, rtConfig.ClientOneURL)
require.NoError(t, err, fmt.Sprintf(errGettingSharedConfig, err))
})

t.Run("Should get shared config for instance two", func(t *testing.T) {
paymailDomainInstanceTwo, err = getPaymailDomain(ctx, adminXPriv, rtConfig.ClientTwoURL)
require.NoError(t, err, fmt.Sprintf(errGettingSharedConfig, err))
})
})

t.Run("Create Users", func(t *testing.T) {
t.Run("Should create user for instance one", func(t *testing.T) {
userName := "instanceOneUser1"
userOne, err = createUser(ctx, userName, paymailDomainInstanceOne, rtConfig.ClientOneURL, adminXPriv)
require.NoError(t, err, fmt.Sprintf(errCreatingUser, err))
})

t.Run("Should create user for instance two", func(t *testing.T) {
userName := "instanceTwoUser1"
userTwo, err = createUser(ctx, userName, paymailDomainInstanceTwo, rtConfig.ClientTwoURL, adminXPriv)
require.NoError(t, err, fmt.Sprintf(errCreatingUser, err))
})
})

defer func() {
t.Run("Cleanup: Remove Paymails", func(t *testing.T) {
t.Run("Should remove user's paymail on first instance", func(t *testing.T) {
if userOne != nil {
err := removeRegisteredPaymail(ctx, userOne.Paymail, rtConfig.ClientOneURL, adminXPriv)
require.NoError(t, err, fmt.Sprintf(errDeletingUserPaymail, err))
}
})

t.Run("Should remove user's paymail on second instance", func(t *testing.T) {
if userTwo != nil {
err := removeRegisteredPaymail(ctx, userTwo.Paymail, rtConfig.ClientTwoURL, adminXPriv)
require.NoError(t, err, fmt.Sprintf(errDeletingUserPaymail, err))
}
})
})
}()

t.Run("Perform Transactions", func(t *testing.T) {
t.Run("Send money to instance 1", func(t *testing.T) {
const amountToSend = 3
transaction, err := sendFunds(ctx, rtConfig.ClientTwoURL, rtConfig.ClientTwoLeaderXPriv, userOne.Paymail, amountToSend)
require.NoError(t, err, fmt.Sprintf(errSendingFunds, err))
require.GreaterOrEqual(t, int64(-1), transaction.OutputValue)

balance, err := getBalance(ctx, rtConfig.ClientOneURL, userOne.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingBalance, err))
require.GreaterOrEqual(t, balance, 1)

transactions, err := getTransactions(ctx, rtConfig.ClientOneURL, userOne.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingTransactions, err))
require.GreaterOrEqual(t, len(transactions), 1)
})

t.Run("Send money to instance 2", func(t *testing.T) {
transaction, err := sendFunds(ctx, rtConfig.ClientOneURL, rtConfig.ClientOneLeaderXPriv, userTwo.Paymail, minimalFundsPerTransaction)
require.NoError(t, err, fmt.Sprintf(errSendingFunds, err))
require.GreaterOrEqual(t, int64(-1), transaction.OutputValue)

balance, err := getBalance(ctx, rtConfig.ClientTwoURL, userTwo.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingBalance, err))
require.GreaterOrEqual(t, balance, 1)

transactions, err := getTransactions(ctx, rtConfig.ClientTwoURL, userTwo.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingTransactions, err))
require.GreaterOrEqual(t, len(transactions), 1)
})

t.Run("Send money from instance 1 to instance 2", func(t *testing.T) {
transaction, err := sendFunds(ctx, rtConfig.ClientOneURL, userOne.XPriv, userTwo.Paymail, minimalFundsPerTransaction)
require.NoError(t, err, fmt.Sprintf(errSendingFunds, err))
require.GreaterOrEqual(t, int64(-1), transaction.OutputValue)

balance, err := getBalance(ctx, rtConfig.ClientTwoURL, userTwo.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingBalance, err))
require.GreaterOrEqual(t, balance, 2)

transactions, err := getTransactions(ctx, rtConfig.ClientTwoURL, userTwo.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingTransactions, err))
require.GreaterOrEqual(t, len(transactions), 2)

balance, err = getBalance(ctx, rtConfig.ClientOneURL, userOne.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingBalance, err))
require.GreaterOrEqual(t, balance, 0)

transactions, err = getTransactions(ctx, rtConfig.ClientOneURL, userOne.XPriv)
require.NoError(t, err, fmt.Sprintf(errGettingTransactions, err))
require.GreaterOrEqual(t, len(transactions), 2)
})
})
}
190 changes: 190 additions & 0 deletions regression_tests/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package regressiontests

import (
"context"
"errors"
"fmt"
"os"
"regexp"
"strings"

walletclient "github.com/bitcoin-sv/spv-wallet-go-client"
"github.com/bitcoin-sv/spv-wallet-go-client/xpriv"
"github.com/bitcoin-sv/spv-wallet/models"
"github.com/bitcoin-sv/spv-wallet/models/filter"
)

const (
atSign = "@"
domainPrefix = "https://"

ClientOneURLEnvVar = "CLIENT_ONE_URL"
ClientTwoURLEnvVar = "CLIENT_TWO_URL"
ClientOneLeaderXPrivEnvVar = "CLIENT_ONE_LEADER_XPRIV"
ClientTwoLeaderXPrivEnvVar = "CLIENT_TWO_LEADER_XPRIV"
)

var (
explicitHTTPURLRegex = regexp.MustCompile(`^https?://`)
errEmptyXPrivEnvVariables = errors.New("missing xpriv variables")
)

type regressionTestUser struct {
XPriv string `json:"xpriv"`
XPub string `json:"xpub"`
Paymail string `json:"paymail"`
}

type regressionTestConfig struct {
ClientOneURL string
ClientTwoURL string
ClientOneLeaderXPriv string
ClientTwoLeaderXPriv string
}

// getEnvVariables retrieves the environment variables needed for the regression tests.
func getEnvVariables() (*regressionTestConfig, error) {
rtConfig := regressionTestConfig{
ClientOneURL: os.Getenv(ClientOneURLEnvVar),
ClientTwoURL: os.Getenv(ClientTwoURLEnvVar),
ClientOneLeaderXPriv: os.Getenv(ClientOneLeaderXPrivEnvVar),
ClientTwoLeaderXPriv: os.Getenv(ClientTwoLeaderXPrivEnvVar),
}

if rtConfig.ClientOneLeaderXPriv == "" || rtConfig.ClientTwoLeaderXPriv == "" {
return nil, errEmptyXPrivEnvVariables
}
if rtConfig.ClientOneURL == "" || rtConfig.ClientTwoURL == "" {
rtConfig.ClientOneURL = "http://localhost:3003"
rtConfig.ClientTwoURL = "http://localhost:3003"
}

rtConfig.ClientOneURL = addPrefixIfNeeded(rtConfig.ClientOneURL)
rtConfig.ClientTwoURL = addPrefixIfNeeded(rtConfig.ClientTwoURL)

return &rtConfig, nil
}

// getPaymailDomain retrieves the shared configuration from the SPV Wallet.
func getPaymailDomain(ctx context.Context, xpriv string, clientUrl string) (string, error) {
wc := walletclient.NewWithXPriv(clientUrl, xpriv)
sharedConfig, err := wc.GetSharedConfig(ctx)
if err != nil {
return "", err
}
if len(sharedConfig.PaymailDomains) != 1 {
return "", fmt.Errorf("expected 1 paymail domain, got %d", len(sharedConfig.PaymailDomains))
}
return sharedConfig.PaymailDomains[0], nil
}

// createUser creates a set of keys and new paymail in the SPV Wallet.
func createUser(ctx context.Context, paymail string, paymailDomain string, instanceUrl string, adminXPriv string) (*regressionTestUser, error) {
keys, err := xpriv.Generate()
if err != nil {
return nil, err
}

user := &regressionTestUser{
XPriv: keys.XPriv(),
XPub: keys.XPub().String(),
Paymail: preparePaymail(paymail, paymailDomain),
}

adminClient := walletclient.NewWithAdminKey(instanceUrl, adminXPriv)

if err := adminClient.AdminNewXpub(ctx, user.XPub, map[string]any{"some_metadata": "remove"}); err != nil {
return nil, err
}

_, err = adminClient.AdminCreatePaymail(ctx, user.XPub, user.Paymail, "Regression tests", "")
if err != nil {
return nil, err
}

return user, nil
}

// removeRegisteredPaymail soft deletes paymail from the SPV Wallet.
func removeRegisteredPaymail(ctx context.Context, paymail string, instanceURL string, adminXPriv string) error {
adminClient := walletclient.NewWithAdminKey(instanceURL, adminXPriv)
err := adminClient.AdminDeletePaymail(ctx, paymail)
if err != nil {
return err
}
return nil
}

// getBalance retrieves the balance from the SPV Wallet.
func getBalance(ctx context.Context, fromInstance string, fromXPriv string) (int, error) {
client := walletclient.NewWithXPriv(fromInstance, fromXPriv)

xpubInfo, err := client.GetXPub(ctx)
if err != nil {
return -1, err
}
return int(xpubInfo.CurrentBalance), nil
}

// getTransactions retrieves the transactions from the SPV Wallet.
func getTransactions(ctx context.Context, fromInstance string, fromXPriv string) ([]*models.Transaction, error) {
client := walletclient.NewWithXPriv(fromInstance, fromXPriv)

metadata := map[string]any{}
conditions := filter.TransactionFilter{}
queryParams := filter.QueryParams{}

txs, err := client.GetTransactions(ctx, &conditions, metadata, &queryParams)
if err != nil {
return nil, err
}
return txs, nil
}

// sendFunds sends funds from one paymail to another.
func sendFunds(ctx context.Context, fromInstance string, fromXPriv string, toPamail string, howMuch int) (*models.Transaction, error) {
client := walletclient.NewWithXPriv(fromInstance, fromXPriv)

balance, err := getBalance(ctx, fromInstance, fromXPriv)
if err != nil {
return nil, err
}
if balance < howMuch {
return nil, fmt.Errorf("insufficient funds: %d", balance)
}

recipient := walletclient.Recipients{To: toPamail, Satoshis: uint64(howMuch)}
recipients := []*walletclient.Recipients{&recipient}
metadata := map[string]any{
"description": "regression-test",
}

transaction, err := client.SendToRecipients(ctx, recipients, metadata)
if err != nil {
return nil, err
}
return transaction, nil
}

// preparePaymail prepares the paymail address by combining the alias and domain.
func preparePaymail(paymailAlias string, domain string) string {
if isValidURL(domain) {
splitedDomain := strings.SplitAfter(domain, "//")
domain = splitedDomain[1]
}
url := paymailAlias + atSign + domain
return url
}

// addPrefixIfNeeded adds the HTTPS prefix to the URL if it is missing.
func addPrefixIfNeeded(url string) string {
if !isValidURL(url) {
return domainPrefix + url
}
return url
}

// isValidURL validates the URL if it has http or https prefix.
func isValidURL(rawURL string) bool {
return explicitHTTPURLRegex.MatchString(rawURL)
}
Loading