From fcbe97348f82164f4e5fbb67b85779a0add32817 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Sat, 25 Jul 2020 14:13:24 -0700 Subject: [PATCH] Add test for more complicated GetOrders call --- graphql/client/client.go | 23 +++++- graphql/client/types.go | 17 ++--- graphql/gqltypes/conversions.go | 74 ++++++++++++++++--- graphql/schema.resolvers.go | 2 +- integration-tests/graphql_integration_test.go | 43 ++++++++++- 5 files changed, 132 insertions(+), 27 deletions(-) diff --git a/graphql/client/client.go b/graphql/client/client.go index 9bda519a4..3bd0d4034 100644 --- a/graphql/client/client.go +++ b/graphql/client/client.go @@ -152,7 +152,26 @@ type GetOrdersOpts struct { func (c *Client) GetOrders(ctx context.Context, opts ...GetOrdersOpts) ([]*OrderWithMetadata, error) { req := NewRequest(ordersQuery) - // TODO(albrow): Pass in opts. + if len(opts) > 0 { + opts := opts[0] + if len(opts.Filters) > 0 { + // Convert each filter value from the native Go type to a JSON-compatible type. + for i, filter := range opts.Filters { + jsonCompatibleValue, err := gqltypes.FilterValueToJSON(filter) + if err != nil { + return nil, err + } + opts.Filters[i].Value = jsonCompatibleValue + } + req.Var("filters", opts.Filters) + } + if len(opts.Sort) > 0 { + req.Var("sort", opts.Sort) + } + if opts.Limit != 0 { + req.Var("limit", opts.Limit) + } + } var resp struct { Orders []*gqltypes.OrderWithMetadata `json:"orders"` @@ -164,5 +183,3 @@ func (c *Client) GetOrders(ctx context.Context, opts ...GetOrdersOpts) ([]*Order } // func (c *Client) GetStats() (*GetStatsResponse, error) -// func (c *Client) SubscribeToHeartbeat(ctx context.Context, ch chan<- string) (*rpc.ClientSubscription, error) -// func (c *Client) SubscribeToOrders(ctx context.Context, ch chan<- []*zeroex.OrderEvent) (*rpc.ClientSubscription, error) diff --git a/graphql/client/types.go b/graphql/client/types.go index 56ffa065c..86adbd681 100644 --- a/graphql/client/types.go +++ b/graphql/client/types.go @@ -115,18 +115,10 @@ type OrderEvent struct { } // A filter on orders. Can be used in queries to only return orders that meet certain criteria. -type OrderFilter struct { - Field OrderField `json:"field"` - Kind FilterKind `json:"kind"` - // value must match the type of the filter field. - Value interface{} `json:"value"` -} +type OrderFilter = gqltypes.OrderFilter // A sort ordering for orders. Can be used in queries to control the order in which results are returned. -type OrderSort struct { - Field OrderField `json:"field"` - Direction SortDirection `json:"direction"` -} +type OrderSort = gqltypes.OrderSort // A signed 0x order along with some additional metadata about the order which is not part of the 0x protocol specification. type OrderWithMetadata struct { @@ -289,3 +281,8 @@ var AllRejectedOrderCode = gqltypes.AllRejectedOrderCode type SortDirection = gqltypes.SortDirection var AllSortDirection = gqltypes.AllSortDirection + +const ( + SortDirectionAsc = gqltypes.SortDirectionAsc + SortDirectionDesc = gqltypes.SortDirectionDesc +) diff --git a/graphql/gqltypes/conversions.go b/graphql/gqltypes/conversions.go index 367f4accf..f2f6560e4 100644 --- a/graphql/gqltypes/conversions.go +++ b/graphql/gqltypes/conversions.go @@ -3,6 +3,7 @@ package gqltypes import ( "fmt" "math/big" + "strings" "github.com/0xProject/0x-mesh/common/types" "github.com/0xProject/0x-mesh/db" @@ -362,22 +363,75 @@ func FilterKindToDBType(kind FilterKind) (db.FilterKind, error) { } } -func ConvertFilterValue(f *OrderFilter) (interface{}, error) { +// FilterValueFromJSON converts the filter value from the JSON type to the +// corresponding Go type. It returns an error if the JSON type does not match +// what was expected based on the filter field. +func FilterValueFromJSON(f OrderFilter) (interface{}, error) { switch f.Field { - case "chainID", "makerAssetAmount", "makerFee", "takerAssetAmount", "takerFee", "expirationTimeSeconds", "salt", "fillableTakerAssetAmount": + case OrderFieldChainID, OrderFieldMakerAssetAmount, OrderFieldMakerFee, OrderFieldTakerAssetAmount, OrderFieldTakerFee, OrderFieldExpirationTimeSeconds, OrderFieldSalt, OrderFieldFillableTakerAssetAmount: return stringToBigInt(f.Value) - case "hash": + case OrderFieldHash: return stringToHash(f.Value) - case "exchangeAddress", "makerAddress", "takerAddress", "senderAddress", "feeRecipientAddress": + case OrderFieldExchangeAddress, OrderFieldMakerAddress, OrderFieldTakerAddress, OrderFieldSenderAddress, OrderFieldFeeRecipientAddress: return stringToAddress(f.Value) - case "makerAssetData", "makerFeeAssetData", "takerAssetData", "takerFeeAssetData": + case OrderFieldMakerAssetData, OrderFieldMakerFeeAssetData, OrderFieldTakerAssetData, OrderFieldTakerFeeAssetData: return stringToBytes(f.Value) default: return "", fmt.Errorf("invalid filter field: %q", f.Field) } } -func filterValueToString(value interface{}) (string, error) { +// FilterValueToJSON converts the filter value from a native Go type to the +// corresponding JSON value. It returns an error if the Go type does not match +// what was expected based on the filter field. +func FilterValueToJSON(f OrderFilter) (string, error) { + switch f.Field { + case OrderFieldChainID, OrderFieldMakerAssetAmount, OrderFieldMakerFee, OrderFieldTakerAssetAmount, OrderFieldTakerFee, OrderFieldExpirationTimeSeconds, OrderFieldSalt, OrderFieldFillableTakerAssetAmount: + return bigIntToString(f.Value) + case OrderFieldHash: + return hashToString(f.Value) + case OrderFieldExchangeAddress, OrderFieldMakerAddress, OrderFieldTakerAddress, OrderFieldSenderAddress, OrderFieldFeeRecipientAddress: + return addressToString(f.Value) + case OrderFieldMakerAssetData, OrderFieldMakerFeeAssetData, OrderFieldTakerAssetData, OrderFieldTakerFeeAssetData: + return bytesToString(f.Value) + default: + return "", fmt.Errorf("invalid filter field: %q", f.Field) + } +} + +func bigIntToString(value interface{}) (string, error) { + bigInt, ok := value.(*big.Int) + if !ok { + return "", fmt.Errorf("invalid type for filter value (expected *big.Int but got %T)", value) + } + return bigInt.String(), nil +} + +func hashToString(value interface{}) (string, error) { + hash, ok := value.(common.Hash) + if !ok { + return "", fmt.Errorf("invalid type for filter value (expected common.Hash but got %T)", value) + } + return hash.Hex(), nil +} + +func addressToString(value interface{}) (string, error) { + address, ok := value.(common.Address) + if !ok { + return "", fmt.Errorf("invalid type for filter value (expected common.Address but got %T)", value) + } + return strings.ToLower(address.Hex()), nil +} + +func bytesToString(value interface{}) (string, error) { + bytes, ok := value.([]byte) + if !ok { + return "", fmt.Errorf("invalid type for filter value (expected []byte but got %T)", value) + } + return common.ToHex(bytes), nil +} + +func filterValueAsString(value interface{}) (string, error) { valueString, ok := value.(string) if !ok { return "", fmt.Errorf("invalid type for filter value (expected string but got %T)", value) @@ -386,7 +440,7 @@ func filterValueToString(value interface{}) (string, error) { } func stringToBigInt(value interface{}) (*big.Int, error) { - valueString, err := filterValueToString(value) + valueString, err := filterValueAsString(value) if err != nil { return nil, err } @@ -398,7 +452,7 @@ func stringToBigInt(value interface{}) (*big.Int, error) { } func stringToHash(value interface{}) (common.Hash, error) { - valueString, err := filterValueToString(value) + valueString, err := filterValueAsString(value) if err != nil { return common.Hash{}, err } @@ -406,7 +460,7 @@ func stringToHash(value interface{}) (common.Hash, error) { } func stringToAddress(value interface{}) (common.Address, error) { - valueString, err := filterValueToString(value) + valueString, err := filterValueAsString(value) if err != nil { return common.Address{}, err } @@ -414,7 +468,7 @@ func stringToAddress(value interface{}) (common.Address, error) { } func stringToBytes(value interface{}) ([]byte, error) { - valueString, err := filterValueToString(value) + valueString, err := filterValueAsString(value) if err != nil { return nil, err } diff --git a/graphql/schema.resolvers.go b/graphql/schema.resolvers.go index ead1db705..278c6186f 100644 --- a/graphql/schema.resolvers.go +++ b/graphql/schema.resolvers.go @@ -60,7 +60,7 @@ func (r *queryResolver) Orders(ctx context.Context, sort []*gqltypes.OrderSort, if err != nil { return nil, err } - filterValue, err := gqltypes.ConvertFilterValue(filter) + filterValue, err := gqltypes.FilterValueFromJSON(*filter) if err != nil { return nil, err } diff --git a/integration-tests/graphql_integration_test.go b/integration-tests/graphql_integration_test.go index 1cac865e4..e85cc385b 100644 --- a/integration-tests/graphql_integration_test.go +++ b/integration-tests/graphql_integration_test.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "math/big" "sort" "sync" "sync/atomic" @@ -141,6 +142,7 @@ func TestGetOrders(t *testing.T) { assert.Len(t, validationResponse.Accepted, numOrders) assert.Len(t, validationResponse.Rejected, 0) + // Get orders without any options. actualOrders, err := client.GetOrders(ctx) require.NoError(t, err) require.Len(t, actualOrders, 10) @@ -172,6 +174,35 @@ func TestGetOrders(t *testing.T) { } assertOrdersAreUnsortedEqual(t, expectedOrders, actualOrders) + // Get orders with filter, sort, and limit. + opts := gqlclient.GetOrdersOpts{ + Filters: []gqlclient.OrderFilter{ + { + Field: gqlclient.OrderFieldChainID, + Kind: gqlclient.FilterKindEqual, + Value: signedTestOrders[0].ChainID, + }, + { + Field: gqlclient.OrderFieldExpirationTimeSeconds, + Kind: gqlclient.FilterKindGreaterOrEqual, + Value: big.NewInt(0), + }, + }, + Sort: []gqlclient.OrderSort{ + { + Field: gqlclient.OrderFieldHash, + Direction: gqlclient.SortDirectionDesc, + }, + }, + Limit: 5, + } + actualOrdersWithOpts, err := client.GetOrders(ctx, opts) + require.NoError(t, err) + require.Len(t, actualOrdersWithOpts, 5) + sortOrdersByHashDesc(expectedOrders) + expectedOrdersWithOpts := expectedOrders[:5] + assertOrderSlicesAreEqual(t, expectedOrdersWithOpts, actualOrdersWithOpts) + cancel() wg.Wait() } @@ -346,10 +377,10 @@ func assertOrdersAreUnsortedEqual(t *testing.T, expected, actual []*gqlclient.Or // Make a copy of the given orders so we don't mess up the original when sorting them. expectedCopy := make([]*gqlclient.OrderWithMetadata, len(expected)) copy(expectedCopy, expected) - sortOrdersByHash(expectedCopy) + sortOrdersByHashAsc(expectedCopy) actualCopy := make([]*gqlclient.OrderWithMetadata, len(actual)) copy(actualCopy, actual) - sortOrdersByHash(actualCopy) + sortOrdersByHashAsc(actualCopy) assertOrderSlicesAreEqual(t, expectedCopy, actualCopy) } @@ -373,8 +404,14 @@ func assertOrderSlicesAreEqual(t *testing.T, expected, actual []*gqlclient.Order } } -func sortOrdersByHash(orders []*gqlclient.OrderWithMetadata) { +func sortOrdersByHashAsc(orders []*gqlclient.OrderWithMetadata) { sort.SliceStable(orders, func(i, j int) bool { return bytes.Compare(orders[i].Hash.Bytes(), orders[j].Hash.Bytes()) == -1 }) } + +func sortOrdersByHashDesc(orders []*gqlclient.OrderWithMetadata) { + sort.SliceStable(orders, func(i, j int) bool { + return bytes.Compare(orders[i].Hash.Bytes(), orders[j].Hash.Bytes()) == 1 + }) +}