Repository: clouditor/clouditor
Pull request #895 (2022-12-10 14:38:32)

Add missing fields to metric configuration
The following fields are missing in the metric configuration and thus the validateion and storage of assessment result fails in the orchestrator: 
- updated_at
- metric_id
- cloud_service_id

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

```diff
@@            Coverage Diff             @@
##             main     #895      +/-   ##
==========================================
+ Coverage   81.89%   81.99%   +0.09%     
==========================================
  Files          85       85              
  Lines        7009     7024      +15     
==========================================
+ Hits         5740     5759      +19     
+ Misses        976      972       -4     
  Partials      293      293              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `81.99% <96.55%> (+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/895?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) | Coverage Δ | |
|---|---|---|
| [service/assessment/assessment.go](https://codecov.io/gh/clouditor/clouditor/pull/895/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9hc3Nlc3NtZW50L2Fzc2Vzc21lbnQuZ28=) | `86.97% <95.83%> (+0.34%)` | :arrow_up: |
| [cmd/engine/engine.go](https://codecov.io/gh/clouditor/clouditor/pull/895/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y21kL2VuZ2luZS9lbmdpbmUuZ28=) | `83.46% <100.00%> (+0.06%)` | :arrow_up: |
| [service/orchestrator/metrics.go](https://codecov.io/gh/clouditor/clouditor/pull/895/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9vcmNoZXN0cmF0b3IvbWV0cmljcy5nbw==) | `79.01% <100.00%> (+0.19%)` | :arrow_up: |
| [api/streams.go](https://codecov.io/gh/clouditor/clouditor/pull/895/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-YXBpL3N0cmVhbXMuZ28=) | `92.19% <0.00%> (+2.83%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)


Patch:
diff --git a/api/assessment/metric.go b/api/assessment/metric.go
index 238caca8..64fa3d97 100644
--- a/api/assessment/metric.go
+++ b/api/assessment/metric.go
@@ -34,6 +34,7 @@ import (
 
 	"clouditor.io/clouditor/persistence"
 	"github.com/google/uuid"
+	"google.golang.org/protobuf/encoding/protojson"
 )
 
 var (
@@ -142,6 +143,14 @@ func (x *MetricConfiguration) Hash() string {
 	return base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%v-%v", x.Operator, x.TargetValue)))
 }
 
+func (x *MetricConfiguration) MarshalJSON() (b []byte, err error) {
+	return protojson.Marshal(x)
+}
+
+func (x *MetricConfiguration) UnmarshalJSON(b []byte) (err error) {
+	return protojson.Unmarshal(b, x)
+}
+
 // CheckCloudServiceID checks if serviceID is available and in the valid UUID format.
 func CheckCloudServiceID(serviceID string) error {
 	// Check if ServiceId is missing
diff --git a/cmd/engine/engine.go b/cmd/engine/engine.go
index e8c7fb33..5c6ac539 100644
--- a/cmd/engine/engine.go
+++ b/cmd/engine/engine.go
@@ -136,7 +136,6 @@ var engineCmd = &cobra.Command{
 func init() {
 	log = logrus.WithField("component", "grpc")
 	log.Logger.Formatter = formatter.CapitalizeFormatter{Formatter: &logrus.TextFormatter{ForceColors: true}}
-
 	cobra.OnInitialize(initConfig)
 
 	engineCmd.Flags().String(APIDefaultUserFlag, DefaultAPIDefaultUser, "Specifies the default API username")
diff --git a/policies/operators.rego b/policies/operators.rego
index a188e29d..1ee9f6ca 100644
--- a/policies/operators.rego
+++ b/policies/operators.rego
@@ -1,36 +1,47 @@
 package clouditor
 
-
 # operator and target_value are declared here to add them to the output of each single policy (so assessment can use it)
 operator = data.operator
+
 target_value = data.target_value
 
+# we also expose the whole metric configuration as well. In the future we then could get rid of operator and target
+# value as individual variables
+config = data.config
+
+compare(operator, target_value, actual_value) {
+	operator == "=="
+	target_value == actual_value
+}
+
 compare(operator, target_value, actual_value) {
-    operator == "=="
-    target_value == actual_value
-}{
-    operator == ">="
-    actual_value >= target_value
-}{
-     operator == "<="
-     actual_value <= target_value
-}{
-     operator == "<"
-     actual_value < target_value
-}{
-     operator == ">"
-     actual_value > target_value
+	operator == ">="
+	actual_value >= target_value
 }
 
+compare(operator, target_value, actual_value) {
+	operator == "<="
+	actual_value <= target_value
+}
+
+compare(operator, target_value, actual_value) {
+	operator == "<"
+	actual_value < target_value
+}
+
+compare(operator, target_value, actual_value) {
+	operator == ">"
+	actual_value > target_value
+}
 
 # Params: target_values (multiple target values), actual_value (single actual value)
 isIn(target_values, actual_value) {
-    # Assess actual value with each compliant value in target values
+	# Assess actual value with each compliant value in target values
 	actual_value == target_values[_]
 }
 
 # Params: target_values (multiple target values), actual_values (multiple actual values)
 isIn(target_values, actual_values) {
-    # Current implementation: It is enough that one output is one of target_values
-    actual_values[_] == target_values[_]
-}
\ No newline at end of file
+	# Current implementation: It is enough that one output is one of target_values
+	actual_values[_] == target_values[_]
+}
diff --git a/policies/policies.go b/policies/policies.go
index ac7e590d..8b1efa51 100644
--- a/policies/policies.go
+++ b/policies/policies.go
@@ -55,9 +55,10 @@ type PolicyEval interface {
 type Result struct {
 	Applicable  bool
 	Compliant   bool
-	TargetValue interface{} `mapstructure:"target_value"`
+	TargetValue interface{}
 	Operator    string
-	MetricId    string
+	MetricID    string
+	Config      *assessment.MetricConfiguration
 }
 
 // MetricsSource is used to retrieve a list of metrics and to retrieve a metric
diff --git a/policies/rego.go b/policies/rego.go
index c494968c..9d8632bc 100644
--- a/policies/rego.go
+++ b/policies/rego.go
@@ -27,6 +27,7 @@ package policies
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"strings"
 	"sync"
@@ -150,7 +151,6 @@ func (re *regoEval) Eval(evidence *evidence.Evidence, src MetricsSource) (data [
 
 			if runMap != nil {
 				cached = append(cached, metric.Id)
-				runMap.MetricId = metric.Id
 
 				data = append(data, runMap)
 			}
@@ -168,7 +168,6 @@ func (re *regoEval) Eval(evidence *evidence.Evidence, src MetricsSource) (data [
 				return nil, err
 			}
 
-			runMap.MetricId = metric
 			data = append(data, runMap)
 		}
 	}
@@ -209,6 +208,8 @@ func (re *regoEval) evalMap(baseDir string, serviceID, metricID string, m map[st
 	// if the metric configuration (i.e. its hash) for a particular service has changed.
 	key = fmt.Sprintf("%s-%s-%s", metricID, serviceID, config.Hash())
 
+	// Try to fetch a cached prepared query for the specified key. If the key is not found, we create a new query with
+	// the function specified as the second parameter
 	query, err = re.qc.Get(key, func(key string) (*rego.PreparedEvalQuery, error) {
 		var (
 			tx   storage.Transaction
@@ -219,14 +220,18 @@ func (re *regoEval) evalMap(baseDir string, serviceID, metricID string, m map[st
 		bundle := fmt.Sprintf("%s/policies/bundles/%s/", baseDir, metricID)
 		operators := fmt.Sprintf("%s/policies/operators.rego", baseDir)
 
-		c := map[string]interface{}{
+		// The contents of the data map is available as the data variable within the Rego evaluation
+		data := map[string]interface{}{
 			"target_value": config.TargetValue.AsInterface(),
 			"operator":     config.Operator,
+			"config":       config,
 		}
 
-		store := inmem.NewFromObject(c)
+		// Create a new in-memory Rego store based on our data map
+		store := inmem.NewFromObject(data)
 		ctx := context.Background()
 
+		// Create a new transaction in the store
 		tx, err = store.NewTransaction(ctx, storage.WriteParams)
 		if err != nil {
 			return nil, fmt.Errorf("could not create transaction: %w", err)
@@ -237,22 +242,26 @@ func (re *regoEval) evalMap(baseDir string, serviceID, metricID string, m map[st
 		// Convert camelCase metric in under_score_style for package name
 		pkg = util.CamelCaseToSnakeCase(metricID)
 
+		// Fetch the metric implementation, i.e., the Rego code from the metric source
 		impl, err = src.MetricImplementation(assessment.MetricImplementation_LANGUAGE_REGO, metricID)
 		if err != nil {
 			return nil, fmt.Errorf("could not fetch policy for metric %s: %w", metricID, err)
 		}
 
+		// Insert/Update the policy. The bundle path depends on the metric ID
 		err = store.UpsertPolicy(context.Background(), tx, bundle+"metric.rego", []byte(impl.Code))
 		if err != nil {
 			return nil, fmt.Errorf("could not upsert policy: %w", err)
 		}
 
+		// Create a new Rego prepared query evaluation, which can later be used to query the metric on any object (input)
 		query, err := rego.New(
 			rego.Query(fmt.Sprintf(`
 			applicable = data.%s.%s.applicable;
 			compliant = data.%s.%s.compliant;
 			operator = data.clouditor.operator;
-			target_value = data.clouditor.target_value`, prefix, pkg, prefix, pkg)),
+			target_value = data.clouditor.target_value;
+			config = data.clouditor.config`, prefix, pkg, prefix, pkg)),
 			rego.Package(prefix),
 			rego.Store(store),
 			rego.Transaction(tx),
@@ -266,6 +275,7 @@ func (re *regoEval) evalMap(baseDir string, serviceID, metricID string, m map[st
 			return nil, fmt.Errorf("could not prepare rego evaluation for metric %s: %w", metricID, err)
 		}
 
+		// Commit the transaction into the store
 		err = store.Commit(ctx, tx)
 		if err != nil {
 			return nil, fmt.Errorf("could not commit transaction: %w", err)
@@ -291,6 +301,18 @@ func (re *regoEval) evalMap(baseDir string, serviceID, metricID string, m map[st
 		Compliant:   results[0].Bindings["compliant"].(bool),
 		Operator:    results[0].Bindings["operator"].(string),
 		TargetValue: results[0].Bindings["target_value"],
+		MetricID:    metricID,
+	}
+
+	// A little trick to convert the map-based metric configuration back to a real object
+	var b []byte
+	if b, err = json.Marshal(results[0].Bindings["config"]); err != nil {
+		return nil, fmt.Errorf("JSON marshal failed: %w", err)
+	}
+
+	result.Config = new(assessment.MetricConfiguration)
+	if err = json.Unmarshal(b, result.Config); err != nil {
+		return nil, fmt.Errorf("JSON unmarshal failed: %w", err)
 	}
 
 	if !result.Applicable {
diff --git a/service/assessment/assessment.go b/service/assessment/assessment.go
index f211448e..39cb8cca 100644
--- a/service/assessment/assessment.go
+++ b/service/assessment/assessment.go
@@ -27,7 +27,6 @@ package assessment
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -47,7 +46,6 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
-	"google.golang.org/protobuf/types/known/structpb"
 	"google.golang.org/protobuf/types/known/timestamppb"
 )
 
@@ -371,33 +369,23 @@ func (svc *Service) handleEvidence(ev *evidence.Evidence, resourceId string) (er
 	}
 
 	for i, data := range evaluations {
-		metricID := data.MetricId
+		metricID := data.MetricID
 
 		log.Debugf("Evaluated evidence %v with metric '%v' as %v", ev.Id, metricID, data.Compliant)
 
-		targetValue := data.TargetValue
-
-		convertedTargetValue, err := convertTargetValue(targetValue)
-		if err != nil {
-			return fmt.Errorf("could not convert target value: %w", err)
-		}
-
 		types, err = ev.ResourceTypes()
 		if err != nil {
 			return fmt.Errorf("could not extract resource types from evidence: %w", err)
 		}
 
 		result := &assessment.AssessmentResult{
-			Id:             uuid.NewString(),
-			Timestamp:      timestamppb.Now(),
-			CloudServiceId: ev.CloudServiceId,
-			MetricId:       metricID,
-			MetricConfiguration: &assessment.MetricConfiguration{
-				TargetValue: convertedTargetValue,
-				Operator:    data.Operator,
-			},
+			Id:                    uuid.NewString(),
+			Timestamp:             timestamppb.Now(),
+			CloudServiceId:        ev.GetCloudServiceId(),
+			MetricId:              metricID,
+			MetricConfiguration:   data.Config,
 			Compliant:             data.Compliant,
-			EvidenceId:            ev.Id,
+			EvidenceId:            ev.GetId(),
 			ResourceId:            resourceId,
 			ResourceTypes:         types,
 			NonComplianceComments: "No comments so far",
@@ -418,21 +406,6 @@ func (svc *Service) handleEvidence(ev *evidence.Evidence, resourceId string) (er
 	return nil
 }
 
-// convertTargetValue converts v in a format accepted by protobuf (structpb.Value)
-func convertTargetValue(v interface{}) (s *structpb.Value, err error) {
-	var b []byte
-
-	// json.Marshal and json.Unmarshal is used instead of structpb.NewValue() which cannot handle json numbers
-	if b, err = json.Marshal(v); err != nil {
-		return nil, fmt.Errorf("JSON marshal failed: %w", err)
-	}
-	if err = json.Unmarshal(b, &s); err != nil {
-		return nil, fmt.Errorf("JSON unmarshal failed: %w", err)
-	}
-	return
-
-}
-
 // informHooks informs the registered hook functions
 func (svc *Service) informHooks(result *assessment.AssessmentResult, err error) {
 	svc.hookMutex.RLock()
diff --git a/service/orchestrator/metrics.go b/service/orchestrator/metrics.go
index 0242cd10..aa6649a2 100644
--- a/service/orchestrator/metrics.go
+++ b/service/orchestrator/metrics.go
@@ -121,6 +121,7 @@ func prepareMetric(m *assessment.Metric) (err error) {
 	}
 
 	config.IsDefault = true
+	config.MetricId = m.GetId()
 
 	defaultMetricConfigurations[m.Id] = config
 
@@ -339,10 +340,20 @@ func (svc *Service) GetMetricConfiguration(ctx context.Context, req *orchestrato
 	if errors.Is(err, persistence.ErrRecordNotFound) {
 		// Otherwise, fall back to our default configuration
 		if config, ok := defaultMetricConfigurations[req.MetricId]; ok {
-			return config, nil
+			// Copy the metric configuration and set the cloud service id
+			newConfig := &assessment.MetricConfiguration{
+				Operator:       config.GetOperator(),
+				TargetValue:    config.GetTargetValue(),
+				IsDefault:      config.GetIsDefault(),
+				UpdatedAt:      config.GetUpdatedAt(),
+				MetricId:       config.GetMetricId(),
+				CloudServiceId: req.GetCloudServiceId(),
+			}
+
+			return newConfig, nil
 		}
 
-		return nil, status.Errorf(codes.NotFound, "metric configuration not found")
+		return nil, status.Errorf(codes.NotFound, "metric configuration not found for metric with id '%s'", req.GetMetricId())
 	} else if err != nil {
 		return nil, status.Errorf(codes.Internal, "database error: %s", err)
 	}
@@ -369,9 +380,10 @@ func (svc *Service) UpdateMetricConfiguration(ctx context.Context, req *orchestr
 		return nil, service.ErrPermissionDenied
 	}
 
-	// Make sure that the configuration also has metric/service ID set
+	// Make sure that the configuration also has metric/service ID and updated at set
 	req.Configuration.CloudServiceId = req.CloudServiceId
 	req.Configuration.MetricId = req.MetricId
+	req.Configuration.UpdatedAt = timestamppb.Now()
 
 	err = svc.storage.Save(&req.Configuration)
 	if err != nil && errors.Is(err, persistence.ErrConstraintFailed) {


Test patch:
diff --git a/policies/policies_test.go b/policies/policies_test.go
index 18b28ced..d81185b6 100644
--- a/policies/policies_test.go
+++ b/policies/policies_test.go
@@ -30,9 +30,12 @@ import (
 	"fmt"
 	"os"
 	"testing"
+	"time"
 
 	"clouditor.io/clouditor/internal/testutil/clitest"
 	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/types/known/structpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
 
 	"clouditor.io/clouditor/api/assessment"
 	"github.com/stretchr/testify/assert"
@@ -89,6 +92,10 @@ func (m *mockMetricsSource) MetricConfiguration(serviceID, metricID string) (*as
 	err = protojson.Unmarshal(b, &config)
 	assert.NoError(m.t, err)
 
+	config.IsDefault = true
+	config.MetricId = metricID
+	config.CloudServiceId = serviceID
+
 	return &config, nil
 }
 
@@ -107,3 +114,18 @@ func (m *mockMetricsSource) MetricImplementation(lang assessment.MetricImplement
 
 	return impl, nil
 }
+
+type updatedMockMetricsSource struct {
+	mockMetricsSource
+}
+
+func (*updatedMockMetricsSource) MetricConfiguration(serviceID, metricID string) (*assessment.MetricConfiguration, error) {
+	return &assessment.MetricConfiguration{
+		Operator:       "==",
+		TargetValue:    structpb.NewBoolValue(false),
+		IsDefault:      false,
+		UpdatedAt:      timestamppb.New(time.Date(2022, 12, 1, 0, 0, 0, 0, time.Local)),
+		MetricId:       metricID,
+		CloudServiceId: serviceID,
+	}, nil
+}
diff --git a/policies/rego_test.go b/policies/rego_test.go
index f47afff8..44701600 100644
--- a/policies/rego_test.go
+++ b/policies/rego_test.go
@@ -29,11 +29,15 @@ import (
 	"testing"
 	"time"
 
+	"clouditor.io/clouditor/api/assessment"
 	"clouditor.io/clouditor/api/evidence"
 	"clouditor.io/clouditor/internal/testutil"
 	"clouditor.io/clouditor/persistence"
 	"clouditor.io/clouditor/voc"
 	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/types/known/structpb"
+	"google.golang.org/protobuf/types/known/timestamppb"
 )
 
 func Test_regoEval_Eval(t *testing.T) {
@@ -304,7 +308,7 @@ func Test_regoEval_Eval(t *testing.T) {
 
 			for _, result := range results {
 				if result.Applicable {
-					compliants[result.MetricId] = result.Compliant
+					compliants[result.MetricID] = result.Compliant
 				}
 			}
 
@@ -312,3 +316,118 @@ func Test_regoEval_Eval(t *testing.T) {
 		})
 	}
 }
+
+func Test_regoEval_evalMap(t *testing.T) {
+	type fields struct {
+		qc   *queryCache
+		mrtc *metricsCache
+		pkg  string
+	}
+	type args struct {
+		baseDir   string
+		serviceID string
+		metricID  string
+		m         map[string]interface{}
+		src       MetricsSource
+	}
+	tests := []struct {
+		name       string
+		fields     fields
+		args       args
+		wantResult *Result
+		wantErr    bool
+	}{
+		{
+			name: "default metric configuration",
+			fields: fields{
+				qc:   newQueryCache(),
+				mrtc: &metricsCache{m: make(map[string][]string)},
+				pkg:  DefaultRegoPackage,
+			},
+			args: args{
+				serviceID: testutil.TestCloudService1,
+				metricID:  "AutomaticUpdatesEnabled",
+				baseDir:   ".",
+				m: map[string]interface{}{
+					"automaticUpdates": map[string]interface{}{
+						"enabled": true,
+					},
+				},
+				src: &mockMetricsSource{t: t},
+			},
+			wantResult: &Result{
+				Applicable:  true,
+				Compliant:   true,
+				TargetValue: true,
+				Operator:    "==",
+				MetricID:    "AutomaticUpdatesEnabled",
+				Config: &assessment.MetricConfiguration{
+					Operator:       "==",
+					TargetValue:    structpb.NewBoolValue(true),
+					IsDefault:      true,
+					UpdatedAt:      nil,
+					MetricId:       "AutomaticUpdatesEnabled",
+					CloudServiceId: testutil.TestCloudService1,
+				},
+			},
+		},
+		{
+			name: "updated metric configuration",
+			fields: fields{
+				qc:   newQueryCache(),
+				mrtc: &metricsCache{m: make(map[string][]string)},
+				pkg:  DefaultRegoPackage,
+			},
+			args: args{
+				serviceID: testutil.TestCloudService1,
+				metricID:  "AutomaticUpdatesEnabled",
+				baseDir:   ".",
+				m: map[string]interface{}{
+					"automaticUpdates": map[string]interface{}{
+						"enabled": true,
+					},
+				},
+				src: &updatedMockMetricsSource{mockMetricsSource{t: t}},
+			},
+			wantResult: &Result{
+				Applicable:  true,
+				Compliant:   false,
+				TargetValue: false,
+				Operator:    "==",
+				MetricID:    "AutomaticUpdatesEnabled",
+				Config: &assessment.MetricConfiguration{
+					Operator:       "==",
+					TargetValue:    structpb.NewBoolValue(false),
+					IsDefault:      false,
+					UpdatedAt:      timestamppb.New(time.Date(2022, 12, 1, 0, 0, 0, 0, time.Local)),
+					MetricId:       "AutomaticUpdatesEnabled",
+					CloudServiceId: testutil.TestCloudService1,
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			re := &regoEval{
+				qc:   tt.fields.qc,
+				mrtc: tt.fields.mrtc,
+				pkg:  tt.fields.pkg,
+			}
+			gotResult, err := re.evalMap(tt.args.baseDir, tt.args.serviceID, tt.args.metricID, tt.args.m, tt.args.src)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("regoEval.evalMap() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+
+			// Assert the configuration using protoequal
+			if !proto.Equal(gotResult.Config, tt.wantResult.Config) {
+				t.Errorf("regoEval.evalMap() = %v, want %v", gotResult.Config, tt.wantResult.Config)
+			}
+
+			// Assert the remaining message regularly
+			tt.wantResult.Config = nil
+			gotResult.Config = nil
+			assert.Equal(t, tt.wantResult, gotResult)
+		})
+	}
+}
diff --git a/service/assessment/assessment_test.go b/service/assessment/assessment_test.go
index c9f6ec93..6c63449d 100644
--- a/service/assessment/assessment_test.go
+++ b/service/assessment/assessment_test.go
@@ -27,7 +27,6 @@ package assessment
 
 import (
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -1014,88 +1013,6 @@ func createMockAssessmentServerStreamWithRecvErr(r *assessment.AssessEvidenceReq
 	return m
 }
 
-func TestConvertTargetValue(t *testing.T) {
-	type args struct {
-		value interface{}
-	}
-	tests := []struct {
-		name                     string
-		args                     args
-		wantConvertedTargetValue *structpb.Value
-		wantErr                  assert.ErrorAssertionFunc
-	}{
-		{
-			name:                     "string",
-			args:                     args{value: "TLS1.3"},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: "TLS1.3"}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name:                     "bool",
-			args:                     args{value: false},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: false}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name:                     "jsonNumber",
-			args:                     args{value: json.Number("4")},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 4.}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name:                     "int",
-			args:                     args{value: 4},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 4.}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name:                     "float64",
-			args:                     args{value: 4.},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 4.}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name:                     "float32",
-			args:                     args{value: float32(4.)},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: 4.}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-		{
-			name: "list of strings",
-			args: args{value: []string{"TLS1.2", "TLS1.3"}},
-			wantConvertedTargetValue: &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{
-				{Kind: &structpb.Value_StringValue{StringValue: "TLS1.2"}},
-				{Kind: &structpb.Value_StringValue{StringValue: "TLS1.3"}},
-			}}}},
-			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
-				return err == nil
-			},
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			gotConvertedTargetValue, err := convertTargetValue(tt.args.value)
-			if !tt.wantErr(t, err, fmt.Sprintf("convertTargetValue(%v)", tt.args.value)) {
-				return
-			}
-			// Checking against 'String()' allows to compare the actual values instead of the respective pointers
-			assert.Equalf(t, tt.wantConvertedTargetValue.String(), gotConvertedTargetValue.String(), "convertTargetValue(%v)", tt.args.value)
-		})
-	}
-}
-
 func TestService_HandleEvidence(t *testing.T) {
 	type fields struct {
 		hasEvidenceStoreStream bool
@@ -1221,6 +1138,11 @@ func TestService_HandleEvidence(t *testing.T) {
 			// Two tests: 1st) wantErr function. 2nd) if wantErr false then check if a result is added to map
 			if !tt.wantErr(t, s.handleEvidence(tt.args.evidence, tt.args.resourceId), fmt.Sprintf("handleEvidence(%v, %v)", tt.args.evidence, tt.args.resourceId)) {
 				assert.NotEmpty(t, s.results)
+				// Check the result by validation
+				for _, result := range s.results {
+					err := result.Validate()
+					assert.NoError(t, err)
+				}
 			}
 
 		})