Repository: clouditor/clouditor
Pull request #465 (2022-01-25 15:58:22)

Update CLI: Change structure and add commands 
- [x] CLI commands should be divided into resources (e.g. `cl evidence list`) and services (e.g. `cl service discovery start`)
- [x] #404:  (see first example above)
- [x] `cl assessment list` should call Orchestrator's method, not of the Assessment. The Assessment's corresponding method should be executable via `cl service assessment list`

codecov[bot]: # [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) Report
> Merging [#465](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (6b3ad53) into [main](https://codecov.io/gh/clouditor/clouditor/commit/b02ac9e168c02b92c75f5d2a3fa164de22881fba?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (b02ac9e) will **increase** coverage by `0.09%`.
> The diff coverage is `79.48%`.

[![Impacted file tree graph](https://codecov.io/gh/clouditor/clouditor/pull/465/graphs/tree.svg?width=650&height=150&src=pr&token=syrXsw5LTX&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)

```diff
@@            Coverage Diff             @@
##             main     #465      +/-   ##
==========================================
+ Coverage   70.77%   70.87%   +0.09%     
==========================================
  Files          40       41       +1     
  Lines        3655     3694      +39     
==========================================
+ Hits         2587     2618      +31     
- Misses        949      956       +7     
- Partials      119      120       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `70.87% <79.48%> (+0.09%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) | Coverage Δ | |
|---|---|---|
| [cli/commands/commands.go](https://codecov.io/gh/clouditor/clouditor/pull/465/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2xpL2NvbW1hbmRzL2NvbW1hbmRzLmdv) | `0.00% <0.00%> (ø)` | |
| [cli/commands/evidence/evidence.go](https://codecov.io/gh/clouditor/clouditor/pull/465/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2xpL2NvbW1hbmRzL2V2aWRlbmNlL2V2aWRlbmNlLmdv) | `81.57% <81.57%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Last update [b02ac9e...6b3ad53](https://codecov.io/gh/clouditor/clouditor/pull/465?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).

lebogg: Now, discovery moved to service CMDs. Should we also add `resource` command for Discovery Query (`cl resource list` == `discovery.query(...)`)?

oxisto: > Now, discovery moved to service CMDs. Should we also add `resource` command for Discovery Query (`cl resource list` == `discovery.query(...)`)?

Yes!

Patch:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 083642a5..75621fad 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -13,6 +13,17 @@
             "cwd": "${workspaceFolder}",
             "env": {},
             "preLaunchTask": "go: generate"
+        },
+        {
+            "name": "Launch engine (in-memory)",
+            "type": "go",
+            "request": "launch",
+            "mode": "auto",
+            "program": "${workspaceFolder}/cmd/engine/engine.go",
+            "cwd": "${workspaceFolder}",
+            "args": ["--db-in-memory"],
+            "env": {},
+            "preLaunchTask": "go: generate"
         }
     ]
 }
\ No newline at end of file
diff --git a/cli/commands/assessmentresult/assessment_result.go b/cli/commands/assessmentresult/assessment_result.go
new file mode 100644
index 00000000..1ae9b4cf
--- /dev/null
+++ b/cli/commands/assessmentresult/assessment_result.go
@@ -0,0 +1,59 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package assessmentresult
+
+import (
+	"clouditor.io/clouditor/cli/commands/service/orchestrator"
+	"github.com/spf13/cobra"
+)
+
+// NewListAssessmentResultsCommand returns a cobra command for the `list` subcommand
+func NewListAssessmentResultsCommand() *cobra.Command {
+	// Use Orchestrator's function for listing assessment results
+	cmd := orchestrator.NewListAssessmentResultsCommand()
+	// Change use for better readability (cl assessment_result list vs cl assessment_result list_assessment_results)
+	cmd.Use = "list"
+	return cmd
+}
+
+// NewAssessmentResultCommand returns a cobra command for `assessment` subcommands
+func NewAssessmentResultCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "assessment-result",
+		Short: "Assessment result commands",
+	}
+
+	AddCommands(cmd)
+
+	return cmd
+}
+
+// AddCommands adds all subcommands
+func AddCommands(cmd *cobra.Command) {
+	cmd.AddCommand(
+		NewListAssessmentResultsCommand(),
+	)
+}
diff --git a/cli/commands/commands.go b/cli/commands/commands.go
index 51ce9af6..477251d8 100644
--- a/cli/commands/commands.go
+++ b/cli/commands/commands.go
@@ -27,13 +27,14 @@ package commands
 
 import (
 	"clouditor.io/clouditor/cli"
-	"clouditor.io/clouditor/cli/commands/assessment"
+	"clouditor.io/clouditor/cli/commands/assessmentresult"
 	"clouditor.io/clouditor/cli/commands/cloud"
 	"clouditor.io/clouditor/cli/commands/completion"
-	"clouditor.io/clouditor/cli/commands/discovery"
+	"clouditor.io/clouditor/cli/commands/evidence"
 	"clouditor.io/clouditor/cli/commands/login"
 	"clouditor.io/clouditor/cli/commands/metric"
-	"clouditor.io/clouditor/cli/commands/orchestrator"
+	"clouditor.io/clouditor/cli/commands/resource"
+	"clouditor.io/clouditor/cli/commands/service"
 	"clouditor.io/clouditor/cli/commands/tool"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
@@ -42,14 +43,17 @@ import (
 // AddCommands adds all subcommands
 func AddCommands(cmd *cobra.Command) {
 	cmd.AddCommand(
+		// commands for resources
 		login.NewLoginCommand(),
-		discovery.NewDiscoveryCommand(),
 		metric.NewMetricCommand(),
 		tool.NewToolCommand(),
-		assessment.NewAssessmentCommand(),
-		orchestrator.NewOrchestratorCommand(),
+		resource.NewResourceCommand(),
+		evidence.NewEvidenceCommand(),
+		assessmentresult.NewAssessmentResultCommand(),
 		completion.NewCompletionCommand(),
 		cloud.NewCloudCommand(),
+		// command consisting of service commands
+		service.NewServiceCommand(),
 	)
 
 	cmd.PersistentFlags().StringP("session-directory", "s", cli.DefaultSessionFolder, "the directory where the session will be saved and loaded from")
diff --git a/cli/commands/evidence/evidence.go b/cli/commands/evidence/evidence.go
new file mode 100644
index 00000000..92e72f14
--- /dev/null
+++ b/cli/commands/evidence/evidence.go
@@ -0,0 +1,85 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package evidence
+
+import (
+	"clouditor.io/clouditor/api/evidence"
+	"clouditor.io/clouditor/cli"
+	"context"
+	"fmt"
+	"github.com/spf13/cobra"
+)
+
+// NewListEvidencesCommand returns a cobra command for the `list` subcommand
+func NewListEvidencesCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "list",
+		Short: "Lists all evidences",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			var (
+				err     error
+				session *cli.Session
+				client  evidence.EvidenceStoreClient
+				res     *evidence.ListEvidencesResponse
+			)
+
+			if session, err = cli.ContinueSession(); err != nil {
+				fmt.Printf("Error while retrieving the session. Please re-authenticate.\n")
+				return nil
+			}
+
+			client = evidence.NewEvidenceStoreClient(session)
+
+			res, err = client.ListEvidences(context.Background(), &evidence.ListEvidencesRequest{})
+
+			return session.HandleResponse(res, err)
+		},
+		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+			return []string{}, cobra.ShellCompDirectiveNoFileComp
+		},
+	}
+
+	return cmd
+}
+
+// NewEvidenceCommand returns a cobra command for `assessment` subcommands
+func NewEvidenceCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "evidence",
+		Short: "Evidence commands",
+	}
+
+	AddCommands(cmd)
+
+	return cmd
+}
+
+// AddCommands adds all subcommands
+func AddCommands(cmd *cobra.Command) {
+	cmd.AddCommand(
+		NewListEvidencesCommand(),
+	)
+}
diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go
index efd766d7..1e8f2f04 100644
--- a/cli/commands/login/login.go
+++ b/cli/commands/login/login.go
@@ -37,6 +37,7 @@ import (
 
 const (
 	// URLFlag is the viper flag for the server url
+	// TODO(oxisto): Unused constant?
 	URLFlag = "url"
 )
 
@@ -83,7 +84,7 @@ func NewLoginCommand() *cobra.Command {
 				return fmt.Errorf("could not save session: %w", err)
 			}
 
-			fmt.Print("\nLogin succesful\n")
+			fmt.Print("\nLogin successful\n")
 
 			return err
 		},
