Repository: clouditor/clouditor
Pull request #433 (2021-12-21 08:40:54)

Api validation tests
Add tests for evidence and assessment_result validation methods. 

codecov[bot]: # [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/433?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) Report
> Merging [#433](https://codecov.io/gh/clouditor/clouditor/pull/433?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (b482eb7) into [main](https://codecov.io/gh/clouditor/clouditor/commit/63a2871e53959831e231407888930fcc059af5c1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (63a2871) will **increase** coverage by `0.48%`.
> The diff coverage is `89.74%`.

> :exclamation: Current head b482eb7 differs from pull request most recent head 20f73ee. Consider uploading reports for the commit 20f73ee to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/clouditor/clouditor/pull/433/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/433?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)

```diff
@@            Coverage Diff             @@
##             main     #433      +/-   ##
==========================================
+ Coverage   68.73%   69.21%   +0.48%     
==========================================
  Files          39       39              
  Lines        3518     3531      +13     
==========================================
+ Hits         2418     2444      +26     
+ Misses        977      967      -10     
+ Partials      123      120       -3     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `69.21% <89.74%> (+0.48%)` | :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/433?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/433/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9hc3Nlc3NtZW50L2Fzc2Vzc21lbnQuZ28=) | `91.66% <81.81%> (+5.42%)` | :arrow_up: |
| [service/evidence/evidence\_store.go](https://codecov.io/gh/clouditor/clouditor/pull/433/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9ldmlkZW5jZS9ldmlkZW5jZV9zdG9yZS5nbw==) | `94.20% <90.69%> (+7.53%)` | :arrow_up: |
| [service/orchestrator/orchestrator.go](https://codecov.io/gh/clouditor/clouditor/pull/433/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-c2VydmljZS9vcmNoZXN0cmF0b3Ivb3JjaGVzdHJhdG9yLmdv) | `81.15% <91.66%> (+2.96%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/clouditor/clouditor/pull/433?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/433?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Last update [63a2871...20f73ee](https://codecov.io/gh/clouditor/clouditor/pull/433?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).


Patch:
diff --git a/api/assessment/assessment.go b/api/assessment/assessment.go
index 179f278b..2eceeda3 100644
--- a/api/assessment/assessment.go
+++ b/api/assessment/assessment.go
@@ -28,13 +28,13 @@ package assessment
 import "errors"
 
 var (
-	ErrTimestampMissing             = errors.New("timestamp in assessment result is missing")
-	ErrMetricIdMissing              = errors.New("metric id in assessment result is missing")
-	ErrMetricDataMissing            = errors.New("metric data in assessment result is missing")
-	ErrEvidenceIdMissing            = errors.New("evidence id in assessment result is missing")
-	ErrNonComplianceCommentsMissing = errors.New("non-compliance comments in assessment result is missing")
-	ErrMetricDataOperatorMissing    = errors.New("operator in metric data is missing")
-	ErrMetricDataTargetValueMissing = errors.New("target value in metric data is missing")
+	ErrTimestampMissing                      = errors.New("timestamp in assessment result is missing")
+	ErrMetricIdMissing                       = errors.New("metric id in assessment result is missing")
+	ErrMetricConfigurationMissing            = errors.New("metric configuration in assessment result is missing")
+	ErrEvidenceIdMissing                     = errors.New("evidence id in assessment result is missing")
+	ErrNonComplianceCommentsMissing          = errors.New("non-compliance comments in assessment result is missing")
+	ErrMetricConfigurationOperatorMissing    = errors.New("operator in metric data is missing")
+	ErrMetricConfigurationTargetValueMissing = errors.New("target value in metric data is missing")
 )
 
 // Validate validates the assessment result according to several required fields
@@ -48,20 +48,22 @@ func (result *AssessmentResult) Validate() (resourceId string, err error) {
 	}
 
 	if result.MetricConfiguration == nil {
-		return "", ErrMetricDataMissing
+		return "", ErrMetricConfigurationMissing
 	}
 
 	if result.MetricConfiguration.Operator == "" {
-		return "", ErrMetricDataOperatorMissing
+		return "", ErrMetricConfigurationOperatorMissing
 	}
 
 	if result.MetricConfiguration.TargetValue == nil {
-		return "", ErrMetricDataTargetValueMissing
+		return "", ErrMetricConfigurationTargetValueMissing
 	}
 
 	if result.EvidenceId == "" {
 		return "", ErrEvidenceIdMissing
 	}
 
+	// TODO(garuppel): Missing resource ID?
+
 	return
 }
diff --git a/api/evidence/evidence.go b/api/evidence/evidence.go
index 0d4d29c7..74ea81ac 100644
--- a/api/evidence/evidence.go
+++ b/api/evidence/evidence.go
@@ -28,13 +28,14 @@ package evidence
 import "errors"
 
 var (
-	ErrNotValidResource    = errors.New("resource in evidence is not a map")
-	ErrResourceNotStruct   = errors.New("resource in evidence is not struct value")
-	ErrResourceNotMap      = errors.New("resource in evidence is not a map")
-	ErrResourceIdMissing   = errors.New("resource in evidence is missing the id field")
-	ErrResourceIdNotString = errors.New("resource id in evidence is not a string")
-	ErrToolIdMissing       = errors.New("tool id in evidence is missing")
-	ErrTimestampMissing    = errors.New("timestamp in evidence is missing")
+	ErrNotValidResource       = errors.New("resource in evidence is missing")
+	ErrResourceNotStruct      = errors.New("resource in evidence is not struct value")
+	ErrResourceNotMap         = errors.New("resource in evidence is not a map")
+	ErrResourceIdMissing      = errors.New("resource in evidence is missing the id field")
+	ErrResourceIdNotString    = errors.New("resource id in evidence is not a string")
+	ErrToolIdMissing          = errors.New("tool id in evidence is missing")
+	ErrTimestampMissing       = errors.New("timestamp in evidence is missing")
+	ErrResourceIdFieldMissing = errors.New("field id is missing")
 )
 
 // Validate validates the evidence according to several required fields
@@ -55,6 +56,8 @@ func (evidence *Evidence) Validate() (resourceId string, err error) {
 
 	field, ok := m["id"]
 	if !ok {
+		return "", ErrResourceIdFieldMissing
+	} else if field == "" {
 		return "", ErrResourceIdMissing
 	}
 
diff --git a/service/assessment/assessment.go b/service/assessment/assessment.go
index 8721d6ab..22bd1747 100644
--- a/service/assessment/assessment.go
+++ b/service/assessment/assessment.go
@@ -52,9 +52,9 @@ Service is an implementation of the Clouditor Assessment service. It should not
 NewService constructor should be used. It implements the AssessmentServer interface
 */
 type Service struct {
-	// ResultHook is a hook function that can be used if one wants to be
+	// resultHooks is a hook function that can be used if one wants to be
 	// informed about each assessment result
-	ResultHook []func(result *assessment.AssessmentResult, err error)
+	resultHooks []func(result *assessment.AssessmentResult, err error)
 
 	results map[string]*assessment.AssessmentResult
 	assessment.UnimplementedAssessmentServer
@@ -75,7 +75,7 @@ func (s Service) AssessEvidence(_ context.Context, req *assessment.AssessEvidenc
 			Status: false,
 		}
 
-		return res, status.Errorf(codes.Internal, "Error while handling evidence: %v", err)
+		return res, status.Errorf(codes.Internal, "error while handling evidence: %v", err)
 	}
 
 	res = &assessment.AssessEvidenceResponse{
@@ -100,12 +100,13 @@ func (s Service) AssessEvidences(stream assessment.Assessment_AssessEvidencesSer
 				log.Infof("Stopped receiving streamed evidences")
 				return stream.SendAndClose(&emptypb.Empty{})
 			}
+
 			return err
 		}
 
 		err = s.handleEvidence(req.Evidence)
 		if err != nil {
-			return status.Errorf(codes.Internal, "Error while handling evidence: %v", err)
+			return status.Errorf(codes.Internal, "error while handling evidence: %v", err)
 		}
 	}
 }
