Skip to content

Commit 418a168

Browse files
authored
Merge pull request #6907 from The-K-R-O-K/UlianaAndrukhiv/6641-websockets-integration-tests
[Access] Implement integration test for new websockets
2 parents 092026b + 6f46c49 commit 418a168

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1704
-447
lines changed

cmd/access/node_builder/access_node_builder.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -1458,11 +1458,25 @@ func (builder *FlowAccessNodeBuilder) extraFlags() {
14581458
defaultConfig.registerDBPruneThreshold,
14591459
fmt.Sprintf("specifies the number of blocks below the latest stored block height to keep in register db. default: %d", defaultConfig.registerDBPruneThreshold))
14601460

1461-
flags.DurationVar(&builder.rpcConf.WebSocketConfig.InactivityTimeout,
1461+
// websockets config
1462+
flags.DurationVar(
1463+
&builder.rpcConf.WebSocketConfig.InactivityTimeout,
14621464
"websocket-inactivity-timeout",
14631465
defaultConfig.rpcConf.WebSocketConfig.InactivityTimeout,
1464-
"specifies the duration a WebSocket connection can remain open without any active subscriptions before being automatically closed")
1465-
1466+
"the duration a WebSocket connection can remain open without any active subscriptions before being automatically closed",
1467+
)
1468+
flags.Uint64Var(
1469+
&builder.rpcConf.WebSocketConfig.MaxSubscriptionsPerConnection,
1470+
"websocket-max-subscriptions-per-connection",
1471+
defaultConfig.rpcConf.WebSocketConfig.MaxSubscriptionsPerConnection,
1472+
"the maximum number of active WebSocket subscriptions allowed per connection",
1473+
)
1474+
flags.Float64Var(
1475+
&builder.rpcConf.WebSocketConfig.MaxResponsesPerSecond,
1476+
"websocket-max-responses-per-second",
1477+
defaultConfig.rpcConf.WebSocketConfig.MaxResponsesPerSecond,
1478+
fmt.Sprintf("the maximum number of responses that can be sent to a single client per second. Default: %f. if set to 0, no limit is applied to the number of responses per second.", defaultConfig.rpcConf.WebSocketConfig.MaxResponsesPerSecond),
1479+
)
14661480
flags.BoolVar(
14671481
&builder.rpcConf.EnableWebSocketsStreamAPI,
14681482
"experimental-enable-websockets-stream-api",

cmd/observer/node_builder/observer_builder.go

+17-3
Original file line numberDiff line numberDiff line change
@@ -820,11 +820,25 @@ func (builder *ObserverServiceBuilder) extraFlags() {
820820
defaultConfig.registerDBPruneThreshold,
821821
fmt.Sprintf("specifies the number of blocks below the latest stored block height to keep in register db. default: %d", defaultConfig.registerDBPruneThreshold))
822822

823-
flags.DurationVar(&builder.rpcConf.WebSocketConfig.InactivityTimeout,
823+
// websockets config
824+
flags.DurationVar(
825+
&builder.rpcConf.WebSocketConfig.InactivityTimeout,
824826
"websocket-inactivity-timeout",
825827
defaultConfig.rpcConf.WebSocketConfig.InactivityTimeout,
826-
"specifies the duration a WebSocket connection can remain open without any active subscriptions before being automatically closed")
827-
828+
"the duration a WebSocket connection can remain open without any active subscriptions before being automatically closed",
829+
)
830+
flags.Uint64Var(
831+
&builder.rpcConf.WebSocketConfig.MaxSubscriptionsPerConnection,
832+
"websocket-max-subscriptions-per-connection",
833+
defaultConfig.rpcConf.WebSocketConfig.MaxSubscriptionsPerConnection,
834+
"the maximum number of active WebSocket subscriptions allowed per connection",
835+
)
836+
flags.Float64Var(
837+
&builder.rpcConf.WebSocketConfig.MaxResponsesPerSecond,
838+
"websocket-max-responses-per-second",
839+
defaultConfig.rpcConf.WebSocketConfig.MaxResponsesPerSecond,
840+
fmt.Sprintf("the maximum number of responses that can be sent to a single client per second. Default: %f. if set to 0, no limit is applied to the number of responses per second.", defaultConfig.rpcConf.WebSocketConfig.MaxResponsesPerSecond),
841+
)
828842
flags.BoolVar(
829843
&builder.rpcConf.EnableWebSocketsStreamAPI,
830844
"experimental-enable-websockets-stream-api",

engine/access/rest/http/request/address.go engine/access/rest/common/parser/address.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"

engine/access/rest/http/request/address_test.go engine/access/rest/common/parser/address_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"strings"

engine/access/rest/http/request/arguments.go engine/access/rest/common/parser/arguments.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"
@@ -7,6 +7,7 @@ import (
77
)
88

99
const maxArgumentsLength = 100
10+
const MaxAllowedScriptArguments = 100
1011

1112
type Arguments [][]byte
1213

@@ -25,7 +26,7 @@ func (a *Arguments) Parse(raw []string) error {
2526
}
2627

2728
if len(args) > maxArgumentsLength {
28-
return fmt.Errorf("too many arguments. Maximum arguments allowed: %d", maxAllowedScriptArguments)
29+
return fmt.Errorf("too many arguments. Maximum arguments allowed: %d", MaxAllowedScriptArguments)
2930
}
3031

3132
*a = args

engine/access/rest/http/request/arguments_test.go engine/access/rest/common/parser/arguments_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"
@@ -21,13 +21,13 @@ func TestArguments_InvalidParse(t *testing.T) {
2121
assert.EqualError(t, err, "invalid argument encoding: illegal base64 data at input byte 0", a)
2222
}
2323

24-
tooLong := make([]string, maxAllowedScriptArguments+1)
24+
tooLong := make([]string, MaxAllowedScriptArguments+1)
2525
for i := range tooLong {
2626
tooLong[i] = "dGVzdA=="
2727
}
2828

2929
err := arguments.Parse(tooLong)
30-
assert.EqualError(t, err, fmt.Sprintf("too many arguments. Maximum arguments allowed: %d", maxAllowedScriptArguments))
30+
assert.EqualError(t, err, fmt.Sprintf("too many arguments. Maximum arguments allowed: %d", MaxAllowedScriptArguments))
3131
}
3232

3333
func TestArguments_ValidParse(t *testing.T) {

engine/access/rest/http/request/proposal_key.go engine/access/rest/common/parser/proposal_key.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"

engine/access/rest/http/request/signature_test.go engine/access/rest/common/parser/signature_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"encoding/hex"

engine/access/rest/http/request/signatures.go engine/access/rest/common/parser/signatures.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"

engine/access/rest/http/request/transaction.go engine/access/rest/common/parser/transaction.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
package request
1+
package parser
22

33
import (
44
"fmt"
55
"io"
66

7-
"github.com/onflow/flow-go/engine/access/rest/common/parser"
7+
"github.com/onflow/flow-go/engine/access/rest/common"
88
"github.com/onflow/flow-go/engine/access/rest/http/models"
99
"github.com/onflow/flow-go/engine/access/rest/util"
1010
"github.com/onflow/flow-go/engine/common/rpc/convert"
1111
"github.com/onflow/flow-go/model/flow"
1212
)
1313

1414
const maxAuthorizers = 100
15-
const maxAllowedScriptArguments = 100
1615

1716
type Transaction flow.TransactionBody
1817

1918
func (t *Transaction) Parse(raw io.Reader, chain flow.Chain) error {
2019
var tx models.TransactionsBody
21-
err := parseBody(raw, &tx)
20+
err := common.ParseBody(raw, &tx)
2221
if err != nil {
2322
return err
2423
}
@@ -35,8 +34,8 @@ func (t *Transaction) Parse(raw io.Reader, chain flow.Chain) error {
3534
if len(tx.Authorizers) > maxAuthorizers {
3635
return fmt.Errorf("too many authorizers. Maximum authorizers allowed: %d", maxAuthorizers)
3736
}
38-
if len(tx.Arguments) > maxAllowedScriptArguments {
39-
return fmt.Errorf("too many arguments. Maximum arguments allowed: %d", maxAllowedScriptArguments)
37+
if len(tx.Arguments) > MaxAllowedScriptArguments {
38+
return fmt.Errorf("too many arguments. Maximum arguments allowed: %d", MaxAllowedScriptArguments)
4039
}
4140
if tx.ReferenceBlockId == "" {
4241
return fmt.Errorf("reference block not provided")
@@ -90,7 +89,7 @@ func (t *Transaction) Parse(raw io.Reader, chain flow.Chain) error {
9089
return fmt.Errorf("invalid transaction script encoding")
9190
}
9291

93-
var blockID parser.ID
92+
var blockID ID
9493
err = blockID.Parse(tx.ReferenceBlockId)
9594
if err != nil {
9695
return fmt.Errorf("invalid reference block ID: %w", err)

engine/access/rest/http/request/transaction_test.go engine/access/rest/common/parser/transaction_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package request
1+
package parser
22

33
import (
44
"bytes"

engine/access/rest/common/utils.go

+74
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package common
22

3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"strings"
9+
)
10+
311
// SliceToMap converts a slice of strings into a map where each string
412
// in the slice becomes a key in the map with the value set to true.
513
func SliceToMap(values []string) map[string]bool {
@@ -9,3 +17,69 @@ func SliceToMap(values []string) map[string]bool {
917
}
1018
return valueMap
1119
}
20+
21+
// ParseBody parses the input data into the destination interface and returns any decoding errors
22+
// updated to be more user-friendly. It also checks that there is exactly one json object in the input
23+
func ParseBody(raw io.Reader, dst interface{}) error {
24+
dec := json.NewDecoder(raw)
25+
dec.DisallowUnknownFields()
26+
27+
err := dec.Decode(&dst)
28+
if err != nil {
29+
var syntaxError *json.SyntaxError
30+
var unmarshalTypeError *json.UnmarshalTypeError
31+
32+
switch {
33+
case errors.As(err, &syntaxError):
34+
return fmt.Errorf("request body contains badly-formed JSON (at position %d)", syntaxError.Offset)
35+
case errors.Is(err, io.ErrUnexpectedEOF):
36+
return fmt.Errorf("request body contains badly-formed JSON")
37+
case errors.As(err, &unmarshalTypeError):
38+
return fmt.Errorf("request body contains an invalid value for the %q field (at position %d)", unmarshalTypeError.Field, unmarshalTypeError.Offset)
39+
case strings.HasPrefix(err.Error(), "json: unknown field "):
40+
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
41+
return fmt.Errorf("request body contains unknown field %s", fieldName)
42+
case errors.Is(err, io.EOF):
43+
return fmt.Errorf("request body must not be empty")
44+
default:
45+
return err
46+
}
47+
}
48+
49+
if dst == nil {
50+
return fmt.Errorf("request body must not be empty")
51+
}
52+
53+
// verify the request contained exactly one json object
54+
err = dec.Decode(&struct{}{})
55+
if err != io.EOF {
56+
return fmt.Errorf("request body must only contain a single JSON object")
57+
}
58+
59+
return nil
60+
}
61+
62+
// ParseInterfaceToStrings converts a slice of interface{} to a slice of strings.
63+
//
64+
// No errors are expected during normal operations.
65+
func ParseInterfaceToStrings(value interface{}) ([]string, error) {
66+
if strSlice, ok := value.([]string); ok {
67+
return strSlice, nil
68+
}
69+
70+
interfaceSlice, ok := value.([]interface{})
71+
if !ok {
72+
return nil, fmt.Errorf("value must be an array. got %T", value)
73+
}
74+
75+
result := make([]string, len(interfaceSlice))
76+
for i, v := range interfaceSlice {
77+
str, ok := v.(string)
78+
if !ok {
79+
return nil, fmt.Errorf("value must be an array of strings. got %T", v)
80+
}
81+
result[i] = str
82+
}
83+
84+
return result, nil
85+
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func Test_ParseBody(t *testing.T) {
12+
13+
invalid := []struct {
14+
in string
15+
err string
16+
}{
17+
{"{f}", "request body contains badly-formed JSON (at position 2)"},
18+
{"foo", "request body contains badly-formed JSON (at position 2)"},
19+
{"", "request body must not be empty"},
20+
{`{"foo": "bar"`, "request body contains badly-formed JSON"},
21+
{`{"foo": "bar" "foo2":"bar2"}`, "request body contains badly-formed JSON (at position 15)"},
22+
{`{"foo":"bar"}, {}`, "request body must only contain a single JSON object"},
23+
{`[][]`, "request body must only contain a single JSON object"},
24+
}
25+
26+
for i, test := range invalid {
27+
readerIn := strings.NewReader(test.in)
28+
var out interface{}
29+
err := ParseBody(readerIn, out)
30+
assert.EqualError(t, err, test.err, fmt.Sprintf("test #%d failed", i))
31+
}
32+
33+
type body struct {
34+
Foo string
35+
Bar bool
36+
Zoo uint64
37+
}
38+
var b body
39+
err := ParseBody(strings.NewReader(`{ "foo": "test", "bar": true }`), &b)
40+
assert.NoError(t, err)
41+
assert.Equal(t, b.Bar, true)
42+
assert.Equal(t, b.Foo, "test")
43+
assert.Equal(t, b.Zoo, uint64(0))
44+
45+
err = ParseBody(strings.NewReader(`{ "foo": false }`), &b)
46+
assert.EqualError(t, err, `request body contains an invalid value for the "Foo" field (at position 14)`)
47+
}

engine/access/rest/http/request/create_transaction.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"io"
55

66
"github.com/onflow/flow-go/engine/access/rest/common"
7+
"github.com/onflow/flow-go/engine/access/rest/common/parser"
78
"github.com/onflow/flow-go/model/flow"
89
)
910

@@ -22,7 +23,7 @@ func (c *CreateTransaction) Build(r *common.Request) error {
2223
}
2324

2425
func (c *CreateTransaction) Parse(rawTransaction io.Reader, chain flow.Chain) error {
25-
var tx Transaction
26+
var tx parser.Transaction
2627
err := tx.Parse(rawTransaction, chain)
2728
if err != nil {
2829
return err

engine/access/rest/http/request/get_account.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package request
22

33
import (
44
"github.com/onflow/flow-go/engine/access/rest/common"
5+
"github.com/onflow/flow-go/engine/access/rest/common/parser"
56
"github.com/onflow/flow-go/model/flow"
67
)
78

@@ -32,7 +33,7 @@ func (g *GetAccount) Build(r *common.Request) error {
3233
}
3334

3435
func (g *GetAccount) Parse(rawAddress string, rawHeight string, chain flow.Chain) error {
35-
address, err := ParseAddress(rawAddress, chain)
36+
address, err := parser.ParseAddress(rawAddress, chain)
3637
if err != nil {
3738
return err
3839
}

engine/access/rest/http/request/get_account_balance.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package request
22

33
import (
44
"github.com/onflow/flow-go/engine/access/rest/common"
5+
"github.com/onflow/flow-go/engine/access/rest/common/parser"
56
"github.com/onflow/flow-go/model/flow"
67
)
78

@@ -33,7 +34,7 @@ func (g *GetAccountBalance) Parse(
3334
rawHeight string,
3435
chain flow.Chain,
3536
) error {
36-
address, err := ParseAddress(rawAddress, chain)
37+
address, err := parser.ParseAddress(rawAddress, chain)
3738
if err != nil {
3839
return err
3940
}

engine/access/rest/http/request/get_account_key.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55

66
"github.com/onflow/flow-go/engine/access/rest/common"
7+
"github.com/onflow/flow-go/engine/access/rest/common/parser"
78
"github.com/onflow/flow-go/engine/access/rest/util"
89
"github.com/onflow/flow-go/model/flow"
910
)
@@ -41,7 +42,7 @@ func (g *GetAccountKey) Parse(
4142
rawHeight string,
4243
chain flow.Chain,
4344
) error {
44-
address, err := ParseAddress(rawAddress, chain)
45+
address, err := parser.ParseAddress(rawAddress, chain)
4546
if err != nil {
4647
return err
4748
}

0 commit comments

Comments
 (0)