diff --git a/cli/commands/resource/resource.go b/cli/commands/resource/resource.go
new file mode 100644
index 00000000..6d1d88a7
--- /dev/null
+++ b/cli/commands/resource/resource.go
@@ -0,0 +1,60 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package resource
+
+import (
+	"clouditor.io/clouditor/cli/commands/service/discovery"
+	"github.com/spf13/cobra"
+)
+
+// NewListResourcesCommand returns a cobra command for the `list` subcommand
+func NewListResourcesCommand() *cobra.Command {
+	// Use Discovery's function for listing resources
+	cmd := discovery.NewQueryDiscoveryCommand()
+	// Change use for consistency (cl resource list vs cl resource query)
+	cmd.Use = "list"
+
+	return cmd
+}
+
+// NewResourceCommand returns a cobra command for `resource` subcommands
+func NewResourceCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "resource",
+		Short: "Resource commands",
+	}
+
+	AddCommands(cmd)
+
+	return cmd
+}
+
+// AddCommands adds all subcommands
+func AddCommands(cmd *cobra.Command) {
+	cmd.AddCommand(
+		NewListResourcesCommand(),
+	)
+}
diff --git a/cli/commands/assessment/assessment.go b/cli/commands/service/assessment/assessment.go
similarity index 88%
rename from cli/commands/assessment/assessment.go
rename to cli/commands/service/assessment/assessment.go
index 4ba1b205..73dbe6f1 100644
--- a/cli/commands/assessment/assessment.go
+++ b/cli/commands/service/assessment/assessment.go
@@ -34,11 +34,11 @@ import (
 	"github.com/spf13/cobra"
 )
 