@@ -117,12 +118,7 @@ func (s Service) handleEvidence(evidence *evidence.Evidence) error {
 		log.Errorf("Invalid evidence: %v", err)
 		newError := fmt.Errorf("invalid evidence: %w", err)
 
-		// Inform our hook, if we have any
-		if s.ResultHook != nil {
-			for _, hook := range s.ResultHook {
-				go hook(nil, newError)
-			}
-		}
+		s.informHook(nil, newError)
 
 		return newError
 	}
@@ -135,12 +131,8 @@ func (s Service) handleEvidence(evidence *evidence.Evidence) error {
 		log.Errorf("Could not evaluate evidence: %v", err)
 		newError := fmt.Errorf("could not evaluate evidence: %w", err)
 
-		// Inform our hook, if we have any
-		if s.ResultHook != nil {
-			for _, hook := range s.ResultHook {
-				go hook(nil, newError)
-			}
-		}
+		s.informHook(nil, newError)
+
 		return newError
 	}
 
@@ -171,17 +163,21 @@ func (s Service) handleEvidence(evidence *evidence.Evidence) error {
 		// Just a little hack to quickly enable multiple results per resource
 		s.results[fmt.Sprintf("%s-%d", resourceId, i)] = result
 
-		// Inform our hook, if we have any
-		if s.ResultHook != nil {
-			for _, hook := range s.ResultHook {
-				go hook(result, nil)
-			}
-		}
+		s.informHook(result, nil)
 	}
 
 	return nil
 }
 
