Repository: clouditor/clouditor (go)

Issue:
CLI: Listing assessment results does not contain `compliant` field
**Describe the bug**
When using the CLI command to list assessment results, all fields but `compliant` will be printed.

**To Reproduce**
Steps to reproduce the behavior:
1. Prep:
   -  `go generate ./...`
   - `go install cmd/cli/cl.go`
2. Run engine: `clouditor.io/clouditor/cmd/engine --db-in-memory`
3. Start discovery (and thus, assessment as well): `cl discovery start`
4. List assessment results: `cl assessment list`

**Output snippet**
 ```
{
      "id":  "b3e65116-064e-433b-b1ad-e20dbde36f92",
      "timestamp":  "2021-12-15T10:07:35.002764902Z",
      "metricId":  "BootLoggingOutput",
      "metricData":  {},
      "evidenceId":  "XXX",
      "resourceId":  "XXX",
      "nonComplianceComments":  "No comments so far"
 }
```


**Expected behavior**
`compliant` field should be also included:
<pre>
{
      "id":  "b3e65116-064e-433b-b1ad-e20dbde36f92",
      "timestamp":  "2021-12-15T10:07:35.002764902Z",
      "metricId":  "BootLoggingOutput",
      "metricData":  {},
      <b>"compliant":  true/false,</b>
      "evidenceId":  "XXX",
      "resourceId":  "XXX",
      "nonComplianceComments":  "No comments so far"
 }
</pre>


Pull request:
CLI: List unpopulated fields as well
Fixes #414

Patch:
diff --git a/cli/commands/login/login.go b/cli/commands/login/login.go
index da2e369a..efd766d7 100644
--- a/cli/commands/login/login.go
+++ b/cli/commands/login/login.go
@@ -60,7 +60,7 @@ func NewLoginCommand() *cobra.Command {
 			}
 
 			if viper.GetString("username") == "" || viper.GetString("password") == "" {
-				if req, err = cli.PromtForLogin(); err != nil {
+				if req, err = cli.PromptForLogin(); err != nil {
 					return fmt.Errorf("could not prompt for password: %w", err)
 				}
 			} else {
diff --git a/cli/session.go b/cli/session.go
index d03b8f86..fe1ce5ce 100644
--- a/cli/session.go
+++ b/cli/session.go
@@ -102,6 +102,7 @@ func ContinueSession() (session *Session, err error) {
 		return
 	}
 
+	// TODO(all): Should we catch errors of deferred close operations?
 	defer file.Close()
 
 	session = new(Session)
@@ -133,6 +134,7 @@ func (s *Session) Save() (err error) {
 		return fmt.Errorf("could not save session.json: %w", err)
 	}
 
+	// TODO(all): Should we catch errors of deferred close operations?
 	defer file.Close()
 
 	if err = json.NewEncoder(file).Encode(s); err != nil {
@@ -158,18 +160,19 @@ func (s *Session) HandleResponse(msg proto.Message, err error) error {
 	}
 
 	opt := protojson.MarshalOptions{
-		Multiline: true,
-		Indent:    "  ",
+		Multiline:       true,
+		Indent:          "  ",
+		EmitUnpopulated: true,
 	}
 
 	b, _ := opt.Marshal(msg)
 
-	fmt.Fprintf(Output, "%s\n", string(b))
+	_, err = fmt.Fprintf(Output, "%s\n", string(b))
 
 	return err
 }
 
-func PromtForLogin() (loginRequest *auth.LoginRequest, err error) {
+func PromptForLogin() (loginRequest *auth.LoginRequest, err error) {
 	reader := bufio.NewReader(os.Stdin)
 
 	fmt.Print("Enter username: ")


Test patch:
diff --git a/cli/session_test.go b/cli/session_test.go
index aada0202..01f8076a 100644
--- a/cli/session_test.go
+++ b/cli/session_test.go
@@ -28,6 +28,7 @@ package cli_test
 import (
 	"context"
 	"fmt"
+	"google.golang.org/protobuf/proto"
 	"io/ioutil"
 	"net"
 	"os"
@@ -124,3 +125,70 @@ func TestSession(t *testing.T) {
 	assert.Equal(t, codes.Unauthenticated, s.Code())
 	assert.Nil(t, response)
 }
+
+func TestSession_HandleResponse(t *testing.T) {
+	type fields struct {
+		URL        string
+		Token      string
+		Folder     string
+		ClientConn *grpc.ClientConn
+	}
+	type args struct {
+		msg proto.Message
+		err error
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		wantErr assert.ErrorAssertionFunc
+	}{
+		{
+			name: "grpc Error",
+			args: args{
+				msg: nil,
+				err: status.Errorf(codes.Internal, "Internal error occurred!"),
+			},
+			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
+				if err != nil {
+					return true
+				} else {
+					t.Errorf("Expected error.")
+					return false
+				}
+			},
+		},
+		{
+			name: "non-grpc error",
+			args: args{
+				msg: nil,
+				err: fmt.Errorf("random error"),
+			},
+			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
+				if err != nil {
+					return true
+				} else {
+					t.Errorf("Expected error.")
+					return false
+				}
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := &cli.Session{
+				URL:        tt.fields.URL,
+				Token:      tt.fields.Token,
+				Folder:     tt.fields.Folder,
+				ClientConn: tt.fields.ClientConn,
+			}
+			tt.wantErr(t, s.HandleResponse(tt.args.msg, tt.args.err), fmt.Sprintf("HandleResponse(%v, %v)", tt.args.msg, tt.args.err))
+		})
+	}
+}
+
+// Test will fail due to no user input
+func TestPromptForLogin(t *testing.T) {
+	_, err := cli.PromptForLogin()
+	assert.NotNil(t, err)
+}