-// NewListResultsCommand returns a cobra command for the `list` subcommand
-func NewListResultsCommand() *cobra.Command {
+// NewListAssessmentResultsCommand returns a cobra command for the `list` subcommand
+func NewListAssessmentResultsCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "list",
-		Short: "Lists all assessment results",
+		Use:   "list-assessment-results",
+		Short: "Lists all assessment results stored in the assessment service",
 		RunE: func(cmd *cobra.Command, args []string) error {
 			var (
 				err     error
@@ -70,7 +70,7 @@ func NewListResultsCommand() *cobra.Command {
 func NewAssessmentCommand() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "assessment",
-		Short: "Assessment result commands",
+		Short: "Assessment service commands",
 	}
 
 	AddCommands(cmd)
@@ -81,6 +81,6 @@ func NewAssessmentCommand() *cobra.Command {
 // AddCommands adds all subcommands
 func AddCommands(cmd *cobra.Command) {
 	cmd.AddCommand(
-		NewListResultsCommand(),
+		NewListAssessmentResultsCommand(),
 	)
 }
diff --git a/cli/commands/discovery/discovery.go b/cli/commands/service/discovery/discovery.go
similarity index 96%
rename from cli/commands/discovery/discovery.go
rename to cli/commands/service/discovery/discovery.go
index 7e9e8d06..405ea6c7 100644
--- a/cli/commands/discovery/discovery.go
+++ b/cli/commands/service/discovery/discovery.go
@@ -97,11 +97,11 @@ func NewQueryDiscoveryCommand() *cobra.Command {
 	return cmd
 }
 
-// NewStartDiscoveryCommand returns a cobra command for `discovery` subcommands
+// NewDiscoveryCommand returns a cobra command for `discovery` subcommands
 func NewDiscoveryCommand() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "discovery",
-		Short: "Discovery commands",
+		Short: "Discovery service commands",
 	}
 
 	AddCommands(cmd)
diff --git a/cli/commands/orchestrator/orchestrator.go b/cli/commands/service/orchestrator/orchestrator.go
similarity index 88%
rename from cli/commands/orchestrator/orchestrator.go
rename to cli/commands/service/orchestrator/orchestrator.go
index 1ce7955f..5d407351 100644
--- a/cli/commands/orchestrator/orchestrator.go
+++ b/cli/commands/service/orchestrator/orchestrator.go
@@ -35,11 +35,11 @@ import (
 	"github.com/spf13/cobra"
 )
 
-// NewListResultsCommand returns a cobra command for the `list` subcommand
-func NewListResultsCommand() *cobra.Command {
+// NewListAssessmentResultsCommand returns a cobra command for the `list` subcommand
+func NewListAssessmentResultsCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "list",
-		Short: "Lists all assessment results",
+		Use:   "list-assessment-results",
+		Short: "Lists all assessment results stored in the Orchestrator service",
 		RunE: func(cmd *cobra.Command, args []string) error {
 			var (
 				err     error
@@ -71,7 +71,7 @@ func NewListResultsCommand() *cobra.Command {
 func NewOrchestratorCommand() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:   "orchestrator",
-		Short: "Orchestrator commands",
+		Short: "Orchestrator service commands",
 	}
 
 	AddCommands(cmd)
@@ -82,6 +82,6 @@ func NewOrchestratorCommand() *cobra.Command {
 // AddCommands adds all subcommands
 func AddCommands(cmd *cobra.Command) {
 	cmd.AddCommand(
-		NewListResultsCommand(),
+		NewListAssessmentResultsCommand(),
 	)
 }
diff --git a/cli/commands/service/service.go b/cli/commands/service/service.go
new file mode 100644
index 00000000..c09b5da9
--- /dev/null
+++ b/cli/commands/service/service.go
@@ -0,0 +1,28 @@
+package service
+
+import (
+	"clouditor.io/clouditor/cli/commands/service/assessment"
+	"clouditor.io/clouditor/cli/commands/service/discovery"
+	"clouditor.io/clouditor/cli/commands/service/orchestrator"
+	"github.com/spf13/cobra"
+)
+
+// NewServiceCommand returns a cobra command for `discovery` subcommands
+func NewServiceCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "service",
+		Short: "Service commands",
+	}
+
+	AddCommands(cmd)
+
+	return cmd
+}
+
+func AddCommands(cmd *cobra.Command) {
+	cmd.AddCommand(
+		discovery.NewDiscoveryCommand(),
+		assessment.NewAssessmentCommand(),
+		orchestrator.NewOrchestratorCommand(),
+	)
+}


Test patch:
diff --git a/cli/commands/assessmentresult/assessment_result_test.go b/cli/commands/assessmentresult/assessment_result_test.go
new file mode 100644
index 00000000..ce0b153d
--- /dev/null
+++ b/cli/commands/assessmentresult/assessment_result_test.go
@@ -0,0 +1,170 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package assessmentresult
+
+import (
+	"bytes"
+	"clouditor.io/clouditor/api/assessment"
+	"clouditor.io/clouditor/api/orchestrator"
+	"clouditor.io/clouditor/cli"
+	"clouditor.io/clouditor/cli/commands/login"
+	"clouditor.io/clouditor/persistence"
+	service_auth "clouditor.io/clouditor/service/auth"
+	service_orchestrator "clouditor.io/clouditor/service/orchestrator"
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/types/known/structpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
+	"io/ioutil"
+	"net"
+	"os"
+	"testing"
+)
+
+var sock net.Listener
+var server *grpc.Server
+
+func TestMain(m *testing.M) {
+	var (
+		err     error
+		dir     string
+		service *service_orchestrator.Service
+	)
+
+	err = os.Chdir("../../../")
+	if err != nil {
+		panic(err)
+	}
+
+	err = persistence.InitDB(true, "", 0)
+	if err != nil {
+		panic(err)
+	}
+
+	service = service_orchestrator.NewService()
+
+	sock, server, err = service_auth.StartDedicatedAuthServer(":0")
+	if err != nil {
+		panic(err)
+	}
+	// Store an assessment result that output of CMD 'list' is not empty
+	orchestrator.RegisterOrchestratorServer(server, service)
+	_, err = service.StoreAssessmentResult(context.TODO(), &orchestrator.StoreAssessmentResultRequest{
+		Result: &assessment.AssessmentResult{
+			Id:         "assessmentResultID",
+			MetricId:   "assessmentResultMetricID",
+			EvidenceId: "evidenceID",
+			Timestamp:  timestamppb.Now(),
+			MetricConfiguration: &assessment.MetricConfiguration{
+				TargetValue: toStruct(1.0),
+				Operator:    "operator",
+				IsDefault:   true,
+			}}})
+
+	if err != nil {
+		panic(err)
+	}
+
+	defer func(sock net.Listener) {
+		err = sock.Close()
+		if err != nil {
+			panic(err)
+		}
+	}(sock)
+	defer server.Stop()
+
+	dir, err = ioutil.TempDir(os.TempDir(), ".clouditor")
+	if err != nil {
+		panic(err)
+	}
+
+	viper.Set("username", "clouditor")
+	viper.Set("password", "clouditor")
+	viper.Set("session-directory", dir)
+
+	cmd := login.NewLoginCommand()
+	err = cmd.RunE(nil, []string{fmt.Sprintf("localhost:%d", sock.Addr().(*net.TCPAddr).Port)})
+	if err != nil {
+		panic(err)
+	}
+	defer os.Exit(m.Run())
+}
+
+func TestAddCommands(t *testing.T) {
+	cmd := NewAssessmentResultCommand()
+
+	// Check if sub commands were added
+	assert.True(t, cmd.HasSubCommands())
+
+	// Check if NewListAssessmentResultsCommand was added
+	for _, v := range cmd.Commands() {
+		if v.Use == "list" {
+			return
+		}
+	}
+	t.Errorf("No list command was added")
+}
+
+func TestNewListResultsCommand(t *testing.T) {
+	var b bytes.Buffer
+
+	cli.Output = &b
+
+	cmd := NewListAssessmentResultsCommand()
+	err := cmd.RunE(nil, []string{})
+	assert.Nil(t, err)
+
+	var response = &assessment.ListAssessmentResultsResponse{}
+	err = protojson.Unmarshal(b.Bytes(), response)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, response)
+	assert.NotEmpty(t, response.Results)
+}
+
+func toStruct(f float32) (s *structpb.Value) {
+	var (
+		b   []byte
+		err error
+	)
+
+	s = new(structpb.Value)
+
+	b, err = json.Marshal(f)
+	if err != nil {
+		return nil
+	}
+	if err = json.Unmarshal(b, &s); err != nil {
+		return nil
+	}
+
+	return
+}
diff --git a/cli/commands/commands_test.go b/cli/commands/commands_test.go
new file mode 100644
index 00000000..45f6099d
--- /dev/null
+++ b/cli/commands/commands_test.go
@@ -0,0 +1,21 @@
+package commands
+
+import (
+	"github.com/spf13/cobra"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestAddCommands(t *testing.T) {
+	cmd := &cobra.Command{}
+	assert.False(t, cmd.HasSubCommands())
+	AddCommands(cmd)
+	assert.True(t, cmd.HasSubCommands())
+	// Check if 'assessment_result' CMD is part of the sub commands
+	for _, v := range cmd.Commands() {
+		if v.Use == "assessment-result" {
+			return
+		}
+	}
+	t.Error("CMD 'assessment-result' is not part of the sub commands but should be")
+}
diff --git a/cli/commands/evidence/evidence_test.go b/cli/commands/evidence/evidence_test.go
new file mode 100644
index 00000000..c8a5c12f
--- /dev/null
+++ b/cli/commands/evidence/evidence_test.go
@@ -0,0 +1,164 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package evidence
+
+import (
+	"bytes"
+	"clouditor.io/clouditor/api/evidence"
+	"clouditor.io/clouditor/cli"
+	"clouditor.io/clouditor/cli/commands/login"
+	"clouditor.io/clouditor/persistence"
+	service_auth "clouditor.io/clouditor/service/auth"
+	service_evidenceStore "clouditor.io/clouditor/service/evidence"
+	"clouditor.io/clouditor/voc"
+	"context"
+	"encoding/json"
+	"fmt"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/types/known/structpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
+	"io/ioutil"
+	"net"
+	"os"
+	"testing"
+)
+
+var sock net.Listener
+var server *grpc.Server
+
+func TestMain(m *testing.M) {
+	var (
+		err     error
+		dir     string
+		service *service_evidenceStore.Service
+	)
+
+	err = os.Chdir("../../../")
+	if err != nil {
+		panic(err)
+	}
+
+	err = persistence.InitDB(true, "", 0)
+	if err != nil {
+		panic(err)
+	}
+
+	service = service_evidenceStore.NewService()
+
+	sock, server, err = service_auth.StartDedicatedAuthServer(":0")
+	if err != nil {
+		panic(err)
+	}
+	evidence.RegisterEvidenceStoreServer(server, service)
+	_, err = service.StoreEvidence(context.TODO(), &evidence.StoreEvidenceRequest{Evidence: &evidence.Evidence{
+		Id:        "mockEvidenceId",
+		ToolId:    "mock",
+		Timestamp: timestamppb.Now(),
+		Resource:  toStruct(voc.VirtualMachine{Compute: &voc.Compute{CloudResource: &voc.CloudResource{ID: "my-resource-id", Type: []string{"VirtualMachine"}}}}),
+	}})
+
+	if err != nil {
+		panic(err)
+	}
+
+	defer func(sock net.Listener) {
+		err = sock.Close()
+		if err != nil {
+			panic(err)
+		}
+	}(sock)
+	defer server.Stop()
+
+	dir, err = ioutil.TempDir(os.TempDir(), ".clouditor")
+	if err != nil {
+		panic(err)
+	}
+
+	viper.Set("username", "clouditor")
+	viper.Set("password", "clouditor")
+	viper.Set("session-directory", dir)
+
+	cmd := login.NewLoginCommand()
+	err = cmd.RunE(nil, []string{fmt.Sprintf("localhost:%d", sock.Addr().(*net.TCPAddr).Port)})
+	if err != nil {
+		panic(err)
+	}
+	defer os.Exit(m.Run())
+}
+
+func TestAddCommands(t *testing.T) {
+	cmd := NewEvidenceCommand()
+
+	// Check if sub commands were added
+	assert.True(t, cmd.HasSubCommands())
+
+	// Check if NewListResultsCommand was added
+	for _, v := range cmd.Commands() {
+		if v.Use == "list" {
+			return
+		}
+	}
+	t.Errorf("No list command was added")
+}
+
+func TestNewListResultsCommand(t *testing.T) {
+	var b bytes.Buffer
+
+	cli.Output = &b
+
+	cmd := NewListEvidencesCommand()
+	err := cmd.RunE(nil, []string{})
+	assert.Nil(t, err)
+
+	var response = &evidence.ListEvidencesResponse{}
+	err = protojson.Unmarshal(b.Bytes(), response)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, response)
+	assert.NotEmpty(t, response.Evidences)
+}
+
+func toStruct(r voc.IsCloudResource) (s *structpb.Value) {
+	var (
+		b   []byte
+		err error
+	)
+
+	s = new(structpb.Value)
+
+	b, err = json.Marshal(r)
+	if err != nil {
+		return nil
+	}
+	if err = json.Unmarshal(b, &s); err != nil {
+		return nil
+	}
+
+	return
+}
diff --git a/cli/commands/metric/metric_test.go b/cli/commands/metric/metric_test.go
index 42f42f49..28c0432b 100644
--- a/cli/commands/metric/metric_test.go
+++ b/cli/commands/metric/metric_test.go
@@ -92,7 +92,6 @@ func TestMain(m *testing.M) {
 		panic(err)
 	}
 
-	// ToDo(lebogg): Check if CRT-D0011 deepsource bug risk warning still occurs
 	defer os.Exit(m.Run())
 }
 
diff --git a/cli/commands/resource/resource_test.go b/cli/commands/resource/resource_test.go
new file mode 100644
index 00000000..9af92e8d
--- /dev/null
+++ b/cli/commands/resource/resource_test.go
@@ -0,0 +1,208 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package resource
+
+import (
+	"bytes"
+	"clouditor.io/clouditor/api/discovery"
+	"clouditor.io/clouditor/cli"
+	"clouditor.io/clouditor/cli/commands/login"
+	"clouditor.io/clouditor/persistence"
+	service_auth "clouditor.io/clouditor/service/auth"
+	service_discovery "clouditor.io/clouditor/service/discovery"
+	"clouditor.io/clouditor/voc"
+	"fmt"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/encoding/protojson"
+	"io/ioutil"
+	"net"
+	"os"
+	"testing"
+	"time"
+)
+
+var sock net.Listener
+var server *grpc.Server
+
+func TestMain(m *testing.M) {
+	var (
+		err     error
+		dir     string
+		service *service_discovery.Service
+	)
+
+	err = os.Chdir("../../../../")
+	if err != nil {
+		panic(err)
+	}
+
+	err = persistence.InitDB(true, "", 0)
+	if err != nil {
+		panic(err)
+	}
+
+	service = service_discovery.NewService()
+	service.StartDiscovery(mockDiscoverer{testCase: 2})
+
+	sock, server, err = service_auth.StartDedicatedAuthServer(":0")
+	if err != nil {
+		panic(err)
+	}
+	discovery.RegisterDiscoveryServer(server, service)
+
+	defer func(sock net.Listener) {
+		err = sock.Close()
+		if err != nil {
+			panic(err)
+		}
+	}(sock)
+	defer server.Stop()
+
+	dir, err = ioutil.TempDir(os.TempDir(), ".clouditor")
+	if err != nil {
+		panic(err)
+	}
+
+	viper.Set("username", "clouditor")
+	viper.Set("password", "clouditor")
+	viper.Set("session-directory", dir)
+
+	cmd := login.NewLoginCommand()
+	err = cmd.RunE(nil, []string{fmt.Sprintf("localhost:%d", sock.Addr().(*net.TCPAddr).Port)})
+	if err != nil {
+		panic(err)
+	}
+	defer os.Exit(m.Run())
+}
+
+func TestAddCommands(t *testing.T) {
+	cmd := NewResourceCommand()
+
+	// Check if sub commands were added
+	assert.True(t, cmd.HasSubCommands())
+
+	// Check if NewListCommand was added
+	for _, v := range cmd.Commands() {
+		if v.Use == "list" {
+			return
+		}
+	}
+	t.Errorf("No list command was added")
+}
+
+func TestNewListCommand(t *testing.T) {
+	var err error
+	var b bytes.Buffer
+
+	cli.Output = &b
+
+	cmd := NewListResourcesCommand()
+	err = cmd.RunE(nil, []string{})
+	assert.Nil(t, err)
+
+	var response = &discovery.QueryResponse{}
+	err = protojson.Unmarshal(b.Bytes(), response)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, response)
+	assert.NotEmpty(t, response.Results.Values)
+
+}
+
+// Mocking code below is copied from clouditor.io/service/discovery
+
+// mockDiscoverer implements Discoverer and mocks the API to cloud resources
+type mockDiscoverer struct {
+	// testCase allows for different implementations for table tests in TestStartDiscovery
+	testCase int
+}
+
+func (mockDiscoverer) Name() string { return "just mocking" }
+
+func (m mockDiscoverer) List() ([]voc.IsCloudResource, error) {
+	switch m.testCase {
+	case 0:
+		return nil, fmt.Errorf("mock error in List()")
+	case 1:
+		return []voc.IsCloudResource{wrongFormattedResource()}, nil
+	case 2:
+		return []voc.IsCloudResource{
+			&voc.ObjectStorage{
+				Storage: &voc.Storage{
+					CloudResource: &voc.CloudResource{
+						ID:   "some-id",
+						Name: "some-name",
+						Type: []string{"ObjectStorage", "Storage", "Resource"},
+					},
+				},
+				HttpEndpoint: &voc.HttpEndpoint{
+					TransportEncryption: &voc.TransportEncryption{
+						Enforced:   false,
+						Enabled:    true,
+						TlsVersion: "TLS1_2",
+					},
+				},
+			},
+		}, nil
+	default:
+		return nil, nil
+	}
+}
+
+func wrongFormattedResource() voc.IsCloudResource {
+	res1 := mockIsCloudResource{Another: nil}
+	res2 := mockIsCloudResource{Another: &res1}
+	res1.Another = &res2
+	return res1
+}
+
+// mockIsCloudResource implements mockIsCloudResource interface.
+// It is used for json.marshal to fail since it contains circular dependency
+type mockIsCloudResource struct {
+	Another *mockIsCloudResource `json:"Another"`
+}
+
+func (mockIsCloudResource) GetID() voc.ResourceID {
+	return "MockResourceId"
+}
+
+func (mockIsCloudResource) GetName() string {
+	return ""
+}
+
+func (mockIsCloudResource) GetType() []string {
+	return nil
+}
+
+func (mockIsCloudResource) HasType(_ string) bool {
+	return false
+}
+
+func (mockIsCloudResource) GetCreationTime() *time.Time {
+	return nil
+}
diff --git a/cli/commands/service/assessment/assessment_test.go b/cli/commands/service/assessment/assessment_test.go
new file mode 100644
index 00000000..4705cb82
--- /dev/null
+++ b/cli/commands/service/assessment/assessment_test.go
@@ -0,0 +1,46 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package assessment
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestAddCommands(t *testing.T) {
+	cmd := NewAssessmentCommand()
+
+	// Check if sub commands were added
+	assert.True(t, cmd.HasSubCommands())
+
+	// Check if NewListResultsCommand was added
+	for _, v := range cmd.Commands() {
+		if v.Use == "list-assessment-results" {
+			return
+		}
+	}
+	t.Errorf("No list command was added")
+}
diff --git a/cli/commands/service/discovery/discovery_test.go b/cli/commands/service/discovery/discovery_test.go
new file mode 100644
index 00000000..ceb1d6e9
--- /dev/null
+++ b/cli/commands/service/discovery/discovery_test.go
@@ -0,0 +1,222 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package discovery
+
+import (
+	"bytes"
+	"clouditor.io/clouditor/api/discovery"
+	"clouditor.io/clouditor/cli"
+	"clouditor.io/clouditor/cli/commands/login"
+	"clouditor.io/clouditor/persistence"
+	service_auth "clouditor.io/clouditor/service/auth"
+	service_discovery "clouditor.io/clouditor/service/discovery"
+	"clouditor.io/clouditor/voc"
+	"fmt"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/encoding/protojson"
+	"io/ioutil"
+	"net"
+	"os"
+	"testing"
+	"time"
+)
+
+var sock net.Listener
+var server *grpc.Server
+
+func TestMain(m *testing.M) {
+	var (
+		err     error
+		dir     string
+		service *service_discovery.Service
+	)
+
+	err = os.Chdir("../../../../")
+	if err != nil {
+		panic(err)
+	}
+
+	err = persistence.InitDB(true, "", 0)
+	if err != nil {
+		panic(err)
+	}
+
+	service = service_discovery.NewService()
+	service.StartDiscovery(mockDiscoverer{testCase: 2})
+
+	sock, server, err = service_auth.StartDedicatedAuthServer(":0")
+	if err != nil {
+		panic(err)
+	}
+	discovery.RegisterDiscoveryServer(server, service)
+
+	defer func(sock net.Listener) {
+		err = sock.Close()
+		if err != nil {
+			panic(err)
+		}
+	}(sock)
+	defer server.Stop()
+
+	dir, err = ioutil.TempDir(os.TempDir(), ".clouditor")
+	if err != nil {
+		panic(err)
+	}
+
+	viper.Set("username", "clouditor")
+	viper.Set("password", "clouditor")
+	viper.Set("session-directory", dir)
+
+	cmd := login.NewLoginCommand()
+	err = cmd.RunE(nil, []string{fmt.Sprintf("localhost:%d", sock.Addr().(*net.TCPAddr).Port)})
+	if err != nil {
+		panic(err)
+	}
+	defer os.Exit(m.Run())
+}
+
+func TestAddCommands(t *testing.T) {
+	cmd := NewDiscoveryCommand()
+
+	// Check if sub commands were added
+	assert.True(t, cmd.HasSubCommands())
+
+	// Check if NewQueryDiscoveryCommand was added
+	for _, v := range cmd.Commands() {
+		if v.Use == "query" {
+			return
+		}
+	}
+	t.Errorf("No query command was added")
+}
+
+// TestNewStartDiscoveryCommand currently tests only the error case
+// For more tests mock stream to assessment
+func TestNewStartDiscoveryCommand(t *testing.T) {
+	var err error
+
+	cmd := NewStartDiscoveryCommand()
+	err = cmd.RunE(nil, []string{})
+	assert.NotNil(t, err)
+
+	var response = &discovery.StartDiscoveryResponse{}
+	assert.NotNil(t, response)
+	assert.False(t, response.Successful)
+
+}
+
+func TestNewQueryDiscoveryCommand(t *testing.T) {
+	var err error
+	var b bytes.Buffer
+
+	cli.Output = &b
+
+	cmd := NewQueryDiscoveryCommand()
+	err = cmd.RunE(nil, []string{})
+	assert.Nil(t, err)
+
+	var response = &discovery.QueryResponse{}
+	err = protojson.Unmarshal(b.Bytes(), response)
+
+	assert.Nil(t, err)
+	assert.NotNil(t, response)
+	assert.NotEmpty(t, response.Results.Values)
+}
+
+// Mocking code below is copied from clouditor.io/service/discovery
+
+// mockDiscoverer implements Discoverer and mocks the API to cloud resources
+type mockDiscoverer struct {
+	// testCase allows for different implementations for table tests in TestStartDiscovery
+	testCase int
+}
+
+func (mockDiscoverer) Name() string { return "just mocking" }
+
+func (m mockDiscoverer) List() ([]voc.IsCloudResource, error) {
+	switch m.testCase {
+	case 0:
+		return nil, fmt.Errorf("mock error in List()")
+	case 1:
+		return []voc.IsCloudResource{wrongFormattedResource()}, nil
+	case 2:
+		return []voc.IsCloudResource{
+			&voc.ObjectStorage{
+				Storage: &voc.Storage{
+					CloudResource: &voc.CloudResource{
+						ID:   "some-id",
+						Name: "some-name",
+						Type: []string{"ObjectStorage", "Storage", "Resource"},
+					},
+				},
+				HttpEndpoint: &voc.HttpEndpoint{
+					TransportEncryption: &voc.TransportEncryption{
+						Enforced:   false,
+						Enabled:    true,
+						TlsVersion: "TLS1_2",
+					},
+				},
+			},
+		}, nil
+	default:
+		return nil, nil
+	}
+}
+
+func wrongFormattedResource() voc.IsCloudResource {
+	res1 := mockIsCloudResource{Another: nil}
+	res2 := mockIsCloudResource{Another: &res1}
+	res1.Another = &res2
+	return res1
+}
+
+// mockIsCloudResource implements mockIsCloudResource interface.
+// It is used for json.marshal to fail since it contains circular dependency
+type mockIsCloudResource struct {
+	Another *mockIsCloudResource `json:"Another"`
+}
+
+func (mockIsCloudResource) GetID() voc.ResourceID {
+	return "MockResourceId"
+}
+
+func (mockIsCloudResource) GetName() string {
+	return ""
+}
+
+func (mockIsCloudResource) GetType() []string {
+	return nil
+}
+
+func (mockIsCloudResource) HasType(_ string) bool {
+	return false
+}
+
+func (mockIsCloudResource) GetCreationTime() *time.Time {
+	return nil
+}
diff --git a/cli/commands/orchestrator/orchestrator_test.go b/cli/commands/service/orchestrator/orchestrator_test.go
similarity index 67%
rename from cli/commands/orchestrator/orchestrator_test.go
rename to cli/commands/service/orchestrator/orchestrator_test.go
index 11012dba..46148879 100644
--- a/cli/commands/orchestrator/orchestrator_test.go
+++ b/cli/commands/service/orchestrator/orchestrator_test.go
@@ -1,3 +1,28 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// 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.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
 package orchestrator
 
 import (
@@ -34,7 +59,7 @@ func TestMain(m *testing.M) {
 		service *service_orchestrator.Service
 	)
 
-	err = os.Chdir("../../../")
+	err = os.Chdir("../../../../")
 	if err != nil {
 		panic(err)
 	}
@@ -51,6 +76,7 @@ func TestMain(m *testing.M) {
 		panic(err)
 	}
 	orchestrator.RegisterOrchestratorServer(server, service)
+	// Store an assessment result that output of CMD 'list_results' is not empty
 	_, err = service.StoreAssessmentResult(context.TODO(), &orchestrator.StoreAssessmentResultRequest{
 		Result: &assessment.AssessmentResult{
 			Id:         "assessmentResultID",
@@ -100,7 +126,7 @@ func TestAddCommands(t *testing.T) {
 
 	// Check if NewListResultsCommand was added
 	for _, v := range cmd.Commands() {
-		if v.Use == "list" {
+		if v.Use == "list-assessment-results" {
 			return
 		}
 	}
@@ -112,7 +138,7 @@ func TestNewListResultsCommand(t *testing.T) {
 
 	cli.Output = &b
 
-	cmd := NewListResultsCommand()
+	cmd := NewListAssessmentResultsCommand()
 	err := cmd.RunE(nil, []string{})
 	assert.Nil(t, err)