chore(wallet): add integration dev tests for pending tx status update

Remove the wallet connect backend dev test code

Updates: #13124
This commit is contained in:
Stefan 2024-01-03 23:37:42 -05:00 committed by Stefan Dunca
parent 7f6636dc94
commit 70767213ff
13 changed files with 519 additions and 862 deletions

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>App and Integration</title>
<style>
iframe {
width: 50vh;
height: 100%;
border: none;
}
</style>
</head>
<body>
<h2>App Page</h2>
<iframe src="http://localhost:8080"></iframe>
<h2>Wallet Page</h2>
<iframe src="http://localhost:8081"></iframe>
</body>
</html>

View File

@ -11,6 +11,9 @@ import (
webview "github.com/webview/webview_go"
)
// main simulates a wallet connect client session with status registered as a wallet connect provider
// this is used as an alternative for testing the deep link integration until the status desktop app goes in production
// and we can register it as a wallet connect provider
func main() {
// Serve files from the ./generated directory
fileServer := http.FileServer(http.Dir("./generated"))

View File

@ -1,207 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
statusgo "github.com/status-im/status-go/mobile"
"github.com/status-im/status-go/multiaccounts"
)
func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error {
absUserFolder, err := filepath.Abs(userFolder)
if err != nil {
return err
}
accountsJson := statusgo.OpenAccounts(absUserFolder)
accounts := make([]multiaccounts.Account, 0)
err = getCAPIResponse(accountsJson, &accounts)
if err != nil {
return err
}
if len(accounts) == 0 {
return fmt.Errorf("no accounts found")
}
account := accounts[0]
keystorePath := filepath.Join(filepath.Join(absUserFolder, "keystore/"), account.KeyUID)
initKeystoreJson := statusgo.InitKeystore(keystorePath)
apiResponse := statusgo.APIResponse{}
err = getCAPIResponse(initKeystoreJson, &apiResponse)
if err != nil {
return err
}
//serialize account of type multiaccounts.Account
accountJson, err := json.Marshal(account)
if err != nil {
return err
}
loginJson := statusgo.LoginWithConfig(string(accountJson), hashedPassword, nodeConfigJson)
err = getCAPIResponse(loginJson, &apiResponse)
if err != nil {
return err
}
return nil
}
type jsonrpcMessage struct {
Version string `json:"jsonrpc"`
ID json.RawMessage `json:"id"`
}
type jsonrpcRequest struct {
jsonrpcMessage
ChainID uint64 `json:"chainId"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
func callPrivateMethod(method string, params []interface{}) string {
var paramsJson json.RawMessage
var err error
if params != nil {
paramsJson, err = json.Marshal(params)
if err != nil {
return ""
}
}
msg := jsonrpcRequest{
jsonrpcMessage: jsonrpcMessage{
Version: "2.0",
},
Method: method,
Params: paramsJson,
}
msgJson, err := json.Marshal(msg)
if err != nil {
return ""
}
return statusgo.CallPrivateRPC(string(msgJson))
}
type Config struct {
HashedPassword string `json:"hashedPassword"`
NodeConfigFile string `json:"nodeConfigFile"`
DataDir *string `json:"dataDir,omitempty"`
}
func processConfigArgs() (config *Config, nodeConfigJson string, userFolder string, err error) {
var configFilePath string
flag.StringVar(&configFilePath, "config", "", "path to json config file")
flag.StringVar(&userFolder, "dataDir", "../../../Status/data", "path to json config file")
flag.Parse()
if configFilePath == "" {
flag.Usage()
return
}
config = &Config{}
// parse config file
configFile, err := os.Open(configFilePath)
if err != nil {
panic(err)
}
defer configFile.Close()
jsonParser := json.NewDecoder(configFile)
if err = jsonParser.Decode(&config); err != nil {
panic(err)
}
// Read config.NodeConfigFile json file and store it as string
nodeConfigFile, err := os.Open(config.NodeConfigFile)
if err != nil {
panic(err)
}
defer nodeConfigFile.Close()
nodeConfigData, err := io.ReadAll(nodeConfigFile)
if err == nil {
nodeConfigJson = string(nodeConfigData)
}
if config.DataDir != nil {
userFolder = *config.DataDir
}
return
}
func getCAPIResponse[T any](responseJson string, res T) error {
apiResponse := statusgo.APIResponse{}
err := json.Unmarshal([]byte(responseJson), &apiResponse)
if err == nil {
if apiResponse.Error != "" {
return fmt.Errorf("API error: %s", apiResponse.Error)
}
}
typeOfT := reflect.TypeOf(res)
kindOfT := typeOfT.Kind()
// Check for valid types: pointer, slice, map
if kindOfT != reflect.Ptr && kindOfT != reflect.Slice && kindOfT != reflect.Map {
return fmt.Errorf("type T must be a pointer, slice, or map")
}
if err := json.Unmarshal([]byte(responseJson), &res); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
return nil
}
type jsonrpcSuccessfulResponse struct {
jsonrpcMessage
Result json.RawMessage `json:"result"`
}
type jsonrpcErrorResponse struct {
jsonrpcMessage
Error jsonError `json:"error"`
}
// jsonError represents Error message for JSON-RPC responses.
type jsonError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func getRPCAPIResponse[T any](responseJson string, res T) error {
errApiResponse := jsonrpcErrorResponse{}
err := json.Unmarshal([]byte(responseJson), &errApiResponse)
if err == nil && errApiResponse.Error.Code != 0 {
return fmt.Errorf("API error: %#v", errApiResponse.Error)
}
apiResponse := jsonrpcSuccessfulResponse{}
err = json.Unmarshal([]byte(responseJson), &apiResponse)
if err != nil {
return fmt.Errorf("failed to unmarshal jsonrpcSuccessfulResponse: %w", err)
}
typeOfT := reflect.TypeOf(res)
kindOfT := typeOfT.Kind()
// Check for valid types: pointer, slice, map
if kindOfT != reflect.Ptr && kindOfT != reflect.Slice && kindOfT != reflect.Map {
return fmt.Errorf("type T must be a pointer, slice, or map")
}
if err := json.Unmarshal(apiResponse.Result, &res); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
return nil
}

View File

@ -1,383 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Wallet Connect status-go test</title>
<style></style>
</head>
<body>
<div id="log"></div>
<script src="bundle.js" type="module"></script>
<script>
// Helper functions
//
function goEcho(message) {
window.echo(message);
}
// If not null add to it
var logEntries = null;
var newSessionButton;
var hashedPasswordInput;
function addHtmlEntry(htmlContent, color = null, entry = null) {
const logDiv = document.getElementById("log");
if (entry) {
entry.remove();
}
entry = document.createElement("div");
logDiv.appendChild(entry);
entry.innerHTML = htmlContent;
if (color) {
entry.style.color = color;
}
entry.scrollIntoView();
if (logEntries) {
logEntries.push(entry);
}
return entry;
}
function addLogEntry(message, color = "black", entry = null) {
return addHtmlEntry(`${message}`, color, entry);
}
function logComponentStatusChange(componentName, statusMessage, color = "black", entry = null) {
const componentHtml = `<span style="color: fuchsia;">${componentName}</span>: `;
const statusHtml = `<span style="color: ${color};">${statusMessage}</span>`;
return addHtmlEntry(`${componentHtml}${statusHtml}`, null, entry);
}
// SDK initialization
//
const statusGoEntry = logComponentStatusChange("status-go", "Initializing...");
var initEventCount = 0;
var eventCount = 0;
const readyToPairEventName = "readyToPair";
async function initializeSDK() {
try {
const sdkEntry = logComponentStatusChange("SDK", "Initializing...");
const conf = await window.getConfiguration();
await window.wc.init(conf.projectId);
logComponentStatusChange("SDK", "Initialized", "green", sdkEntry);
initEventCount++;
} catch (error) {
goEcho(`SDK init error: ${error}`);
logComponentStatusChange("SDK", "FAIL initializing ${error}", "red", sdkEntry);
}
}
// Simulate statusObject
window.statusq = {
channel: {
objects: {
statusObject: {
sdkInitialized: function (error) {
window.statusObject_sdkInitialized(error);
},
onSessionProposal: function (sessionProposal) {
window.statusObject_onSessionProposal(JSON.stringify(sessionProposal)).then((success) => {
if (!success) {
goEcho(`statusObject: onSessionProposal call failed ${sessionProposal.id}`);
return;
}
});
},
onSessionRequest: function (sessionRequest) {
eventCount++;
logComponentStatusChange("SDK", `received "session_request" event`, "green");
addLogEntry(`Data: ${JSON.stringify(sessionRequest)}`);
addHtmlEntry(
`<button id="acceptSessionButton${eventCount}">Accept</button> <button id="rejectSessionButton${eventCount}">Reject</button>`
);
const acceptSessionButton = document.getElementById(`acceptSessionButton${eventCount}`);
const rejectSessionButton = document.getElementById(`rejectSessionButton${eventCount}`);
acceptSessionButton.addEventListener("click", function () {
const sessionReqEntry = logComponentStatusChange("status-go", `sessionRequest called`, "orange");
window.sessionRequest(JSON.stringify(sessionRequest), hashedPasswordInput.value).then((success) => {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
if (success) {
logComponentStatusChange("status-go", `sessionRequest OK`, "green", sessionReqEntry);
// waiting for "sessionRequestResult" event
} else {
logComponentStatusChange(
"status-go",
`sessionRequest call failed for topic ${sessionRequest.topic}`,
"red",
sessionReqEntry
);
window.wc.rejectSessionRequest(sessionRequest.topic, sessionRequest.id, true);
setStatus(`Session ${sessionRequest.id} rejected, internal error`, "purple");
}
});
});
rejectSessionButton.addEventListener("click", function () {
acceptSessionButton.disabled = true;
rejectSessionButton.disabled = true;
window.wc.rejectSessionRequest(sessionRequest.topic, sessionRequest.id).then(
() => {
addLogEntry(`Session ${sessionRequest.id} rejected`);
},
(err) => {
addLogEntry(`Session ${sessionRequest.id} reject error: ${err.message}`, "red");
}
);
});
},
onSessionDelete: function (deletePayload) {
goEcho(`statusObject: onSessionDelete ${JSON.stringify(deletePayload)}`);
},
onSessionExpire: function (expirePayload) {
goEcho(`statusObject: onSessionExpire ${JSON.stringify(expirePayload)}`);
},
onSessionUpdate: function (updatePayload) {
goEcho(`statusObject: onSessionUpdate ${JSON.stringify(updatePayload)}`);
},
onSessionExtend: function (extendPayload) {
goEcho(`statusObject: onSessionExtend ${JSON.stringify(extendPayload)}`);
},
onSessionPing: function (pingPayload) {
goEcho(`statusObject: onSessionPing ${JSON.stringify(pingPayload)}`);
},
onSessionEvent: function (eventPayload) {
goEcho(`statusObject: onSessionEvent ${JSON.stringify(eventPayload)}`);
},
onSessionRequest: function (sessionRequestPayload) {
goEcho(`statusObject: onSessionRequest ${JSON.stringify(sessionRequestPayload)}`);
},
onSessionRequestSent: function (sessionRequestSentPayload) {
goEcho(`statusObject: onSessionRequestSent ${JSON.stringify(sessionRequestSentPayload)}`);
},
onProposalExpire: function (proposalExpirePayload) {
goEcho(`statusObject: onProposalExpire ${JSON.stringify(proposalExpirePayload)}`);
},
},
},
},
};
var pairLinkInput = null;
var pairButton = null;
function newPairWorkflow() {
// Remove all the previous entries
if (logEntries) {
for (let i = 0; i < logEntries.length; i++) {
logEntries[i].remove();
}
}
logEntries = [];
eventCount++;
// Add session reset and password input
addHtmlEntry(`<button id="newSessionButton" style="display: none;">New Session</button>`);
newSessionButton = document.getElementById("newSessionButton");
newSessionButton.addEventListener("click", function () {
newPairWorkflow();
});
addHtmlEntry(
`<input type="text" id="hashedPasswordInput" placeholder="Insert hashed password" value="0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e"/>`
);
hashedPasswordInput = document.getElementById(`hashedPasswordInput`);
addHtmlEntry(
`<input type="text" id="pairLinkInput" placeholder="Insert pair link" /><button id="pairButton" disabled>Pair</button>`
);
// List existing pairing sessions
const pairingsRes = window.wc.getPairings();
let pairings = [];
if (pairingsRes) {
if (!!pairingsRes.error) {
goEcho(`getPairings() error: ${pairingsRes.error}`);
return;
} else if (pairingsRes.result) {
pairings = pairingsRes.result;
}
}
if (pairings.length > 0) {
addHtmlEntry(`Existing pairings:`, "fuchsia");
}
for (let i = 0; i < pairings.length; i++) {
const p = pairings[i];
const disconnectEntry = addHtmlEntry(
`[${i + 1}] <span style="color: ${p.active ? "green" : "orange"};">${
p.active ? "ACTIVE" : "INACTIVE"
}</span> <span class="elide-text">${p.topic}</span>; Expires: ${timestampToStr(
p.expiry
)} <button id="unpairButton${i}">Disconnect</button>`
);
const unpairButton = document.getElementById(`unpairButton${i}`);
unpairButton.addEventListener("click", function () {
const res = window.wc.disconnect(p.topic);
if (res && !!res.error) {
addLogEntry(`Pairing ${p.topic} disconnect error: ${err.message}`, "red", disconnectEntry);
return;
}
addLogEntry(`Pairing ${p.topic} disconnected`, "green", disconnectEntry);
unpairButton.remove();
});
}
// Add pairing options
pairLinkInput = document.getElementById(`pairLinkInput`);
pairButton = document.getElementById(`pairButton`);
pairLinkInput.addEventListener("input", function () {
pairButton.disabled = !(pairLinkInput.value.length > 0);
});
pairButton.addEventListener("click", function () {
newSessionButton.style.display = "inline";
pairButton.disabled = true;
pairLinkInput.disabled = true;
const sdkEntry = logComponentStatusChange("SDK", "Pairing...");
const result = window.wc.pair(pairLinkInput.value);
if (result && !!result.error) {
goEcho("pair() error: ", result.error);
logComponentStatusChange("SDK", `Pairing error ${error.message}`, "red", sdkEntry);
return;
}
logComponentStatusChange("SDK", "got Pair session proposal", "green", sdkEntry);
const goSessionEntry = logComponentStatusChange("GO.pairSessionProposal", "waiting status-go", "pink");
});
}
document.addEventListener(`proposeUserPair`, function (event) {
logComponentStatusChange("GO.proposeUserPair", `received "proposeUserPair"`, "green");
addLogEntry(JSON.stringify(event.detail.supportedNamespaces));
if (!document.getElementById(`acceptPairButton`)) {
addHtmlEntry(`<button id="acceptPairButton">Accept</button><button id="rejectPairButton">Reject</button>`);
}
const acceptPairButton = document.getElementById(`acceptPairButton`);
const rejectPairButton = document.getElementById(`rejectPairButton`);
const sessionProposal = event.detail.sessionProposal;
acceptPairButton.addEventListener("click", function () {
const result = window.wc.approvePairSession(sessionProposal, event.detail.supportedNamespaces);
if (result && !!result.error) {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approve error: ${result.error}`,
"red",
goSessionEntry
);
return;
}
acceptPairButton.remove();
rejectPairButton.remove();
root.controller_recordSuccessfulPairing(JSON.stringify(sessionProposal));
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} approved`,
"green",
goSessionEntry
);
});
rejectPairButton.addEventListener("click", function () {
const result = window.wc.rejectPairSession(sessionProposal.id);
if (result && !!result.error) {
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} reject error: ${result.error}`,
"red",
goSessionEntry
);
return;
}
acceptPairButton.remove();
rejectPairButton.remove();
logComponentStatusChange(
"GO.pairSessionProposal",
`Pair session ${sessionProposal.id} rejected`,
"green",
goSessionEntry
);
});
});
document.addEventListener("sessionRequestResult", function (event) {
let req = event.detail.sessionRequest;
const res = window.wc.respondSessionRequest(req.topic, req.id, event.detail.signed);
addLogEntry(`Session ${req.topic} approval accepted`);
addHtmlEntry(
`</br><a href="https://goerli.etherscan.io/tx/${event.detail.signed}" target="_blank">${event.detail.signed}</a>`
);
});
// Add start from scratch option
document.addEventListener(readyToPairEventName, function () {
newPairWorkflow();
});
async function processGoEvents() {
while (true) {
try {
const event = await window.popNextEvent();
switch (event.name) {
case "nodeReady":
logComponentStatusChange("status-go", "Ready", "green", statusGoEntry);
initEventCount++;
break;
default:
// Handle status-go and SDK bootstrap events
if (initEventCount == 2) {
initEventCount++;
document.dispatchEvent(new CustomEvent(readyToPairEventName, {}));
} else if (event.name != "") {
goEcho(`GO event: ${event.name}`);
document.dispatchEvent(new CustomEvent(event.name, { detail: event.payload }));
} else {
await new Promise((resolve) => setTimeout(resolve, 100));
}
break;
}
} catch (err) {
goEcho(`GO event error: ${err.message}`);
}
}
}
processGoEvents();
// Call the initializeSDK function on page load
window.addEventListener("DOMContentLoaded", (event) => {
initializeSDK();
});
function timestampToStr(timestamp) {
const date = new Date(timestamp * 1000);
const readableDate = date.toLocaleDateString();
const readableTime = date.toLocaleTimeString();
return `${readableDate} - ${readableTime}`;
}
</script>
</body>
<style>
.elide-text {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
transition: max-width 0.3s ease;
vertical-align: middle;
}
.elide-text:hover {
max-width: none;
background-color: #f0f0f0;
z-index: 1;
position: relative;
}
</style>
</html>

