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 10 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
120 changes: 120 additions & 0 deletions regression_tests/regression_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//go:build regression
// +build regression

package regressiontests

import (
"testing"

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

const (
fundsPerTest = 2
adminXPriv = "xprv9s21ZrQH143K3CbJXirfrtpLvhT3Vgusdo8coBritQ3rcS7Jy7sxWhatuxG5h2y1Cqj8FKmPp69536gmjYRpfga2MJdsGyBsnB12E19CESK"
adminXPub = "xpub661MyMwAqRbcFgfmdkPgE2m5UjHXu9dj124DbaGLSjaqVESTWfCD4VuNmEbVPkbYLCkykwVZvmA8Pbf8884TQr1FgdG2nPoHR8aB36YdDQh"
errorWhileGettingTransaction = "error while getting transaction: %s"
errorWhileCreatingUser = "error while creating user: %s"
errorWhileGettingBalance = "error while getting transaction: %s"
errorWhileSendingFunds = "error while sending funds: %s"
errorWhileGettingSharedConfig = "error while getting shared config: %s"
errorWhileGettingEnvVariables = "error while getting env variables: %s"
)

var (
clientOneURL string
clientTwoURL string
clientOneLeaderXPriv string
clientTwoLeaderXPriv string
)

func TestRegression(t *testing.T) {
ac4ch marked this conversation as resolved.
Show resolved Hide resolved
rtConfig, err := getEnvVariables()
if err != nil {
t.Errorf(errorWhileGettingEnvVariables, err)
}

sharedConfigInstanceOne, err := getSharedConfig(adminXPub, rtConfig.ClientOneURL)
wregulski marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Errorf(errorWhileGettingSharedConfig, err)
}
sharedConfigInstanceTwo, err := getSharedConfig(adminXPub, rtConfig.ClientTwoURL)
if err != nil {
t.Errorf(errorWhileGettingSharedConfig, err)
}

userName := "instanceOneUser1"
userOne, err := createUser(userName, sharedConfigInstanceOne.PaymailDomains[0], rtConfig.ClientOneURL, adminXPriv)
if err != nil {
t.Errorf(errorWhileCreatingUser, err)
}
defer deleteUser(userOne.Paymail, rtConfig.ClientOneURL, adminXPriv)

userName = "instanceTwoUser1"
userTwo, err := createUser(userName, sharedConfigInstanceTwo.PaymailDomains[0], rtConfig.ClientTwoURL, adminXPriv)
if err != nil {
t.Errorf(errorWhileCreatingUser, err)
}
defer deleteUser(userTwo.Paymail, rtConfig.ClientTwoURL, adminXPriv)

t.Run("TestInitialBalancesAndTransactionsBeforeAndAfterFundTransfers", func(t *testing.T) {
// Given
balance, err := getBalance(rtConfig.ClientOneURL, userOne.XPriv)
if err != nil {
wregulski marked this conversation as resolved.
Show resolved Hide resolved
t.Errorf(errorWhileGettingBalance, err)
}
transactions, err := getTransactions(rtConfig.ClientOneURL, userOne.XPriv)
if err != nil {
t.Errorf(errorWhileGettingTransaction, err)
}
assert.Equal(t, 0, balance)
wregulski marked this conversation as resolved.
Show resolved Hide resolved
assert.Equal(t, 0, len(transactions))
wregulski marked this conversation as resolved.
Show resolved Hide resolved

balance, err = getBalance(rtConfig.ClientTwoURL, userTwo.XPriv)
if err != nil {
t.Errorf(errorWhileGettingBalance, err)
}
transactions, err = getTransactions(rtConfig.ClientTwoURL, userTwo.XPriv)
if err != nil {
t.Errorf(errorWhileGettingTransaction, err)
}
assert.Equal(t, 0, balance)
assert.Equal(t, 0, len(transactions))

// When
transactionOne, err := sendFunds(rtConfig.ClientOneURL, rtConfig.ClientOneLeaderXPriv, userTwo.Paymail, 2)
if err != nil {
t.Errorf(errorWhileSendingFunds, err)
}
assert.GreaterOrEqual(t, int64(-1), transactionOne.OutputValue)

transactionTwo, err := sendFunds(rtConfig.ClientTwoURL, rtConfig.ClientTwoLeaderXPriv, userOne.Paymail, 2)
if err != nil {
t.Errorf(errorWhileSendingFunds, err)
}
assert.GreaterOrEqual(t, int64(-1), transactionTwo.OutputValue)

// Then
balance, err = getBalance(rtConfig.ClientOneURL, userOne.XPriv)
if err != nil {
t.Errorf(errorWhileGettingBalance, err)
}
transactions, err = getTransactions(rtConfig.ClientOneURL, userOne.XPriv)
if err != nil {
t.Errorf(errorWhileGettingTransaction, err)
}
assert.GreaterOrEqual(t, balance, 1)
assert.GreaterOrEqual(t, len(transactions), 1)

balance, err = getBalance(rtConfig.ClientTwoURL, userTwo.XPriv)
if err != nil {
t.Errorf(errorWhileGettingBalance, err)
}
transactions, err = getTransactions(rtConfig.ClientTwoURL, userTwo.XPriv)
if err != nil {
t.Errorf(errorWhileGettingTransaction, err)
}
assert.GreaterOrEqual(t, balance, 1)
assert.GreaterOrEqual(t, len(transactions), 1)
})
}
222 changes: 222 additions & 0 deletions regression_tests/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package regressiontests

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
"time"

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://"
domainSuffixSharedConfig = "/v1/shared-config"

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

