diff --git a/client/client.go b/client/client.go index 16462d73..875c298d 100644 --- a/client/client.go +++ b/client/client.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2024 The Decred developers +// Copyright (c) 2022-2025 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. @@ -66,8 +66,11 @@ func (c *Client) FeeAddress(ctx context.Context, req types.FeeAddressRequest, func (c *Client) PayFee(ctx context.Context, req types.PayFeeRequest, commitmentAddr stdaddr.Address) (*types.PayFeeResponse, error) { - // TSpendPolicy and TreasuryPolicy are optional but must be an empty map - // rather than nil. + // VoteChoices, TSpendPolicy and TreasuryPolicy are optional but must be an + // empty map rather than nil. + if req.VoteChoices == nil { + req.VoteChoices = map[string]string{} + } if req.TSpendPolicy == nil { req.TSpendPolicy = map[string]string{} } @@ -119,8 +122,11 @@ func (c *Client) TicketStatus(ctx context.Context, req types.TicketStatusRequest func (c *Client) SetVoteChoices(ctx context.Context, req types.SetVoteChoicesRequest, commitmentAddr stdaddr.Address) (*types.SetVoteChoicesResponse, error) { - // TSpendPolicy and TreasuryPolicy are optional but must be an empty map - // rather than nil. + // VoteChoices, TSpendPolicy and TreasuryPolicy are optional but must be an + // empty map rather than nil. + if req.VoteChoices == nil { + req.VoteChoices = map[string]string{} + } if req.TSpendPolicy == nil { req.TSpendPolicy = map[string]string{} } diff --git a/internal/webapi/binding_test.go b/internal/webapi/binding_test.go new file mode 100644 index 00000000..23b42ffa --- /dev/null +++ b/internal/webapi/binding_test.go @@ -0,0 +1,114 @@ +package webapi + +import ( + "testing" + + "github.com/decred/vspd/types/v3" + "github.com/gin-gonic/gin/binding" +) + +// TestGinJSONBinding does not test code in this package. It is a +// characterization test to determine exactly how Gin handles JSON binding tags. +func TestGinJSONBinding(t *testing.T) { + tests := map[string]struct { + req []byte + expectedErr string + }{ + + "Filled arrays bind without error": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": {"k": "v"}, + "tspendpolicy": {"k": "v"}, + "treasurypolicy": {"k": "v"} + }`), + expectedErr: "", + }, + + "Array filled beyond max does not bind": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": {"k": "v"}, + "tspendpolicy": {"k": "v"}, + "treasurypolicy": {"k1": "v","k2": "v","k3": "v","k4": "v"} + }`), + expectedErr: "Key: 'SetVoteChoicesRequest.TreasuryPolicy' Error:Field validation for 'TreasuryPolicy' failed on the 'max' tag", + }, + + "Empty arrays bind without error": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": {}, + "tspendpolicy": {}, + "treasurypolicy": {} + }`), + expectedErr: "", + }, + + "Missing array with 'required' tag does not bind": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "tspendpolicy": {}, + "treasurypolicy": {} + }`), + expectedErr: "Key: 'SetVoteChoicesRequest.VoteChoices' Error:Field validation for 'VoteChoices' failed on the 'required' tag", + }, + + "Missing array with 'max' tag binds without error": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": {}, + "treasurypolicy": {} + }`), + expectedErr: "", + }, + + "Null array with 'required' tag does not bind": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": null, + "tspendpolicy": {}, + "treasurypolicy": {} + }`), + expectedErr: "Key: 'SetVoteChoicesRequest.VoteChoices' Error:Field validation for 'VoteChoices' failed on the 'required' tag", + }, + + "Null array with 'max' tag binds without error": { + req: []byte(`{ + "timestamp": 12345, + "tickethash": "hash", + "votechoices": {}, + "tspendpolicy": null, + "treasurypolicy": {} + }`), + expectedErr: "", + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + err := binding.JSON.BindBody(test.req, &types.SetVoteChoicesRequest{}) + if test.expectedErr == "" { + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + } else { + if err == nil { + t.Fatalf("expected error but got none") + } + if err.Error() != test.expectedErr { + t.Fatalf("incorrect error, got %q expected %q", + err.Error(), test.expectedErr) + } + } + + }) + } + +}