View File

@ -1,229 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"github.com/ethereum/go-ethereum/log"
webview "github.com/webview/webview_go"
statusgo "github.com/status-im/status-go/mobile"
wc "github.com/status-im/status-go/services/wallet/walletconnect"
"github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/signal"
)
// l is used for local logging
var l log.Logger
func init() {
l = log.New()
l.SetHandler(log.CallerFileHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
}
type PairResult struct {
SessionProposal string `json:"sessionProposal"`
}
type Configuration struct {
ProjectId string `json:"projectId"`
}
type GoEvent struct {
Name string `json:"name"`
Payload interface{} `json:"payload"`
}
var eventQueue chan GoEvent = make(chan GoEvent, 10000)
func signalHandler(jsonEvent string) {
// parse signal.Envelope from jsonEvent
envelope := signal.Envelope{}
err := json.Unmarshal([]byte(jsonEvent), &envelope)
if err != nil {
// check for error in json
apiResponse := statusgo.APIResponse{}
err = json.Unmarshal([]byte(jsonEvent), &apiResponse)
if err != nil {
log.Error("Error parsing the signal event: ", err)
return
}
}
if envelope.Type == signal.EventNodeReady {
eventQueue <- GoEvent{Name: "nodeReady", Payload: ""}
} else if envelope.Type == "wallet" {
// parse envelope.Event to json
walletEvent := walletevent.Event{}
err := json.Unmarshal([]byte(jsonEvent), &walletEvent)
if err != nil {
log.Error("Error parsing the wallet event: ", err)
return
}
// TODO: continue from here
// if walletEvent.Type == "WalletConnectProposeUserPair" {
// eventQueue <- GoEvent{Name: "proposeUserPair", Payload: walletEvent.Message}
// }
}
}
func main() {
// Setup status-go logger
log.Root().SetHandler(log.CallerFileHandler(log.StdoutHandler))
signal.SetDefaultNodeNotificationHandler(signalHandler)
config, nodeConfigJson, userFolder, err := processConfigArgs()
if err != nil {
panic(err)
}
// Login to first account
err = loginToAccount(config.HashedPassword, userFolder, nodeConfigJson)
if err != nil {
panic(err)
}
// Start WebView
w := webview.New(true)
defer w.Destroy()
w.SetTitle("WC status-go test")
w.SetSize(1280, 1024, webview.HintNone)
w.Bind("sessionRequest", func(sessionRequestJson, hashedPassword string) bool {
fmt.Println("sessionRequestJson:", sessionRequestJson)
sessionReqRes := callPrivateMethod("wallet_wCSessionRequest", []interface{}{sessionRequestJson})
fmt.Println("sessionReqRes:", sessionReqRes)
var apiResponse wc.SessionRequestResponse
err = getRPCAPIResponse(sessionReqRes, &apiResponse)
if err != nil {
l.Error("Error parsing wallet_wCSessionRequest response", "error", err)
return false
}
if apiResponse.SignOnKeycard {
l.Error("SignOnKeycard is not supported in this test")
return false
}
sessionReqRes = callPrivateMethod("wallet_wCSignMessage", []interface{}{apiResponse.MessageToSign, apiResponse.Address, hashedPassword})
fmt.Println("sessionReqRes:", sessionReqRes)
var signature string
err = getRPCAPIResponse(sessionReqRes, &signature)
if err != nil {
l.Error("Error parsing wallet_wCSignMessage response", "error", err)
return false
}
// TODO: process the request type ...
go func() {
eventQueue <- GoEvent{Name: "sessionRequestResult", Payload: apiResponse}
}()
return true
})
w.Bind("getConfiguration", func() Configuration {
projectID := os.Getenv("STATUS_BUILD_WALLET_CONNECT_PROJECT_ID")
if projectID == "" {
projectID = "87815d72a81d739d2a7ce15c2cfdefb3"
}
return Configuration{ProjectId: projectID}
})
w.Bind("echo", func(message string) bool {
fmt.Println("<D> WebView:", message)
return true
})
// Setup go to webview event queue
w.Bind("popNextEvent", func() GoEvent {
select {
case event := <-eventQueue:
return event
default:
return GoEvent{Name: "", Payload: ""}
}
})
mockStatusObject(w)
mockController(w)
// Start a local server to serve the files
http.HandleFunc("/bundle.js", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
http.ServeFile(w, r, "../../../../ui/app/AppLayouts/Wallet/views/walletconnect/sdk/generated/bundle.js")
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0")
http.ServeFile(w, r, "./index.html")
})
go http.ListenAndServe(":8081", nil)
w.Navigate("http://localhost:8081")
w.Run()
}
func mockController(w webview.WebView) {
w.Bind("controller_recordSuccessfulPairing", func(sessionProposalJson string) {
fmt.Println("controller_recordSuccessfulPairing:", sessionProposalJson)
sessionProposalRes := callPrivateMethod("wallet_wCRecordSuccessfulPairing", []interface{}{sessionProposalJson})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
}
})
w.Bind("controller_changePairingState", func(topic string, active bool) {
sessionProposalRes := callPrivateMethod("wallet_wCChangePairingState", []interface{}{topic, active})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
}
})
}
func mockStatusObject(w webview.WebView) {
w.Bind("statusObject_sdkInitialized", func(error string) {
// All OK here
})
w.Bind("statusObject_onSessionProposal", func(sessionProposalJson string) bool {
sessionProposalRes := callPrivateMethod("wallet_wCPairSessionProposal", []interface{}{sessionProposalJson})
var apiResponse wc.PairSessionResponse
err := getRPCAPIResponse(sessionProposalRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
return false
}
go func() {
eventQueue <- GoEvent{Name: "proposeUserPair", Payload: apiResponse}
}()
return true
})
w.Bind("statusObject_onSessionRequest", func(sessionRequestJson string) bool {
sessionReqRes := callPrivateMethod("wallet_wCSessionRequest", []interface{}{sessionRequestJson})
var apiResponse wc.SessionRequestResponse
err := getRPCAPIResponse(sessionReqRes, &apiResponse)
if err != nil {
l.Error("Error parsing the API response", "error", err)
return false
}
return true
})
// function onSessionUpdate(details)
// function onSessionExtend(details)
// function onSessionPing(details)
// function onSessionDelete(details)
// function onSessionExpire(details)
// function onSessionRequestSent(details)
// function onSessionEvent(details)
// function onProposalExpire(details)
}

