Skip to content

Commit

Permalink
test(systemtests): fix gRPC tests for v1 & v2 (#22774)
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrbrt authored Dec 6, 2024
1 parent 3a2a0ac commit 4caac04
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 170 deletions.
2 changes: 1 addition & 1 deletion server/v2/appmanager/appmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func (a appManager[T]) Query(ctx context.Context, version uint64, request transa
_, queryState, err = a.db.StateLatest()
}
if err != nil {
return nil, err
return nil, fmt.Errorf("invalid height: %w", err)
}
return a.stf.Query(ctx, queryState, a.config.QueryGasLimit, request)
}
Expand Down
4 changes: 4 additions & 0 deletions systemtests/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j

## [Unreleased]

## [v1.0.0-rc.3] - 2024-12-05

* [#22774](https://github.com/cosmos/cosmos-sdk/pull/22774) Add greater than or equal support in Rest test suite

## [v1.0.0-rc.2] - 2024-11-26

* [#22577](https://github.com/cosmos/cosmos-sdk/pull/22577) Support invalid RPC response for CometBFT v1
Expand Down
99 changes: 79 additions & 20 deletions systemtests/rest_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/testutil"
)

type RestTestCase struct {
Name string
Url string
ExpCode int
ExpOut string
Name string
Url string
ExpCode int
ExpCodeGTE int
ExpOut string
}

// RunRestQueries runs given Rest testcases by making requests and
Expand All @@ -25,33 +24,71 @@ func RunRestQueries(t *testing.T, testCases ...RestTestCase) {

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
resp := GetRequestWithHeaders(t, tc.Url, nil, tc.ExpCode)
if tc.ExpCodeGTE > 0 && tc.ExpCode > 0 {
require.Fail(t, "only one of ExpCode or ExpCodeGTE should be set")
}

var resp []byte
if tc.ExpCodeGTE > 0 {
resp = GetRequestWithHeadersGreaterThanOrEqual(t, tc.Url, nil, tc.ExpCodeGTE)
} else {
resp = GetRequestWithHeaders(t, tc.Url, nil, tc.ExpCode)
}
require.JSONEq(t, tc.ExpOut, string(resp))
})
}
}

