diff --git a/Makefile b/Makefile index ceecb441b7..8d6541d061 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ ifdef BUILD_TAGS BUILD_FLAGS+=-tags $(BUILD_TAGS) endif -TEST_FLAGS=-race -shuffle=on -timeout 70s +TEST_FLAGS=-race -shuffle=on -timeout 120s PLAYGROUND_DIRECTORY=playground LENS_TEST_DIRECTORY=tests/integration/schema/migrations @@ -243,6 +243,7 @@ test\:lens: .PHONY: test\:cli test\:cli: + @$(MAKE) deps:lens gotestsum --format testname -- ./$(CLI_TEST_DIRECTORY)/... $(TEST_FLAGS) # Using go-acc to ensure integration tests are included. diff --git a/cli/errors.go b/cli/errors.go index 2f26f09a2b..81ee179de9 100644 --- a/cli/errors.go +++ b/cli/errors.go @@ -10,7 +10,11 @@ package cli -import "github.com/sourcenetwork/defradb/errors" +import ( + "strings" + + "github.com/sourcenetwork/defradb/errors" +) const ( errMissingArg string = "missing argument" @@ -61,8 +65,12 @@ func NewErrMissingArg(name string) error { return errors.New(errMissingArg, errors.NewKV("Name", name)) } -func NewErrMissingArgs(count int, provided int) error { - return errors.New(errMissingArgs, errors.NewKV("Required", count), errors.NewKV("Provided", provided)) +func NewErrMissingArgs(names []string) error { + return errors.New(errMissingArgs, errors.NewKV("Required", strings.Join(names, ", "))) +} + +func NewErrTooManyArgs(max, actual int) error { + return errors.New(errTooManyArgs, errors.NewKV("Max", max), errors.NewKV("Actual", actual)) } func NewFailedToReadFile(inner error) error { diff --git a/cli/schema_migration_get.go b/cli/schema_migration_get.go index 2fad443538..7d0db308ca 100644 --- a/cli/schema_migration_get.go +++ b/cli/schema_migration_get.go @@ -36,13 +36,11 @@ Example: defradb client schema migration get' Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, - Args: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) (err error) { if err := cobra.NoArgs(cmd, args); err != nil { - return errors.New("this command take no arguments") + return NewErrTooManyArgs(0, len(args)) } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + endpoint, err := httpapi.JoinPaths(cfg.API.AddressToURL(), httpapi.SchemaMigrationPath) if err != nil { return errors.Wrap("join paths failed", err) diff --git a/cli/schema_migration_set.go b/cli/schema_migration_set.go index d274dbce9f..f30c1c5663 100644 --- a/cli/schema_migration_set.go +++ b/cli/schema_migration_set.go @@ -45,16 +45,14 @@ Example: add from stdin: cat schema_migration.lens | defradb client schema migration set bae123 bae456 - Learn more about the DefraDB GraphQL Schema Language on https://docs.source.network.`, - Args: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) (err error) { if err := cobra.MinimumNArgs(2)(cmd, args); err != nil { - return errors.New("must specify src and dst schema versions, as well as a lens cfg") + return NewErrMissingArgs([]string{"src", "dst", "cfg"}) } if err := cobra.MaximumNArgs(3)(cmd, args); err != nil { - return errors.New("must specify src and dst schema versions, as well as a lens cfg") + return NewErrTooManyArgs(3, len(args)) } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + var lensCfgJson string var srcSchemaVersionID string var dstSchemaVersionID string @@ -72,7 +70,7 @@ Learn more about the DefraDB GraphQL Schema Language on https://docs.source.netw } else if len(args) == 2 { // If the lensFile flag has not been provided then it must be provided as an arg // and thus len(args) cannot be 2 - return errors.Wrap("must provide a lens cfg", err) + return NewErrMissingArg("cfg") } else if isFileInfoPipe(fi) && args[2] != "-" { log.FeedbackInfo( cmd.Context(), @@ -98,17 +96,20 @@ Learn more about the DefraDB GraphQL Schema Language on https://docs.source.netw dstSchemaVersionID = args[1] if lensCfgJson == "" { - return errors.New("empty lens configuration provided") + return NewErrMissingArg("cfg") } if srcSchemaVersionID == "" { - return errors.New("no source schema version id provided") + return NewErrMissingArg("src") } if dstSchemaVersionID == "" { - return errors.New("no destination schema version id provided") + return NewErrMissingArg("dst") } + decoder := json.NewDecoder(strings.NewReader(lensCfgJson)) + decoder.DisallowUnknownFields() + var lensCfg model.Lens - err = json.Unmarshal([]byte(lensCfgJson), &lensCfg) + err = decoder.Decode(&lensCfg) if err != nil { return errors.Wrap("invalid lens configuration", err) } diff --git a/cli/schema_patch.go b/cli/schema_patch.go index 47643f90cb..b1e962c51a 100644 --- a/cli/schema_patch.go +++ b/cli/schema_patch.go @@ -54,7 +54,7 @@ To learn more about the DefraDB GraphQL Schema Language, refer to https://docs.s if err = cmd.Usage(); err != nil { return err } - return ErrTooManyArgs + return NewErrTooManyArgs(1, len(args)) } if patchFile != "" { diff --git a/tests/integration/cli/client_schema_migration_get_test.go b/tests/integration/cli/client_schema_migration_get_test.go new file mode 100644 index 0000000000..dd70879433 --- /dev/null +++ b/tests/integration/cli/client_schema_migration_get_test.go @@ -0,0 +1,110 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package clitest + +import ( + "fmt" + "testing" + + "github.com/sourcenetwork/defradb/tests/lenses" +) + +func TestSchemaMigrationGet_GivenOneArg_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "get", + "notAnArg", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "too many arguments. Max: 0, Actual: 1") +} + +func TestSchemaMigrationGet_GivenNoMigrations_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "get", + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, `{"data":{"configuration":[]}}`) +} + +func TestSchemaMigrationGet_GivenEmptyMigrationObj_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", "{}", + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "get", + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, + `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":null}]}}`, + ) +} + +func TestSchemaMigrationGet_GivenEmptyMigration_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", `{"lenses": []}`, + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "get", + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, + `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":[]}]}}`, + ) +} + +func TestSchemaMigrationGet_GivenMigration_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", + fmt.Sprintf(`{"lenses": [{"path":"%s","arguments":{"dst":"verified","value":true}}]}`, lenses.SetDefaultModulePath), + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "get", + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, + `{"data":{"configuration":[{"SourceSchemaVersionID":"bae123","DestinationSchemaVersionID":"bae456","Lenses":[`+ + fmt.Sprintf( + `{"Path":"%s",`, + lenses.SetDefaultModulePath, + )+ + `"Inverse":false,"Arguments":{"dst":"verified","value":true}}`+ + `]}]}}`, + ) +} diff --git a/tests/integration/cli/client_schema_migration_set_test.go b/tests/integration/cli/client_schema_migration_set_test.go new file mode 100644 index 0000000000..d97a4e77d8 --- /dev/null +++ b/tests/integration/cli/client_schema_migration_set_test.go @@ -0,0 +1,244 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package clitest + +import ( + "fmt" + "testing" + + "github.com/sourcenetwork/defradb/tests/lenses" +) + +func TestSchemaMigrationSet_GivenEmptyArgs_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{"client", "schema", "migration", "set"}) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing arguments. Required: src, dst, cfg") +} + +func TestSchemaMigrationSet_GivenOneArg_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing arguments. Required: src, dst, cfg") +} + +func TestSchemaMigrationSet_GivenTwoArgs_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing argument. Name: cfg") +} + +func TestSchemaMigrationSet_GivenFourArgs_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", "cfg", "extraArg", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "too many arguments. Max: 3, Actual: 4") +} + +func TestSchemaMigrationSet_GivenEmptySrcArg_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "", "bae", "path", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing argument. Name: src") +} + +func TestSchemaMigrationSet_GivenEmptyDstArg_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae", "", "path", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing argument. Name: dst") +} + +func TestSchemaMigrationSet_GivenEmptyCfgArg_ShouldReturnError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", "", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "missing argument. Name: cfg") +} + +func TestSchemaMigrationSet_GivenInvalidCfgJsonObject_ShouldError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", "{--notvalidjson", + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "invalid lens configuration: invalid character") +} + +func TestSchemaMigrationSet_GivenEmptyCfgObject_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", "{}", + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, "success") +} + +func TestSchemaMigrationSet_GivenCfgWithNoLenses_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", `{"lenses": []}`, + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, "success") +} + +func TestSchemaMigrationSet_GivenCfgWithNoLensesUppercase_ShouldSucceed(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", `{"Lenses": []}`, + }) + _ = stopDefra() + + assertContainsSubstring(t, stdout, "success") +} + +func TestSchemaMigrationSet_GivenCfgWithUnknownProp_ShouldError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", `{"NotAProp": []}`, + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "invalid lens configuration: json: unknown field") +} + +func TestSchemaMigrationSet_GivenCfgWithUnknownPath_ShouldError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + _, stderr := runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bae123", "bae456", `{"Lenses": [{"path":"notAPath"}]}`, + }) + _ = stopDefra() + + assertContainsSubstring(t, stderr, "no such file or directory") +} + +func TestSchemaMigrationSet_GivenCfgWithLenses_ShouldSucceedAndMigrateDoc(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", `type Users { name: String }`}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `mutation { create_Users(data:"{\"name\":\"John\"}") { name } }`}) + assertContainsSubstring(t, stdout, `{"data":[{"name":"John"}]}`) + + stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", + `[{ "op": "add", "path": "/Users/Schema/Fields/-", "value": {"Name": "verified", "Kind": "Boolean"} }]`, + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bafkreihn4qameldz3j7rfundmd4ldhxnaircuulk6h2vcwnpcgxl4oqffq", + "bafkreia56p6i6o3l4jijayiqd5eiijsypjjokbldaxnmqgeav6fe576hcy", + fmt.Sprintf(`{"lenses": [{"path":"%s","arguments":{"dst":"verified","value":true}}]}`, lenses.SetDefaultModulePath), + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { Users { name verified } }"}) + _ = stopDefra() + + assertContainsSubstring(t, stdout, `{"data":[{"name":"John","verified":true}]}`) +} + +func TestSchemaMigrationSet_GivenCfgWithLenseError_ShouldError(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", `type Users { name: String }`}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `mutation { create_Users(data:"{\"name\":\"John\"}") { name } }`}) + assertContainsSubstring(t, stdout, `{"data":[{"name":"John"}]}`) + + stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", + `[{ "op": "add", "path": "/Users/Schema/Fields/-", "value": {"Name": "verified", "Kind": "Boolean"} }]`, + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{ + "client", "schema", "migration", "set", + "bafkreihn4qameldz3j7rfundmd4ldhxnaircuulk6h2vcwnpcgxl4oqffq", + "bafkreia56p6i6o3l4jijayiqd5eiijsypjjokbldaxnmqgeav6fe576hcy", + // Do not set lens parameters in order to generate error + fmt.Sprintf(`{"lenses": [{"path":"%s"}]}`, lenses.SetDefaultModulePath), + }) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { Users { name verified } }"}) + _ = stopDefra() + + // Error generated from within lens module lazily executing within the query + assertContainsSubstring(t, stdout, "Parameters have not been set.") +}