1
test/status-go/integration/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.integration_tests_config.json

View File

@ -0,0 +1,30 @@
# Development integration tests for status-go
These integration tests are an experiment. They rely on an existing developer environment (working user folder, blockchain access tokens) and internet connection.
If it proves its usefulness we might consider automating them and make it independent of internet services
## How to run tests
Setup steps
- Dump the node config passed to `Login` status-go call as `.node_config.json` and use its path later on in as `nodeConfigFile` in `.integration_tests_config.json`
- Ensure the blockchain access tokens are configured when dumping the configuration file
- Copy [integration_tests_config-template.json](./integration_tests_config-template.json) to tests sub-folders and rename it as `.integration_tests_config.json`, then update it with your own values.
- Update `nodeConfigFile` with the previously extracted node config path
- The `hashedPassword` should be the "0x" + `keccak256(clearPassword)`
- For `dataDir` it is expected an working status-go user folder (e.g. the usual `status-desktop/Second/data` used with `make run` command)
Run wallet tests
- once
```sh
(cd test/status-go/integration && go test -v ./wallet/... --tags=gowaku_no_rln,gowaku_skip_migrations)
```
- continuously on code changes
```sh
(cd test/status-go/integration && nodemon --watch ../../../vendor/status-go/ --watch . --ext "*.go,*.sql" --exec 'go test -v ./wallet/... --tags=gowaku_no_rln,gowaku_skip_migrations 2>&1 | tee ~/proj/tmp/status-go-tests.log || exit 1')
```

