Repository: coinbase/rosetta-cli
Pull request #219 (2021-03-09 21:11:33)

Sort array fields for asserter configuration
### Motivation
Right now, we don't order errors by code when printing out the asserter configuration file. This causes there to be a diff between what are otherwise 2 equivalent configs confusing the caller.

### Solution
Sort array fields so that repeated file generations are equivalent.

https://jira.coinbase-corp.com/browse/PROTO-1753


Patch:
diff --git a/Makefile b/Makefile
index 2242922..644b071 100644
--- a/Makefile
+++ b/Makefile
@@ -12,8 +12,8 @@ ADDLICENCE_SCRIPT=${ADDLICENSE_CMD} -c "Coinbase, Inc." -l "apache" -v
 GOLINES_CMD=go run github.com/segmentio/golines
 GOVERALLS_CMD=go run github.com/mattn/goveralls
 COVERAGE_TEST_DIRECTORIES=./configuration/... ./pkg/results/... \
-	./pkg/logger/...
-TEST_SCRIPT=go test -v ./pkg/... ./configuration/...
+	./pkg/logger/... ./cmd
+TEST_SCRIPT=go test -v ./pkg/... ./configuration/... ./cmd
 COVERAGE_TEST_SCRIPT=go test -v ${COVERAGE_TEST_DIRECTORIES}
 
 deps:
diff --git a/cmd/utils_asserter_configuration.go b/cmd/utils_asserter_configuration.go
index 6fde409..ff70365 100644
--- a/cmd/utils_asserter_configuration.go
+++ b/cmd/utils_asserter_configuration.go
@@ -16,8 +16,10 @@ package cmd
 
 import (
 	"fmt"
+	"sort"
 	"time"
 
+	"github.com/coinbase/rosetta-sdk-go/asserter"
 	"github.com/coinbase/rosetta-sdk-go/fetcher"
 	"github.com/coinbase/rosetta-sdk-go/utils"
 	"github.com/fatih/color"
@@ -30,7 +32,7 @@ var (
 		Short: "Generate a static configuration file for the Asserter",
 		Long: `In production deployments, it is useful to initialize the response
 Asserter (https://github.com/coinbase/rosetta-sdk-go/tree/master/asserter) using
-a static configuration instead of intializing a configuration dynamically
+a static configuration instead of initializing a configuration dynamically
 from the node. This allows a client to error on new types/statuses that may
 have been added in an update instead of silently erroring.
 
@@ -61,6 +63,8 @@ func runCreateConfigurationCmd(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("%w: unable to generate spec", err)
 	}
 
+	sortArrayFieldsOnConfiguration(configuration)
+
 	if err := utils.SerializeAndWrite(args[0], configuration); err != nil {
 		return fmt.Errorf("%w: unable to serialize asserter configuration", err)
 	}
@@ -68,3 +72,13 @@ func runCreateConfigurationCmd(cmd *cobra.Command, args []string) error {
 	color.Green("Configuration file saved!")
 	return nil
 }
+
+func sortArrayFieldsOnConfiguration(configuration *asserter.Configuration) {
+	sort.Strings(configuration.AllowedOperationTypes)
+	sort.Slice(configuration.AllowedOperationStatuses, func(i, j int) bool {
+		return configuration.AllowedOperationStatuses[i].Status < configuration.AllowedOperationStatuses[j].Status
+	})
+	sort.Slice(configuration.AllowedErrors, func(i, j int) bool {
+		return configuration.AllowedErrors[i].Code < configuration.AllowedErrors[j].Code
+	})
+}
\ No newline at end of file


Test patch:
diff --git a/cmd/utils_asserter_configuration_test.go b/cmd/utils_asserter_configuration_test.go
new file mode 100644
index 0000000..ce6f3ca
--- /dev/null
+++ b/cmd/utils_asserter_configuration_test.go
@@ -0,0 +1,111 @@
+// Copyright 2020 Coinbase, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import "github.com/coinbase/rosetta-sdk-go/types"
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/coinbase/rosetta-sdk-go/asserter"
+)
+
+var (
+	basicNetwork = &types.NetworkIdentifier{
+		Blockchain: "blockchain",
+		Network:    "network",
+	}
+
+	basicBlock = &types.BlockIdentifier{
+		Index: 10,
+		Hash:  "block 10",
+	}
+
+	allowedOperationTypes = []string{"OUTPUT", "INPUT", "TRANSFER"}
+
+	allowedOperationStatuses = []*types.OperationStatus{
+		{
+			Status:     "SUCCESS",
+			Successful: true,
+		},
+		{
+			Status:     "SKIPPED",
+			Successful: true,
+		},
+	}
+
+	allowedErrors = []*types.Error{
+		{
+			Code: 4,
+			Message: "Block not found",
+			Retriable: false,
+		},
+		{
+			Code: 0,
+			Message: "Endpoint not implemented",
+			Retriable: false,
+		},
+		{
+			Code: 3,
+			Message: "Bitcoind error",
+			Retriable: false,
+		},
+	}
+
+	timestampStartIndex = int64(6)
+)
+
+func TestSortArrayFields(t *testing.T) {
+	var clientConfiguration = &asserter.Configuration{
+		NetworkIdentifier:          basicNetwork,
+		GenesisBlockIdentifier:     basicBlock,
+		AllowedOperationTypes:      allowedOperationTypes,
+		AllowedOperationStatuses:   allowedOperationStatuses,
+		AllowedErrors:              allowedErrors,
+		AllowedTimestampStartIndex: timestampStartIndex,
+	}
+	var assert = assert.New(t)
+	sortArrayFieldsOnConfiguration(clientConfiguration)
+	assert.Equal([]string{"INPUT", "OUTPUT", "TRANSFER"}, clientConfiguration.AllowedOperationTypes)
+	assert.Equal([]*types.OperationStatus{
+		{
+			Status:     "SKIPPED",
+			Successful: true,
+		},
+		{
+			Status:     "SUCCESS",
+			Successful: true,
+		},
+	}, clientConfiguration.AllowedOperationStatuses)
+	assert.Equal([]*types.Error{
+		{
+			Code: 0,
+			Message: "Endpoint not implemented",
+			Retriable: false,
+		},
+		{
+			Code: 3,
+			Message: "Bitcoind error",
+			Retriable: false,
+		},
+		{
+			Code: 4,
+			Message: "Block not found",
+			Retriable: false,
+		},
+	}, clientConfiguration.AllowedErrors)
+}