timeoutDuration = 30 * time.Second
)

var (
explicitHTTPURLRegex = regexp.MustCompile(`^https?://`)
envVariableError = 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, envVariableError
}
if rtConfig.ClientOneURL == "" || rtConfig.ClientTwoURL == "" {
rtConfig.ClientOneURL = "http://localhost:3003"
rtConfig.ClientTwoURL = "http://localhost:3003"
}
return &rtConfig, nil
}

// getSharedConfig retrieves the shared configuration from the SPV Wallet.
func getSharedConfig(xpub string, clientUrl string) (*models.SharedConfig, error) {
req, err := http.NewRequest(http.MethodGet, clientUrl+domainSuffixSharedConfig, nil)
if err != nil {
return nil, err
}

req.Header.Set(models.AuthHeader, xpub)
client := http.Client{
Timeout: timeoutDuration,
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get shared config: %s", resp.Status)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var configResponse models.SharedConfig
if err := json.Unmarshal(body, &configResponse); err != nil {
return nil, err
}

if len(configResponse.PaymailDomains) != 1 {
return nil, fmt.Errorf("expected 1 paymail domain, got %d", len(configResponse.PaymailDomains))
}
return &configResponse, nil
}

// createUser creates a set of keys and new paymail in the SPV Wallet.
func createUser(paymail string, paymailDomain string, instanceUrl string, adminXPriv string) (*regressionTestUser, error) {
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
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(addPrefixIfNeeded(instanceUrl), adminXPriv)
ctx := context.Background()

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
}

// deleteUser soft deletes paymail from the SPV Wallet.
func deleteUser(paymail string, instanceURL string, adminXPriv string) error {
adminClient := walletclient.NewWithAdminKey(addPrefixIfNeeded(instanceURL), adminXPriv)
wregulski marked this conversation as resolved.
Show resolved Hide resolved
ctx := context.Background()
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved
err := adminClient.AdminDeletePaymail(ctx, paymail)
if err != nil {
return err
}
return nil
}

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

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(fromInstance string, fromXPriv string) ([]*models.Transaction, error) {
client := walletclient.NewWithXPriv(addPrefixIfNeeded(fromInstance), fromXPriv)
ctx := context.Background()
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved

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(fromInstance string, fromXPriv string, toPamail string, howMuch int) (*models.Transaction, error) {
client := walletclient.NewWithXPriv(fromInstance, fromXPriv)
ctx := context.Background()
dorzepowski marked this conversation as resolved.
Show resolved Hide resolved

balance, err := getBalance(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