test(wallet)_: status-go integration tests (#5302)

This commit is contained in:
Anton Danchenko 2024-06-11 13:36:20 +02:00 committed by GitHub
parent 6a72afce8e
commit 74e9ce93bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 808 additions and 1 deletions

8
.gitignore vendored
View File

@ -101,3 +101,11 @@ test-bob/
# Nix
/.nix-gcroots/
# integration-tests
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
.envrc
report/results.xml

View File

@ -1,5 +1,5 @@
# Build status-go in a Go builder container
FROM golang:1.20-alpine3.18 as builder
FROM golang:1.21-alpine3.18 as builder
RUN apk add --no-cache make gcc g++ musl-dev linux-headers
@ -24,9 +24,11 @@ LABEL description="status-go is an underlying part of Status - a browser, messen
RUN apk add --no-cache ca-certificates bash libgcc libstdc++
RUN mkdir -p /static/keys
RUN mkdir -p /static/configs
COPY --from=builder /go/src/github.com/status-im/status-go/build/bin/$build_target /usr/local/bin/
COPY --from=builder /go/src/github.com/status-im/status-go/static/keys/* /static/keys/
COPY --from=builder /go/src/github.com/status-im/status-go/integration-tests/config.json /static/configs/
RUN ln -s /usr/local/bin/$build_target /usr/local/bin/entrypoint

View File

@ -0,0 +1,40 @@
## Overview
Integration tests for status-go
## Table of Contents
- [Overview](#overview)
- [How to Install](#how-to-install)
- [How to Run](#how-to-run)
- [Running Tests](#running-tests)
- [Implementation details](#implementation-details)
## How to Install
* Install [Docker](https://docs.docker.com/engine/install/) and [Docker Compose](https://docs.docker.com/compose/install/)
- Checkout all listed contract repositories into one folder:
- [Status Network Token V2](https://github.com/status-im/status-network-token-v2)
- [Communities Contracts](https://github.com/status-im/communities-contracts?tab=readme-ov-file#installation-and-development)
* Add environment variable, path to contract repos e.g. `export PATH_TO_CONTRACT_REPOS=~/directory-with-every-contract-repo`
* Install [Python 3.10.14](https://www.python.org/downloads/)
* In `integration-tests` directory, run `pip install -r requirements.txt`
* **Optional (for test development)**: Use Python virtual environment for better dependency management. You can follow the guide [here](https://akrabat.com/creating-virtual-environments-with-pyenv/):
## How to Run
- In `integration-tests/tests` run `docker compose up --build`, as result:
* a container with [status-go as deamon](https://github.com/status-im/status-go/issues/5175) will be created with APIModules exposed on `0.0.0.0:3333`
* status-go will use [anvil](https://book.getfoundry.sh/reference/anvil/) as RPCURL with ChainID 31337
* also all Status-im contracts are going to be deployed to the new network
### Running Tests
* In `integration-tests/tests` directory run `pytest -m wallet`
## Implementation details
- Integration tests are implemented in `integration-tests/tests` based on [pytest](https://docs.pytest.org/en/8.2.x/)
- Every test has two types of verifications:
- `verify_is_valid_json_rpc_response()` checks for status code 200, non-empty response, JSON-RPC structure, presence of the `result` field, and expected ID.
- `jsonschema.validate()` is used to check that the response contains expected data, including required fields and types. Schemas are stored in `/schemas/wallet_MethodName`
- New schemas can be generated using `integration-tests/schema_builder.py` by passing a response to the `CustomSchemaBuilder(schema_name).create_schema(response.json())` method, should be used only on test creation phase, please search `how to create schema:` to see an example in a test

View File

@ -0,0 +1,23 @@
{
"DataDir": "/tmp/status-go-data",
"NodeKey": "b5c65bcfdb02261052b71cb5f6dec095d0e10df649b4a2b7d881e55e306cbebc",
"Rendezvous": false,
"NoDiscovery": false,
"ListenAddr": "0.0.0.0:30303",
"HTTPEnabled": true,
"HTTPHost": "0.0.0.0",
"HTTPPort": 3333,
"APIModules": "eth,admin,wallet",
"WalletConfig": {
"Enabled": true
},
"WakuConfig": {
"Enabled": false
},
"Networks": [
{
"ChainID": 31337,
"RPCURL": "http://anvil:8545"
}
]
}

View File

@ -0,0 +1,43 @@
import os
from dataclasses import dataclass
def pytest_addoption(parser):
parser.addoption(
"--rpc_url",
action="store",
help="",
default="http://0.0.0.0:3333",
)
parser.addoption(
"--password",
action="store",
help="",
default="Strong12345",
)
@dataclass
class Account():
address: str
private_key: str
user_1 = Account(
address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
private_key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
)
user_2 = Account(
address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
private_key="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
)
@dataclass
class Option:
pass
option = Option()
def pytest_configure(config):
global option
option = config.option
option.base_dir = os.path.dirname(os.path.abspath(__file__))

View File

@ -0,0 +1,56 @@
services:
anvil:
image: ghcr.io/foundry-rs/foundry:latest
platform: linux/amd64
command:
- anvil --host 0.0.0.0
ports:
- 8545:8545
deploy-sntv2:
environment:
- API_KEY_ETHERSCAN="" # value isn't used, but env var is required
depends_on:
- anvil
image: ghcr.io/foundry-rs/foundry:latest
platform: linux/amd64
working_dir: /usr/local/bin/status-network-token-v2
command:
- forge clean && forge build && forge script script/Deploy.s.sol --broadcast --fork-url http://anvil:8545 --private-key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
volumes:
- ${PATH_TO_CONTRACT_REPOS}/status-network-token-v2:/usr/local/bin/status-network-token-v2
deploy-communities-contracts:
environment:
# values arn't used, but env vars are required
- API_KEY_ETHERSCAN=""
- API_KEY_ARBISCAN=""
- API_KEY_OPTIMISTIC_ETHERSCAN=""
depends_on:
- anvil
- deploy-sntv2
image: ghcr.io/foundry-rs/foundry:latest
platform: linux/amd64
working_dir: /usr/local/bin/communities-contracts
command:
- forge clean && forge build && forge script script/DeployContracts.s.sol --broadcast --fork-url http://anvil:8545 --private-key="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
volumes:
- ${PATH_TO_CONTRACT_REPOS}/communities-contracts:/usr/local/bin/communities-contracts
status-go:
build:
context: ../
dockerfile: _assets/build/Dockerfile
args:
build_tags: gowaku_no_rln
build_target: statusd
build_flags: -ldflags="-X github.com/status-im/status-go/params.Version= -X github.com/status-im/status-go/params.GitCommit=11f83780d -X github.com/status-im/status-go/params.IpfsGatewayURL=https://ipfs.status.im/ -X github.com/status-im/status-go/vendor/github.com/ethereum/go-ethereum/metrics.EnabledStr=true"
ports:
- 3333:3333
- 8080:8080
- 30303:30303
- 30303:30303/udp
- 30304:30304/udp
entrypoint: ["statusd", "-c", "/static/configs/config.json", "--seed-phrase=test test test test test test test test test test test junk", "--password=Strong12345"]

View File

@ -0,0 +1,7 @@
[pytest]
addopts = -s -v --tb=short
markers =
rpc
wallet
tx

View File

@ -0,0 +1,5 @@
deepdiff==5.5.0
jsonschema~=3.2.0
pytest==6.2.4
requests==2.31.0
genson~=1.2.2

View File

@ -0,0 +1,20 @@
import json
import os
from conftest import option
from genson import SchemaBuilder
class CustomSchemaBuilder(SchemaBuilder):
def __init__(self, schema_name):
super().__init__()
self.path = f"{option.base_dir}/schemas/{schema_name}"
def create_schema(self, response_json):
builder = SchemaBuilder()
builder.add_object(response_json)
schema = builder.to_schema()
os.makedirs(os.path.dirname(self.path), exist_ok=True)
with open(self.path, "w") as file:
json.dump(schema, file, sort_keys=True, indent=4, ensure_ascii=False)

View File

@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"type": "null"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "integer"
},
"jsonrpc": {
"type": "string"
},
"result": {
"properties": {
"hashes": {
"properties": {
"31337": {
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"31337"
],
"type": "object"
},
"id": {
"type": "integer"
}
},
"required": [
"hashes",
"id"
],
"type": "object"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,66 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"items": {
"properties": {
"description": {
"type": "string"
},
"fees": {
"type": "string"
},
"hostname": {
"type": "string"
},
"logoUrl": {
"type": "string"
},
"name": {
"type": "string"
},
"params": {
"type": "null"
},
"recurrentSiteUrl": {
"type": "string"
},
"siteUrl": {
"type": "string"
},
"supportedChainIds": {
"items": {
"type": "integer"
},
"type": "array"
}
},
"required": [
"description",
"fees",
"hostname",
"logoUrl",
"name",
"params",
"recurrentSiteUrl",
"siteUrl",
"supportedChainIds"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"type": "null"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,91 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"items": {
"properties": {
"additionalData": {
"type": "string"
},
"autoDelete": {
"type": "boolean"
},
"data": {
"type": "string"
},
"from": {
"type": "string"
},
"gasLimit": {
"type": "string"
},
"gasPrice": {
"type": "string"
},
"hash": {
"type": "string"
},
"multi_transaction_id": {
"type": "integer"
},
"network_id": {
"type": "integer"
},
"nonce": {
"type": "integer"
},
"status": {
"type": "string"
},
"symbol": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"to": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"additionalData",
"autoDelete",
"data",
"from",
"gasLimit",
"gasPrice",
"hash",
"multi_transaction_id",
"network_id",
"nonce",
"status",
"symbol",
"timestamp",
"to",
"type",
"value"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,91 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"items": {
"properties": {
"additionalData": {
"type": "string"
},
"autoDelete": {
"type": "boolean"
},
"data": {
"type": "string"
},
"from": {
"type": "string"
},
"gasLimit": {
"type": "string"
},
"gasPrice": {
"type": "string"
},
"hash": {
"type": "string"
},
"multi_transaction_id": {
"type": "integer"
},
"network_id": {
"type": "integer"
},
"nonce": {
"type": "integer"
},
"status": {
"type": "string"
},
"symbol": {
"type": "string"
},
"timestamp": {
"type": "integer"
},
"to": {
"type": "string"
},
"type": {
"type": "string"
},
"value": {
"type": "string"
}
},
"required": [
"additionalData",
"autoDelete",
"data",
"from",
"gasLimit",
"gasPrice",
"hash",
"multi_transaction_id",
"network_id",
"nonce",
"status",
"symbol",
"timestamp",
"to",
"type",
"value"
],
"type": "object"
},
"type": "array"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,94 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"properties": {
"data": {
"items": {
"properties": {
"name": {
"type": "string"
},
"source": {
"type": "string"
},
"tokens": {
"items": {
"properties": {
"address": {
"type": "string"
},
"chainId": {
"type": "integer"
},
"decimals": {
"type": "integer"
},
"name": {
"type": "string"
},
"pegSymbol": {
"type": "string"
},
"symbol": {
"type": "string"
},
"tokenListId": {
"type": "string"
},
"verified": {
"type": "boolean"
}
},
"required": [
"address",
"chainId",
"decimals",
"name",
"pegSymbol",
"symbol",
"tokenListId",
"verified"
],
"type": "object"
},
"type": "array"
},
"version": {
"type": "string"
}
},
"required": [
"name",
"source",
"tokens",
"version"
],
"type": "object"
},
"type": "array"
},
"updatedAt": {
"type": "integer"
}
},
"required": [
"data",
"updatedAt"
],
"type": "object"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/schema#",
"properties": {
"id": {
"type": "string"
},
"jsonrpc": {
"type": "string"
},
"result": {
"type": "null"
}
},
"required": [
"id",
"jsonrpc",
"result"
],
"type": "object"
}

View File

@ -0,0 +1,89 @@
import json
import requests
from conftest import option, user_1, user_2
class RpcTestCase:
def setup_method(self):
self.network_id = 31337
def verify_is_valid_json_rpc_response(self, response, _id=None):
assert response.status_code == 200
assert response.content
try:
response.json()["result"]
except json.JSONDecodeError:
raise AssertionError(f"invalid JSON in {response.content}")
except KeyError:
raise AssertionError(f"no 'result' in {response.json()}")
if _id:
try:
if _id != response.json()["id"]:
raise AssertionError(
f"got id: {response.json()['id']} instead of expected id: {_id}"
)
except KeyError:
raise AssertionError(f"no id in response {response.json()}")
return response
def rpc_request(self, method, params=[], _id=None):
data = {"jsonrpc": "2.0", "method": method}
if params:
data["params"] = params
data["id"] = _id if _id else 13
response = requests.post(option.rpc_url, json=data)
return response
class TransactionTestCase(RpcTestCase):
def wallet_create_multi_transaction(self):
method = "wallet_createMultiTransaction"
params = [
{
"fromAddress": user_1.address,
"fromAmount": "0x5af3107a4000",
"fromAsset": "ETH",
"multiTxType": "MultiTransactionSend",
"toAddress": user_2.address,
"toAsset": "ETH",
},
[
{
"bridgeName": "Transfer",
"chainID": 31337,
"transferTx": {
"data": "",
"from": user_1.address,
"gas": "0x5BBF",
"input": "",
"maxFeePerGas": "0xbcc0f04fd",
"maxPriorityFeePerGas": "0x3b9aca00",
"to": user_2.address,
"type": "0x02",
"value": "0x5af3107a4000",
},
}
],
f"{option.password}",
]
response = self.rpc_request(method, params, 13)
self.verify_is_valid_json_rpc_response(response)
return response
def setup_method(self):
super().setup_method()
response = self.wallet_create_multi_transaction()
try:
self.tx_hash = response.json()["result"]["hashes"][str(self.network_id)][0]
except (KeyError, json.JSONDecodeError):
raise Exception(response.content)

View File

@ -0,0 +1,69 @@
import random
import pytest
import jsonschema
import json
from conftest import option, user_1, user_2
from test_cases import RpcTestCase, TransactionTestCase
@pytest.mark.wallet
@pytest.mark.tx
class TestTransactionRpc(TransactionTestCase):
@pytest.mark.parametrize(
"method, params",
[
(
"wallet_checkRecentHistoryForChainIDs",
[[31337], ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]],
),
(
"wallet_getPendingTransactionsForIdentities",
[[{"chainId": None, "hash": None}]],
),
],
)
def test_tx_(self, method, params):
_id = str(random.randint(1, 9999))
if method in ["wallet_getPendingTransactionsForIdentities"]:
params[0][0]["chainId"] = self.network_id
params[0][0]["hash"] = self.tx_hash
response = self.rpc_request(method, params, _id)
self.verify_is_valid_json_rpc_response(response)
with open(f"{option.base_dir}/schemas/{method}", "r") as schema:
jsonschema.validate(instance=response.json(), schema=json.load(schema))
def test_create_multi_transaction(self):
response = self.wallet_create_multi_transaction()
# how to create schema:
# from schema_builder import CustomSchemaBuilder
# CustomSchemaBuilder("wallet_createMultiTransaction").create_schema(response.json())
with open(f"{option.base_dir}/schemas/wallet_createMultiTransaction", "r") as schema:
jsonschema.validate(instance=response.json(), schema=json.load(schema))
@pytest.mark.wallet
class TestRpc(RpcTestCase):
@pytest.mark.parametrize(
"method, params",
[
("wallet_startWallet", []),
("wallet_getEthereumChains", []),
("wallet_startWallet", []),
("wallet_getTokenList", []),
("wallet_getCryptoOnRamps", []),
],
)
def test_(self, method, params):
_id = str(random.randint(1, 8888))
response = self.rpc_request(method, params, _id)
self.verify_is_valid_json_rpc_response(response)
with open(f"{option.base_dir}/schemas/{method}", "r") as schema:
jsonschema.validate(instance=response.json(), schema=json.load(schema))