View File

@ -1,8 +1,10 @@
module main
module github.com/status-im/status-desktop/test/status-go/integration
go 1.20
replace github.com/status-im/status-go => ../../../../vendor/status-go
replace github.com/status-im/status-desktop/test/status-go/integration => ./
replace github.com/status-im/status-go => ../../../vendor/status-go
// Keep these in sync with status-go/go.mod aliases
replace github.com/ethereum/go-ethereum v1.10.26 => github.com/status-im/go-ethereum v1.10.25-status.11
@ -18,14 +20,13 @@ replace github.com/mutecomm/go-sqlcipher/v4 v4.4.2 => github.com/status-im/go-sq
require (
github.com/ethereum/go-ethereum v1.10.26
github.com/status-im/status-go v0.171.7
github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070
github.com/stretchr/testify v1.8.4
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/PuerkitoBio/goquery v1.6.1 // indirect
github.com/RoaringBitmap/roaring v0.9.4 // indirect
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect
github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 // indirect
github.com/anacrolix/chansync v0.3.0 // indirect
@ -46,6 +47,7 @@ require (
github.com/anacrolix/utp v0.1.0 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/andybalholm/cascadia v1.2.0 // indirect
github.com/avast/retry-go/v4 v4.5.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beevik/ntp v0.3.0 // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
@ -70,7 +72,7 @@ require (
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/elastic/gosigar v0.14.2 // indirect
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect
@ -79,7 +81,7 @@ require (
github.com/forPelevin/gomoji v1.1.2 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
@ -211,7 +213,7 @@ require (
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/peerdiscovery v1.7.0 // indirect
github.com/shirou/gopsutil v3.21.5+incompatible // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/siphiuel/lc-proxy-wrapper v0.0.0-20230516150924-246507cee8c7 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
@ -222,9 +224,9 @@ require (
github.com/status-im/migrate/v4 v4.6.2-status.3 // indirect
github.com/status-im/rendezvous v1.3.7 // indirect
github.com/status-im/status-go/extkeys v1.1.2 // indirect
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 // indirect
github.com/status-im/tcp-shaker v1.1.1-status // indirect
github.com/status-im/zxcvbn-go v0.0.0-20220311183720-5e8676676857 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
github.com/tklauser/go-sysconf v0.3.6 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
@ -234,8 +236,8 @@ require (
github.com/vacp2p/mvds v0.0.24-0.20201124060106-26d8e94130d8 // indirect
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 // indirect
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 // indirect
github.com/waku-org/go-waku v0.8.1-0.20231103161423-351dd55a1498 // indirect
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd // indirect
github.com/waku-org/go-waku v0.8.1-0.20240104144340-585648c4eefe // indirect
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 // indirect
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b // indirect
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 // indirect
github.com/waku-org/go-zerokit-rln-x86_64 v0.0.0-20230916171518-2a77c3734dd1 // indirect
@ -250,6 +252,7 @@ require (
github.com/yeqown/go-qrcode/v2 v2.2.1 // indirect
github.com/yeqown/go-qrcode/writer/standard v1.2.1 // indirect
github.com/yeqown/reedsolomon v1.0.0 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
github.com/zenthangplus/goccm v0.0.0-20211005163543-2f2e522aca15 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/atomic v1.11.0 // indirect

View File

@ -158,7 +158,6 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
@ -346,6 +345,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o=
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@ -711,8 +712,9 @@ github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRP
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
@ -828,8 +830,9 @@ github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@ -1900,8 +1903,9 @@ github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfP
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
@ -1999,8 +2003,8 @@ github.com/status-im/resize v0.0.0-20201215164250-7c6d9f0d3088 h1:ClCAP2FPCvl8hG
github.com/status-im/resize v0.0.0-20201215164250-7c6d9f0d3088/go.mod h1:+92j1tN27DypDeBFxkg0uzkqfh1bNHTZe3Bv2PjvxpM=
github.com/status-im/status-go/extkeys v1.1.2 h1:FSjARgDathJ3rIapJt851LsIXP9Oyuu2M2jPJKuzloU=
github.com/status-im/status-go/extkeys v1.1.2/go.mod h1:hCmFzb2jiiVF2voZKYbzuhOQiHHCmyLJsZJXrFFg7BY=
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 h1:oa0KU5jJRNtXaM/P465MhvSFo/HM2O8qi2DDuPcd7ro=
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501/go.mod h1:RYo/itke1oU5k/6sj9DNM3QAwtE5rZSgg5JnkOv83hk=
github.com/status-im/tcp-shaker v1.1.1-status h1:TnVeeWlq2SKCWotHc4Vi6qZQfY8TTe3VLmu1xpEFYhg=
github.com/status-im/tcp-shaker v1.1.1-status/go.mod h1:RYo/itke1oU5k/6sj9DNM3QAwtE5rZSgg5JnkOv83hk=
github.com/status-im/zxcvbn-go v0.0.0-20220311183720-5e8676676857 h1:sPkzT7Z7uLmejOsBRlZ0kwDWpqjpHJsp834o5nbhqho=
github.com/status-im/zxcvbn-go v0.0.0-20220311183720-5e8676676857/go.mod h1:lq9I5ROto5tcua65GmCE6SIW7VE0ucdEBs1fn4z7uWU=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
@ -2013,7 +2017,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -2025,7 +2031,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@ -2088,10 +2096,10 @@ github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98 h1:xwY0kW5XZF
github.com/waku-org/go-discover v0.0.0-20221209174356-61c833f34d98/go.mod h1:eBHgM6T4EG0RZzxpxKy+rGz/6Dw2Nd8DWxS0lm9ESDw=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7 h1:0e1h+p84yBp0IN7AqgbZlV7lgFBjm214lgSOE7CeJmE=
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20230628220917-7b4e5ae4c0e7/go.mod h1:pFvOZ9YTFsW0o5zJW7a0B5tr1owAijRWJctXJ2toL04=
github.com/waku-org/go-waku v0.8.1-0.20231103161423-351dd55a1498 h1:2Y06Ni3tBj2LQA0ys1o1PspZxZPM9GOKwNEGolbueQ4=
github.com/waku-org/go-waku v0.8.1-0.20231103161423-351dd55a1498/go.mod h1:hem2hnXK5BdabxwJULszM0Rh1Yj+gD9IxjwLCGPPaxs=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd h1:cu7CsUo7BK6ac/v193RIaqAzUcmpa6MNY4xYW9AenQI=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20230916173259-d284a3d8f2fd/go.mod h1:1PdBdPzyTaKt3VnpAHk3zj+r9dXPFOr3IHZP9nFle6E=
github.com/waku-org/go-waku v0.8.1-0.20240104144340-585648c4eefe h1:2D97fbaKlIQRjWMz/iTjnYcxi2z6ekKvspTGtcuPHgU=
github.com/waku-org/go-waku v0.8.1-0.20240104144340-585648c4eefe/go.mod h1:+b5fPPJ4YUIAPJtPOtwB7bTrOQ9lF15I2LnQjV6NMIA=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 h1:jisj+OCI6QydLtFq3Pyhu49wl9ytPN7oAHjMfepHDrA=
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59/go.mod h1:1PdBdPzyTaKt3VnpAHk3zj+r9dXPFOr3IHZP9nFle6E=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE=
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b/go.mod h1:KYykqtdApHVYZ3G0spwMnoxc5jH5eI3jyO9SwsSfi48=
github.com/waku-org/go-zerokit-rln-arm v0.0.0-20230916171929-1dd9494ff065 h1:Sd7QD/1Yo2o2M1MY49F8Zr4KNBPUEK5cz5HoXQVJbrs=
@ -2104,8 +2112,6 @@ github.com/wealdtech/go-multicodec v1.4.0 h1:iq5PgxwssxnXGGPTIK1srvt6U5bJwIp7k6k
github.com/wealdtech/go-multicodec v1.4.0/go.mod h1:aedGMaTeYkIqi/KCPre1ho5rTb3hGpu/snBOS3GQLw4=
github.com/wealdtech/go-string2eth v1.1.0 h1:USJQmysUrBYYmZs7d45pMb90hRSyEwizP7lZaOZLDAw=
github.com/wealdtech/go-string2eth v1.1.0/go.mod h1:RUzsLjJtbZaJ/3UKn9kY19a/vCCUHtEWoUW3uiK6yGU=
github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070 h1:imZLWyo1ondeQjqfb/eHuYgFiOAYg6ugSMCnGfPTPmg=
github.com/webview/webview_go v0.0.0-20230901181450-5a14030a9070/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
@ -2148,6 +2154,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=

View File

@ -0,0 +1,47 @@
package helpers
import (
"github.com/status-im/status-go/multiaccounts/accounts"
)
func GetAllAccounts() (res []accounts.Account, err error) {
jsonRes, err := CallPrivateMethod("accounts_getAccounts", []interface{}{})
if err != nil {
return nil, err
}
var allAccounts []accounts.Account
err = GetRPCAPIResponse(jsonRes, &allAccounts)
if err != nil {
return nil, err
}
return allAccounts, nil
}
func GetWalletWatchOnlyAccounts() (res []accounts.Account, err error) {
accounts, err := GetAllAccounts()
if err != nil {
return nil, err
}
for _, acc := range accounts {
if !acc.IsWalletNonWatchOnlyAccount() {
res = append(res, acc)
}
}
return res, nil
}
func GetWalletOperableAccounts() (res []accounts.Account, err error) {
accounts, err := GetAllAccounts()
if err != nil {
return nil, err
}
for _, acc := range accounts {
if acc.IsWalletAccountReadyForTransaction() {
res = append(res, acc)
}
}
return res, nil
}

View File

@ -0,0 +1,314 @@
package helpers
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/log"
statusgo "github.com/status-im/status-go/mobile"
"github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/services/wallet/walletevent"
"github.com/status-im/status-go/signal"
)
type StatusGoEventName string
const NodeReadyEvent StatusGoEventName = "nodeReady"
const WalletEvent StatusGoEventName = "wallet"
type GoEvent struct {
Name StatusGoEventName `json:"name"`
Payload interface{} `json:"payload"`
}
type envelope struct {
Type string `json:"type"`
Event json.RawMessage `json:"event"` // Use json.RawMessage to delay parsing
}
func signalHandler(eventQueue chan GoEvent, jsonEvent string) {
envelope := envelope{}
err := json.Unmarshal([]byte(jsonEvent), &envelope)
if err != nil {
apiResponse := statusgo.APIResponse{}
err = json.Unmarshal([]byte(jsonEvent), &apiResponse)
if err != nil {
log.Error("Error parsing the signal event: ", err)
} else if apiResponse.Error != "" {
log.Error("Error from status-go: ", apiResponse.Error)
} else {
log.Error("Unknown JSON content for event", jsonEvent)
}
return
}
if envelope.Type == signal.EventNodeReady {
eventQueue <- GoEvent{Name: NodeReadyEvent, Payload: string(envelope.Event)}
} else if envelope.Type == string(WalletEvent) {
walletEvent := walletevent.Event{}
err := json.Unmarshal(envelope.Event, &walletEvent)
if err != nil {
log.Error("Error parsing the wallet event: ", err)
return
}
eventQueue <- GoEvent{Name: WalletEvent, Payload: walletEvent}
}
}
func LoginToTestAccount(t *testing.T) (eventQueue chan GoEvent, config *Config, l log.Logger) {
l = log.New()
l.SetHandler(log.CallerFileHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
// Setup status-go logger
log.Root().SetHandler(log.CallerFileHandler(log.StdoutHandler))
eventQueue = make(chan GoEvent, 10000)
signal.SetDefaultNodeNotificationHandler(func(jsonEvent string) {
signalHandler(eventQueue, jsonEvent)
})
conf, nodeConfigJson, userFolder, err := processConfigArgs("./.integration_tests_config.json")
if err != nil {
t.Fatal(err)
}
config = conf
// Login to first account
err = loginToAccount(config.HashedPassword, userFolder, nodeConfigJson)
if err != nil {
t.Fatal(err)
}
return
}
func WaitForEvent(eventQueue chan GoEvent, eventName StatusGoEventName, timeout time.Duration) (event *GoEvent, err error) {
for {
select {
case event := <-eventQueue:
if event.Name == eventName {
return &event, nil
}
case <-time.After(timeout):
return nil, fmt.Errorf("timeout waiting for event %s", eventName)
}
}
}
func WaitForWalletEvent[T any](eventQueue chan GoEvent, eventName walletevent.EventType, timeout time.Duration) (payload *T, err error) {
var event *GoEvent
for {
event, err = WaitForEvent(eventQueue, WalletEvent, timeout)
if err != nil {
return nil, err
}
walletEvent, ok := event.Payload.(walletevent.Event)
if !ok {
return nil, errors.New("event payload is not a wallet event")
}
var newPayload T
if walletEvent.Type == eventName {
if walletEvent.Message != "" {
err = json.Unmarshal([]byte(walletEvent.Message), &newPayload)
if err != nil {
return nil, err
}
return &newPayload, nil
}
return nil, nil
}
}
}
func loginToAccount(hashedPassword, userFolder, nodeConfigJson string) error {
absUserFolder, err := filepath.Abs(userFolder)
if err != nil {
return err
}
accountsJson := statusgo.OpenAccounts(absUserFolder)
accounts := make([]multiaccounts.Account, 0)
err = GetCAPIResponse(accountsJson, &accounts)
if err != nil {
return err
}
if len(accounts) == 0 {
return fmt.Errorf("no accounts found")
}
account := accounts[0]
keystorePath := filepath.Join(filepath.Join(absUserFolder, "keystore/"), account.KeyUID)
initKeystoreJson := statusgo.InitKeystore(keystorePath)
apiResponse := statusgo.APIResponse{}
err = GetCAPIResponse(initKeystoreJson, &apiResponse)
if err != nil {
return err
}
//serialize account of type multiaccounts.Account
accountJson, err := json.Marshal(account)
if err != nil {
return err
}
loginJson := statusgo.LoginWithConfig(string(accountJson), hashedPassword, nodeConfigJson)
err = GetCAPIResponse(loginJson, &apiResponse)
if err != nil {
return err
}
return nil
}
type jsonrpcMessage struct {
Version string `json:"jsonrpc"`
ID json.RawMessage `json:"id"`
}
type jsonrpcRequest struct {
jsonrpcMessage
ChainID uint64 `json:"chainId"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
func CallPrivateMethod(method string, params []interface{}) (string, error) {
var paramsJson json.RawMessage
var err error
if params != nil {
paramsJson, err = json.Marshal(params)
if err != nil {
return "", err
}
}
msg := jsonrpcRequest{
jsonrpcMessage: jsonrpcMessage{
Version: "2.0",
},
Method: method,
Params: paramsJson,
}
msgJson, err := json.Marshal(msg)
if err != nil {
return "", err
}
return statusgo.CallPrivateRPC(string(msgJson)), nil
}
type Config struct {
HashedPassword string `json:"hashedPassword"`
NodeConfigFile string `json:"nodeConfigFile"`
DataDir string `json:"dataDir"`
}
// processConfigArgs expects that configFilePath points to a JSON file that contains a Config struct
// For now this are for developer to manually run them using an existing user folder.
// TODO: ideally we would generate a temporary user folder to be used in the entire suite.
func processConfigArgs(configFilePath string) (config *Config, nodeConfigJson string, userFolder string, err error) {
config = &Config{}
// parse config file
configFile, err := os.Open(configFilePath)
if err != nil {
return nil, "", "", err
}
defer configFile.Close()
jsonParser := json.NewDecoder(configFile)
if err = jsonParser.Decode(&config); err != nil {
panic(err)
}
nodeConfigFile, err := os.Open(config.NodeConfigFile)
if err != nil {
panic(err)
}
defer nodeConfigFile.Close()
nodeConfigData, err := io.ReadAll(nodeConfigFile)
if err == nil {
nodeConfigJson = string(nodeConfigData)
}
userFolder = config.DataDir
return
}
func GetCAPIResponse[T any](responseJson string, res T) error {
apiResponse := statusgo.APIResponse{}
err := json.Unmarshal([]byte(responseJson), &apiResponse)
if err == nil {
if apiResponse.Error != "" {
return fmt.Errorf("API error: %s", apiResponse.Error)
}
}
typeOfT := reflect.TypeOf(res)
kindOfT := typeOfT.Kind()
// Check for valid types: pointer, slice, map
if kindOfT != reflect.Ptr && kindOfT != reflect.Slice && kindOfT != reflect.Map {
return fmt.Errorf("type T must be a pointer, slice, or map")
}
if err := json.Unmarshal([]byte(responseJson), &res); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
return nil
}
type jsonrpcSuccessfulResponse struct {
jsonrpcMessage
Result json.RawMessage `json:"result"`
}
type jsonrpcErrorResponse struct {
jsonrpcMessage
Error jsonError `json:"error"`
}
// jsonError represents Error message for JSON-RPC responses.
type jsonError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func GetRPCAPIResponse[T any](responseJson string, res T) error {
errApiResponse := jsonrpcErrorResponse{}
err := json.Unmarshal([]byte(responseJson), &errApiResponse)
if err == nil && errApiResponse.Error.Code != 0 {
return fmt.Errorf("API error: %#v", errApiResponse.Error)
}
apiResponse := jsonrpcSuccessfulResponse{}
err = json.Unmarshal([]byte(responseJson), &apiResponse)
if err != nil {
return fmt.Errorf("failed to unmarshal jsonrpcSuccessfulResponse: %w", err)
}
typeOfT := reflect.TypeOf(res)
kindOfT := typeOfT.Kind()
// Check for valid types: pointer, slice, map
if kindOfT != reflect.Ptr && kindOfT != reflect.Slice && kindOfT != reflect.Map {
return fmt.Errorf("type T must be a pointer, slice, or map")
}
if err := json.Unmarshal(apiResponse.Result, &res); err != nil {
return fmt.Errorf("failed to unmarshal data: %w", err)
}
return nil
}

View File

@ -0,0 +1,5 @@
{
"hashedPassword": "0x<hashed password as expected by the login call>",
"nodeConfigFile": "<node config JSON as expected by the login call>",
"dataDir": "<optional user's status data dir (usually status-desktop/Status/data>"
}

View File

@ -0,0 +1,85 @@
// These tests are for development only to be run manually
// There is more work needed to automate them not to depend on an existing account and internet connection
package wallet
import (
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-desktop/test/status-go/integration/helpers"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/services/wallet/bridge"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/transactions"
)
// TestPendingTx_NotificationStatus tests that a pending transaction is created, then updated and finally deleted.
func TestPendingTx_NotificationStatus(t *testing.T) {
eventQueue, conf, _ := helpers.LoginToTestAccount(t)
_, err := helpers.WaitForEvent(eventQueue, helpers.NodeReadyEvent, 60*time.Second)
require.NoError(t, err)
opAccounts, err := helpers.GetWalletOperableAccounts()
require.NoError(t, err)
require.Greater(t, len(opAccounts), 0)
sender := opAccounts[0]
watchAccounts, err := helpers.GetWalletWatchOnlyAccounts()
require.NoError(t, err)
require.Greater(t, len(watchAccounts), 0)
recipient := watchAccounts[0]
mTCommand := transfer.MultiTransactionCommand{
FromAddress: common.Address(sender.Address),
ToAddress: common.Address(recipient.Address),
FromAsset: "ETH",
ToAsset: "ETH",
FromAmount: (*hexutil.Big)(new(big.Int).SetUint64(100000)),
Type: transfer.MultiTransactionSend,
}
data := []*bridge.TransactionBridge{
{
BridgeName: "Transfer",
ChainID: 5,
TransferTx: &transactions.SendTxArgs{
From: sender.Address,
To: &recipient.Address,
Value: (*hexutil.Big)(new(big.Int).Set(mTCommand.FromAmount.ToInt())),
},
},
}
password := conf.HashedPassword
// Step 1: send a transaction that will generate a pending entry
sessionReqRes, err := helpers.CallPrivateMethod("wallet_createMultiTransaction", []interface{}{mTCommand, data, password})
require.NoError(t, err)
var apiResponse *transfer.MultiTransactionCommandResult
err = helpers.GetRPCAPIResponse(sessionReqRes, &apiResponse)
require.NoError(t, err)
require.Equal(t, 1, len(apiResponse.Hashes))
// Step 2: wait for the pending entry to be confirmed
statusPayload, err := helpers.WaitForWalletEvent[transactions.StatusChangedPayload](eventQueue, transactions.EventPendingTransactionStatusChanged, 60*time.Second)
require.NoError(t, err)
require.Equal(t, statusPayload.Status, transactions.Success)
// Step 3: Trigger downloading of the new transaction ...
_, err = helpers.CallPrivateMethod("wallet_checkRecentHistoryForChainIDs", []interface{}{[]uint64{5}, []types.Address{sender.Address, recipient.Address}})
require.NoError(t, err)
// ... and wait for the new transaction download to trigger deletion from pending_transactions
updatePayload, err := helpers.WaitForWalletEvent[transactions.PendingTxUpdatePayload](eventQueue, transactions.EventPendingTransactionUpdate, 60*time.Second)
require.NoError(t, err)
require.True(t, updatePayload.Deleted)
}