+func (s Service) informHook(result *assessment.AssessmentResult, err error) {
+	// Inform our hook, if we have any
+	if s.resultHooks != nil {
+		for _, hook := range s.resultHooks {
+			go hook(result, err)
+		}
+	}
+}
+
 // ListAssessmentResults is a method implementation of the assessment interface
 func (s Service) ListAssessmentResults(_ context.Context, _ *assessment.ListAssessmentResultsRequest) (res *assessment.ListAssessmentResultsResponse, err error) {
 	res = new(assessment.ListAssessmentResultsResponse)
@@ -195,5 +191,5 @@ func (s Service) ListAssessmentResults(_ context.Context, _ *assessment.ListAsse
 }
 
 func (s *Service) RegisterAssessmentResultHook(assessmentResultsHook func(result *assessment.AssessmentResult, err error)) {
-	s.ResultHook = append(s.ResultHook, assessmentResultsHook)
+	s.resultHooks = append(s.resultHooks, assessmentResultsHook)
 }
diff --git a/service/evidence/evidence_store.go b/service/evidence/evidence_store.go
index 2f0eba50..cf2d2521 100644
--- a/service/evidence/evidence_store.go
+++ b/service/evidence/evidence_store.go
@@ -20,7 +20,7 @@ type Service struct {
 	evidences map[string]*evidence.Evidence
 
 	// Hook
-	EvidenceHook []func(result *evidence.Evidence, err error)
+	evidenceHook []func(result *evidence.Evidence, err error)
 
 	evidence.UnimplementedEvidenceStoreServer
 }
@@ -36,63 +36,66 @@ func init() {
 }
 
 // StoreEvidence is a method implementation of the evidenceServer interface: It receives an req and stores it
-func (s *Service) StoreEvidence(_ context.Context, req *evidence.StoreEvidenceRequest) (*evidence.StoreEvidenceResponse, error) {
-	var (
-		resp = &evidence.StoreEvidenceResponse{}
-		err  error
-		e    = req.GetEvidence()
-	)
+func (s *Service) StoreEvidence(_ context.Context, req *evidence.StoreEvidenceRequest) (resp *evidence.StoreEvidenceResponse, err error) {
 
-	_, err = e.Validate()
-	if err != nil {
-		log.Errorf("Invalid evidence: %v", err)
-		newError := fmt.Errorf("invalid evidence: %w", err)
+	err = s.handleEvidence(req.GetEvidence())
 
-		// Inform our hook, if we have any
-		if s.EvidenceHook != nil {
-			for _, hook := range s.EvidenceHook {
-				go hook(nil, newError)
-			}
+	if err != nil {
+		resp = &evidence.StoreEvidenceResponse{
+			Status: false,
 		}
 
-		return resp, status.Errorf(codes.InvalidArgument, "invalid req: %v", err)
+		return resp, status.Errorf(codes.Internal, "error while handling evidence: %v", err)
 	}
 
-	s.evidences[e.Id] = e
-	resp.Status = true
-
-	// Inform our hook, if we have any
-	if s.EvidenceHook != nil {
-		for _, hook := range s.EvidenceHook {
-			go hook(e, nil)
-		}
+	resp = &evidence.StoreEvidenceResponse{
+		Status: true,
 	}
 
-	return resp, err
+	return resp, nil
 }
 
 // StoreEvidences is a method implementation of the evidenceServer interface: It receives evidences and stores them
 func (s *Service) StoreEvidences(stream evidence.EvidenceStore_StoreEvidencesServer) (err error) {
-	var (
-		req *evidence.StoreEvidenceRequest
-		e   *evidence.Evidence
-	)
+	var req *evidence.StoreEvidenceRequest
+
 	for {
 		req, err = stream.Recv()
-		e = req.GetEvidence()
-		if err == io.EOF {
-			log.Infof("Stopped receiving streamed evidences")
-			return stream.SendAndClose(&emptypb.Empty{})
-		}
-		s.evidences[e.Id] = e
 
-		// Inform our hook, if we have any
-		if s.EvidenceHook != nil {
-			for _, hook := range s.EvidenceHook {
-				go hook(e, nil)
+		if err != nil {
+			// If no more input of the stream is available, return SendAndClose `error`
+			if err == io.EOF {
+				log.Infof("Stopped receiving streamed evidences")
+				return stream.SendAndClose(&emptypb.Empty{})
 			}
+
+			return err
 		}
+
+		err = s.handleEvidence(req.GetEvidence())
+		if err != nil {
+			return status.Errorf(codes.Internal, "error while handling evidence: %v", err)
+		}
+	}
+}
+
+func (s *Service) handleEvidence(e *evidence.Evidence) (err error) {
+
+	_, err = e.Validate()
+	if err != nil {
+		log.Errorf("Invalid evidence: %v", err)
+		newError := fmt.Errorf("invalid evidence: %w", err)
+
+		s.informHook(nil, newError)
+
+		return
 	}
+
+	s.evidences[e.Id] = e
+
+	s.informHook(e, nil)
+
+	return
 }
 
 // ListEvidences is a method implementation of the evidenceServer interface: It returns the evidences lying in the req storage
@@ -106,5 +109,14 @@ func (s *Service) ListEvidences(_ context.Context, _ *evidence.ListEvidencesRequ
 }
 
 func (s *Service) RegisterEvidenceHook(evidenceHook func(result *evidence.Evidence, err error)) {
-	s.EvidenceHook = append(s.EvidenceHook, evidenceHook)
+	s.evidenceHook = append(s.evidenceHook, evidenceHook)
+}
+
+func (s Service) informHook(result *evidence.Evidence, err error) {
+	// Inform our hook, if we have any
+	if s.evidenceHook != nil {
+		for _, hook := range s.evidenceHook {
+			go hook(result, err)
+		}
+	}
 }
diff --git a/service/orchestrator/cloud_service.go b/service/orchestrator/cloud_service.go
index 6215ae0d..e67919be 100644
--- a/service/orchestrator/cloud_service.go
+++ b/service/orchestrator/cloud_service.go
@@ -43,11 +43,11 @@ const DefaultTargetCloudServiceDescription = "The default target cloud service"
 
 func (s *Service) RegisterCloudService(_ context.Context, req *orchestrator.RegisterCloudServiceRequest) (service *orchestrator.CloudService, err error) {
 	if req == nil || req.Service == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "Service is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service is empty")
 	}
 
 	if req.Service.Name == "" {
-		return nil, status.Errorf(codes.InvalidArgument, "Service name is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service name is empty")
 	}
 
 	service = new(orchestrator.CloudService)
@@ -77,13 +77,13 @@ func (s *Service) ListCloudServices(_ context.Context, _ *orchestrator.ListCloud
 
 func (s *Service) GetCloudService(_ context.Context, req *orchestrator.GetCloudServiceRequest) (response *orchestrator.CloudService, err error) {
 	if req == nil || req.ServiceId == "" {
-		return nil, status.Errorf(codes.InvalidArgument, "Service id is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service id is empty")
 	}
 
 	response = new(orchestrator.CloudService)
 	err = s.db.First(&response, "Id = ?", req.ServiceId).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return nil, status.Errorf(codes.NotFound, "Service not found")
+		return nil, status.Errorf(codes.NotFound, "service not found")
 	} else if err != nil {
 		return nil, status.Errorf(codes.Internal, "database error: %s", err)
 	}
@@ -93,11 +93,11 @@ func (s *Service) GetCloudService(_ context.Context, req *orchestrator.GetCloudS
 
 func (s *Service) UpdateCloudService(_ context.Context, req *orchestrator.UpdateCloudServiceRequest) (response *orchestrator.CloudService, err error) {
 	if req.Service == nil {
-		return nil, status.Errorf(codes.InvalidArgument, "Service is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service is empty")
 	}
 
 	if req.ServiceId == "" {
-		return nil, status.Errorf(codes.InvalidArgument, "Service id is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service id is empty")
 	}
 
 	var count int64
@@ -107,7 +107,7 @@ func (s *Service) UpdateCloudService(_ context.Context, req *orchestrator.Update
 	}
 
 	if count == 0 {
-		return nil, status.Error(codes.NotFound, "Service not found")
+		return nil, status.Error(codes.NotFound, "service not found")
 	}
 
 	req.Service.Id = req.ServiceId
@@ -121,12 +121,12 @@ func (s *Service) UpdateCloudService(_ context.Context, req *orchestrator.Update
 
 func (s *Service) RemoveCloudService(_ context.Context, req *orchestrator.RemoveCloudServiceRequest) (response *emptypb.Empty, err error) {
 	if req.ServiceId == "" {
-		return nil, status.Errorf(codes.InvalidArgument, "Service id is empty")
+		return nil, status.Errorf(codes.InvalidArgument, "service id is empty")
 	}
 
 	err = s.db.Delete(&orchestrator.CloudService{Id: req.ServiceId}).Error
 	if errors.Is(err, gorm.ErrRecordNotFound) {
-		return nil, status.Errorf(codes.NotFound, "Service not found")
+		return nil, status.Errorf(codes.NotFound, "service not found")
 	} else if err != nil {
 		return nil, status.Errorf(codes.Internal, "database error: %s", err)
 	}
diff --git a/service/orchestrator/orchestrator.go b/service/orchestrator/orchestrator.go
index d87661aa..1d318fb9 100644
--- a/service/orchestrator/orchestrator.go
+++ b/service/orchestrator/orchestrator.go
@@ -64,7 +64,7 @@ type Service struct {
 	results map[string]*assessment.AssessmentResult
 
 	// Hook
-	AssessmentResultsHook []func(result *assessment.AssessmentResult, err error)
+	AssessmentResultHooks []func(result *assessment.AssessmentResult, err error)
 
 	db *gorm.DB
 }
@@ -147,7 +147,7 @@ func (*Service) GetMetric(_ context.Context, request *orchestrator.GetMetricsReq
 	var metric *assessment.Metric
 
 	if metric, ok = metricIndex[request.MetricId]; !ok {
-		return nil, status.Errorf(codes.NotFound, "Could not find metric with id %s", request.MetricId)
+		return nil, status.Errorf(codes.NotFound, "could not find metric with id %s", request.MetricId)
 	}
 
 	response = metric
@@ -166,7 +166,7 @@ func (s *Service) GetMetricConfiguration(_ context.Context, req *orchestrator.Ge
 		return config, nil
 	}
 
-	return nil, status.Errorf(codes.NotFound, "Could not find metric configuration for metric %s in service %s", req.MetricId, req.ServiceId)
+	return nil, status.Errorf(codes.NotFound, "could not find metric configuration for metric %s in service %s", req.MetricId, req.ServiceId)
 }
 
 // ListMetricConfigurations retrieves a list of MetricConfiguration objects for a particular target
@@ -195,14 +195,14 @@ func (s *Service) ListMetricConfigurations(ctx context.Context, req *orchestrato
 }
 
 // StoreAssessmentResult is a method implementation of the orchestrator interface: It receives an assessment result and stores it
-func (s *Service) StoreAssessmentResult(_ context.Context, req *orchestrator.StoreAssessmentResultRequest) (response *orchestrator.StoreAssessmentResultResponse, err error) {
+func (s *Service) StoreAssessmentResult(_ context.Context, req *orchestrator.StoreAssessmentResultRequest) (resp *orchestrator.StoreAssessmentResultResponse, err error) {
 
-	response = &orchestrator.StoreAssessmentResultResponse{}
+	resp = &orchestrator.StoreAssessmentResultResponse{}
 
 	err = s.handleResult(req.Result)
 
 	if err != nil {
-		return response, status.Errorf(codes.InvalidArgument, "invalid assessment result: %v", err)
+		return resp, status.Errorf(codes.InvalidArgument, "error while handling assessment result: %v", err)
 	}
 
 	return
@@ -213,49 +213,54 @@ func (s *Service) StoreAssessmentResults(stream orchestrator.Orchestrator_StoreA
 
 	for {
 		result, err = stream.Recv()
-		if err == io.EOF {
-			log.Infof("Stopped receiving streamed assessment results")
-			return stream.SendAndClose(&emptypb.Empty{})
+
+		if err != nil {
+			// If no more input of the stream is available, return SendAndClose `error`
+			if err == io.EOF {
+				log.Infof("Stopped receiving streamed assessment results")
+				return stream.SendAndClose(&emptypb.Empty{})
+			}
+
+			return err
 		}
 
 		err = s.handleResult(result)
 		if err != nil {
-			return fmt.Errorf("error handle assessment result: %w", err)
+			return status.Errorf(codes.Internal, "error while handling assessment result: %v", err)
 		}
 	}
 
 }
 
-func (s Service) handleResult(result *assessment.AssessmentResult) error {
-	_, err := result.Validate()
+func (s Service) handleResult(result *assessment.AssessmentResult) (err error) {
+	_, err = result.Validate()
 	if err != nil {
 		log.Errorf("Invalid assessment result: %v", err)
 		newError := fmt.Errorf("invalid assessment result: %w", err)
 
-		// Inform our hook, if we have any
-		if s.AssessmentResultsHook != nil {
-			for _, hook := range s.AssessmentResultsHook {
-				go hook(nil, newError)
-			}
-		}
+		s.informHook(nil, newError)
 
-		return newError
+		return
 	}
 
 	s.results[result.Id] = result
 
-	// Inform our hook, if we have any
-	if s.AssessmentResultsHook != nil {
-		for _, hook := range s.AssessmentResultsHook {
-			go hook(result, nil)
-		}
-	}
+	s.informHook(result, nil)
 
-	return nil
+	return
 }
 
 func (s *Service) RegisterAssessmentResultHook(hook func(result *assessment.AssessmentResult, err error)) {
-	s.AssessmentResultsHook = append(s.AssessmentResultsHook, hook)
+	s.AssessmentResultHooks = append(s.AssessmentResultHooks, hook)
+}
+
+func (s Service) informHook(result *assessment.AssessmentResult, err error) {
+	// Inform our hook, if we have any
+	if s.AssessmentResultHooks != nil {
+		for _, hook := range s.AssessmentResultHooks {
+			go hook(result, err)
+		}
+	}
 }
 
 //// Tools


Test patch:
diff --git a/cli/session_test.go b/cli/session_test.go
index 12ef3888..f48a868c 100644
--- a/cli/session_test.go
+++ b/cli/session_test.go
@@ -146,7 +146,7 @@ func TestSession_HandleResponse(t *testing.T) {
 			name: "grpc Error",
 			args: args{
 				msg: nil,
-				err: status.Errorf(codes.Internal, "Internal error occurred!"),
+				err: status.Errorf(codes.Internal, "internal error occurred!"),
 			},
 			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
 				if err != nil {
diff --git a/service/assessment/assessment_test.go b/service/assessment/assessment_test.go
index b0d00c00..e5588d8c 100644
--- a/service/assessment/assessment_test.go
+++ b/service/assessment/assessment_test.go
@@ -191,7 +191,7 @@ func TestService_AssessEvidences(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			s := Service{
-				ResultHook:                    tt.fields.ResultHook,
+				resultHooks:                   tt.fields.ResultHook,
 				results:                       tt.fields.results,
 				UnimplementedAssessmentServer: tt.fields.UnimplementedAssessmentServer,
 			}
@@ -287,7 +287,7 @@ func TestAssessmentResultHook(t *testing.T) {
 				s.RegisterAssessmentResultHook(hookFunction)
 
 				// Check if hook is registered
-				funcName1 := runtime.FuncForPC(reflect.ValueOf(s.ResultHook[i]).Pointer()).Name()
+				funcName1 := runtime.FuncForPC(reflect.ValueOf(s.resultHooks[i]).Pointer()).Name()
 				funcName2 := runtime.FuncForPC(reflect.ValueOf(hookFunction).Pointer()).Name()
 				assert.Equal(t, funcName1, funcName2)
 			}
@@ -310,7 +310,7 @@ func TestAssessmentResultHook(t *testing.T) {
 	}
 }
 
-func TestListAssessmentResults(t *testing.T) {
+func TestService_ListAssessmentResults(t *testing.T) {
 	s := NewService()
 	_, err := s.AssessEvidence(context.TODO(), &assessment.AssessEvidenceRequest{
 		Evidence: &evidence.Evidence{
diff --git a/service/discovery/discovery_test.go b/service/discovery/discovery_test.go
index 5578bbcf..98c1a768 100644
--- a/service/discovery/discovery_test.go
+++ b/service/discovery/discovery_test.go
@@ -337,7 +337,7 @@ func (mockIsCloudResource) GetCreationTime() *time.Time {
 //	var ready = make(chan bool)
 //
 //	assessmentServer := standalone.NewAssessmentServer().(*service_assessment.Service)
-//	assessmentServer.ResultHook = func(result *assessment.Result, err error) {
+//	assessmentServer.resultHook = func(result *assessment.Result, err error) {
 //		if result.MetricId == 1 {
 //			assert.Nil(t, err)
 //			assert.NotNil(t, result)
diff --git a/service/evidence/evidence_store_test.go b/service/evidence/evidence_store_test.go
index fca600eb..093a7c28 100644
--- a/service/evidence/evidence_store_test.go
+++ b/service/evidence/evidence_store_test.go
@@ -194,12 +194,12 @@ func TestEvidenceHook(t *testing.T) {
 	service.RegisterEvidenceHook(secondHookFunction)
 
 	// Check if first hook is registered
-	funcName1 := runtime.FuncForPC(reflect.ValueOf(service.EvidenceHook[0]).Pointer()).Name()
+	funcName1 := runtime.FuncForPC(reflect.ValueOf(service.evidenceHook[0]).Pointer()).Name()
 	funcName2 := runtime.FuncForPC(reflect.ValueOf(firstHookFunction).Pointer()).Name()
 	assert.Equal(t, funcName1, funcName2)
 
 	// Check if second hook is registered
-	funcName1 = runtime.FuncForPC(reflect.ValueOf(service.EvidenceHook[1]).Pointer()).Name()
+	funcName1 = runtime.FuncForPC(reflect.ValueOf(service.evidenceHook[1]).Pointer()).Name()
 	funcName2 = runtime.FuncForPC(reflect.ValueOf(secondHookFunction).Pointer()).Name()
 	assert.Equal(t, funcName1, funcName2)
 
@@ -289,19 +289,33 @@ func (m *mockStreamer) Recv() (req *evidence.StoreEvidenceRequest, err error) {
 		m.counter++
 		return &evidence.StoreEvidenceRequest{Evidence: &evidence.Evidence{
 			Id:        "MockEvidenceId-1",
+			ToolId:    "MockToolId-1",
 			ServiceId: "MockServiceId-1",
 			Timestamp: timestamppb.Now(),
 			Raw:       "",
-			Resource:  nil,
+			Resource: toStructWithoutTest(voc.VirtualMachine{
+				Compute: &voc.Compute{
+					CloudResource: &voc.CloudResource{
+						ID: "mock-id-1",
+					},
+				},
+			}),
 		}}, nil
 	} else if m.counter == 1 {
 		m.counter++
 		return &evidence.StoreEvidenceRequest{Evidence: &evidence.Evidence{
 			Id:        "MockEvidenceId-2",
+			ToolId:    "MockToolId-2",
 			ServiceId: "MockServiceId-2",
 			Timestamp: timestamppb.Now(),
 			Raw:       "",
-			Resource:  nil,
+			Resource: toStructWithoutTest(voc.VirtualMachine{
+				Compute: &voc.Compute{
+					CloudResource: &voc.CloudResource{
+						ID: "mock-id-1",
+					},
+				},
+			}),
 		}}, nil
 	} else {
 		return nil, io.EOF
@@ -340,3 +354,12 @@ func toStruct(r voc.IsCloudResource, t *testing.T) (s *structpb.Value) {
 
 	return
 }
+
+func toStructWithoutTest(r voc.IsCloudResource) (s *structpb.Value) {
+	s, err := voc.ToStruct(r)
+	if err != nil {
+		log.Errorf("eror getting struct of resource: %v", err)
+	}
+
+	return
+}
diff --git a/service/orchestrator/cloud_service_test.go b/service/orchestrator/cloud_service_test.go
index 206ee29a..d4f3ab37 100644
--- a/service/orchestrator/cloud_service_test.go
+++ b/service/orchestrator/cloud_service_test.go
@@ -22,13 +22,13 @@ func TestRegisterCloudService(t *testing.T) {
 			"missing service",
 			&orchestrator.RegisterCloudServiceRequest{},
 			nil,
-			status.Error(codes.InvalidArgument, "Service is empty"),
+			status.Error(codes.InvalidArgument, "service is empty"),
 		},
 		{
 			"missing service name",
 			&orchestrator.RegisterCloudServiceRequest{Service: &orchestrator.CloudService{}},
 			nil,
-			status.Error(codes.InvalidArgument, "Service name is empty"),
+			status.Error(codes.InvalidArgument, "service name is empty"),
 		},
 		{
 			"valid",
@@ -77,19 +77,19 @@ func TestGetCloudService(t *testing.T) {
 			"missing request",
 			nil,
 			nil,
-			status.Error(codes.InvalidArgument, "Service id is empty"),
+			status.Error(codes.InvalidArgument, "service id is empty"),
 		},
 		{
 			"missing service id",
 			&orchestrator.GetCloudServiceRequest{},
 			nil,
-			status.Error(codes.InvalidArgument, "Service id is empty"),
+			status.Error(codes.InvalidArgument, "service id is empty"),
 		},
 		{
 			"invalid service id",
 			&orchestrator.GetCloudServiceRequest{ServiceId: "does-not-exist"},
 			nil,
-			status.Error(codes.NotFound, "Service not found"),
+			status.Error(codes.NotFound, "service not found"),
 		},
 		{
 			"valid",
diff --git a/service/orchestrator/orchestrator_test.go b/service/orchestrator/orchestrator_test.go
index 1f0aac04..1c109cfd 100644
--- a/service/orchestrator/orchestrator_test.go
+++ b/service/orchestrator/orchestrator_test.go
@@ -94,12 +94,12 @@ func TestAssessmentResultHook(t *testing.T) {
 	service.RegisterAssessmentResultHook(secondHookFunction)
 
 	// Check if first hook is registered
-	funcName1 := runtime.FuncForPC(reflect.ValueOf(service.AssessmentResultsHook[0]).Pointer()).Name()
+	funcName1 := runtime.FuncForPC(reflect.ValueOf(service.AssessmentResultHooks[0]).Pointer()).Name()
 	funcName2 := runtime.FuncForPC(reflect.ValueOf(firstHookFunction).Pointer()).Name()
 	assert.Equal(t, funcName1, funcName2)
 
 	// Check if second hook is registered
-	funcName1 = runtime.FuncForPC(reflect.ValueOf(service.AssessmentResultsHook[1]).Pointer()).Name()
+	funcName1 = runtime.FuncForPC(reflect.ValueOf(service.AssessmentResultHooks[1]).Pointer()).Name()
 	funcName2 = runtime.FuncForPC(reflect.ValueOf(secondHookFunction).Pointer()).Name()
 	assert.Equal(t, funcName1, funcName2)