Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
288 lines
7.6 KiB
Go
288 lines
7.6 KiB
Go
package envtest
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/buger/jsonparser"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
sdktypes "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/ignite/cli/v29/ignite/pkg/multiformatname"
|
|
"github.com/ignite/cli/v29/ignite/templates/field"
|
|
"github.com/ignite/cli/v29/ignite/templates/field/datatype"
|
|
)
|
|
|
|
// testValue determines the default test value for a given datatype.
|
|
func testValue(name datatype.Name) string {
|
|
// Repeated custom message fields in AutoCLI expect one JSON object per argument.
|
|
// Using "{}" keeps simulation compatible with both singular and repeated custom types.
|
|
if name == datatype.CustomSlice {
|
|
return "{}"
|
|
}
|
|
|
|
dt, _ := datatype.IsSupportedType(name)
|
|
return dt.DefaultTestValue
|
|
}
|
|
|
|
// txArgs generates transaction arguments as strings from a given set of fields.
|
|
func txArgs(fields field.Fields) []string {
|
|
args := make([]string, len(fields))
|
|
for i, f := range fields {
|
|
args[i] = testValue(f.DatatypeName)
|
|
}
|
|
return args
|
|
}
|
|
|
|
// assertJSONData verifies that the JSON data contains expected values for the given fields.
|
|
func (a *App) assertJSONData(data []byte, msgName string, fields field.Fields) {
|
|
for _, f := range fields {
|
|
dt := testValue(f.DatatypeName)
|
|
value, _, _, err := jsonparser.Get(data, msgName, f.Name.Snake)
|
|
require.NoError(a.env.T(), err)
|
|
if dt == "{}" {
|
|
continue
|
|
}
|
|
v := string(value)
|
|
switch {
|
|
case f.DatatypeName == datatype.Coin:
|
|
|
|
c, err := sdktypes.ParseCoinNormalized(dt)
|
|
require.NoError(a.env.T(), err)
|
|
amount, err := jsonparser.GetString(value, "amount")
|
|
require.NoError(a.env.T(), err)
|
|
require.EqualValues(a.env.T(), amount, c.Amount.String())
|
|
denom, err := jsonparser.GetString(value, "denom")
|
|
require.NoError(a.env.T(), err)
|
|
require.EqualValues(a.env.T(), denom, c.Denom)
|
|
|
|
case f.DatatypeName == datatype.Coins || f.DatatypeName == datatype.CoinSliceAlias:
|
|
|
|
c, err := sdktypes.ParseCoinsNormalized(dt)
|
|
require.NoError(a.env.T(), err)
|
|
cJSON, err := c.MarshalJSON()
|
|
require.NoError(a.env.T(), err)
|
|
dt = string(cJSON)
|
|
require.JSONEq(a.env.T(), dt, v)
|
|
|
|
case f.DatatypeName == datatype.DecCoin || f.DatatypeName == datatype.DecCoins || f.DatatypeName == datatype.DecCoinSliceAlias:
|
|
|
|
c, err := sdktypes.ParseCoinNormalized(dt)
|
|
require.NoError(a.env.T(), err)
|
|
// TODO find a better way to compare DecCoins as they have a different result pattern from CLI and Query
|
|
require.Contains(a.env.T(), v, c.Denom)
|
|
require.Contains(a.env.T(), v, c.Amount.String())
|
|
|
|
case f.IsSlice():
|
|
|
|
var slice []string
|
|
_, err = jsonparser.ArrayEach(value, func(value []byte, _ jsonparser.ValueType, _ int, _ error) {
|
|
slice = append(slice, string(value))
|
|
})
|
|
require.NoError(a.env.T(), err)
|
|
v = strings.Join(slice, ",")
|
|
require.EqualValues(a.env.T(), dt, v)
|
|
|
|
default:
|
|
require.EqualValues(a.env.T(), dt, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// assertJSONList verifies that a JSON array contains expected values for the given fields.
|
|
func (a *App) assertJSONList(data []byte, msgName string, fields field.Fields) {
|
|
value, _, _, err := jsonparser.Get(data, msgName)
|
|
require.NoError(a.env.t, err)
|
|
|
|
a.assertJSONData(value, "[0]", fields)
|
|
}
|
|
|
|
// createTx sends a transaction to create a resource and verifies the response from the chain.
|
|
func (a *App) createTx(
|
|
servers Hosts,
|
|
module string,
|
|
name multiformatname.Name,
|
|
args ...string,
|
|
) {
|
|
// Submit the transaction and verify it was accepted
|
|
txResponse := a.CLITx(
|
|
servers.RPC,
|
|
module,
|
|
"create-"+name.Kebab,
|
|
args...,
|
|
)
|
|
require.Equal(a.env.T(), 0, txResponse.Code,
|
|
"tx failed code=%d log=%s", txResponse.Code, txResponse.RawLog)
|
|
|
|
// Query the transaction using its hash
|
|
tx := a.CLIQueryTx(
|
|
servers.RPC,
|
|
txResponse.TxHash,
|
|
)
|
|
require.Equal(a.env.T(), 0, tx.Code,
|
|
"tx failed code=%d log=%s", txResponse.Code, txResponse.RawLog)
|
|
}
|
|
|
|
// RunChainAndSimulateTxs starts the blockchain network and runs transaction simulations.
|
|
func (a *App) RunChainAndSimulateTxs(servers Hosts) {
|
|
ctx, cancel := context.WithCancel(a.env.ctx)
|
|
defer cancel()
|
|
|
|
// Start serving the blockchain in a separate goroutine
|
|
go func() {
|
|
a.MustServe(ctx)
|
|
}()
|
|
|
|
// Wait until the chain is up and running
|
|
a.WaitChainUp(ctx, servers.API)
|
|
|
|
// Run the transaction simulations
|
|
a.RunSimulationTxs(ctx, servers)
|
|
}
|
|
|
|
// RunSimulationTxs runs different types of transactions for modules and queries the chain.
|
|
func (a *App) RunSimulationTxs(ctx context.Context, servers Hosts) {
|
|
for _, s := range a.scaffolded {
|
|
module := s.module
|
|
if module == "" {
|
|
module = a.name
|
|
}
|
|
name, err := multiformatname.NewName(s.name)
|
|
require.NoError(a.env.t, err)
|
|
|
|
// Handle different types of scaffolds
|
|
switch s.typeName {
|
|
case "module":
|
|
// No transactions for "module" type
|
|
case "list":
|
|
a.SendListTxsAndQueryFirst(ctx, servers, module, name, s.fields)
|
|
case "map":
|
|
a.SendMapTxsAndQuery(ctx, servers, module, name, s.fields, s.index)
|
|
case "single":
|
|
a.SendSingleTxsAndQuery(ctx, servers, module, name, s.fields)
|
|
case "params":
|
|
case "message":
|
|
case "query":
|
|
case "configs":
|
|
case "type":
|
|
case "packet":
|
|
}
|
|
}
|
|
}
|
|
|
|
// SendSingleTxsAndQuery submits a single transaction and queries the result from both CLI and API.
|
|
func (a *App) SendSingleTxsAndQuery(
|
|
ctx context.Context,
|
|
servers Hosts,
|
|
module string,
|
|
name multiformatname.Name,
|
|
fields field.Fields,
|
|
) {
|
|
// Generate transaction arguments and submit the transaction
|
|
args := txArgs(fields)
|
|
a.createTx(servers, module, name, args...)
|
|
|
|
// Query the state via CLI
|
|
queryResponse := a.CLIQuery(
|
|
servers.RPC,
|
|
module,
|
|
"get-"+name.Kebab,
|
|
)
|
|
a.assertJSONData(queryResponse, name.Snake, fields)
|
|
|
|
// Query the state via API
|
|
apiResponse := a.APIQuery(
|
|
ctx,
|
|
servers.API,
|
|
a.namespace,
|
|
module,
|
|
name.Snake,
|
|
)
|
|
a.assertJSONData(apiResponse, name.Snake, fields)
|
|
|
|
// Ensure CLI and API responses match
|
|
require.JSONEq(a.env.t, string(queryResponse), string(apiResponse))
|
|
}
|
|
|
|
// SendListTxsAndQueryFirst sends a list transaction and queries the first element using both CLI and API.
|
|
func (a *App) SendListTxsAndQueryFirst(
|
|
ctx context.Context,
|
|
servers Hosts,
|
|
module string,
|
|
name multiformatname.Name,
|
|
fields field.Fields,
|
|
) {
|
|
a.SendTxsAndQuery(ctx, servers, module, name, fields, "0")
|
|
}
|
|
|
|
// SendMapTxsAndQuery sends a map transaction and queries the element using both CLI and API.
|
|
func (a *App) SendMapTxsAndQuery(
|
|
ctx context.Context,
|
|
servers Hosts,
|
|
module string,
|
|
name multiformatname.Name,
|
|
fields field.Fields,
|
|
index field.Field,
|
|
) {
|
|
a.SendTxsAndQuery(
|
|
ctx,
|
|
servers,
|
|
module,
|
|
name,
|
|
append(field.Fields{index}, fields...),
|
|
testValue(index.DatatypeName),
|
|
)
|
|
}
|
|
|
|
// SendTxsAndQuery sends a transaction and queries the element using both CLI and API.
|
|
func (a *App) SendTxsAndQuery(
|
|
ctx context.Context,
|
|
servers Hosts,
|
|
module string,
|
|
name multiformatname.Name,
|
|
fields field.Fields,
|
|
index string,
|
|
) {
|
|
// Generate transaction arguments and submit the transaction
|
|
args := txArgs(fields)
|
|
a.createTx(servers, module, name, args...)
|
|
|
|
// Query the chain for the first element via CLI
|
|
queryResponse := a.CLIQuery(
|
|
servers.RPC,
|
|
module,
|
|
"get-"+name.Kebab,
|
|
index,
|
|
)
|
|
a.assertJSONData(queryResponse, name.Snake, fields)
|
|
|
|
// Query the chain for the first element via API
|
|
apiResponse := a.APIQuery(
|
|
ctx,
|
|
servers.API,
|
|
a.namespace,
|
|
module,
|
|
name.Snake,
|
|
index,
|
|
)
|
|
a.assertJSONData(apiResponse, name.Snake, fields)
|
|
|
|
// Query the full list via CLI
|
|
queryListResponse := a.CLIQuery(
|
|
servers.RPC,
|
|
module,
|
|
"list-"+name.Kebab,
|
|
)
|
|
a.assertJSONList(queryListResponse, name.Snake, fields)
|
|
|
|
// Query the full list via API
|
|
apiListResponse := a.APIQuery(
|
|
ctx,
|
|
servers.API,
|
|
a.namespace,
|
|
module,
|
|
name.Snake,
|
|
)
|
|
a.assertJSONList(apiListResponse, name.Snake, fields)
|
|
}
|