diff --git a/tests/integration/cli/client_blocks_test.go b/tests/integration/cli/client_blocks_test.go new file mode 100644 index 0000000000..08d1c22684 --- /dev/null +++ b/tests/integration/cli/client_blocks_test.go @@ -0,0 +1,41 @@ +// Copyright 2022 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 "testing" + +func TestClientBlocksEmpty(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks"}) + assertContainsSubstring(t, stdout, "Usage:") +} + +func TestClientBlocksGetEmpty(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get"}) + assertContainsSubstring(t, stdout, "Usage:") +} + +func TestClientBlocksGetInvalidCID(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get", "invalid-cid"}) + _ = stopDefra() + assertContainsSubstring(t, stdout, "\"errors\"") +} + +func TestClientBlocksGetNonExistentCID(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + stdout, _ := runDefraCommand(t, conf, []string{"client", "blocks", "get", "bafybeieelb43ol5e5jiick2p7k4p577ph72ecwcuowlhbops4hpz24zhz4"}) + _ = stopDefra() + assertContainsSubstring(t, stdout, "could not find") +} diff --git a/tests/integration/cli/client_peerid_test.go b/tests/integration/cli/client_peerid_test.go new file mode 100644 index 0000000000..8ad19983cc --- /dev/null +++ b/tests/integration/cli/client_peerid_test.go @@ -0,0 +1,34 @@ +// Copyright 2022 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 ( + "testing" +) + +func TestPeerID(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "peerid"}) + + defraLogLines := stopDefra() + + assertNotContainsSubstring(t, defraLogLines, "ERROR") + + assertContainsSubstring(t, stdout, "peerID") +} + +func TestPeerIDWithNoHost(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{"client", "peerid"}) + assertContainsSubstring(t, stderr, "failed to request peer ID") +} diff --git a/tests/integration/cli/client_ping_test.go b/tests/integration/cli/client_ping_test.go new file mode 100644 index 0000000000..1a77c218ca --- /dev/null +++ b/tests/integration/cli/client_ping_test.go @@ -0,0 +1,63 @@ +// Copyright 2022 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" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/config" +) + +func TestPingSimple(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "ping"}) + + nodeLog := stopDefra() + + assert.Contains(t, stdout, `{"data":{"response":"pong"}}`) + for _, line := range nodeLog { + assert.NotContains(t, line, "ERROR") + } +} + +func TestPingCommandToInvalidHost(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + _, stderr := runDefraCommand(t, conf, []string{"client", "ping", "--url", "'1!2:3!4'"}) + + nodeLog := stopDefra() + + for _, line := range nodeLog { + assert.NotContains(t, line, "ERROR") + } + // for some line in stderr to contain the error message + for _, line := range stderr { + if strings.Contains(line, config.ErrFailedToValidateConfig.Error()) { + return + } + } + t.Error("expected error message not found in stderr") +} + +func TestPingCommandNoHost(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + p, err := findFreePortInRange(49152, 65535) + assert.NoError(t, err) + addr := fmt.Sprintf("localhost:%d", p) + _, stderr := runDefraCommand(t, conf, []string{"client", "ping", "--url", addr}) + assertContainsSubstring(t, stderr, "failed to send ping") +} diff --git a/tests/integration/cli/client_query_test.go b/tests/integration/cli/client_query_test.go new file mode 100644 index 0000000000..00271ada94 --- /dev/null +++ b/tests/integration/cli/client_query_test.go @@ -0,0 +1,66 @@ +// Copyright 2022 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 ( + "testing" +) + +func TestRequestSimple(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "query", + "query IntrospectionQuery {__schema {queryType { name }}}", + }) + nodeLog := stopDefra() + + assertContainsSubstring(t, stdout, "Query") + assertNotContainsSubstring(t, nodeLog, "ERROR") +} + +func TestRequestInvalidQuery(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "query", "{}}"}) + _ = stopDefra() + + assertContainsSubstring(t, stdout, "Syntax Error") +} + +func TestRequestWithErrorNoType(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + defer stopDefra() + + stdout, _ := runDefraCommand(t, conf, []string{"client", "query", "query { User { whatever } }"}) + + assertContainsSubstring(t, stdout, "Cannot query field") +} + +func TestRequestWithErrorNoField(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + defer stopDefra() + + fname := schemaFileFixture(t, "schema.graphql", ` + type User { + id: ID + name: String + }`) + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", "query { User { nonexistent } }"}) + + assertContainsSubstring(t, stdout, `Cannot query field \"nonexistent\"`) +} diff --git a/tests/integration/cli/client_rpc_p2p_collection_test.go b/tests/integration/cli/client_rpc_p2p_collection_test.go new file mode 100644 index 0000000000..b44abcaefb --- /dev/null +++ b/tests/integration/cli/client_rpc_p2p_collection_test.go @@ -0,0 +1,13 @@ +// Copyright 2022 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 + +// TBD diff --git a/tests/integration/cli/client_rpc_replicator_test.go b/tests/integration/cli/client_rpc_replicator_test.go new file mode 100644 index 0000000000..afe6b031c0 --- /dev/null +++ b/tests/integration/cli/client_rpc_replicator_test.go @@ -0,0 +1,22 @@ +// Copyright 2022 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 + +/* WIP client rpc replicator getall is broken currently +func TestReplicatorGetAllEmpty(t *testing.T) { +conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + defer stopDefra() + + _, stderr := runDefraCommand(t, conf, []string{"client", "rpc", "replicator", "getall"}) + assertContainsSubstring(t, stderr, "No replicator found") +} +*/ diff --git a/tests/integration/cli/client_schema_add_test.go b/tests/integration/cli/client_schema_add_test.go new file mode 100644 index 0000000000..8c505785c0 --- /dev/null +++ b/tests/integration/cli/client_schema_add_test.go @@ -0,0 +1,95 @@ +// Copyright 2022 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestAddSchemaFromFile(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + fname := schemaFileFixture(t, "schema.graphql", ` + type User { + id: ID + name: String + }`) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) + + nodeLog := stopDefra() + + assert.Contains(t, stdout, `{"data":{"result":"success"}}`) + assertNotContainsSubstring(t, nodeLog, "ERROR") +} + +func TestAddSchemaWithDuplicateType(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + fname1 := schemaFileFixture(t, "schema1.graphql", `type Post { id: ID title: String }`) + fname2 := schemaFileFixture(t, "schema2.graphql", `type Post { id: ID author: String }`) + + stdout1, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname1}) + stdout2, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname2}) + + _ = stopDefra() + + assertContainsSubstring(t, stdout1, `{"data":{"result":"success"}}`) + assertContainsSubstring(t, stdout2, `schema type already exists. Name: Post`) +} + +/* disabled because current implementation doesn't support this currently +func TestAddSchemaWithMultipleFiles(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + fname1 := schemaFileFixture(t, "schema1.graphql", `type Post { id: ID title: String }`) + fname2 := schemaFileFixture(t, "schema2.graphql", `type User { id: ID name: String }`) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname1, "-f", fname2}) + + nodeLog := stopDefra() + + assertContainsSubstring(t, stdout, `{"data":{"result":"success"}}`) + assertNotContainsSubstring(t, nodeLog, "ERROR") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", + `query IntrospectionQuery { __schema { types { name } } }`}) + assertContainsSubstring(t, stdout, `{"name":"Post"}`) + assertContainsSubstring(t, stdout, `{"name":"User"}`) +} +*/ + +/* disabled because current implementation doesn't support this currently +func TestAddSchemaWithMultipleFilesWithIntertwinedSchemas(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + + fname1 := schemaFileFixture(t, "schema1.graphql", `type Post { id: ID title: String }`) + fname2 := schemaFileFixture(t, "schema2.graphql", `type User { id: ID posts: [Post] }`) + + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname1, "-f", fname2}) + + nodeLog := stopDefra() + + assertContainsSubstring(t, stdout, `{"data":{"result":"success"}}`) + assertNotContainsSubstring(t, nodeLog, "ERROR") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", + `query IntrospectionQuery { __schema { types { name } } }`}) + assertContainsSubstring(t, stdout, `{"name":"Post"}`) + assertContainsSubstring(t, stdout, `{"name":"User"}`) +} +*/ diff --git a/tests/integration/cli/client_schema_patch_test.go b/tests/integration/cli/client_schema_patch_test.go new file mode 100644 index 0000000000..487dc9eda5 --- /dev/null +++ b/tests/integration/cli/client_schema_patch_test.go @@ -0,0 +1,53 @@ +// Copyright 2022 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 ( + "testing" +) + +func TestClientSchemaPatch(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + defer stopDefra() + + fname := schemaFileFixture(t, "schema.graphql", ` + type User { + id: ID + name: String + }`) + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", `[{ "op": "add", "path": "/User/Schema/Fields/-", "value": {"Name": "address", "Kind": "String"} }]`}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "query", `query IntrospectionQuery { __type (name: "User") { fields { name } }}`}) + assertContainsSubstring(t, stdout, "address") +} + +func TestClientSchemaPatch_InvalidJSONPatch(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stopDefra := runDefraNode(t, conf) + defer stopDefra() + + fname := schemaFileFixture(t, "schema.graphql", ` + type User { + id: ID + name: String + } + `) + stdout, _ := runDefraCommand(t, conf, []string{"client", "schema", "add", "-f", fname}) + assertContainsSubstring(t, stdout, "success") + + stdout, _ = runDefraCommand(t, conf, []string{"client", "schema", "patch", `[{ "op": "invalidOp" }]`}) + assertContainsSubstring(t, stdout, "Internal Server Error") +} diff --git a/tests/integration/cli/init_test.go b/tests/integration/cli/init_test.go new file mode 100644 index 0000000000..7292d920c3 --- /dev/null +++ b/tests/integration/cli/init_test.go @@ -0,0 +1,51 @@ +// Copyright 2022 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 ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/config" +) + +// Executing init command creates valid config file. +func TestCLIInitCommand(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) + cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) + assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) + if !assert.FileExists(t, cfgfilePath) { + t.Fatal("Config file not created") + } +} + +func TestCLIInitCommandTwiceErrors(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) + _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) + assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) + _, stderr = runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) + assertContainsSubstring(t, stderr, "Configuration file already exists at "+cfgfilePath) +} + +// Executing init command twice, but second time reinitializing. +func TestInitCommandTwiceReinitalize(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + cfgfilePath := filepath.Join(conf.rootDir, config.DefaultConfigFileName) + _, stderr := runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir}) + assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) + _, stderr = runDefraCommand(t, conf, []string{"init", "--rootdir", conf.rootDir, "--reinitialize"}) + assertContainsSubstring(t, stderr, "Deleted config file at "+cfgfilePath) + assertContainsSubstring(t, stderr, "Created config file at "+cfgfilePath) +} diff --git a/tests/integration/cli/log_config_test.go b/tests/integration/cli/log_config_test.go index 043eae5b7c..55d1b18154 100644 --- a/tests/integration/cli/log_config_test.go +++ b/tests/integration/cli/log_config_test.go @@ -8,7 +8,7 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. -package cli +package clitest import ( "bufio" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" "github.com/sourcenetwork/defradb/logging" ) @@ -80,7 +81,11 @@ func captureLogLines(t *testing.T, setup func(), predicate func()) []string { os.Args = append(os.Args, "init", "--rootdir", directory) setup() - cli.Execute() + cfg := config.DefaultConfig() + defraCmd := cli.NewDefraCommand(cfg) + if err := defraCmd.Execute(context.Background()); err != nil { + t.Fatal(err) + } predicate() log1.Flush() log2.Flush() @@ -88,7 +93,7 @@ func captureLogLines(t *testing.T, setup func(), predicate func()) []string { w.Close() var buf bytes.Buffer - io.Copy(&buf, r) + _, _ = io.Copy(&buf, r) logLines, err := parseLines(&buf) if err != nil { t.Fatal(err) diff --git a/tests/integration/cli/root_test.go b/tests/integration/cli/root_test.go new file mode 100644 index 0000000000..33df29fc4d --- /dev/null +++ b/tests/integration/cli/root_test.go @@ -0,0 +1,43 @@ +// Copyright 2022 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 ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRootCommandEmptyRootDir(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, _ := runDefraCommand(t, conf, []string{}) + assert.Contains(t, stdout, "Usage:") +} + +func TestRootCommandRootDirWithDefaultConfig(t *testing.T) { + conf := DefraNodeConfig{ + logPath: t.TempDir(), + } + stdout, _ := runDefraCommand(t, conf, []string{}) + assert.Contains(t, stdout, "Usage:") +} + +func TestRootCommandRootDirFromEnv(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, _ := runDefraCommand(t, conf, []string{}) + assert.Contains(t, stdout, "Usage:") +} + +func TestRootCommandRootWithNonexistentFlag(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, _ := runDefraCommand(t, conf, []string{"--foo"}) + assert.Contains(t, stdout, "Usage:") +} diff --git a/tests/integration/cli/serverdump_test.go b/tests/integration/cli/serverdump_test.go new file mode 100644 index 0000000000..ed8fcd4d9f --- /dev/null +++ b/tests/integration/cli/serverdump_test.go @@ -0,0 +1,28 @@ +// Copyright 2022 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 ( + "testing" +) + +func TestServerDumpMemoryErrs(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{"server-dump", "--store", "memory"}) + assertContainsSubstring(t, stderr, "server-side dump is only supported for the Badger datastore") +} + +func TestServerDumpInvalidStoreErrs(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{"server-dump", "--store", "invalid"}) + // assertContainsSubstring(t, stderr, "invalid datastore type") + assertContainsSubstring(t, stderr, "server-side dump is only supported for the Badger datastore") +} diff --git a/tests/integration/cli/start_test.go b/tests/integration/cli/start_test.go new file mode 100644 index 0000000000..a49bba9c0d --- /dev/null +++ b/tests/integration/cli/start_test.go @@ -0,0 +1,90 @@ +// Copyright 2022 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" +) + +func TestStartCommandBasic(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{ + "start", + "--url", conf.APIURL, + "--tcpaddr", conf.GRPCAddr, + }) + assertContainsSubstring(t, stderr, "Starting DefraDB service...") + assertNotContainsSubstring(t, stderr, "Error") +} + +func TestStartCommandWithTLSIncomplete(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{ + "start", + "--tls", + "--url", conf.APIURL, + "--tcpaddr", conf.GRPCAddr, + }) + assertContainsSubstring(t, stderr, "Starting DefraDB service...") + assertContainsSubstring(t, stderr, "Error") +} + +func TestStartCommandWithStoreMemory(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{ + "start", "--store", "memory", + "--url", conf.APIURL, + "--tcpaddr", conf.GRPCAddr, + }) + assertContainsSubstring(t, stderr, "Starting DefraDB service...") + assertContainsSubstring(t, stderr, "Building new memory store") + assertNotContainsSubstring(t, stderr, "Error") +} + +func TestStartCommandWithP2PAddr(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + p2pport, err := findFreePortInRange(49152, 65535) + if err != nil { + t.Fatal(err) + } + addr := fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", p2pport) + _, stderr := runDefraCommand(t, conf, []string{ + "start", + "--p2paddr", addr, + "--url", conf.APIURL, + "--tcpaddr", conf.GRPCAddr, + }) + assertContainsSubstring(t, stderr, "Starting DefraDB service...") + logstring := fmt.Sprintf("Starting P2P node, {\"P2P address\": \"%s\"}", addr) + assertContainsSubstring(t, stderr, logstring) + assertNotContainsSubstring(t, stderr, "Error") +} + +func TestStartCommandWithNoP2P(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{ + "start", + "--no-p2p", + }) + assertContainsSubstring(t, stderr, "Starting DefraDB service...") + assertNotContainsSubstring(t, stderr, "Starting P2P node") + assertNotContainsSubstring(t, stderr, "Error") +} + +func TestStartCommandWithInvalidStoreType(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + _, stderr := runDefraCommand(t, conf, []string{ + "start", + "--store", "invalid", + }) + assertContainsSubstring(t, stderr, "failed to load config: failed to validate config: invalid store type") +} diff --git a/tests/integration/cli/utils.go b/tests/integration/cli/utils.go new file mode 100644 index 0000000000..aba0f8dc88 --- /dev/null +++ b/tests/integration/cli/utils.go @@ -0,0 +1,244 @@ +// Copyright 2022 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 provides a testing framework for the Defra CLI, along with CLI integration tests. +*/ +package clitest + +import ( + "bufio" + "bytes" + "context" + "errors" + "fmt" + "io" + "math/rand" + "net" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/sourcenetwork/defradb/cli" + "github.com/sourcenetwork/defradb/config" +) + +const COMMAND_TIMEOUT_SECONDS = 2 + +type DefraNodeConfig struct { + rootDir string + logPath string + APIURL string + GRPCAddr string +} + +func NewDefraNodeDefaultConfig(t *testing.T) DefraNodeConfig { + t.Helper() + portAPI, err := findFreePortInRange(49152, 65535) + if err != nil { + t.Fatal(err) + } + portGRPC, err := findFreePortInRange(49152, 65535) + if err != nil { + t.Fatal(err) + } + + return DefraNodeConfig{ + rootDir: t.TempDir(), + logPath: "", + APIURL: fmt.Sprintf("localhost:%d", portAPI), + GRPCAddr: fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", portGRPC), + } +} + +// runDefraNode runs a defra node in a separate goroutine and returns a stopping function +// which also returns the node's execution log lines. +func runDefraNode(t *testing.T, conf DefraNodeConfig) func() []string { + t.Helper() + + if conf.logPath == "" { + conf.logPath = filepath.Join(t.TempDir(), "defra.log") + } + + var args []string + if conf.rootDir != "" { + args = append(args, "--rootdir", conf.rootDir) + } + if conf.APIURL != "" { + args = append(args, "--url", conf.APIURL) + } + if conf.GRPCAddr != "" { + args = append(args, "--tcpaddr", conf.GRPCAddr) + } + args = append(args, "--logoutput", conf.logPath) + + cfg := config.DefaultConfig() + ctx, cancel := context.WithCancel(context.Background()) + go func() { + defraCmd := cli.NewDefraCommand(cfg) + defraCmd.RootCmd.SetArgs( + append([]string{"start"}, args...), + ) + err := defraCmd.Execute(ctx) + assert.NoError(t, err) + }() + time.Sleep(1 * time.Second) // time buffer for it to start + cancelAndOutput := func() []string { + cancel() + time.Sleep(1 * time.Second) // time buffer for it to stop + lines, err := readLoglines(t, conf.logPath) + assert.NoError(t, err) + return lines + } + return cancelAndOutput +} + +// Runs a defra command and returns the stdout and stderr output. +func runDefraCommand(t *testing.T, conf DefraNodeConfig, args []string) (stdout, stderr []string) { + t.Helper() + cfg := config.DefaultConfig() + args = append([]string{ + "--url", conf.APIURL, + }, args...) + if !contains(args, "--rootdir") { + args = append(args, "--rootdir", t.TempDir()) + } + + ctx, cancel := context.WithTimeout(context.Background(), COMMAND_TIMEOUT_SECONDS*time.Second) + defer cancel() + + stdout, stderr = captureOutput(func() { + defraCmd := cli.NewDefraCommand(cfg) + t.Log("executing defra command with args", args) + defraCmd.RootCmd.SetArgs(args) + _ = defraCmd.Execute(ctx) + }) + return stdout, stderr +} + +func contains(args []string, arg string) bool { + for _, a := range args { + if a == arg { + return true + } + } + return false +} + +func readLoglines(t *testing.T, fpath string) ([]string, error) { + f, err := os.Open(fpath) + if err != nil { + return nil, err + } + defer f.Close() //nolint:errcheck + scanner := bufio.NewScanner(f) + lines := make([]string, 0) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + err = scanner.Err() + assert.NoError(t, err) + return lines, nil +} + +func captureOutput(f func()) (stdout, stderr []string) { + oldStdout := os.Stdout + oldStderr := os.Stderr + rStdout, wStdout, err := os.Pipe() + if err != nil { + panic(err) + } + rStderr, wStderr, err := os.Pipe() + if err != nil { + panic(err) + } + os.Stdout = wStdout + os.Stderr = wStderr + + f() + + if err := wStdout.Close(); err != nil { + panic(err) + } + if err := wStderr.Close(); err != nil { + panic(err) + } + + os.Stdout = oldStdout + os.Stderr = oldStderr + + var stdoutBuf, stderrBuf bytes.Buffer + if _, err := io.Copy(&stdoutBuf, rStdout); err != nil { + panic(err) + } + if _, err := io.Copy(&stderrBuf, rStderr); err != nil { + panic(err) + } + + stdout = strings.Split(strings.TrimSuffix(stdoutBuf.String(), "\n"), "\n") + stderr = strings.Split(strings.TrimSuffix(stderrBuf.String(), "\n"), "\n") + + return +} + +// findFreePortInRange returns a free port in the range [minPort, maxPort]. +// The range of ports that are unfrequently used is [49152, 65535]. +func findFreePortInRange(minPort, maxPort int) (int, error) { + if minPort < 1 || maxPort > 65535 || minPort > maxPort { + return 0, errors.New("invalid port range") + } + + const maxAttempts = 100 + for i := 0; i < maxAttempts; i++ { + port := rand.Intn(maxPort-minPort+1) + minPort + addr := fmt.Sprintf("127.0.0.1:%d", port) + listener, err := net.Listen("tcp", addr) + if err == nil { + _ = listener.Close() + return port, nil + } + } + + return 0, errors.New("unable to find a free port") +} + +func assertContainsSubstring(t *testing.T, haystack []string, substring string) { + t.Helper() + if !containsSubstring(haystack, substring) { + t.Fatalf("expected %q to contain %q", haystack, substring) + } +} + +func assertNotContainsSubstring(t *testing.T, haystack []string, substring string) { + t.Helper() + if containsSubstring(haystack, substring) { + t.Fatalf("expected %q to not contain %q", haystack, substring) + } +} + +func containsSubstring(haystack []string, substring string) bool { + for _, s := range haystack { + if strings.Contains(s, substring) { + return true + } + } + return false +} + +func schemaFileFixture(t *testing.T, fname string, schema string) string { + absFname := filepath.Join(t.TempDir(), fname) + err := os.WriteFile(absFname, []byte(schema), 0644) + assert.NoError(t, err) + return absFname +} diff --git a/tests/integration/cli/version_test.go b/tests/integration/cli/version_test.go new file mode 100644 index 0000000000..ffa9820c98 --- /dev/null +++ b/tests/integration/cli/version_test.go @@ -0,0 +1,47 @@ +// Copyright 2022 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 ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +// note: this assumes the version information *without* build-time info integrated. +func TestExecVersion(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, stderr := runDefraCommand(t, conf, []string{"version"}) + for _, line := range stderr { + assert.NotContains(t, line, "ERROR") + } + output := strings.Join(stdout, " ") + assert.Contains(t, output, "defradb") + assert.Contains(t, output, "built with Go") +} + +func TestExecVersionJSON(t *testing.T) { + conf := NewDefraNodeDefaultConfig(t) + stdout, stderr := runDefraCommand(t, conf, []string{"version", "--format", "json"}) + for _, line := range stderr { + assert.NotContains(t, line, "ERROR") + } + output := strings.Join(stdout, " ") + assert.Contains(t, output, "go\":") + assert.Contains(t, output, "commit\":") + assert.Contains(t, output, "commitdate\":") + + var data map[string]any + err := json.Unmarshal([]byte(output), &data) + assert.NoError(t, err) +}