// TestRestQueryIgnoreNumbers runs given rest testcases by making requests and
// RunRestQueriesIgnoreNumbers runs given rest testcases by making requests and
// checking response with expected output ignoring number values
// This method is used when number values in response are non-deterministic
func TestRestQueryIgnoreNumbers(t *testing.T, testCases ...RestTestCase) {
func RunRestQueriesIgnoreNumbers(t *testing.T, testCases ...RestTestCase) {
t.Helper()

// regex for standalone quoted numbers (e.g., "-3" or "0.02")
standaloneQuotedNumberRegex := regexp.MustCompile(`"(-?\d+(\.\d+)?)"`)
// regex for numbers in escaped strings (e.g., \"-3\")
escapedNumberRegex := regexp.MustCompile(`\\\"(-?\d+(\.\d+)?)\\\"`)
// regex for unquoted numbers (e.g., 2, -1, 0.02,)
unquotedNumberRegex := regexp.MustCompile(`\b-?\d+(\.\d+)?\b,`)

replaceNumber := func(input string) string {
// handle numbers in escaped strings
result := escapedNumberRegex.ReplaceAllStringFunc(input, func(match string) string {
// replace with escaped "NUMBER"
return `\"NUMBER\"`
})

// handle standalone quoted numbers
result = standaloneQuotedNumberRegex.ReplaceAllStringFunc(result, func(match string) string {
// replace with "NUMBER" (quotes preserved)
return `"NUMBER"`
})

// handle unquoted numbers
result = unquotedNumberRegex.ReplaceAllStringFunc(result, func(match string) string {
// replace with "NUMBER" (add quotes to ensure json validity)
return `"NUMBER",`
})

return result
}

for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
resp, err := testutil.GetRequest(tc.Url)
require.NoError(t, err)

// regular expression pattern to match any numeric value in the JSON
numberRegexPattern := `"\d+(\.\d+)?"`
if tc.ExpCodeGTE > 0 && tc.ExpCode > 0 {
require.Fail(t, "only one of ExpCode or ExpCodeGTE should be set")
}

// compile the regex
r, err := regexp.Compile(numberRegexPattern)
require.NoError(t, err)
var resp []byte
if tc.ExpCodeGTE > 0 {
resp = GetRequestWithHeadersGreaterThanOrEqual(t, tc.Url, nil, tc.ExpCodeGTE)
} else {
resp = GetRequestWithHeaders(t, tc.Url, nil, tc.ExpCode)
}

// replace all numeric values in the above JSONs with `NUMBER` text
expectedJSON := r.ReplaceAllString(tc.ExpOut, `"NUMBER"`)
actualJSON := r.ReplaceAllString(string(resp), `"NUMBER"`)
expectedJSON := replaceNumber(tc.ExpOut)
actualJSON := replaceNumber(string(resp))

// compare two jsons
require.JSONEq(t, expectedJSON, actualJSON)
Expand Down Expand Up @@ -85,3 +122,25 @@ func GetRequestWithHeaders(t *testing.T, url string, headers map[string]string,

return body
}

func GetRequestWithHeadersGreaterThanOrEqual(t *testing.T, url string, headers map[string]string, expCode int) []byte {
t.Helper()
req, err := http.NewRequest("GET", url, nil)
require.NoError(t, err)

for key, value := range headers {
req.Header.Set(key, value)
}

httpClient := &http.Client{}
res, err := httpClient.Do(req)
require.NoError(t, err)
defer func() {
_ = res.Body.Close()
}()
body, err := io.ReadAll(res.Body)
require.NoError(t, err)
require.GreaterOrEqual(t, res.StatusCode, expCode, "status code should be greater or equal to %d, got: %d, %s", expCode, res.StatusCode, body)

return body
}
142 changes: 71 additions & 71 deletions tests/systemtests/authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,78 +682,78 @@ func TestAuthzGRPCQueries(t *testing.T) {

grantTestCases := []systest.RestTestCase{
{
"invalid granter address",
fmt.Sprintf(grantURL, "invalid_granter", grantee1Addr, msgSendTypeURL),
http.StatusInternalServerError,
bech32FailOutput,
Name: "invalid granter address",
Url: fmt.Sprintf(grantURL, "invalid_granter", grantee1Addr, msgSendTypeURL),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: bech32FailOutput,
},
{
"invalid grantee address",
fmt.Sprintf(grantURL, granterAddr, "invalid_grantee", msgSendTypeURL),
http.StatusInternalServerError,
bech32FailOutput,
Name: "invalid grantee address",
Url: fmt.Sprintf(grantURL, granterAddr, "invalid_grantee", msgSendTypeURL),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: bech32FailOutput,
},
{
"with empty granter",
fmt.Sprintf(grantURL, "", grantee1Addr, msgSendTypeURL),
http.StatusInternalServerError,
emptyStrOutput,
Name: "with empty granter",
Url: fmt.Sprintf(grantURL, "", grantee1Addr, msgSendTypeURL),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: emptyStrOutput,
},
{
"with empty grantee",
fmt.Sprintf(grantURL, granterAddr, "", msgSendTypeURL),
http.StatusInternalServerError,
emptyStrOutput,
Name: "with empty grantee",
Url: fmt.Sprintf(grantURL, granterAddr, "", msgSendTypeURL),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: emptyStrOutput,
},
{
"invalid msg-type",
fmt.Sprintf(grantURL, granterAddr, grantee1Addr, "invalidMsg"),
http.StatusInternalServerError,
invalidMsgTypeOutput,
Name: "invalid msg-type",
Url: fmt.Sprintf(grantURL, granterAddr, grantee1Addr, "invalidMsg"),
ExpCode: http.StatusInternalServerError,
ExpOut: invalidMsgTypeOutput,
},
{
"valid grant query",
fmt.Sprintf(grantURL, granterAddr, grantee1Addr, msgSendTypeURL),
http.StatusOK,
expGrantOutput,
Name: "valid grant query",
Url: fmt.Sprintf(grantURL, granterAddr, grantee1Addr, msgSendTypeURL),
ExpCode: http.StatusOK,
ExpOut: expGrantOutput,
},
}

systest.RunRestQueries(t, grantTestCases...)
systest.RunRestQueriesIgnoreNumbers(t, grantTestCases...)

// test query grants grpc endpoint
grantsURL := baseurl + "/cosmos/authz/v1beta1/grants?granter=%s&grantee=%s"

grantsTestCases := []systest.RestTestCase{
{
"expect single grant",
fmt.Sprintf(grantsURL, granterAddr, grantee1Addr),
http.StatusOK,
fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"1"}}`, grant1),
Name: "expect single grant",
Url: fmt.Sprintf(grantsURL, granterAddr, grantee1Addr),
ExpCode: http.StatusOK,
ExpOut: fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"1"}}`, grant1),
},
{
"expect two grants",
fmt.Sprintf(grantsURL, granterAddr, grantee2Addr),
http.StatusOK,
fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"2"}}`, grant2, grant3),
Name: "expect two grants",
Url: fmt.Sprintf(grantsURL, granterAddr, grantee2Addr),
ExpCode: http.StatusOK,
ExpOut: fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"2"}}`, grant2, grant3),
},
{
"expect single grant with pagination",
fmt.Sprintf(grantsURL+"&pagination.limit=1", granterAddr, grantee2Addr),
http.StatusOK,
fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":"L2Nvc21vcy5nb3YudjEuTXNnVm90ZQ==","total":"0"}}`, grant2),
Name: "expect single grant with pagination",
Url: fmt.Sprintf(grantsURL+"&pagination.limit=1", granterAddr, grantee2Addr),
ExpCode: http.StatusOK,
ExpOut: fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":"L2Nvc21vcy5nb3YudjEuTXNnVm90ZQ==","total":"0"}}`, grant2),
},
{
"expect single grant with pagination limit and offset",
fmt.Sprintf(grantsURL+"&pagination.limit=1&pagination.offset=1", granterAddr, grantee2Addr),
http.StatusOK,
fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant3),
Name: "expect single grant with pagination limit and offset",
Url: fmt.Sprintf(grantsURL+"&pagination.limit=1&pagination.offset=1", granterAddr, grantee2Addr),
ExpCode: http.StatusOK,
ExpOut: fmt.Sprintf(`{"grants":[{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant3),
},
{
"expect two grants with pagination",
fmt.Sprintf(grantsURL+"&pagination.limit=2", granterAddr, grantee2Addr),
http.StatusOK,
fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant2, grant3),
Name: "expect two grants with pagination",
Url: fmt.Sprintf(grantsURL+"&pagination.limit=2", granterAddr, grantee2Addr),
ExpCode: http.StatusOK,
ExpOut: fmt.Sprintf(`{"grants":[{%s},{%s}],"pagination":{"next_key":null,"total":"0"}}`, grant2, grant3),
},
}

Expand All @@ -768,53 +768,53 @@ func TestAuthzGRPCQueries(t *testing.T) {

granterTestCases := []systest.RestTestCase{
{
"invalid granter account address",
fmt.Sprintf(grantsByGranterURL, "invalid address"),
http.StatusInternalServerError,
decodingFailedOutput,
Name: "invalid granter account address",
Url: fmt.Sprintf(grantsByGranterURL, "invalid address"),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: decodingFailedOutput,
},
{
"no authorizations found from granter",
fmt.Sprintf(grantsByGranterURL, grantee2Addr),
http.StatusOK,
noAuthorizationsOutput,
Name: "no authorizations found from granter",
Url: fmt.Sprintf(grantsByGranterURL, grantee2Addr),
ExpCode: http.StatusOK,
ExpOut: noAuthorizationsOutput,
},
{
"valid granter query",
fmt.Sprintf(grantsByGranterURL, grantee1Addr),
http.StatusOK,
granterQueryOutput,
Name: "valid granter query",
Url: fmt.Sprintf(grantsByGranterURL, grantee1Addr),
ExpCode: http.StatusOK,
ExpOut: granterQueryOutput,
},
}

systest.RunRestQueries(t, granterTestCases...)
systest.RunRestQueriesIgnoreNumbers(t, granterTestCases...)

// test query grants by grantee grpc endpoint
grantsByGranteeURL := baseurl + "/cosmos/authz/v1beta1/grants/grantee/%s"
grantee1GrantsOutput := fmt.Sprintf(`{"grants":[{"granter":"%s","grantee":"%s",%s}],"pagination":{"next_key":null,"total":"1"}}`, granterAddr, grantee1Addr, grant1)

granteeTestCases := []systest.RestTestCase{
{
"invalid grantee account address",
fmt.Sprintf(grantsByGranteeURL, "invalid address"),
http.StatusInternalServerError,
decodingFailedOutput,
Name: "invalid grantee account address",
Url: fmt.Sprintf(grantsByGranteeURL, "invalid address"),
ExpCodeGTE: http.StatusBadRequest,
ExpOut: decodingFailedOutput,
},
{
"no authorizations found from grantee",
fmt.Sprintf(grantsByGranteeURL, granterAddr),
http.StatusOK,
noAuthorizationsOutput,
Name: "no authorizations found from grantee",
Url: fmt.Sprintf(grantsByGranteeURL, granterAddr),
ExpCode: http.StatusOK,
ExpOut: noAuthorizationsOutput,
},
{
"valid grantee query",
fmt.Sprintf(grantsByGranteeURL, grantee1Addr),
http.StatusOK,
grantee1GrantsOutput,
Name: "valid grantee query",
Url: fmt.Sprintf(grantsByGranteeURL, grantee1Addr),
ExpCode: http.StatusOK,
ExpOut: grantee1GrantsOutput,
},
}

systest.RunRestQueries(t, granteeTestCases...)
systest.RunRestQueriesIgnoreNumbers(t, granteeTestCases...)
}

func setupChain(t *testing.T) (*systest.CLIWrapper, string, string) {
Expand Down
Loading

0 comments on commit 4caac04

Please sign in to comment.