From 8624bd4f57ff86e7b1c5a0fcba7eed13c91bf52d Mon Sep 17 00:00:00 2001 From: PetarTodorovv <31803034+PetarTodorovv@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:49:46 +0300 Subject: [PATCH 01/19] Scenarios labels log improvement (#3790) --- chart/compass/values.yaml | 2 +- components/director/internal/domain/labeldef/service.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 429dffd219..644f021a0e 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3789" + version: "PR-3790" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/labeldef/service.go b/components/director/internal/domain/labeldef/service.go index ebbe98be3c..d64b0bc04a 100644 --- a/components/director/internal/domain/labeldef/service.go +++ b/components/director/internal/domain/labeldef/service.go @@ -138,9 +138,9 @@ func (s *service) ValidateExistingLabelsAgainstSchema(ctx context.Context, schem if len(formationNames) == 0 && len(existingLabels) != 0 { labelInfo := make([]string, 0, len(existingLabels)) for _, label := range existingLabels { - labelInfo = append(labelInfo, fmt.Sprintf("key=%q and value=%q", label.Key, label.Value)) + labelInfo = append(labelInfo, fmt.Sprintf("key='%s' and value='%s' for object ID='%s' and object type='%s'", label.Key, label.Value, label.ObjectID, label.ObjectType)) } - return apperrors.NewInvalidDataError(fmt.Sprintf(`labels: %s are not valid against empty schema`, strings.Join(labelInfo, ","))) + return apperrors.NewInvalidDataError(fmt.Sprintf(`labels with the following data: %s are not valid against empty schema`, strings.Join(labelInfo, ","))) } validator, err := jsonschema.NewValidatorFromRawSchema(schema) @@ -155,7 +155,7 @@ func (s *service) ValidateExistingLabelsAgainstSchema(ctx context.Context, schem } if !result.Valid { - return apperrors.NewInvalidDataError(fmt.Sprintf(`label with key="%s" and value="%s" is not valid against new schema for %s with ID="%s": %s`, label.Key, label.Value, label.ObjectType, label.ObjectID, result.Error)) + return apperrors.NewInvalidDataError(fmt.Sprintf(`label with key='%s' and value='%s' for object ID='%s' and object type='%s' is not valid against new schema: %s`, label.Key, label.Value, label.ObjectID, label.ObjectType, result.Error)) } } return nil From 301274a49d7537422b23dad0d0fd70ed5bec4b39 Mon Sep 17 00:00:00 2001 From: PetarTodorovv <31803034+PetarTodorovv@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:05:47 +0300 Subject: [PATCH 02/19] Fix instance creator log parameters (#3792) --- chart/compass/values.yaml | 2 +- components/instance-creator/internal/handler/handler.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 644f021a0e..8b7d53dca4 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -191,7 +191,7 @@ global: name: compass-kyma-adapter instance_creator: dir: dev/incubator/ - version: "PR-3760" + version: "PR-3792" name: compass-instance-creator default_tenant_mapping_handler: dir: dev/incubator/ diff --git a/components/instance-creator/internal/handler/handler.go b/components/instance-creator/internal/handler/handler.go index a48b67e78a..34691cfd24 100644 --- a/components/instance-creator/internal/handler/handler.go +++ b/components/instance-creator/internal/handler/handler.go @@ -570,13 +570,13 @@ func (i *InstanceCreatorHandler) createServiceInstances(ctx context.Context, req return nil, errors.Wrapf(err, "while retrieving service plan with catalog name %q and offering ID %q", serviceOfferingCatalogName, serviceOfferingID) } - log.C(ctx).Debugf("Creating service instance with name %q, plan id %q, parameters %q and labels %v for subaccount %q and region %q...", serviceInstanceName, servicePlanID, serviceInstanceParameters, smLabels, region, subaccount) + log.C(ctx).Debugf("Creating service instance with name %q, plan id %q, parameters %q and labels %v for region %q and subaccount %q...", serviceInstanceName, servicePlanID, serviceInstanceParameters, smLabels, region, subaccount) serviceInstanceID, err := i.SMClient.CreateResource(ctx, region, subaccount, &types.ServiceInstanceReqBody{Name: serviceInstanceName, ServicePlanID: servicePlanID, Parameters: serviceInstanceParameters, Labels: smLabels}, &types.ServiceInstance{}) if err != nil { return nil, errors.Wrapf(err, "while creating service instance with name %q", serviceInstanceName) } - log.C(ctx).Debugf("Getting raw service instance with id %q for subaccount %q and region %q...", serviceInstanceID, region, subaccount) + log.C(ctx).Debugf("Getting raw service instance with id %q for %q region and subaccount %q...", serviceInstanceID, region, subaccount) serviceInstanceRaw, err := i.SMClient.RetrieveRawResourceByID(ctx, region, subaccount, &types.ServiceInstance{ID: serviceInstanceID}) if err != nil { return nil, errors.Wrapf(err, "while retrieving service instance with ID %q", serviceInstanceID) @@ -598,13 +598,13 @@ func (i *InstanceCreatorHandler) createServiceInstances(ctx context.Context, req return nil, errors.Wrapf(err, "while extracting the parameters of service binding for a service instance with id: %d", idx) } - log.C(ctx).Debugf("Creating service binding with name %q, service instance id %q and parameters %q for subaccount %q and region %q...", serviceBindingName, serviceInstanceID, serviceBindingParameters, region, subaccount) + log.C(ctx).Debugf("Creating service binding with name %q, service instance id %q and parameters %q for region %q and subaccount %q...", serviceBindingName, serviceInstanceID, serviceBindingParameters, region, subaccount) serviceBindingID, err := i.SMClient.CreateResource(ctx, region, subaccount, &types.ServiceBindingReqBody{Name: serviceBindingName, ServiceBindingID: serviceInstanceID, Parameters: serviceBindingParameters}, &types.ServiceBinding{}) if err != nil { return nil, errors.Wrapf(err, "while creating service instance binding for service instance with ID %q", serviceInstanceID) } - log.C(ctx).Debugf("Getting raw service binding with id %q for subaccount %q and region %q...", serviceBindingID, region, subaccount) + log.C(ctx).Debugf("Getting raw service binding with id %q for region %q and subaccount %q...", serviceBindingID, region, subaccount) serviceBindingRaw, err := i.SMClient.RetrieveRawResourceByID(ctx, region, subaccount, &types.ServiceBinding{ID: serviceBindingID}) if err != nil { return nil, errors.Wrapf(err, "while retrieving service instance binding with ID %q", serviceBindingID) From 3a3d4fccbd400223751bf56ba9d44b04078f8617 Mon Sep 17 00:00:00 2001 From: Lachezar Bogomilov <64094901+la4ezar@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:39:48 +0300 Subject: [PATCH 03/19] Fix formationTemplate gql query not found error (#3791) * Initial implementation and unit test. * Update values.yaml * Change unit test name. --- chart/compass/values.yaml | 2 +- .../director/internal/domain/formationtemplate/resolver.go | 3 --- .../internal/domain/formationtemplate/resolver_test.go | 6 +++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 8b7d53dca4..62265a89f4 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3790" + version: "PR-3791" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formationtemplate/resolver.go b/components/director/internal/domain/formationtemplate/resolver.go index 00e0436168..5e61b83de4 100644 --- a/components/director/internal/domain/formationtemplate/resolver.go +++ b/components/director/internal/domain/formationtemplate/resolver.go @@ -140,9 +140,6 @@ func (r *Resolver) FormationTemplate(ctx context.Context, id string) (*graphql.F formationTemplate, err := r.formationTemplateSvc.Get(ctx, id) if err != nil { - if apperrors.IsNotFoundError(err) { - return nil, tx.Commit() - } return nil, err } diff --git a/components/director/internal/domain/formationtemplate/resolver_test.go b/components/director/internal/domain/formationtemplate/resolver_test.go index 5bbfba945b..8f1d419afb 100644 --- a/components/director/internal/domain/formationtemplate/resolver_test.go +++ b/components/director/internal/domain/formationtemplate/resolver_test.go @@ -70,8 +70,8 @@ func TestResolver_FormationTemplate(t *testing.T) { ExpectedError: testErr, }, { - Name: "Returns nil when formation template not found", - TxFn: txGen.ThatSucceeds, + Name: "Returns error when formation template not found", + TxFn: txGen.ThatDoesntExpectCommit, FormationTemplateService: func() *automock.FormationTemplateService { svc := &automock.FormationTemplateService{} svc.On("Get", txtest.CtxWithDBMatcher(), testID).Return(nil, apperrors.NewNotFoundError(resource.FormationTemplate, testID)) @@ -80,7 +80,7 @@ func TestResolver_FormationTemplate(t *testing.T) { }, FormationTemplateConverter: UnusedFormationTemplateConverter, ExpectedOutput: nil, - ExpectedError: nil, + ExpectedError: apperrors.NewNotFoundError(resource.FormationTemplate, testID), }, { Name: "Returns error when failing on the committing of a transaction", From 7a100acabfa07ff6c5ed13036154fbcce79f1a78 Mon Sep 17 00:00:00 2001 From: ivantenevvasilev <48180084+ivantenevvasilev@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:28:37 +0300 Subject: [PATCH 04/19] Ensure that configuration is provided to status reset API (#3794) * add check and unit test * bump director version in values.yaml --- chart/compass/values.yaml | 2 +- .../internal/formationmapping/handler.go | 6 ++++++ .../internal/formationmapping/handler_test.go | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 62265a89f4..eff786ae2a 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3791" + version: "PR-3794" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/formationmapping/handler.go b/components/director/internal/formationmapping/handler.go index 6b04c33313..aaec8a7bcf 100644 --- a/components/director/internal/formationmapping/handler.go +++ b/components/director/internal/formationmapping/handler.go @@ -190,6 +190,12 @@ func (h *Handler) updateFormationAssignmentStatus(w http.ResponseWriter, r *http return } + if formationassignmentpkg.IsConfigEmpty(string(notificationStatusReport.Configuration)) { + errResp := errors.Errorf("Cannot reset formation assignment with source %q and target %q to state %s because provided configuration is empty. X-Request-Id: %s", fa.Source, fa.Target, assignmentReqBody.State, correlationID) + respondWithError(ctx, w, http.StatusBadRequest, errResp) + return + } + if fa.State != string(model.ReadyAssignmentState) { errResp := errors.Errorf("Cannot reset formation assignment with source %q and target %q because assignment is not in %q state. X-Request-Id: %s", fa.Source, fa.Target, model.ReadyAssignmentState, correlationID) respondWithError(ctx, w, http.StatusBadRequest, errResp) diff --git a/components/director/internal/formationmapping/handler_test.go b/components/director/internal/formationmapping/handler_test.go index 38510d2489..3ae57b63eb 100644 --- a/components/director/internal/formationmapping/handler_test.go +++ b/components/director/internal/formationmapping/handler_test.go @@ -1619,6 +1619,25 @@ func TestHandler_ResetFormationAssignmentStatus(t *testing.T) { }, expectedStatusCode: http.StatusBadRequest, }, + { + name: "Fail when trying to reset assignment without configuration", + transactFn: txGen.ThatDoesntExpectCommit, + faServiceFn: func() *automock.FormationAssignmentService { + faSvc := &automock.FormationAssignmentService{} + faSvc.On("GetGlobalByIDAndFormationID", txtest.CtxWithDBMatcher(), testFormationAssignmentID, testFormationID).Return(faWithSourceAppAndTargetRuntime(model.InitialAssignmentState), nil).Once() + return faSvc + }, + formationSvcFn: func() *automock.FormationService { + formationSvc := &automock.FormationService{} + formationSvc.On("Get", txtest.CtxWithDBMatcher(), testFormationID).Return(testFormationWithReadyState, nil).Once() + return formationSvc + }, + reqBody: fm.FormationAssignmentRequestBody{ + State: model.ReadyAssignmentState, + }, + expectedStatusCode: http.StatusBadRequest, + }, + { name: "Fail when failing to get reverse assignment", transactFn: txGen.ThatDoesntExpectCommit, From 6e7c9ced0bac133d596943d04f325c7525cea002 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:36:36 +0300 Subject: [PATCH 05/19] Refactor IAS adapter to be async (#3784) * refactor ias adapter to be async * add ucl service unit tests * change request body struct and fix unit tests * adopt async v2 request body * refactor async processing * remove duplicate tests * refactor app creation * fix processor logging * fix log msg * update ias adapter image --- chart/compass/values.yaml | 2 +- .../handlers/automock/async_processor.go | 36 ++++ .../handlers/automock/health_service.go | 13 +- .../automock/tenant_mappings_service.go | 21 +-- .../internal/api/internal/handlers/health.go | 1 - .../api/internal/handlers/tenant_mapping.go | 60 ++----- .../internal/handlers/tenant_mapping_test.go | 120 +++++-------- .../internal/api/internal/response.go | 20 --- components/ias-adapter/internal/api/server.go | 4 +- components/ias-adapter/internal/app/app.go | 20 ++- .../internal/service/automock/ias_service.go | 25 +-- .../automock/tenant_mappings_storage.go | 21 +-- .../internal/service/ias/client.go | 59 ------- .../internal/service/ias/ias_test.go | 13 -- .../internal/service/ias/service.go | 9 +- .../internal/service/ias/service_test.go | 19 ++- .../internal/service/outbound/client.go | 68 ++++++++ .../service/{ias => outbound}/client_test.go | 4 +- .../automock/tenant_mappings_service.go | 83 +++++++++ .../service/processor/automock/ucl_service.go | 45 +++++ .../internal/service/processor/processor.go | 97 +++++++++++ .../service/processor/processor_test.go | 160 ++++++++++++++++++ .../internal/service/tenant_mapping.go | 29 ++-- .../internal/service/tenant_mapping_test.go | 57 ++++--- .../internal/service/ucl/service.go | 56 ++++++ .../internal/service/ucl/service_test.go | 66 ++++++++ .../storage/postgres/tenant_mapping.go | 6 +- .../internal/types/tenant_mapping.go | 87 +++++----- .../internal/types/tenant_mapping_test.go | 39 ++--- 29 files changed, 844 insertions(+), 396 deletions(-) create mode 100644 components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go delete mode 100644 components/ias-adapter/internal/service/ias/client.go delete mode 100644 components/ias-adapter/internal/service/ias/ias_test.go create mode 100644 components/ias-adapter/internal/service/outbound/client.go rename components/ias-adapter/internal/service/{ias => outbound}/client_test.go (93%) create mode 100644 components/ias-adapter/internal/service/processor/automock/tenant_mappings_service.go create mode 100644 components/ias-adapter/internal/service/processor/automock/ucl_service.go create mode 100644 components/ias-adapter/internal/service/processor/processor.go create mode 100644 components/ias-adapter/internal/service/processor/processor_test.go create mode 100644 components/ias-adapter/internal/service/ucl/service.go create mode 100644 components/ias-adapter/internal/service/ucl/service_test.go diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index eff786ae2a..9df5ad50b3 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -183,7 +183,7 @@ global: name: compass-hydrator ias_adapter: dir: dev/incubator/ - version: "PR-3768" + version: "PR-3784" name: compass-ias-adapter kyma_adapter: dir: dev/incubator/ diff --git a/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go b/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go new file mode 100644 index 0000000000..23fe3a3119 --- /dev/null +++ b/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go @@ -0,0 +1,36 @@ +// Code generated by mockery. DO NOT EDIT. + +package automock + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" +) + +// AsyncProcessor is an autogenerated mock type for the AsyncProcessor type +type AsyncProcessor struct { + mock.Mock +} + +// ProcessTMRequest provides a mock function with given fields: ctx, tenantMapping +func (_m *AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping types.TenantMapping) { + _m.Called(ctx, tenantMapping) +} + +type mockConstructorTestingTNewAsyncProcessor interface { + mock.TestingT + Cleanup(func()) +} + +// NewAsyncProcessor creates a new instance of AsyncProcessor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewAsyncProcessor(t mockConstructorTestingTNewAsyncProcessor) *AsyncProcessor { + mock := &AsyncProcessor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/components/ias-adapter/internal/api/internal/handlers/automock/health_service.go b/components/ias-adapter/internal/api/internal/handlers/automock/health_service.go index 98d252cd22..83fd8ab3cd 100644 --- a/components/ias-adapter/internal/api/internal/handlers/automock/health_service.go +++ b/components/ias-adapter/internal/api/internal/handlers/automock/health_service.go @@ -19,10 +19,6 @@ type HealthService struct { func (_m *HealthService) CheckHealth(ctx context.Context) (types.HealthStatus, error) { ret := _m.Called(ctx) - if len(ret) == 0 { - panic("no return value specified for CheckHealth") - } - var r0 types.HealthStatus var r1 error if rf, ok := ret.Get(0).(func(context.Context) (types.HealthStatus, error)); ok { @@ -43,12 +39,13 @@ func (_m *HealthService) CheckHealth(ctx context.Context) (types.HealthStatus, e return r0, r1 } -// NewHealthService creates a new instance of HealthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewHealthService(t interface { +type mockConstructorTestingTNewHealthService interface { mock.TestingT Cleanup(func()) -}) *HealthService { +} + +// NewHealthService creates a new instance of HealthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewHealthService(t mockConstructorTestingTNewHealthService) *HealthService { mock := &HealthService{} mock.Mock.Test(t) diff --git a/components/ias-adapter/internal/api/internal/handlers/automock/tenant_mappings_service.go b/components/ias-adapter/internal/api/internal/handlers/automock/tenant_mappings_service.go index 37b7029589..74c83a7d4a 100644 --- a/components/ias-adapter/internal/api/internal/handlers/automock/tenant_mappings_service.go +++ b/components/ias-adapter/internal/api/internal/handlers/automock/tenant_mappings_service.go @@ -19,10 +19,6 @@ type TenantMappingsService struct { func (_m *TenantMappingsService) CanSafelyRemoveTenantMapping(ctx context.Context, formationID string) (bool, error) { ret := _m.Called(ctx, formationID) - if len(ret) == 0 { - panic("no return value specified for CanSafelyRemoveTenantMapping") - } - var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { @@ -47,10 +43,6 @@ func (_m *TenantMappingsService) CanSafelyRemoveTenantMapping(ctx context.Contex func (_m *TenantMappingsService) ProcessTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error { ret := _m.Called(ctx, tenantMapping) - if len(ret) == 0 { - panic("no return value specified for ProcessTenantMapping") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.TenantMapping) error); ok { r0 = rf(ctx, tenantMapping) @@ -65,10 +57,6 @@ func (_m *TenantMappingsService) ProcessTenantMapping(ctx context.Context, tenan func (_m *TenantMappingsService) RemoveTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error { ret := _m.Called(ctx, tenantMapping) - if len(ret) == 0 { - panic("no return value specified for RemoveTenantMapping") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.TenantMapping) error); ok { r0 = rf(ctx, tenantMapping) @@ -79,12 +67,13 @@ func (_m *TenantMappingsService) RemoveTenantMapping(ctx context.Context, tenant return r0 } -// NewTenantMappingsService creates a new instance of TenantMappingsService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewTenantMappingsService(t interface { +type mockConstructorTestingTNewTenantMappingsService interface { mock.TestingT Cleanup(func()) -}) *TenantMappingsService { +} + +// NewTenantMappingsService creates a new instance of TenantMappingsService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewTenantMappingsService(t mockConstructorTestingTNewTenantMappingsService) *TenantMappingsService { mock := &TenantMappingsService{} mock.Mock.Test(t) diff --git a/components/ias-adapter/internal/api/internal/handlers/health.go b/components/ias-adapter/internal/api/internal/handlers/health.go index f90a4c33aa..a5c89500a7 100644 --- a/components/ias-adapter/internal/api/internal/handlers/health.go +++ b/components/ias-adapter/internal/api/internal/handlers/health.go @@ -10,7 +10,6 @@ import ( ) //go:generate mockery --name=HealthService --output=automock --outpkg=automock --case=underscore --disable-version-string - type HealthService interface { CheckHealth(ctx context.Context) (types.HealthStatus, error) } diff --git a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go index 4d450934c6..86ff7a46bd 100644 --- a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go +++ b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/gin-gonic/gin" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/api/internal" "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" @@ -16,7 +15,7 @@ import ( ) const ( - S4SAPManagedCommunicationScenario = "SAP_COM_1002" + locationHeader = "Location" ) //go:generate mockery --name=TenantMappingsService --output=automock --outpkg=automock --case=underscore --disable-version-string @@ -26,8 +25,14 @@ type TenantMappingsService interface { RemoveTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error } +//go:generate mockery --name=AsyncProcessor --output=automock --outpkg=automock --case=underscore --disable-version-string +type AsyncProcessor interface { + ProcessTMRequest(ctx context.Context, tenantMapping types.TenantMapping) +} + type TenantMappingsHandler struct { - Service TenantMappingsService + Service TenantMappingsService + AsyncProcessor AsyncProcessor } func (h TenantMappingsHandler) Patch(ctx *gin.Context) { @@ -38,7 +43,7 @@ func (h TenantMappingsHandler) Patch(ctx *gin.Context) { return } - if err := tenantMapping.AssignedTenants[0].SetConfiguration(ctx); err != nil { + if err := tenantMapping.AssignedTenant.SetConfiguration(ctx); err != nil { err = errors.Newf("failed to set assigned tenant configuration: %w", err) internal.RespondWithError(ctx, http.StatusBadRequest, err) return @@ -55,53 +60,16 @@ func (h TenantMappingsHandler) Patch(ctx *gin.Context) { tenantMapping.ReceiverTenant.ApplicationURL = "https://" + tenantMapping.ReceiverTenant.ApplicationURL } - reverseAssignmentState := tenantMapping.AssignedTenants[0].ReverseAssignmentState - if tenantMapping.AssignedTenants[0].Operation == types.OperationAssign { - if reverseAssignmentState != types.StateInitial && reverseAssignmentState != types.StateReady { - errMsgf := "skipped processing tenant mapping notification with $.assignedTenants[0].reverseAssignmentState '%s'" - err := errors.Newf(errMsgf, reverseAssignmentState) - internal.RespondWithError(ctx, internal.IncompleteStatusCode, err) - return - } - } - if err := h.Service.ProcessTenantMapping(ctx, tenantMapping); err != nil { - err = errors.Newf("failed to process tenant mapping notification: %w", err) - operation := tenantMapping.AssignedTenants[0].Operation - - if operation == types.OperationAssign { - if errors.Is(err, errors.IASApplicationNotFound) { - internal.RespondWithError(ctx, internal.NotFoundStatusCode, err) - return - } - - if errors.Is(err, errors.S4CertificateNotFound) { - logger.FromContext(ctx).Info().Msgf("S/4 certificate not provided. Responding with CONFIG_PENDING.") - s4Config := &types.TenantMappingConfiguration{ - Credentials: types.Credentials{ - OutboundCommunicationCredentials: types.CommunicationCredentials{ - OAuth2mTLSAuthentication: types.OAuth2mTLSAuthentication{ - CorrelationIds: []string{S4SAPManagedCommunicationScenario}, - }, - }, - }, - } - internal.RespondWithConfigPending(ctx, s4Config) - return - } - } - - internal.RespondWithError(ctx, internal.ErrorStatusCode, err) - return - } - - ctx.Status(http.StatusOK) + ctx.AbortWithStatus(http.StatusAccepted) + ctx.Set(locationHeader, ctx.GetHeader(locationHeader)) + h.AsyncProcessor.ProcessTMRequest(ctx, tenantMapping) } func (h TenantMappingsHandler) handleValidateError(ctx *gin.Context, err error, tenantMapping *types.TenantMapping) { - operation := tenantMapping.AssignedTenants[0].Operation + operation := tenantMapping.Operation if operation != types.OperationUnassign || errors.Is(err, types.ErrInvalidFormationID) || - errors.Is(err, types.ErrInvalidAssignedTenantID) { + errors.Is(err, types.ErrInvalidAssignedTenantAppID) { internal.RespondWithError(ctx, http.StatusBadRequest, err) return } diff --git a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go index 61deb6c744..bff73081aa 100644 --- a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go +++ b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go @@ -6,9 +6,8 @@ import ( "net/http/httptest" "net/url" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/api/internal/handlers/automock" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -17,10 +16,11 @@ import ( var _ = Describe("Tenant Mapping Handler", func() { var ( - tenantMapping *types.TenantMapping - errExpected = errors.New("errExpected") - mockService *automock.TenantMappingsService - handler *TenantMappingsHandler + tenantMapping *types.TenantMapping + errExpected = errors.New("errExpected") + mockService *automock.TenantMappingsService + mockAsyncProcessor *automock.AsyncProcessor + handler *TenantMappingsHandler expectError = func(w *httptest.ResponseRecorder, expectedCode int, expectedMessage string) { responseBody, err := io.ReadAll(w.Body) @@ -38,140 +38,112 @@ var _ = Describe("Tenant Mapping Handler", func() { BeforeEach(func() { mockService = &automock.TenantMappingsService{} + mockAsyncProcessor = &automock.AsyncProcessor{} tenantMapping = &types.TenantMapping{ - FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Context: types.Context{ + FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Operation: types.OperationAssign, + }, ReceiverTenant: types.ReceiverTenant{ ApplicationURL: "localhost", }, - AssignedTenants: []types.AssignedTenant{ - { - UCLApplicationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - UCLApplicationType: "test-app-type", - LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - Operation: types.OperationAssign, - Parameters: types.AssignedTenantParameters{ - ClientID: "clientID", - }, - ReverseAssignmentState: "", + AssignedTenant: types.AssignedTenant{ + AppID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + AppNamespace: "sap.test.namespace", + LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Parameters: types.AssignedTenantParameters{ + ClientID: "clientID", }, + ReverseAssignmentState: "", }, } - handler = &TenantMappingsHandler{Service: mockService} + handler = &TenantMappingsHandler{ + Service: mockService, + AsyncProcessor: mockAsyncProcessor, + } }) AfterEach(func() { mockService.AssertExpectations(test) + mockAsyncProcessor.AssertExpectations(test) }) When("Tenant mapping cannot be decoded", func() { It("Should fail with 400", func() { w, ctx := createTestRequest("unprocessable body") + handler.Patch(ctx) expectError(w, http.StatusBadRequest, "failed to decode tenant mapping body") }) }) + When("Tenant mapping is invalid", func() { BeforeEach(func() { - tenantMapping.AssignedTenants[0].Parameters.ClientID = "" + tenantMapping.AssignedTenant.Parameters.ClientID = "" }) + When("Operation is assign", func() { It("Should fail with 400", func() { w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) expectError(w, http.StatusBadRequest, "tenant mapping body is invalid") }) }) + When("Operation is unassign", func() { BeforeEach(func() { - tenantMapping.AssignedTenants[0].Operation = types.OperationUnassign + tenantMapping.Operation = types.OperationUnassign }) + It("Should fail with 400 if tenantMappings are 2", func() { mockService.On("CanSafelyRemoveTenantMapping", mock.Anything, mock.Anything).Return(false, nil) w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) expectError(w, http.StatusBadRequest, "tenant mapping body is invalid") }) + It("Should fail with 500 if tenantMappings check fails", func() { mockService.On("CanSafelyRemoveTenantMapping", mock.Anything, mock.Anything).Return(false, errExpected) w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) expectError(w, http.StatusInternalServerError, errExpected.Error()) }) + It("Should fail with 500 if tenantMappings remove call fails", func() { mockService.On("CanSafelyRemoveTenantMapping", mock.Anything, mock.Anything).Return(true, nil) mockService.On("RemoveTenantMapping", mock.Anything, mock.Anything).Return(errExpected) w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) expectError(w, http.StatusInternalServerError, errExpected.Error()) }) + It("Should succeed if tenantMappings are less then 2", func() { mockService.On("CanSafelyRemoveTenantMapping", mock.Anything, mock.Anything).Return(true, nil) mockService.On("RemoveTenantMapping", mock.Anything, mock.Anything).Return(nil) + w, ctx := createTestRequest(tenantMapping) handler.Patch(ctx) expectSuccess(w, "") }) }) }) + When("Reverse assignment state is neither INITIAL nor READY", func() { BeforeEach(func() { - tenantMapping.AssignedTenants[0].ReverseAssignmentState = "CREATE_ERROR" - }) - It("Should fail with 422 CONFIG_PENDING", func() { - w, ctx := createTestRequest(tenantMapping) - handler.Patch(ctx) - Expect(w.Code).To(Equal(http.StatusUnprocessableEntity)) - expectError(w, http.StatusUnprocessableEntity, "") - }) - }) - When("Consumed APIs cannot be updated", func() { - BeforeEach(func() { - tenantMapping.AssignedTenants[0].ReverseAssignmentState = types.StateInitial - }) - It("Should fail with 500", func() { - mockService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(errExpected) - w, ctx := createTestRequest(tenantMapping) - handler.Patch(ctx) - expectError(w, http.StatusInternalServerError, errExpected.Error()) - }) - }) - When("Consumed APIs cannot be updated due to not found IAS application", func() { - When("Operation is Assign", func() { - BeforeEach(func() { - tenantMapping.AssignedTenants[0].ReverseAssignmentState = types.StateInitial - tenantMapping.AssignedTenants[0].Operation = types.OperationAssign - }) - It("Should return 404", func() { - err := errors.Newf("could not process tenant mapping: %w", errors.IASApplicationNotFound) - mockService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(err) - w, ctx := createTestRequest(tenantMapping) - handler.Patch(ctx) - expectError(w, http.StatusNotFound, err.Error()) - }) + tenantMapping.AssignedTenant.ReverseAssignmentState = "CONFIG_PENDING" }) - }) - When("One of the participants is S/4 and there is no certificate provided", func() { - BeforeEach(func() { - tenantMapping.AssignedTenants[0].ReverseAssignmentState = types.StateInitial - tenantMapping.AssignedTenants[0].UCLApplicationType = types.S4ApplicationType - tenantMapping.AssignedTenants[0].Parameters.ClientID = "" - }) - It("Should return 200 and CONFIG_PENDING with S/4 configuration", func() { - mockService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(errors.S4CertificateNotFound) - w, ctx := createTestRequest(tenantMapping) - handler.Patch(ctx) - expectSuccess(w, S4SAPManagedCommunicationScenario) - }) - }) - When("Consumed APIs are successfully updated", func() { - BeforeEach(func() { - tenantMapping.AssignedTenants[0].ReverseAssignmentState = types.StateReady - }) - It("Should return 200", func() { - mockService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(nil) + + It("Should return status 202 and handle the request asynchronously", func() { + mockAsyncProcessor.On("ProcessTMRequest", mock.Anything, mock.Anything).Return() w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) - expectSuccess(w, "") + Expect(w.Code).To(Equal(http.StatusAccepted)) + Expect(mockAsyncProcessor.AssertNumberOfCalls(test, "ProcessTMRequest", 1)).To(BeTrue()) }) }) }) diff --git a/components/ias-adapter/internal/api/internal/response.go b/components/ias-adapter/internal/api/internal/response.go index 531ba3ed22..ceeaa26a8a 100644 --- a/components/ias-adapter/internal/api/internal/response.go +++ b/components/ias-adapter/internal/api/internal/response.go @@ -1,21 +1,11 @@ package internal import ( - "net/http" "net/url" "github.com/gin-gonic/gin" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" logCtx "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger/context" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" -) - -const ( - SuccessStatusCode = http.StatusOK - IncompleteStatusCode = http.StatusUnprocessableEntity - NotFoundStatusCode = http.StatusNotFound - ErrorStatusCode = http.StatusInternalServerError ) type errorResponse struct { @@ -30,13 +20,3 @@ func RespondWithError(ctx *gin.Context, statusCode int, err error) { errorMessage := url.QueryEscape(err.Error()) ctx.AbortWithStatusJSON(statusCode, errorResponse{Error: errorMessage, RequestID: requestID.(string)}) } - -func RespondWithConfigPending(ctx *gin.Context, config *types.TenantMappingConfiguration) { - tenantMappingResponse := types.TenantMappingResponse{ - State: types.StateConfigPending, - } - if config != nil { - tenantMappingResponse.Configuration = *config - } - ctx.AbortWithStatusJSON(http.StatusOK, tenantMappingResponse) -} diff --git a/components/ias-adapter/internal/api/server.go b/components/ias-adapter/internal/api/server.go index fe784743b0..91ebff63c5 100644 --- a/components/ias-adapter/internal/api/server.go +++ b/components/ias-adapter/internal/api/server.go @@ -22,6 +22,7 @@ func init() { type Services struct { HealthService handlers.HealthService TenantMappingsService handlers.TenantMappingsService + AsyncProcessor handlers.AsyncProcessor } func NewServer(ctx context.Context, cfg config.Config, services Services) (*http.Server, error) { @@ -49,7 +50,8 @@ func NewServer(ctx context.Context, cfg config.Config, services Services) (*http routerGroup.Use(authMiddleware.Auth) tenantMappingRouter.Use(authMiddleware.Auth) tenantMappingsHandler := handlers.TenantMappingsHandler{ - Service: services.TenantMappingsService, + Service: services.TenantMappingsService, + AsyncProcessor: services.AsyncProcessor, } tenantMappingRouter.PATCH(paths.TenantMappingsPath, tenantMappingsHandler.Patch) diff --git a/components/ias-adapter/internal/app/app.go b/components/ias-adapter/internal/app/app.go index 76019dd3e1..a2cc56f8c5 100644 --- a/components/ias-adapter/internal/app/app.go +++ b/components/ias-adapter/internal/app/app.go @@ -12,6 +12,9 @@ import ( "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" "github.com/kyma-incubator/compass/components/ias-adapter/internal/service" "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ias" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/outbound" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/processor" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" "github.com/kyma-incubator/compass/components/ias-adapter/internal/storage/postgres" ) @@ -35,19 +38,30 @@ func Start(cfg config.Config) { Storage: postgresConnection, } - iasClient, err := ias.NewClient(cfg.IASConfig) + outboundClientCert, err := outbound.LoadClientCert(cfg.IASConfig.CockpitSecretPath) if err != nil { - log.Fatal().Msgf("Failed to create IAS HTTPS client: %s", err) + log.Fatal().Msgf("Failed to load outbound client cert: %s", err) } + outboundClientConfig := outbound.ClientConfig{ + Certificate: outboundClientCert, + Timeout: cfg.IASConfig.RequestTimeout, + } + outboundClient := outbound.NewClient(outboundClientConfig) tenantMappingsService := service.TenantMappingsService{ Storage: postgresConnection, - IASService: ias.NewService(cfg.IASConfig, iasClient), + IASService: ias.NewService(outboundClient), + } + + asyncProcessor := processor.AsyncProcessor{ + TenantMappingsService: tenantMappingsService, + UCLService: ucl.NewService(outboundClient), } server, err := api.NewServer(globalCtx, cfg, api.Services{ HealthService: healthService, TenantMappingsService: tenantMappingsService, + AsyncProcessor: asyncProcessor, }) if err != nil { log.Fatal().Msgf("Failed to create server: %s", err) diff --git a/components/ias-adapter/internal/service/automock/ias_service.go b/components/ias-adapter/internal/service/automock/ias_service.go index ccff188a5f..14849ed216 100644 --- a/components/ias-adapter/internal/service/automock/ias_service.go +++ b/components/ias-adapter/internal/service/automock/ias_service.go @@ -20,10 +20,6 @@ type IASService struct { func (_m *IASService) CreateApplication(ctx context.Context, iasHost string, app *types.Application) (string, error) { ret := _m.Called(ctx, iasHost, app) - if len(ret) == 0 { - panic("no return value specified for CreateApplication") - } - var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, *types.Application) (string, error)); ok { @@ -48,10 +44,6 @@ func (_m *IASService) CreateApplication(ctx context.Context, iasHost string, app func (_m *IASService) GetApplicationByClientID(ctx context.Context, iasHost string, clientID string, appTenantID string) (types.Application, error) { ret := _m.Called(ctx, iasHost, clientID, appTenantID) - if len(ret) == 0 { - panic("no return value specified for GetApplicationByClientID") - } - var r0 types.Application var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (types.Application, error)); ok { @@ -76,10 +68,6 @@ func (_m *IASService) GetApplicationByClientID(ctx context.Context, iasHost stri func (_m *IASService) GetApplicationByName(ctx context.Context, iasHost string, name string) (types.Application, error) { ret := _m.Called(ctx, iasHost, name) - if len(ret) == 0 { - panic("no return value specified for GetApplicationByName") - } - var r0 types.Application var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (types.Application, error)); ok { @@ -104,10 +92,6 @@ func (_m *IASService) GetApplicationByName(ctx context.Context, iasHost string, func (_m *IASService) UpdateApplicationConsumedAPIs(ctx context.Context, data ias.UpdateData) error { ret := _m.Called(ctx, data) - if len(ret) == 0 { - panic("no return value specified for UpdateApplicationConsumedAPIs") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, ias.UpdateData) error); ok { r0 = rf(ctx, data) @@ -118,12 +102,13 @@ func (_m *IASService) UpdateApplicationConsumedAPIs(ctx context.Context, data ia return r0 } -// NewIASService creates a new instance of IASService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewIASService(t interface { +type mockConstructorTestingTNewIASService interface { mock.TestingT Cleanup(func()) -}) *IASService { +} + +// NewIASService creates a new instance of IASService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewIASService(t mockConstructorTestingTNewIASService) *IASService { mock := &IASService{} mock.Mock.Test(t) diff --git a/components/ias-adapter/internal/service/automock/tenant_mappings_storage.go b/components/ias-adapter/internal/service/automock/tenant_mappings_storage.go index 7ccf32353f..43148ba019 100644 --- a/components/ias-adapter/internal/service/automock/tenant_mappings_storage.go +++ b/components/ias-adapter/internal/service/automock/tenant_mappings_storage.go @@ -19,10 +19,6 @@ type TenantMappingsStorage struct { func (_m *TenantMappingsStorage) DeleteTenantMapping(ctx context.Context, formationID string, applicationID string) error { ret := _m.Called(ctx, formationID, applicationID) - if len(ret) == 0 { - panic("no return value specified for DeleteTenantMapping") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, formationID, applicationID) @@ -37,10 +33,6 @@ func (_m *TenantMappingsStorage) DeleteTenantMapping(ctx context.Context, format func (_m *TenantMappingsStorage) ListTenantMappings(ctx context.Context, formationID string) (map[string]types.TenantMapping, error) { ret := _m.Called(ctx, formationID) - if len(ret) == 0 { - panic("no return value specified for ListTenantMappings") - } - var r0 map[string]types.TenantMapping var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (map[string]types.TenantMapping, error)); ok { @@ -67,10 +59,6 @@ func (_m *TenantMappingsStorage) ListTenantMappings(ctx context.Context, formati func (_m *TenantMappingsStorage) UpsertTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error { ret := _m.Called(ctx, tenantMapping) - if len(ret) == 0 { - panic("no return value specified for UpsertTenantMapping") - } - var r0 error if rf, ok := ret.Get(0).(func(context.Context, types.TenantMapping) error); ok { r0 = rf(ctx, tenantMapping) @@ -81,12 +69,13 @@ func (_m *TenantMappingsStorage) UpsertTenantMapping(ctx context.Context, tenant return r0 } -// NewTenantMappingsStorage creates a new instance of TenantMappingsStorage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewTenantMappingsStorage(t interface { +type mockConstructorTestingTNewTenantMappingsStorage interface { mock.TestingT Cleanup(func()) -}) *TenantMappingsStorage { +} + +// NewTenantMappingsStorage creates a new instance of TenantMappingsStorage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewTenantMappingsStorage(t mockConstructorTestingTNewTenantMappingsStorage) *TenantMappingsStorage { mock := &TenantMappingsStorage{} mock.Mock.Test(t) diff --git a/components/ias-adapter/internal/service/ias/client.go b/components/ias-adapter/internal/service/ias/client.go deleted file mode 100644 index d7dcb66383..0000000000 --- a/components/ias-adapter/internal/service/ias/client.go +++ /dev/null @@ -1,59 +0,0 @@ -package ias - -import ( - "crypto/tls" - "net/http" - "os" - - "gopkg.in/yaml.v2" - - "github.com/kyma-incubator/compass/components/ias-adapter/internal/config" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" - logCtx "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger/context" -) - -type iasCockpit struct { - Cert string `yaml:"cert"` - Key string `yaml:"key"` -} - -func NewClient(cfg config.IAS) (*http.Client, error) { - bytes, err := os.ReadFile(cfg.CockpitSecretPath) - if err != nil { - return nil, errors.Newf("failed to read IAS cockpit secret: %w", err) - } - - var iasCockpit iasCockpit - err = yaml.Unmarshal(bytes, &iasCockpit) - if err != nil { - return nil, errors.Newf("failed to unmarshal IAS cockpit secret: %w", err) - } - - clientCert, err := tls.X509KeyPair([]byte(iasCockpit.Cert), []byte(iasCockpit.Key)) - if err != nil { - return nil, errors.Newf("failed to load IAS client cert: %w", err) - } - - transport := &headerTransport{clientTransport: &http.Transport{ - TLSClientConfig: &tls.Config{ - Certificates: []tls.Certificate{clientCert}, - }, - }} - return &http.Client{ - Transport: transport, - Timeout: cfg.RequestTimeout, - }, nil -} - -type headerTransport struct { - clientTransport http.RoundTripper -} - -func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) { - requestID := req.Context().Value(logCtx.RequestIDCtxKey).(string) - if requestID != "" { - req.Header.Add(logCtx.RequestIDHeader, requestID) - } - req.Header.Add("Content-Type", "application/json") - return t.clientTransport.RoundTrip(req) -} diff --git a/components/ias-adapter/internal/service/ias/ias_test.go b/components/ias-adapter/internal/service/ias/ias_test.go deleted file mode 100644 index 64733e663f..0000000000 --- a/components/ias-adapter/internal/service/ias/ias_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package ias - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestIASService(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "IAS Service Test Suite") -} diff --git a/components/ias-adapter/internal/service/ias/service.go b/components/ias-adapter/internal/service/ias/service.go index 2adc3bb522..c7eed8fa92 100644 --- a/components/ias-adapter/internal/service/ias/service.go +++ b/components/ias-adapter/internal/service/ias/service.go @@ -10,7 +10,6 @@ import ( "net/url" "strings" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/config" "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" @@ -19,13 +18,11 @@ import ( const applicationsPath = "/Applications/v1" type Service struct { - cfg config.IAS client *http.Client } -func NewService(cfg config.IAS, client *http.Client) Service { +func NewService(client *http.Client) Service { return Service{ - cfg: cfg, client: client, } } @@ -38,7 +35,7 @@ type UpdateData struct { } func (s Service) UpdateApplicationConsumedAPIs(ctx context.Context, data UpdateData) error { - consumerTenant := data.TenantMapping.AssignedTenants[0] + consumerTenant := data.TenantMapping.AssignedTenant consumedAPIs := data.ConsumerApplication.Authentication.ConsumedAPIs consumedAPIsLen := len(consumedAPIs) @@ -64,7 +61,7 @@ func (s Service) UpdateApplicationConsumedAPIs(ctx context.Context, data UpdateD if consumedAPIsLen != len(consumedAPIs) { iasHost := data.TenantMapping.ReceiverTenant.ApplicationURL if err := s.updateApplication(ctx, iasHost, data.ConsumerApplication.ID, consumedAPIs); err != nil { - return errors.Newf("failed to update IAS application '%s' with UCL ID '%s': %w", data.ConsumerApplication.ID, consumerTenant.UCLApplicationID, err) + return errors.Newf("failed to update IAS application '%s' with UCL ID '%s': %w", data.ConsumerApplication.ID, consumerTenant.AppID, err) } } diff --git a/components/ias-adapter/internal/service/ias/service_test.go b/components/ias-adapter/internal/service/ias/service_test.go index a567a6d55c..088339025b 100644 --- a/components/ias-adapter/internal/service/ias/service_test.go +++ b/components/ias-adapter/internal/service/ias/service_test.go @@ -6,14 +6,19 @@ import ( "io" "net/http" "strings" + "testing" - "github.com/kyma-incubator/compass/components/ias-adapter/internal/config" "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func TestIASService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "IAS Service Test Suite") +} + var testConsumedAPI = types.ApplicationConsumedAPI{ Name: "name1", APIName: "apiname1", @@ -100,7 +105,6 @@ var _ = Describe("Removing consumed API", func() { }) var _ = Describe("Getting application by client ID", func() { - config := config.IAS{} ctx := context.Background() iasHost := "ias-host" clientID := "client-id" @@ -109,7 +113,7 @@ var _ = Describe("Getting application by client ID", func() { When("IAS returns an error", func() { It("Returns an error", func() { err := errors.New("connection reset") - service := NewService(config, &http.Client{Transport: &testTransport{err: err}}) + service := NewService(&http.Client{Transport: &testTransport{err: err}}) _, err = service.GetApplicationByClientID(ctx, iasHost, clientID, appTenantId) Expect(err).To(Equal(err)) }) @@ -121,7 +125,7 @@ var _ = Describe("Getting application by client ID", func() { b, err := json.Marshal(apps) Expect(err).ToNot(HaveOccurred()) - service := NewService(config, &http.Client{Transport: &testTransport{status: http.StatusOK, body: string(b)}}) + service := NewService(&http.Client{Transport: &testTransport{status: http.StatusOK, body: string(b)}}) _, err = service.GetApplicationByClientID(ctx, iasHost, clientID, appTenantId) Expect(err).To(MatchError(errors.IASApplicationNotFound)) }) @@ -143,7 +147,7 @@ var _ = Describe("Getting application by client ID", func() { b, err := json.Marshal(apps) Expect(err).ToNot(HaveOccurred()) - service := NewService(config, &http.Client{Transport: &testTransport{status: http.StatusOK, body: string(b)}}) + service := NewService(&http.Client{Transport: &testTransport{status: http.StatusOK, body: string(b)}}) _, err = service.GetApplicationByClientID(ctx, iasHost, clientID, appTenantId) Expect(err).To(MatchError(errors.IASApplicationNotFound)) }) @@ -151,7 +155,6 @@ var _ = Describe("Getting application by client ID", func() { }) var _ = Describe("Getting application by name", func() { - config := config.IAS{} ctx := context.Background() iasHost := "ias-host" name := "ias-app-name" @@ -159,7 +162,7 @@ var _ = Describe("Getting application by name", func() { When("IAS returns an error", func() { It("Returns an error", func() { err := errors.New("connection reset") - service := NewService(config, &http.Client{Transport: &testTransport{err: err}}) + service := NewService(&http.Client{Transport: &testTransport{err: err}}) _, err = service.GetApplicationByName(ctx, iasHost, name) Expect(err).To(Equal(err)) }) @@ -167,7 +170,7 @@ var _ = Describe("Getting application by name", func() { When("There are no applications with the specified name and IAS returns 404", func() { It("Returns IAS App not found error", func() { - service := NewService(config, &http.Client{Transport: &testTransport{status: http.StatusNotFound}}) + service := NewService(&http.Client{Transport: &testTransport{status: http.StatusNotFound}}) _, err := service.GetApplicationByName(ctx, iasHost, name) Expect(err).To(MatchError(errors.IASApplicationNotFound)) }) diff --git a/components/ias-adapter/internal/service/outbound/client.go b/components/ias-adapter/internal/service/outbound/client.go new file mode 100644 index 0000000000..b548efcd16 --- /dev/null +++ b/components/ias-adapter/internal/service/outbound/client.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "crypto/tls" + "net/http" + "os" + "time" + + "gopkg.in/yaml.v2" + + "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" + logCtx "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger/context" +) + +type cert struct { + Crt string `yaml:"cert"` + Key string `yaml:"key"` +} + +func LoadClientCert(certPath string) (tls.Certificate, error) { + bytes, err := os.ReadFile(certPath) + if err != nil { + return tls.Certificate{}, errors.Newf("failed to read outbound certificate file: %w", err) + } + + var certificate cert + err = yaml.Unmarshal(bytes, &certificate) + if err != nil { + return tls.Certificate{}, errors.Newf("failed to unmarshal outbound certificate: %w", err) + } + + clientCert, err := tls.X509KeyPair([]byte(certificate.Crt), []byte(certificate.Key)) + if err != nil { + return tls.Certificate{}, errors.Newf("failed to load outbound certificate: %w", err) + } + + return clientCert, nil +} + +type ClientConfig struct { + Certificate tls.Certificate + Timeout time.Duration +} + +func NewClient(cfg ClientConfig) *http.Client { + transport := &headerTransport{clientTransport: &http.Transport{ + TLSClientConfig: &tls.Config{ + Certificates: []tls.Certificate{cfg.Certificate}, + }, + }} + return &http.Client{ + Transport: transport, + Timeout: cfg.Timeout, + } +} + +type headerTransport struct { + clientTransport http.RoundTripper +} + +func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) { + requestID := req.Context().Value(logCtx.RequestIDCtxKey).(string) + if requestID != "" { + req.Header.Add(logCtx.RequestIDHeader, requestID) + } + req.Header.Add("Content-Type", "application/json") + return t.clientTransport.RoundTrip(req) +} diff --git a/components/ias-adapter/internal/service/ias/client_test.go b/components/ias-adapter/internal/service/outbound/client_test.go similarity index 93% rename from components/ias-adapter/internal/service/ias/client_test.go rename to components/ias-adapter/internal/service/outbound/client_test.go index 46a0752adc..9adc042d9f 100644 --- a/components/ias-adapter/internal/service/ias/client_test.go +++ b/components/ias-adapter/internal/service/outbound/client_test.go @@ -1,4 +1,4 @@ -package ias +package outbound import ( "net/http" @@ -11,7 +11,7 @@ import ( logCtx "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger/context" ) -var _ = Describe("IAS Client", func() { +var _ = Describe("Outbound Client", func() { When("Outbound request is sent", func() { It("Request ID header should be present", func() { testClient := &http.Client{ diff --git a/components/ias-adapter/internal/service/processor/automock/tenant_mappings_service.go b/components/ias-adapter/internal/service/processor/automock/tenant_mappings_service.go new file mode 100644 index 0000000000..74c83a7d4a --- /dev/null +++ b/components/ias-adapter/internal/service/processor/automock/tenant_mappings_service.go @@ -0,0 +1,83 @@ +// Code generated by mockery. DO NOT EDIT. + +package automock + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + types "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" +) + +// TenantMappingsService is an autogenerated mock type for the TenantMappingsService type +type TenantMappingsService struct { + mock.Mock +} + +// CanSafelyRemoveTenantMapping provides a mock function with given fields: ctx, formationID +func (_m *TenantMappingsService) CanSafelyRemoveTenantMapping(ctx context.Context, formationID string) (bool, error) { + ret := _m.Called(ctx, formationID) + + var r0 bool + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (bool, error)); ok { + return rf(ctx, formationID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) bool); ok { + r0 = rf(ctx, formationID) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, formationID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ProcessTenantMapping provides a mock function with given fields: ctx, tenantMapping +func (_m *TenantMappingsService) ProcessTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error { + ret := _m.Called(ctx, tenantMapping) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.TenantMapping) error); ok { + r0 = rf(ctx, tenantMapping) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveTenantMapping provides a mock function with given fields: ctx, tenantMapping +func (_m *TenantMappingsService) RemoveTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error { + ret := _m.Called(ctx, tenantMapping) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, types.TenantMapping) error); ok { + r0 = rf(ctx, tenantMapping) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewTenantMappingsService interface { + mock.TestingT + Cleanup(func()) +} + +// NewTenantMappingsService creates a new instance of TenantMappingsService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewTenantMappingsService(t mockConstructorTestingTNewTenantMappingsService) *TenantMappingsService { + mock := &TenantMappingsService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/components/ias-adapter/internal/service/processor/automock/ucl_service.go b/components/ias-adapter/internal/service/processor/automock/ucl_service.go new file mode 100644 index 0000000000..d57c671e5e --- /dev/null +++ b/components/ias-adapter/internal/service/processor/automock/ucl_service.go @@ -0,0 +1,45 @@ +// Code generated by mockery. DO NOT EDIT. + +package automock + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + ucl "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" +) + +// UCLService is an autogenerated mock type for the UCLService type +type UCLService struct { + mock.Mock +} + +// ReportStatus provides a mock function with given fields: ctx, url, statusReport +func (_m *UCLService) ReportStatus(ctx context.Context, url string, statusReport ucl.StatusReport) error { + ret := _m.Called(ctx, url, statusReport) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, ucl.StatusReport) error); ok { + r0 = rf(ctx, url, statusReport) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +type mockConstructorTestingTNewUCLService interface { + mock.TestingT + Cleanup(func()) +} + +// NewUCLService creates a new instance of UCLService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewUCLService(t mockConstructorTestingTNewUCLService) *UCLService { + mock := &UCLService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/components/ias-adapter/internal/service/processor/processor.go b/components/ias-adapter/internal/service/processor/processor.go new file mode 100644 index 0000000000..4f727abd49 --- /dev/null +++ b/components/ias-adapter/internal/service/processor/processor.go @@ -0,0 +1,97 @@ +package processor + +import ( + "context" + + "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" +) + +//go:generate mockery --name=TenantMappingsService --output=automock --outpkg=automock --case=underscore --disable-version-string +type TenantMappingsService interface { + CanSafelyRemoveTenantMapping(ctx context.Context, formationID string) (bool, error) + ProcessTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error + RemoveTenantMapping(ctx context.Context, tenantMapping types.TenantMapping) error +} + +//go:generate mockery --name=UCLService --output=automock --outpkg=automock --case=underscore --disable-version-string +type UCLService interface { + ReportStatus(ctx context.Context, url string, statusReport ucl.StatusReport) error +} + +type AsyncProcessor struct { + TenantMappingsService TenantMappingsService + UCLService UCLService +} + +const locationHeader = "Location" + +var s4Config = &types.TenantMappingConfiguration{ + Credentials: types.Credentials{ + OutboundCommunicationCredentials: types.CommunicationCredentials{ + OAuth2mTLSAuthentication: types.OAuth2mTLSAuthentication{ + CorrelationIds: []string{types.S4SAPManagedCommunicationScenario}, + }, + }, + }, +} + +func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping types.TenantMapping) { + log := logger.FromContext(ctx) + reverseAssignmentState := tenantMapping.AssignedTenant.ReverseAssignmentState + if tenantMapping.Operation == types.OperationAssign { + if reverseAssignmentState != types.StateInitial && reverseAssignmentState != types.StateReady { + log.Warn().Msgf("skipping processing tenant mapping notification with $.assignedTenant.state '%s'", + reverseAssignmentState) + p.reportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending}) + return + } + } + + operation := tenantMapping.Operation + + if err := p.TenantMappingsService.ProcessTenantMapping(ctx, tenantMapping); err != nil { + err = errors.Newf("failed to process tenant mapping notification: %w", err) + + if operation == types.OperationAssign { + if errors.Is(err, errors.IASApplicationNotFound) { + p.reportStatus(ctx, ucl.StatusReport{State: errorState(operation), Error: err.Error()}) + return + } + + if errors.Is(err, errors.S4CertificateNotFound) { + log.Info().Msgf("S/4 certificate not provided. Responding with CONFIG_PENDING.") + p.reportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending, Configuration: s4Config}) + return + } + } + + p.reportStatus(ctx, ucl.StatusReport{State: errorState(operation), Error: err.Error()}) + return + } + + p.reportStatus(ctx, ucl.StatusReport{State: readyState(operation)}) +} + +func (p AsyncProcessor) reportStatus(ctx context.Context, statusReport ucl.StatusReport) { + statusReportURL := ctx.Value(locationHeader).(string) + if err := p.UCLService.ReportStatus(ctx, statusReportURL, statusReport); err != nil { + logger.FromContext(ctx).Error().Msgf("failed to report status to '%s': %s", statusReportURL, err) + } +} + +func readyState(operation types.Operation) types.State { + if operation == types.OperationAssign { + return types.StateCreateReady + } + return types.StateDeleteReady +} + +func errorState(operation types.Operation) types.State { + if operation == types.OperationAssign { + return types.StateCreateError + } + return types.StateDeleteError +} diff --git a/components/ias-adapter/internal/service/processor/processor_test.go b/components/ias-adapter/internal/service/processor/processor_test.go new file mode 100644 index 0000000000..cc2e276ca5 --- /dev/null +++ b/components/ias-adapter/internal/service/processor/processor_test.go @@ -0,0 +1,160 @@ +package processor + +import ( + "context" + "fmt" + "testing" + + "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/processor/automock" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" +) + +var test *testing.T + +func TestProcessor(t *testing.T) { + test = t + RegisterFailHandler(Fail) + RunSpecs(t, "Processor Test Suite") +} + +var _ = Describe("Processor", func() { + var ( + ctx = context.WithValue(context.Background(), locationHeader, "valid.url") + tenantMapping *types.TenantMapping + errExpected = errors.New("errExpected") + mockTMService *automock.TenantMappingsService + mockUCLService *automock.UCLService + asyncProcessor *AsyncProcessor + ) + + BeforeEach(func() { + mockTMService = &automock.TenantMappingsService{} + mockUCLService = &automock.UCLService{} + tenantMapping = &types.TenantMapping{ + Context: types.Context{ + FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Operation: types.OperationAssign, + }, + ReceiverTenant: types.ReceiverTenant{ + ApplicationURL: "localhost", + }, + AssignedTenant: types.AssignedTenant{ + AppID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + AppNamespace: "sap.test.namespace", + LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Parameters: types.AssignedTenantParameters{ + ClientID: "clientID", + }, + ReverseAssignmentState: "", + }, + } + asyncProcessor = &AsyncProcessor{ + TenantMappingsService: mockTMService, + UCLService: mockUCLService, + } + }) + + AfterEach(func() { + mockTMService.AssertExpectations(test) + mockUCLService.AssertExpectations(test) + }) + + When("Reverse assignment state is neither INITIAL nor READY", func() { + BeforeEach(func() { + tenantMapping.AssignedTenant.ReverseAssignmentState = "CREATE_ERROR" + }) + + It("Should report CONFIG_PENDING status", func() { + expectedStatusReport := ucl.StatusReport{State: types.StateConfigPending} + mockUCLService.On("ReportStatus", mock.Anything, mock.Anything, expectedStatusReport).Return(nil) + + asyncProcessor.ProcessTMRequest(ctx, *tenantMapping) + }) + }) + + When("Consumed APIs cannot be updated", func() { + BeforeEach(func() { + tenantMapping.AssignedTenant.ReverseAssignmentState = types.StateInitial + }) + + It("Should report CREATE_ERROR status", func() { + mockTMService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(errExpected) + expectedStatusReport := ucl.StatusReport{ + State: types.StateCreateError, + Error: fmt.Sprintf("failed to process tenant mapping notification: %s", errExpected.Error()), + } + mockUCLService.On("ReportStatus", mock.Anything, mock.Anything, expectedStatusReport).Return(nil) + + asyncProcessor.ProcessTMRequest(ctx, *tenantMapping) + }) + }) + + When("Consumed APIs cannot be updated due to not found IAS application", func() { + When("Operation is Assign", func() { + BeforeEach(func() { + tenantMapping.AssignedTenant.ReverseAssignmentState = types.StateInitial + tenantMapping.Operation = types.OperationAssign + }) + + It("Should report CREATE_ERROR status", func() { + err := errors.Newf("could not process tenant mapping: %w", errors.IASApplicationNotFound) + mockTMService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(err) + expectedStatusReport := ucl.StatusReport{ + State: types.StateCreateError, + Error: fmt.Sprintf("failed to process tenant mapping notification: %s", err.Error()), + } + mockUCLService.On("ReportStatus", mock.Anything, mock.Anything, expectedStatusReport).Return(nil) + + asyncProcessor.ProcessTMRequest(ctx, *tenantMapping) + }) + }) + }) + + When("One of the participants is S/4 and there is no certificate provided", func() { + BeforeEach(func() { + tenantMapping.AssignedTenant.ReverseAssignmentState = types.StateInitial + tenantMapping.AssignedTenant.AppNamespace = types.S4ApplicationNamespace + tenantMapping.AssignedTenant.Parameters.ClientID = "" + }) + + It("Should report CONFIG_PENDING status with S/4 configuration", func() { + mockTMService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(errors.S4CertificateNotFound) + expectedStatusReport := ucl.StatusReport{ + State: types.StateConfigPending, + Configuration: &types.TenantMappingConfiguration{ + Credentials: types.Credentials{ + OutboundCommunicationCredentials: types.CommunicationCredentials{ + OAuth2mTLSAuthentication: types.OAuth2mTLSAuthentication{ + CorrelationIds: []string{types.S4SAPManagedCommunicationScenario}, + }, + }, + }, + }, + } + mockUCLService.On("ReportStatus", mock.Anything, mock.Anything, expectedStatusReport).Return(nil) + + asyncProcessor.ProcessTMRequest(ctx, *tenantMapping) + }) + }) + + When("Consumed APIs are successfully updated", func() { + BeforeEach(func() { + tenantMapping.AssignedTenant.ReverseAssignmentState = types.StateReady + }) + + It("Should report CREATE_READY status", func() { + mockTMService.On("ProcessTenantMapping", mock.Anything, mock.Anything).Return(nil) + expectedStatusReport := ucl.StatusReport{ + State: types.StateCreateReady, + } + mockUCLService.On("ReportStatus", mock.Anything, mock.Anything, expectedStatusReport).Return(nil) + + asyncProcessor.ProcessTMRequest(ctx, *tenantMapping) + }) + }) +}) diff --git a/components/ias-adapter/internal/service/tenant_mapping.go b/components/ias-adapter/internal/service/tenant_mapping.go index 3a4e85ad55..46609a7f11 100644 --- a/components/ias-adapter/internal/service/tenant_mapping.go +++ b/components/ias-adapter/internal/service/tenant_mapping.go @@ -46,22 +46,21 @@ func (s TenantMappingsService) ProcessTenantMapping(ctx context.Context, tenantM logger.FromContext(ctx).Err(err).Msgf("Failed to get tenant mappings for formation '%s'", formationID) return errors.Newf("failed to get tenant mappings for formation '%s': %w", formationID, postgres.Error(err)) } - operation := tenantMapping.AssignedTenants[0].Operation - switch operation { + switch tenantMapping.Operation { case types.OperationAssign: return s.handleAssign(ctx, tenantMapping, tenantMappingsFromDB) case types.OperationUnassign: return s.handleUnassign(ctx, tenantMapping, tenantMappingsFromDB) default: - panic(errors.Newf("invalid tenant mapping operation %s", operation)) + panic(errors.Newf("invalid tenant mapping operation %s", tenantMapping.Operation)) } } func (s TenantMappingsService) RemoveTenantMapping( ctx context.Context, tenantMapping types.TenantMapping) error { formationID := tenantMapping.FormationID - err := s.Storage.DeleteTenantMapping(ctx, formationID, tenantMapping.AssignedTenants[0].UCLApplicationID) + err := s.Storage.DeleteTenantMapping(ctx, formationID, tenantMapping.AssignedTenant.AppID) if err != nil { logger.FromContext(ctx).Err(err).Msgf("Failed to clean up tenant mapping for formation '%s'", formationID) return errors.Newf("failed to clean up tenant mapping for formation '%s': %w", @@ -74,18 +73,18 @@ func (s TenantMappingsService) handleAssign(ctx context.Context, tenantMapping types.TenantMapping, tenantMappingsFromDB map[string]types.TenantMapping) error { formationID := tenantMapping.FormationID - assignedTenant := tenantMapping.AssignedTenants[0] - uclAppID := assignedTenant.UCLApplicationID + assignedTenant := tenantMapping.AssignedTenant + uclAppID := assignedTenant.AppID _, tenantMappingAlreadyInDB := tenantMappingsFromDB[uclAppID] - if assignedTenant.UCLApplicationType == types.S4ApplicationType && !tenantMappingAlreadyInDB { + if assignedTenant.AppNamespace == types.S4ApplicationNamespace && !tenantMappingAlreadyInDB { appID, err := s.createIfNotExistsIASApp(ctx, tenantMapping) if err != nil { logger.FromContext(ctx).Err(err).Msgf("Failed to create/find suitable IAS application") return errors.Newf("could not create/find suitable IAS application: %w", err) } - tenantMapping.AssignedTenants[0].Parameters.IASApplicationID = appID + tenantMapping.AssignedTenant.Parameters.IASApplicationID = appID } if tenantMappingAlreadyInDB && len(assignedTenant.Configuration.ConsumedAPIs) == 0 { @@ -144,7 +143,7 @@ func (s TenantMappingsService) updateIASAppsConsumedAPIs(ctx context.Context, for idx, consumerApp := range iasApps { tenantMapping := tenantMappings[idx] - uclAppID := tenantMapping.AssignedTenants[0].UCLApplicationID + uclAppID := tenantMapping.AssignedTenant.AppID providerAppID := iasApps[abs(idx-1)].ID log.Info().Msgf( @@ -180,10 +179,10 @@ func (s TenantMappingsService) handleUnassign(ctx context.Context, func (s TenantMappingsService) getIASApplication( ctx context.Context, tenantMapping types.TenantMapping) (types.Application, error) { iasHost := tenantMapping.ReceiverTenant.ApplicationURL - tenantMappingUCLApplicationID := tenantMapping.AssignedTenants[0].UCLApplicationID - clientID := tenantMapping.AssignedTenants[0].Parameters.ClientID - localTenantID := tenantMapping.AssignedTenants[0].LocalTenantID - iasAppID := tenantMapping.AssignedTenants[0].Parameters.IASApplicationID + tenantMappingUCLApplicationID := tenantMapping.AssignedTenant.AppID + clientID := tenantMapping.AssignedTenant.Parameters.ClientID + localTenantID := tenantMapping.AssignedTenant.LocalTenantID + iasAppID := tenantMapping.AssignedTenant.Parameters.IASApplicationID if iasAppID != "" { return types.Application{ID: iasAppID}, nil @@ -219,12 +218,12 @@ func (s TenantMappingsService) getIASApps(ctx context.Context, triggerOperation func (s TenantMappingsService) createIfNotExistsIASApp(ctx context.Context, tenantMapping types.TenantMapping) (string, error) { iasHost := tenantMapping.ReceiverTenant.ApplicationURL - s4Certificate := tenantMapping.AssignedTenants[0].Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate + s4Certificate := tenantMapping.AssignedTenant.Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate if s4Certificate == "" { return "", errors.S4CertificateNotFound } - s4AppName := string(types.S4ApplicationType) + "-" + tenantMapping.AssignedTenants[0].LocalTenantID + s4AppName := string(types.S4ApplicationNamespace) + "-" + tenantMapping.AssignedTenant.LocalTenantID existingS4App, err := s.IASService.GetApplicationByName(ctx, iasHost, s4AppName) if err == nil { logger.FromContext(ctx).Info().Msgf("Found existing IAS application with name: %s", s4AppName) diff --git a/components/ias-adapter/internal/service/tenant_mapping_test.go b/components/ias-adapter/internal/service/tenant_mapping_test.go index 183a04100f..7bfeb0c203 100644 --- a/components/ias-adapter/internal/service/tenant_mapping_test.go +++ b/components/ias-adapter/internal/service/tenant_mapping_test.go @@ -31,24 +31,24 @@ var _ = Describe("Tenant mappings service", func() { tenantMappingsStorage = &automock.TenantMappingsStorage{} iasService = &automock.IASService{} tenantMapping = types.TenantMapping{ - FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Context: types.Context{ + Operation: types.OperationAssign, + FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + }, ReceiverTenant: types.ReceiverTenant{ ApplicationURL: "localhost", }, - AssignedTenants: []types.AssignedTenant{ - { - UCLApplicationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - UCLApplicationType: "test-app-type", - LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - Operation: types.OperationAssign, - Parameters: types.AssignedTenantParameters{ - ClientID: "clientID", - }, - Configuration: types.AssignedTenantConfiguration{ - ConsumedAPIs: []string{}, - }, - ReverseAssignmentState: "", + AssignedTenant: types.AssignedTenant{ + AppID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + AppNamespace: "sap.test.namespace", + LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Parameters: types.AssignedTenantParameters{ + ClientID: "clientID", + }, + Configuration: types.AssignedTenantConfiguration{ + ConsumedAPIs: []string{}, }, + ReverseAssignmentState: "", }, } }) @@ -71,36 +71,47 @@ var _ = Describe("Tenant mappings service", func() { When("tenant mapping with S/4 participant is received", func() { BeforeEach(func() { - tenantMapping.AssignedTenants[0].UCLApplicationType = types.S4ApplicationType - tenantMapping.AssignedTenants[0].Parameters.ClientID = "" + tenantMapping.AssignedTenant.AppNamespace = types.S4ApplicationNamespace + tenantMapping.AssignedTenant.Parameters.ClientID = "" tenantMappingsStorage.On("ListTenantMappings", ctx, mock.Anything).Return(map[string]types.TenantMapping{}, nil) }) + It("should return error when default S/4 certificate is not provided", func() { tms := TenantMappingsService{Storage: tenantMappingsStorage, IASService: iasService} err := tms.ProcessTenantMapping(ctx, tenantMapping) Expect(err).Error().To(MatchError(errors.S4CertificateNotFound)) }) + It("should create application for S/4 in IAS if it doesn't exist", func() { iasAppID := "appId" - tenantMapping.AssignedTenants[0].Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate = "s4TestCert" - tenantMappingsStorage.On("UpsertTenantMapping", ctx, mock.Anything).Return(nil) + tenantMapping.AssignedTenant.Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate = "s4TestCert" + + expectedTenantMapping := tenantMapping + expectedTenantMapping.AssignedTenant.Parameters.IASApplicationID = iasAppID + tenantMappingsStorage.On("UpsertTenantMapping", ctx, expectedTenantMapping).Return(nil) + iasService.On("GetApplicationByName", ctx, mock.Anything, mock.Anything).Return(types.Application{}, errors.IASApplicationNotFound) iasService.On("CreateApplication", ctx, mock.Anything, mock.Anything).Return(iasAppID, nil) + tms := TenantMappingsService{Storage: tenantMappingsStorage, IASService: iasService} err := tms.ProcessTenantMapping(ctx, tenantMapping) - Expect(tenantMapping.AssignedTenants[0].Parameters.IASApplicationID).To(Equal(iasAppID)) Expect(err).Error().ToNot(HaveOccurred()) }) + It("should get the application for S/4 in IAS if it exists", func() { iasAppID := "appId" - tenantMapping.AssignedTenants[0].Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate = "s4TestCert" - tenantMappingsStorage.On("UpsertTenantMapping", ctx, mock.Anything).Return(nil) + tenantMapping.AssignedTenant.Configuration.Credentials.InboundCommunicationCredentials.OAuth2mTLSAuthentication.Certificate = "s4TestCert" + + expectedTenantMapping := tenantMapping + expectedTenantMapping.AssignedTenant.Parameters.IASApplicationID = iasAppID + tenantMappingsStorage.On("UpsertTenantMapping", ctx, expectedTenantMapping).Return(nil) + iasService.On("GetApplicationByName", ctx, mock.Anything, mock.Anything).Return(types.Application{ID: iasAppID}, nil) - Expect(tenantMappingsStorage.AssertNotCalled(GinkgoT(), "CreateApplication")).To(BeTrue()) + tms := TenantMappingsService{Storage: tenantMappingsStorage, IASService: iasService} err := tms.ProcessTenantMapping(ctx, tenantMapping) - Expect(tenantMapping.AssignedTenants[0].Parameters.IASApplicationID).To(Equal(iasAppID)) Expect(err).Error().ToNot(HaveOccurred()) + Expect(tenantMappingsStorage.AssertNotCalled(GinkgoT(), "CreateApplication")).To(BeTrue()) }) }) }) diff --git a/components/ias-adapter/internal/service/ucl/service.go b/components/ias-adapter/internal/service/ucl/service.go new file mode 100644 index 0000000000..08f0917ffb --- /dev/null +++ b/components/ias-adapter/internal/service/ucl/service.go @@ -0,0 +1,56 @@ +package ucl + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" +) + +type Service struct { + client *http.Client +} + +func NewService(client *http.Client) Service { + return Service{ + client: client, + } +} + +type StatusReport struct { + State types.State `json:"state"` + Configuration any `json:"configuration,omitempty"` + Error string `json:"error,omitempty"` +} + +func (s Service) ReportStatus(ctx context.Context, url string, statusReport StatusReport) error { + body, err := json.Marshal(statusReport) + if err != nil { + return fmt.Errorf("failed to json marshal status report %+v: %w", statusReport, err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPatch, url, bytes.NewBuffer(body)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + resp, err := s.client.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unexpected response status %d, failed to read response body %w", resp.StatusCode, err) + } + return fmt.Errorf("unexpected response status %d, body: %s", resp.StatusCode, string(responseBody)) + } + + return nil +} diff --git a/components/ias-adapter/internal/service/ucl/service_test.go b/components/ias-adapter/internal/service/ucl/service_test.go new file mode 100644 index 0000000000..b399650f5e --- /dev/null +++ b/components/ias-adapter/internal/service/ucl/service_test.go @@ -0,0 +1,66 @@ +package ucl + +import ( + "context" + "net/http" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestUCLService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "UCL Service Test Suite") +} + +var _ = Describe("Reporting status", func() { + var ctx context.Context = context.Background() + + It("Errors When json marshal fails", func() { + service := NewService(&http.Client{}) + err := service.ReportStatus(ctx, "url", StatusReport{Configuration: make(chan int)}) + Expect(err.Error()).To(HavePrefix("failed to json marshal status report ")) + }) + + It("Errors When request creation fails", func() { + service := NewService(&http.Client{}) + var nilCtx context.Context = nil + err := service.ReportStatus(nilCtx, "invalid-url", StatusReport{}) + Expect(err.Error()).To(HavePrefix("failed to create request:")) + }) + + It("Errors When request execution fails", func() { + service := NewService(&http.Client{}) + err := service.ReportStatus(ctx, "invalid-url", StatusReport{}) + Expect(err.Error()).To(HavePrefix("failed to execute request:")) + }) + + It("Errors When status is not 200 and body is invalid", func() { + service := NewService(&http.Client{Transport: &testTransport{statusCode: http.StatusInternalServerError}}) + err := service.ReportStatus(ctx, "https://valid.url", StatusReport{}) + Expect(err.Error()).To(HavePrefix("unexpected response status 500, body:")) + }) + + It("Succeeds When status is 200", func() { + service := NewService(&http.Client{Transport: &testTransport{statusCode: http.StatusOK}}) + err := service.ReportStatus(ctx, "https://valid.url", StatusReport{}) + Expect(err).ToNot(HaveOccurred()) + }) +}) + +type testTransport struct { + err error + statusCode int +} + +func (tt *testTransport) RoundTrip(r *http.Request) (*http.Response, error) { + if tt.err != nil { + return nil, tt.err + } + + return &http.Response{ + StatusCode: tt.statusCode, + Status: http.StatusText(tt.statusCode), + }, nil +} diff --git a/components/ias-adapter/internal/storage/postgres/tenant_mapping.go b/components/ias-adapter/internal/storage/postgres/tenant_mapping.go index ecc6a7731a..5900bdbc30 100644 --- a/components/ias-adapter/internal/storage/postgres/tenant_mapping.go +++ b/components/ias-adapter/internal/storage/postgres/tenant_mapping.go @@ -48,10 +48,10 @@ func (c Connection) ListTenantMappings(ctx context.Context, formationID string) if err := json.Unmarshal([]byte(tenantMappingJSONString), &tenantMapping); err != nil { return tenantMappings, errors.Newf("failed to unmarshal tenant mapping: %w", err) } - if err := tenantMapping.AssignedTenants[0].SetConfiguration(ctx); err != nil { + if err := tenantMapping.AssignedTenant.SetConfiguration(ctx); err != nil { return tenantMappings, errors.Newf("failed to set tenant mapping assigned tenant configuration: %w", err) } - tenantMappings[tenantMapping.AssignedTenants[0].UCLApplicationID] = tenantMapping + tenantMappings[tenantMapping.AssignedTenant.AppID] = tenantMapping } return tenantMappings, nil @@ -71,7 +71,7 @@ func tenantMappingFields(tenantMapping types.TenantMapping) ([]any, error) { } return []any{ tenantMapping.FormationID, - tenantMapping.AssignedTenants[0].UCLApplicationID, + tenantMapping.AssignedTenant.AppID, string(tenantMappingBytes), }, nil } diff --git a/components/ias-adapter/internal/types/tenant_mapping.go b/components/ias-adapter/internal/types/tenant_mapping.go index 96595448fd..2232279b4e 100644 --- a/components/ias-adapter/internal/types/tenant_mapping.go +++ b/components/ias-adapter/internal/types/tenant_mapping.go @@ -10,24 +10,29 @@ import ( "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" ) +const ( + S4SAPManagedCommunicationScenario = "SAP_COM_1002" +) + var ( - ErrInvalidFormationID = errors.New("$.formationId is invalid or missing") - ErrInvalidAssignedTenantID = errors.New("$.assignedTenants[0].uclApplicationId is invalid or missing") + ErrInvalidFormationID = errors.New("$.context.uclFormationId is invalid or missing") + ErrInvalidAssignedTenantAppID = errors.New("$.assignedTenant.uclSystemTenantId is invalid or missing") ) type TenantMapping struct { - FormationID string `json:"formationId"` - ReceiverTenant ReceiverTenant `json:"receiverTenant"` - AssignedTenants []AssignedTenant `json:"assignedTenants"` + Context `json:"context"` + ReceiverTenant ReceiverTenant `json:"receiverTenant"` + AssignedTenant AssignedTenant `json:"assignedTenants"` +} + +type Context struct { + FormationID string `json:"uclFormationId"` + Operation Operation `json:"operation"` } func (tm TenantMapping) String() string { - if len(tm.AssignedTenants) == 0 { - return fmt.Sprintf("$.formationId: %s, $.receiverTenant.applicationUrl: %s, no assigned tenants", tm.FormationID, tm.ReceiverTenant.ApplicationURL) - } - assignedTenant := tm.AssignedTenants[0] - return fmt.Sprintf("$.formationId: '%s', $.receiverTenant.applicationUrl: '%s', $.assignedTenants[0]: (%s)", - tm.FormationID, tm.ReceiverTenant.ApplicationURL, &assignedTenant) + return fmt.Sprintf("$.context.uclFormationId: '%s', $.context.operation: '%s', $.receiverTenant.applicationUrl: '%s', $.assignedTenant: (%s)", + tm.FormationID, tm.Operation, tm.ReceiverTenant.ApplicationURL, tm.AssignedTenant) } type ReceiverTenant struct { @@ -35,9 +40,9 @@ type ReceiverTenant struct { } type ( - Operation string - State string - ApplicationType string + Operation string + State string + ApplicationNamespace string ) const ( @@ -45,18 +50,21 @@ const ( OperationUnassign Operation = "unassign" StateInitial State = "INITIAL" - StateReady State = "READY" StateConfigPending State = "CONFIG_PENDING" + StateCreateError State = "CREATE_ERROR" + StateDeleteError State = "DELETE_ERROR" + StateCreateReady State = "CREATE_READY" + StateDeleteReady State = "DELETE_READY" + StateReady State = "READY" - S4ApplicationType ApplicationType = "SAP S/4HANA Cloud" + S4ApplicationNamespace ApplicationNamespace = "sap.s4" ) type AssignedTenant struct { - UCLApplicationID string `json:"uclApplicationId"` - UCLApplicationType ApplicationType `json:"uclApplicationType"` - LocalTenantID string `json:"localTenantId"` - Operation Operation `json:"operation"` - ReverseAssignmentState State `json:"reverseAssignmentState"` + AppID string `json:"uclSystemTenantId"` + AppNamespace ApplicationNamespace `json:"applicationNamespace"` + LocalTenantID string `json:"applicationTenantId"` + ReverseAssignmentState State `json:"state"` Parameters AssignedTenantParameters `json:"parameters"` Config any `json:"configuration"` Configuration AssignedTenantConfiguration `json:"-"` @@ -64,23 +72,23 @@ type AssignedTenant struct { func (at *AssignedTenant) String() string { return fmt.Sprintf( - "$.operation: %s, $.localTenantId: %s, $.uclApplicationId: %s, $.uclApplicationType: %s, $.parameters.technicalIntegrationId: %s, $.configuration: %+v", - at.Operation, at.LocalTenantID, at.UCLApplicationID, at.UCLApplicationType, at.Parameters.ClientID, at.Configuration) + "$.applicationTenantId: %s, $.uclSystemTenantId: %s, $.applicationNamespace: %s, $.parameters.technicalIntegrationId: %s, $.configuration: %+v", + at.LocalTenantID, at.AppID, at.AppNamespace, at.Parameters.ClientID, at.Configuration) } func (at *AssignedTenant) SetConfiguration(ctx context.Context) error { log := logger.FromContext(ctx) if at.Config == nil { - log.Info().Msg("$.assignedTenants[0].configuration is empty") + log.Info().Msg("$.assignedTenant.configuration is empty") return nil } b, err := json.Marshal(at.Config) if err != nil { - return errors.Newf("failed to marshal $.assignedTenants[0].configuration: %w", err) + return errors.Newf("failed to marshal $.assignedTenant.configuration: %w", err) } if err := json.Unmarshal(b, &at.Configuration); err != nil || len(at.Configuration.ConsumedAPIs) == 0 { - log.Info().Msg("$.assignedTenants[0].configuration doesn't contain apis") + log.Info().Msg("$.assignedTenant.configuration doesn't contain apis") return nil } @@ -101,33 +109,28 @@ func (tm TenantMapping) Validate() error { if _, err := uuid.Parse(tm.FormationID); err != nil { return ErrInvalidFormationID } - if _, err := uuid.Parse(tm.AssignedTenants[0].UCLApplicationID); err != nil { - return ErrInvalidAssignedTenantID + if tm.Operation != OperationAssign && tm.Operation != OperationUnassign { + return errors.New("$.context.operation can only be assign or unassign") + } + if _, err := uuid.Parse(tm.AssignedTenant.AppID); err != nil { + return ErrInvalidAssignedTenantAppID } if tm.ReceiverTenant.ApplicationURL == "" { return errors.New("$.receiverTenant.applicationUrl is required") } - if tm.AssignedTenants[0].LocalTenantID == "" { - return errors.New("$.assignedTenants[0].localTenantId is required") + if tm.AssignedTenant.LocalTenantID == "" { + return errors.New("$.assignedTenant.applicationTenantId is required") } - if tm.AssignedTenants[0].UCLApplicationType == "" { - return errors.New("$.assignedTenants[0].uclApplicationType is required") - } - if tm.AssignedTenants[0].Operation != OperationAssign && tm.AssignedTenants[0].Operation != OperationUnassign { - return errors.New("$.assignedTenants[0].operation can only be assign or unassign") + if tm.AssignedTenant.AppNamespace == "" { + return errors.New("$.assignedTenant.applicationNamespace is required") } // S/4 applications are created by the IAS adapter and therefore the tenant mapping does not contain its clientID - if tm.AssignedTenants[0].UCLApplicationType != S4ApplicationType && tm.AssignedTenants[0].Parameters.ClientID == "" { - return errors.New("$.assignedTenants[0].parameters.technicalIntegrationId is required") + if tm.AssignedTenant.AppNamespace != S4ApplicationNamespace && tm.AssignedTenant.Parameters.ClientID == "" { + return errors.New("$.assignedTenant.parameters.technicalIntegrationId is required") } return nil } -type TenantMappingResponse struct { - State State `json:"state"` - Configuration TenantMappingConfiguration `json:"configuration"` -} - type TenantMappingConfiguration struct { Credentials Credentials `json:"credentials"` } diff --git a/components/ias-adapter/internal/types/tenant_mapping_test.go b/components/ias-adapter/internal/types/tenant_mapping_test.go index 21a100a436..93dbe8a4da 100644 --- a/components/ias-adapter/internal/types/tenant_mapping_test.go +++ b/components/ias-adapter/internal/types/tenant_mapping_test.go @@ -1,9 +1,10 @@ -package types +package types_test import ( "context" "testing" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -14,30 +15,30 @@ func TestTypes(t *testing.T) { } var _ = Describe("Tenant Mapping Type", func() { - When("Tenant mapping has $.assignedTenants[0].configuration", func() { + When("Tenant mapping has $.assignedTenant.configuration", func() { It("Should set the Configuration typed field", func() { - tenantMapping := TenantMapping{ - FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - ReceiverTenant: ReceiverTenant{ + tenantMapping := types.TenantMapping{ + Context: types.Context{ + FormationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Operation: types.OperationAssign, + }, + ReceiverTenant: types.ReceiverTenant{ ApplicationURL: "localhost", }, - AssignedTenants: []AssignedTenant{ - { - UCLApplicationID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", - Operation: OperationAssign, - Parameters: AssignedTenantParameters{ - ClientID: "clientID", - }, - Config: AssignedTenantConfiguration{ - ConsumedAPIs: []string{"qwe"}, - }, + AssignedTenant: types.AssignedTenant{ + AppID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + LocalTenantID: "2d933ae2-10c4-4d6f-b4d4-5e1553e4ff05", + Parameters: types.AssignedTenantParameters{ + ClientID: "clientID", + }, + Config: types.AssignedTenantConfiguration{ + ConsumedAPIs: []string{"qwe"}, }, }, } - Expect(tenantMapping.AssignedTenants[0].Configuration).To(Equal(AssignedTenantConfiguration{})) - Expect(tenantMapping.AssignedTenants[0].SetConfiguration(context.Background())).To(Succeed()) - Expect(tenantMapping.AssignedTenants[0].Configuration).To(Equal(AssignedTenantConfiguration{ConsumedAPIs: []string{"qwe"}})) + Expect(tenantMapping.AssignedTenant.Configuration).To(Equal(types.AssignedTenantConfiguration{})) + Expect(tenantMapping.AssignedTenant.SetConfiguration(context.Background())).To(Succeed()) + Expect(tenantMapping.AssignedTenant.Configuration).To(Equal(types.AssignedTenantConfiguration{ConsumedAPIs: []string{"qwe"}})) }) }) }) From 90fb7bc5dbd909333705679b30c0a85a418dd390 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:02:36 +0300 Subject: [PATCH 06/19] Fix IAS Adapter body unmarshal (#3797) * fix ias adapter body unmarshal * fix PR version --- chart/compass/values.yaml | 2 +- components/ias-adapter/internal/types/tenant_mapping.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 9df5ad50b3..88bf35f954 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -183,7 +183,7 @@ global: name: compass-hydrator ias_adapter: dir: dev/incubator/ - version: "PR-3784" + version: "PR-3797" name: compass-ias-adapter kyma_adapter: dir: dev/incubator/ diff --git a/components/ias-adapter/internal/types/tenant_mapping.go b/components/ias-adapter/internal/types/tenant_mapping.go index 2232279b4e..fd138eed18 100644 --- a/components/ias-adapter/internal/types/tenant_mapping.go +++ b/components/ias-adapter/internal/types/tenant_mapping.go @@ -22,7 +22,7 @@ var ( type TenantMapping struct { Context `json:"context"` ReceiverTenant ReceiverTenant `json:"receiverTenant"` - AssignedTenant AssignedTenant `json:"assignedTenants"` + AssignedTenant AssignedTenant `json:"assignedTenant"` } type Context struct { From 56e9100a3a5a847d47bba3579a56fa64fe56c4e2 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:14:37 +0300 Subject: [PATCH 07/19] remove leftover sync status report from IAS Adapter (#3798) --- .../handlers/automock/async_processor.go | 7 +++++ .../api/internal/handlers/tenant_mapping.go | 8 ++++-- .../internal/handlers/tenant_mapping_test.go | 16 +++++------- .../internal/service/processor/processor.go | 26 +++++-------------- .../internal/types/tenant_mapping.go | 14 ++++++++++ 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go b/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go index 23fe3a3119..9e0bf031a2 100644 --- a/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go +++ b/components/ias-adapter/internal/api/internal/handlers/automock/async_processor.go @@ -8,6 +8,8 @@ import ( mock "github.com/stretchr/testify/mock" types "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" + + ucl "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" ) // AsyncProcessor is an autogenerated mock type for the AsyncProcessor type @@ -20,6 +22,11 @@ func (_m *AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping ty _m.Called(ctx, tenantMapping) } +// ReportStatus provides a mock function with given fields: ctx, statusReport +func (_m *AsyncProcessor) ReportStatus(ctx context.Context, statusReport ucl.StatusReport) { + _m.Called(ctx, statusReport) +} + type mockConstructorTestingTNewAsyncProcessor interface { mock.TestingT Cleanup(func()) diff --git a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go index 86ff7a46bd..4b9be0043a 100644 --- a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go +++ b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping.go @@ -11,6 +11,7 @@ import ( "github.com/kyma-incubator/compass/components/ias-adapter/internal/api/internal" "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" "github.com/kyma-incubator/compass/components/ias-adapter/internal/logger" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" ) @@ -28,6 +29,7 @@ type TenantMappingsService interface { //go:generate mockery --name=AsyncProcessor --output=automock --outpkg=automock --case=underscore --disable-version-string type AsyncProcessor interface { ProcessTMRequest(ctx context.Context, tenantMapping types.TenantMapping) + ReportStatus(ctx context.Context, statusReport ucl.StatusReport) } type TenantMappingsHandler struct { @@ -36,6 +38,8 @@ type TenantMappingsHandler struct { } func (h TenantMappingsHandler) Patch(ctx *gin.Context) { + ctx.Set(locationHeader, ctx.GetHeader(locationHeader)) + var tenantMapping types.TenantMapping if err := json.NewDecoder(ctx.Request.Body).Decode(&tenantMapping); err != nil { err = errors.Newf("failed to decode tenant mapping body: %w", err) @@ -61,7 +65,6 @@ func (h TenantMappingsHandler) Patch(ctx *gin.Context) { } ctx.AbortWithStatus(http.StatusAccepted) - ctx.Set(locationHeader, ctx.GetHeader(locationHeader)) h.AsyncProcessor.ProcessTMRequest(ctx, tenantMapping) } @@ -94,7 +97,8 @@ func (h TenantMappingsHandler) handleValidateError(ctx *gin.Context, err error, } logger.FromContext(ctx).Info().Msgf("%s. Responding OK as assignment is safe to remove", err.Error()) - ctx.Status(http.StatusOK) + ctx.AbortWithStatus(http.StatusAccepted) + h.AsyncProcessor.ReportStatus(ctx, ucl.StatusReport{State: types.ReadyState(operation)}) } func logProcessing(ctx context.Context, tenantMapping types.TenantMapping) { diff --git a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go index bff73081aa..043e4324d5 100644 --- a/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go +++ b/components/ias-adapter/internal/api/internal/handlers/tenant_mapping_test.go @@ -8,6 +8,7 @@ import ( "github.com/kyma-incubator/compass/components/ias-adapter/internal/api/internal/handlers/automock" "github.com/kyma-incubator/compass/components/ias-adapter/internal/errors" + "github.com/kyma-incubator/compass/components/ias-adapter/internal/service/ucl" "github.com/kyma-incubator/compass/components/ias-adapter/internal/types" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -28,12 +29,6 @@ var _ = Describe("Tenant Mapping Handler", func() { Expect(responseBody).To(ContainSubstring(url.QueryEscape(expectedMessage))) Expect(w.Code).To(Equal(expectedCode)) } - expectSuccess = func(w *httptest.ResponseRecorder, expectedMessage string) { - responseBody, err := io.ReadAll(w.Body) - Expect(err).Error().ToNot(HaveOccurred()) - Expect(responseBody).To(ContainSubstring(url.QueryEscape(expectedMessage))) - Expect(w.Code).To(Equal(http.StatusOK)) - } ) BeforeEach(func() { @@ -121,13 +116,16 @@ var _ = Describe("Tenant Mapping Handler", func() { expectError(w, http.StatusInternalServerError, errExpected.Error()) }) - It("Should succeed if tenantMappings are less then 2", func() { + It("Should succeed if tenantMappings are less than 2", func() { mockService.On("CanSafelyRemoveTenantMapping", mock.Anything, mock.Anything).Return(true, nil) mockService.On("RemoveTenantMapping", mock.Anything, mock.Anything).Return(nil) - + expectedStatusReport := ucl.StatusReport{State: types.StateDeleteReady} + mockAsyncProcessor.On("ReportStatus", mock.Anything, expectedStatusReport).Return() w, ctx := createTestRequest(tenantMapping) + handler.Patch(ctx) - expectSuccess(w, "") + Expect(w.Code).To(Equal(http.StatusAccepted)) + Expect(mockAsyncProcessor.AssertNumberOfCalls(test, "ReportStatus", 1)).To(BeTrue()) }) }) }) diff --git a/components/ias-adapter/internal/service/processor/processor.go b/components/ias-adapter/internal/service/processor/processor.go index 4f727abd49..fc1ff1bca0 100644 --- a/components/ias-adapter/internal/service/processor/processor.go +++ b/components/ias-adapter/internal/service/processor/processor.go @@ -45,7 +45,7 @@ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping type if reverseAssignmentState != types.StateInitial && reverseAssignmentState != types.StateReady { log.Warn().Msgf("skipping processing tenant mapping notification with $.assignedTenant.state '%s'", reverseAssignmentState) - p.reportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending}) + p.ReportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending}) return } } @@ -57,41 +57,27 @@ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping type if operation == types.OperationAssign { if errors.Is(err, errors.IASApplicationNotFound) { - p.reportStatus(ctx, ucl.StatusReport{State: errorState(operation), Error: err.Error()}) + p.ReportStatus(ctx, ucl.StatusReport{State: types.ErrorState(operation), Error: err.Error()}) return } if errors.Is(err, errors.S4CertificateNotFound) { log.Info().Msgf("S/4 certificate not provided. Responding with CONFIG_PENDING.") - p.reportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending, Configuration: s4Config}) + p.ReportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending, Configuration: s4Config}) return } } - p.reportStatus(ctx, ucl.StatusReport{State: errorState(operation), Error: err.Error()}) + p.ReportStatus(ctx, ucl.StatusReport{State: types.ErrorState(operation), Error: err.Error()}) return } - p.reportStatus(ctx, ucl.StatusReport{State: readyState(operation)}) + p.ReportStatus(ctx, ucl.StatusReport{State: types.ReadyState(operation)}) } -func (p AsyncProcessor) reportStatus(ctx context.Context, statusReport ucl.StatusReport) { +func (p AsyncProcessor) ReportStatus(ctx context.Context, statusReport ucl.StatusReport) { statusReportURL := ctx.Value(locationHeader).(string) if err := p.UCLService.ReportStatus(ctx, statusReportURL, statusReport); err != nil { logger.FromContext(ctx).Error().Msgf("failed to report status to '%s': %s", statusReportURL, err) } } - -func readyState(operation types.Operation) types.State { - if operation == types.OperationAssign { - return types.StateCreateReady - } - return types.StateDeleteReady -} - -func errorState(operation types.Operation) types.State { - if operation == types.OperationAssign { - return types.StateCreateError - } - return types.StateDeleteError -} diff --git a/components/ias-adapter/internal/types/tenant_mapping.go b/components/ias-adapter/internal/types/tenant_mapping.go index fd138eed18..d72c9666db 100644 --- a/components/ias-adapter/internal/types/tenant_mapping.go +++ b/components/ias-adapter/internal/types/tenant_mapping.go @@ -60,6 +60,20 @@ const ( S4ApplicationNamespace ApplicationNamespace = "sap.s4" ) +func ReadyState(operation Operation) State { + if operation == OperationAssign { + return StateCreateReady + } + return StateDeleteReady +} + +func ErrorState(operation Operation) State { + if operation == OperationAssign { + return StateCreateError + } + return StateDeleteError +} + type AssignedTenant struct { AppID string `json:"uclSystemTenantId"` AppNamespace ApplicationNamespace `json:"applicationNamespace"` From 2d1e49956b4a7d897120f1341d4a5c1a58a618a7 Mon Sep 17 00:00:00 2001 From: PetarTodorovv <31803034+PetarTodorovv@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:04:39 +0300 Subject: [PATCH 08/19] Minor log improvement (#3799) * Minor log improvement * fix image version --- chart/compass/values.yaml | 2 +- components/director/internal/domain/formation/resolver.go | 2 +- components/director/internal/domain/formation/service.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 88bf35f954..9a6880f420 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3794" + version: "PR-3799" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formation/resolver.go b/components/director/internal/domain/formation/resolver.go index feb46890fb..853c16fa78 100644 --- a/components/director/internal/domain/formation/resolver.go +++ b/components/director/internal/domain/formation/resolver.go @@ -605,7 +605,7 @@ func (r *Resolver) ResynchronizeFormationNotifications(ctx context.Context, form updatedFormation, err := r.service.ResynchronizeFormationNotifications(ctx, formationID, shouldReset) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "while resynchronizing formation with ID: %s", formationID) } if err = tx.Commit(); err != nil { diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index 99dbb44319..bd51cc244a 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -1141,7 +1141,6 @@ func (s *service) FinalizeDraftFormation(ctx context.Context, formationID string // ResynchronizeFormationNotifications sends all notifications that are in error or initial state func (s *service) ResynchronizeFormationNotifications(ctx context.Context, formationID string, shouldReset bool) (*model.Formation, error) { - log.C(ctx).Infof("Resynchronizing formation with ID: %q", formationID) tenantID, err := tenant.LoadFromContext(ctx) if err != nil { return nil, errors.Wrapf(err, "while loading tenant from context") @@ -1151,6 +1150,7 @@ func (s *service) ResynchronizeFormationNotifications(ctx context.Context, forma if err != nil { return nil, errors.Wrapf(err, "while getting formation with ID %q for tenant %q", formationID, tenantID) } + log.C(ctx).Infof("Resynchronizing formation with ID: %s and name: %s", formationID, formation.Name) return s.resynchronizeFormation(ctx, formation, tenantID, shouldReset) } From 5302fab69f6bdbabf72994a3ea5268c455cf71a0 Mon Sep 17 00:00:00 2001 From: Lachezar Bogomilov <64094901+la4ezar@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:08:40 +0300 Subject: [PATCH 09/19] Fix resynchronization when there is a participant without webhook but with error (#3802) * Initial implementation. * Add unit tests and adjust the if case for unassign. * Update values.yaml --- chart/compass/values.yaml | 2 +- .../formation/finalize_formation_test.go | 8 +-- .../domain/formation/fixtures_test.go | 2 +- .../domain/formation/notifications_test.go | 4 +- .../internal/domain/formation/service.go | 20 ++++--- .../internal/domain/formation/service_test.go | 57 +++++++++++++++++-- .../domain/formationassignment/service.go | 4 ++ .../formationassignment/service_test.go | 20 +++++++ 8 files changed, 96 insertions(+), 21 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 9a6880f420..c62269d2be 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3799" + version: "PR-3802" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formation/finalize_formation_test.go b/components/director/internal/domain/formation/finalize_formation_test.go index 20715b863b..0843e96d0d 100644 --- a/components/director/internal/domain/formation/finalize_formation_test.go +++ b/components/director/internal/domain/formation/finalize_formation_test.go @@ -34,10 +34,10 @@ func TestServiceFinalizeDraftFormation(t *testing.T) { allStates := model.ResynchronizableFormationAssignmentStates - fa1 := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialFormationState) - fa2 := fixFormationAssignmentModelWithParameters("id2", FormationID, RuntimeContextID, ApplicationID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeApplication, model.CreateErrorFormationState) - fa3 := fixFormationAssignmentModelWithParameters("id3", FormationID, RuntimeID, RuntimeContextID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeRuntimeContext, model.DeletingFormationState) - fa4 := fixFormationAssignmentModelWithParameters("id4", FormationID, RuntimeContextID, RuntimeContextID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeRuntimeContext, model.DeleteErrorFormationState) + fa1 := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialAssignmentState) + fa2 := fixFormationAssignmentModelWithParameters("id2", FormationID, RuntimeContextID, ApplicationID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeApplication, model.CreateErrorAssignmentState) + fa3 := fixFormationAssignmentModelWithParameters("id3", FormationID, RuntimeID, RuntimeContextID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeRuntimeContext, model.DeletingAssignmentState) + fa4 := fixFormationAssignmentModelWithParameters("id4", FormationID, RuntimeContextID, RuntimeContextID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeRuntimeContext, model.DeleteErrorAssignmentState) formationAssignments := []*model.FormationAssignment{fa1, fa2, fa3, fa4} formationAssignmentsInDeletingState := cloneFormationAssignments(formationAssignments) diff --git a/components/director/internal/domain/formation/fixtures_test.go b/components/director/internal/domain/formation/fixtures_test.go index 5d7a218d6b..5416f66470 100644 --- a/components/director/internal/domain/formation/fixtures_test.go +++ b/components/director/internal/domain/formation/fixtures_test.go @@ -1450,7 +1450,7 @@ func fixFormationAssignmentModel(state string, configValue json.RawMessage) *mod } } -func fixFormationAssignmentModelWithParameters(id, formationID, source, target string, sourceType, targetType model.FormationAssignmentType, state model.FormationState) *model.FormationAssignment { +func fixFormationAssignmentModelWithParameters(id, formationID, source, target string, sourceType, targetType model.FormationAssignmentType, state model.FormationAssignmentState) *model.FormationAssignment { return &model.FormationAssignment{ ID: id, FormationID: formationID, diff --git a/components/director/internal/domain/formation/notifications_test.go b/components/director/internal/domain/formation/notifications_test.go index c01e4d1032..b3bf23b3bb 100644 --- a/components/director/internal/domain/formation/notifications_test.go +++ b/components/director/internal/domain/formation/notifications_test.go @@ -686,8 +686,8 @@ func Test_NotificationsService_SendNotification(t *testing.T) { subtype := "subtype" - fa := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialFormationState) - reverseFa := fixFormationAssignmentModelWithParameters("id2", FormationID, ApplicationID, RuntimeID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.InitialFormationState) + fa := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialAssignmentState) + reverseFa := fixFormationAssignmentModelWithParameters("id2", FormationID, ApplicationID, RuntimeID, model.FormationAssignmentTypeApplication, model.FormationAssignmentTypeRuntime, model.InitialAssignmentState) formationLifecycleTemplateInputWithCreateOperation := fixFormationLifecycleInput(model.CreateFormation, TntCustomerID, TntExternalID) formationLifecycleTemplateInputWithDeleteOperation := fixFormationLifecycleInput(model.DeleteFormation, TntCustomerID, TntExternalID) diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index bd51cc244a..02730dad95 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -1247,16 +1247,18 @@ func (s *service) resynchronizeFormationAssignmentNotifications(ctx context.Cont } } - if notificationForFA != nil { - faClone := fa.Clone() - if operation == model.UnassignFormation { - faClone.SetStateToDeleting() - formationassignment.ResetAssignmentConfigAndError(faClone) - } else if operation == model.AssignFormation { - faClone.State = string(model.InitialAssignmentState) - // Cleanup the error if present as new notification will be sent. The previous configuration should be left intact. - faClone.Error = nil + faClone := fa.Clone() + if notificationForFA != nil && operation == model.UnassignFormation { + faClone.SetStateToDeleting() + formationassignment.ResetAssignmentConfigAndError(faClone) + if err := s.formationAssignmentService.Update(ctxWithTransact, faClone.ID, faClone); err != nil { + return errors.Wrapf(err, "while updating formation assignment with ID: '%s' to '%s' state", faClone.ID, faClone.State) } + } + if operation == model.AssignFormation { + faClone.State = string(model.InitialAssignmentState) + // Cleanup the error if present as new notification will be sent. The previous configuration should be left intact. + faClone.Error = nil if err := s.formationAssignmentService.Update(ctxWithTransact, faClone.ID, faClone); err != nil { return errors.Wrapf(err, "while updating formation assignment with ID: '%s' to '%s' state", faClone.ID, faClone.State) } diff --git a/components/director/internal/domain/formation/service_test.go b/components/director/internal/domain/formation/service_test.go index 914ef18c4f..9fc51326bc 100644 --- a/components/director/internal/domain/formation/service_test.go +++ b/components/director/internal/domain/formation/service_test.go @@ -3127,10 +3127,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { Version: 0, } - fa1 := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialFormationState) - fa2 := fixFormationAssignmentModelWithParameters("id2", FormationID, RuntimeContextID, ApplicationID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeApplication, model.CreateErrorFormationState) - fa3 := fixFormationAssignmentModelWithParameters("id3", FormationID, RuntimeID, RuntimeContextID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeRuntimeContext, model.DeletingFormationState) - fa4 := fixFormationAssignmentModelWithParameters("id4", FormationID, RuntimeContextID, RuntimeContextID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeRuntimeContext, model.DeleteErrorFormationState) + fa1 := fixFormationAssignmentModelWithParameters("id1", FormationID, RuntimeID, ApplicationID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeApplication, model.InitialAssignmentState) + fa2 := fixFormationAssignmentModelWithParameters("id2", FormationID, RuntimeContextID, ApplicationID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeApplication, model.CreateErrorAssignmentState) + fa4 := fixFormationAssignmentModelWithParameters("id4", FormationID, RuntimeContextID, RuntimeContextID, model.FormationAssignmentTypeRuntimeContext, model.FormationAssignmentTypeRuntimeContext, model.DeleteErrorAssignmentState) + fa3 := fixFormationAssignmentModelWithParameters("id3", FormationID, RuntimeID, RuntimeContextID, model.FormationAssignmentTypeRuntime, model.FormationAssignmentTypeRuntimeContext, model.DeletingAssignmentState) formationAssignments := []*model.FormationAssignment{fa1, fa2, fa3, fa4} formationAssignmentsInDeletingState := cloneFormationAssignments(formationAssignments) @@ -3139,6 +3139,10 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { formationAssignmentsInInitialState := cloneFormationAssignments(formationAssignments) setAssignmentsToState(model.InitialAssignmentState, formationAssignmentsInInitialState...) + formationAssignmentsInReadyAndOneCreateErrorStates := cloneFormationAssignments(formationAssignments) + setAssignmentsToState(model.ReadyAssignmentState, formationAssignmentsInReadyAndOneCreateErrorStates...) + formationAssignmentsInReadyAndOneCreateErrorStates[3].State = string(model.CreateErrorAssignmentState) + reverseAssignment := &model.FormationAssignment{ ID: "id1", FormationID: FormationID, @@ -3184,6 +3188,12 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { formationAssignmentInitialPairs = append(formationAssignmentInitialPairs, fixFormationAssignmentPairWithNoReverseAssignment(notificationsForAssignments[i], formationAssignmentsInInitialState[i])) } + var formationAssignmentReadyAndCreateErrorPairs = make([]*formationassignment.AssignmentMappingPairWithOperation, 0, len(formationAssignmentsInReadyAndOneCreateErrorStates)) + for i := range formationAssignmentsInReadyAndOneCreateErrorStates { + formationAssignmentReadyAndCreateErrorPairs = append(formationAssignmentReadyAndCreateErrorPairs, fixFormationAssignmentPairWithNoReverseAssignment(nil, formationAssignmentsInReadyAndOneCreateErrorStates[i])) + formationAssignmentReadyAndCreateErrorPairs[i].Operation = model.AssignFormation + } + testSchema, err := labeldef.NewSchemaForFormations([]string{testScenario, testFormationName}) assert.NoError(t, err) testSchemaLblDef := fixScenariosLabelDefinition(TntInternalID, testSchema) @@ -3255,6 +3265,45 @@ func TestServiceResynchronizeFormationNotifications(t *testing.T) { return repo }, }, + { + Name: "success when resynchronization is successful and there is formation assignment in create error state and error but no webhook", + FormationAssignments: formationAssignmentsInReadyAndOneCreateErrorStates, + TxFn: func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) { + return txGen.ThatSucceedsMultipleTimes(2) + }, + FormationAssignmentServiceFn: func() *automock.FormationAssignmentService { + svc := &automock.FormationAssignmentService{} + svc.On("GetAssignmentsForFormationWithStates", txtest.CtxWithDBMatcher(), TntInternalID, FormationID, allStates).Return(formationAssignmentsInReadyAndOneCreateErrorStates, nil).Once() + + for _, fa := range formationAssignmentsInReadyAndOneCreateErrorStates { + svc.On("GetReverseBySourceAndTarget", txtest.CtxWithDBMatcher(), FormationID, fa.Source, fa.Target).Return(nil, apperrors.NewNotFoundError(resource.FormationAssignment, "")).Once() + } + + svc.On("ProcessFormationAssignmentPair", txtest.CtxWithDBMatcher(), formationAssignmentReadyAndCreateErrorPairs[0]).Return(false, nil).Once() + svc.On("ProcessFormationAssignmentPair", txtest.CtxWithDBMatcher(), formationAssignmentReadyAndCreateErrorPairs[1]).Return(false, nil).Once() + svc.On("ProcessFormationAssignmentPair", txtest.CtxWithDBMatcher(), formationAssignmentReadyAndCreateErrorPairs[2]).Return(false, nil).Once() + svc.On("ProcessFormationAssignmentPair", txtest.CtxWithDBMatcher(), formationAssignmentReadyAndCreateErrorPairs[3]).Return(false, nil).Once() + + svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[0].ID, formationAssignmentsInInitialState[0]).Return(nil).Once() + svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[1].ID, formationAssignmentsInInitialState[1]).Return(nil).Once() + svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[2].ID, formationAssignmentsInInitialState[2]).Return(nil).Once() + svc.On("Update", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[3].ID, formationAssignmentsInInitialState[3]).Return(nil).Once() + return svc + }, + FormationAssignmentNotificationServiceFN: func() *automock.FormationAssignmentNotificationsService { + svc := &automock.FormationAssignmentNotificationsService{} + svc.On("GenerateFormationAssignmentNotification", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[0], model.AssignFormation).Return(nil, nil).Once() + svc.On("GenerateFormationAssignmentNotification", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[1], model.AssignFormation).Return(nil, nil).Once() + svc.On("GenerateFormationAssignmentNotification", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[2], model.AssignFormation).Return(nil, nil).Once() + svc.On("GenerateFormationAssignmentNotification", txtest.CtxWithDBMatcher(), formationAssignmentsInReadyAndOneCreateErrorStates[3], model.AssignFormation).Return(nil, nil).Once() + return svc + }, + FormationRepositoryFn: func() *automock.FormationRepository { + repo := &automock.FormationRepository{} + repo.On("Get", ctx, FormationID, TntInternalID).Return(testFormation, nil).Once() + return repo + }, + }, { Name: "success when resynchronization is successful and there are NO left formation assignments should unassign", FormationAssignments: formationAssignments, diff --git a/components/director/internal/domain/formationassignment/service.go b/components/director/internal/domain/formationassignment/service.go index 866dd83d1a..db471219dc 100644 --- a/components/director/internal/domain/formationassignment/service.go +++ b/components/director/internal/domain/formationassignment/service.go @@ -633,6 +633,10 @@ func (s *service) processFormationAssignmentsWithReverseNotification(ctx context if assignmentReqMappingClone.Request == nil { assignment.State = string(model.ReadyAssignmentState) + if mappingPair.Operation == model.AssignFormation { + // In case of error in the assignment we want to clear it + assignment.Error = nil + } log.C(ctx).Infof("In the formation assignment mapping pair, assignment with ID: %q hasn't attached webhook request. Updating the formation assignment to %q state without sending notification", assignment.ID, assignment.State) if err := s.Update(ctx, assignment.ID, assignment); err != nil { return errors.Wrapf(err, "while updating formation assignment for formation with ID: %q with source: %q and target: %q", assignment.FormationID, assignment.Source, assignment.Target) diff --git a/components/director/internal/domain/formationassignment/service_test.go b/components/director/internal/domain/formationassignment/service_test.go index 249a20be21..dd8d486174 100644 --- a/components/director/internal/domain/formationassignment/service_test.go +++ b/components/director/internal/domain/formationassignment/service_test.go @@ -2303,6 +2303,26 @@ func TestService_ProcessFormationAssignmentPair(t *testing.T) { return repo }, }, + { + Name: "Success: ready state assignment with error and no request", + Context: ctxWithTenant, + FormationAssignmentPairWithOperation: &formationassignment.AssignmentMappingPairWithOperation{ + AssignmentMappingPair: &formationassignment.AssignmentMappingPair{ + AssignmentReqMapping: &formationassignment.FormationAssignmentRequestMapping{ + Request: nil, + FormationAssignment: createErrorStateAssignment, + }, + ReverseAssignmentReqMapping: nil, + }, + Operation: model.AssignFormation, + }, + FormationAssignmentRepo: func() *automock.FormationAssignmentRepository { + repo := &automock.FormationAssignmentRepository{} + repo.On("Exists", ctxWithTenant, TestID, TestTenantID).Return(true, nil).Once() + repo.On("Update", ctxWithTenant, readyStateAssignment).Return(nil).Once() + return repo + }, + }, { Name: "Error when there is no request and update fails", Context: ctxWithTenant, From b3f680dad24bf28077017c36f5617fda86d823ab Mon Sep 17 00:00:00 2001 From: StanislavStefanov <39959090+StanislavStefanov@users.noreply.github.com> Date: Thu, 11 Apr 2024 11:08:39 +0300 Subject: [PATCH 10/19] Encode the OData query parameters of the URL in ORD Service e2e tests (#3803) * encode query params * adjust PR version --- chart/compass/values.yaml | 2 +- tests/instance-creator/tests/config.go | 3 +- tests/ord-service/tests/api_test.go | 229 ++++++++++++++---- .../tests/subscription_flow_test.go | 68 ++++-- .../fixtures/application_template_queries.go | 1 + tests/pkg/fixtures/formation_queries.go | 4 +- ..._assigments_async_custom_config_matcher.go | 5 +- .../asserters/formation_is_deleted.go | 5 +- .../asserters/lifecycle_notification.go | 7 +- .../notifications/asserters/notifications.go | 7 +- .../asserters/notifications_unassign.go | 5 +- .../operations/assign_application.go | 3 +- .../operations/create_formation.go | 3 +- .../operations/delete_formation.go | 3 +- .../operations/update_webhook.go | 3 +- .../resource-providers/formation.go | 3 +- 16 files changed, 263 insertions(+), 88 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index c62269d2be..39e3a0dd30 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -232,7 +232,7 @@ global: name: compass-console e2e_tests: dir: dev/incubator/ - version: "PR-3789" + version: "PR-3803" name: compass-e2e-tests isLocalEnv: false isForTesting: false diff --git a/tests/instance-creator/tests/config.go b/tests/instance-creator/tests/config.go index 211d4bb2f5..88d4fbddf4 100644 --- a/tests/instance-creator/tests/config.go +++ b/tests/instance-creator/tests/config.go @@ -1,12 +1,13 @@ package tests import ( + "time" + directorcfg "github.com/kyma-incubator/compass/components/director/pkg/config" "github.com/kyma-incubator/compass/components/director/pkg/credloader" "github.com/kyma-incubator/compass/tests/pkg/certs/certprovider" "github.com/kyma-incubator/compass/tests/pkg/config" "github.com/kyma-incubator/compass/tests/pkg/subscription" - "time" ) type InstanceCreatorConfig struct { diff --git a/tests/ord-service/tests/api_test.go b/tests/ord-service/tests/api_test.go index 34e387cbf7..01c1f12939 100644 --- a/tests/ord-service/tests/api_test.go +++ b/tests/ord-service/tests/api_test.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "net/http" + "net/url" urlpkg "net/url" "strings" "testing" @@ -164,19 +165,35 @@ func TestORDService(t *testing.T) { extIssuerCertHttpClient := CreateHttpClientWithCert(providerClientKey, providerRawCertChain, conf.SkipSSLValidation) t.Run("401 when requests to ORD Service are unsecured", func(t *testing.T) { - makeRequestWithStatusExpect(t, unsecuredHttpClient, conf.ORDServiceURL+"/$metadata?$format=json", http.StatusUnauthorized) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/$metadata?" + params.Encode() + makeRequestWithStatusExpect(t, unsecuredHttpClient, serviceURL, http.StatusUnauthorized) }) t.Run("400 when requests to ORD Service do not have tenant header", func(t *testing.T) { - makeRequestWithStatusExpect(t, intSystemHttpClient, conf.ORDServiceURL+"/consumptionBundles?$format=json", http.StatusBadRequest) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/consumptionBundles?" + params.Encode() + makeRequestWithStatusExpect(t, intSystemHttpClient, serviceURL, http.StatusBadRequest) }) t.Run("400 when requests to ORD Service have wrong tenant header", func(t *testing.T) { - request.MakeRequestWithHeadersAndStatusExpect(t, intSystemHttpClient, conf.ORDServiceURL+"/consumptionBundles?$format=json", map[string][]string{tenantHeader: {" "}}, http.StatusBadRequest, conf.ORDServiceDefaultResponseType) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/consumptionBundles?" + params.Encode() + request.MakeRequestWithHeadersAndStatusExpect(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {" "}}, http.StatusBadRequest, conf.ORDServiceDefaultResponseType) }) t.Run("400 when requests to ORD Service api specification do not have tenant header", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/apis?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/apis?" + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.Equal(t, len(appInput.Bundles[0].APIDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -187,7 +204,11 @@ func TestORDService(t *testing.T) { }) t.Run("400 when requests to ORD Service event specification do not have tenant header", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/events?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/events?" + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.Equal(t, len(appInput.Bundles[0].EventDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -198,7 +219,11 @@ func TestORDService(t *testing.T) { }) t.Run("400 when requests to ORD Service api specification have wrong tenant header", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/apis?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/apis?" + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.Equal(t, len(appInput.Bundles[0].APIDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -209,7 +234,11 @@ func TestORDService(t *testing.T) { }) t.Run("400 when requests to ORD Service event specification have wrong tenant header", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/events?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/events?" + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.Equal(t, len(appInput.Bundles[0].EventDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -228,14 +257,23 @@ func TestORDService(t *testing.T) { }) t.Run("Requesting Packages returns empty", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, fmt.Sprintf("%s/packages?$expand=apis,events&$format=json", conf.ORDServiceURL), map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$expand", "apis,events") + params.Add("$format", "json") + + serviceURL := conf.ORDServiceURL + "/packages?" + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.Equal(t, 0, len(gjson.Get(respBody, "value").Array())) }) t.Run("Requesting filtering of Bundles that do not have only ODATA APIs", func(t *testing.T) { - escapedFilterValue := urlpkg.PathEscape("apis/any(d:d/apiProtocol ne 'odata-v2')") + params := url.Values{} + params.Add("$filter", "apis/any(d:d/apiProtocol ne 'odata-v2')") + params.Add("$expand", "apis") + params.Add("$format", "json") - respBody := makeRequestWithHeaders(t, intSystemHttpClient, fmt.Sprintf("%s/consumptionBundles?$filter=%s&$expand=apis&$format=json", conf.ORDServiceURL, escapedFilterValue), map[string][]string{tenantHeader: {tenantAPIProtocolFiltering}}) + serviceURL := conf.ORDServiceURL + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {tenantAPIProtocolFiltering}}) require.Equal(t, len(appInputAPIProtocolFiltering.Bundles)-1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, appInputAPIProtocolFiltering.Bundles[0].Name, gjson.Get(respBody, "value.0.title").String()) @@ -257,9 +295,13 @@ func TestORDService(t *testing.T) { }) t.Run("Requesting filtering of Bundles that have only ODATA APIs", func(t *testing.T) { - escapedFilterValue := urlpkg.PathEscape("apis/all(d:d/apiProtocol eq 'odata-v2')") + params := url.Values{} + params.Add("$filter", "apis/all(d:d/apiProtocol eq 'odata-v2')") + params.Add("$expand", "apis") + params.Add("$format", "json") - respBody := makeRequestWithHeaders(t, intSystemHttpClient, fmt.Sprintf("%s/consumptionBundles?$filter=%s&$expand=apis&$format=json", conf.ORDServiceURL, escapedFilterValue), map[string][]string{tenantHeader: {tenantAPIProtocolFiltering}}) + serviceURL := conf.ORDServiceURL + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {tenantAPIProtocolFiltering}}) require.Equal(t, len(appInputAPIProtocolFiltering.Bundles)-1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, appInputAPIProtocolFiltering.Bundles[1].Name, gjson.Get(respBody, "value.0.title").String()) @@ -282,7 +324,11 @@ func TestORDService(t *testing.T) { for _, resource := range []string{"vendors", "tombstones", "products"} { // This tests assert integrity between ORD Service JPA model and our Database model t.Run(fmt.Sprintf("Requesting %s", resource), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, fmt.Sprintf("%s/%s?$format=json", conf.ORDServiceURL, resource), map[string][]string{tenantHeader: {defaultTestTenant}}) + params := url.Values{} + params.Add("$format", "json") + + serviceURL := fmt.Sprintf("%s/%s?", conf.ORDServiceURL, resource) + params.Encode() + respBody := makeRequestWithHeaders(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) require.True(t, gjson.Get(respBody, "value").Exists()) }) } @@ -297,7 +343,10 @@ func TestORDService(t *testing.T) { fixtures.AssignFormationWithTenantObjectType(t, ctx, certSecuredGraphQLClient, formationInput, subTenantID, tenantFilteringTenant) // assert no system instances are visible without formation - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/systemInstances?$format=json", map[string][]string{tenantHeader: {subTenantID}}) + params := url.Values{} + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, intSystemHttpClient, conf.ORDServiceURL+"/systemInstances?", map[string][]string{tenantHeader: {subTenantID}}, params) require.Equal(t, 0, len(gjson.Get(respBody, "value").Array())) // assign application to scenario @@ -352,7 +401,10 @@ func TestORDService(t *testing.T) { } { t.Run(fmt.Sprintf("Requesting System Instances for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/systemInstances?$format=json", testData.headers) + params := url.Values{} + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/systemInstances?", testData.headers, params) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Name, gjson.Get(respBody, "value.0.title").String()) @@ -360,7 +412,11 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting System Instances with apis for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/systemInstances?$expand=apis&$format=json", testData.headers) + params := url.Values{} + params.Add("$expand", "apis") + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/systemInstances?", testData.headers, params) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Name, gjson.Get(respBody, "value.0.title").String()) require.Equal(t, *testData.appInput.Description, gjson.Get(respBody, "value.0.description").String()) @@ -369,7 +425,11 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting System Instances with events for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/systemInstances?$expand=events&$format=json", testData.headers) + params := url.Values{} + params.Add("$expand", "events") + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/systemInstances?", testData.headers, params) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Name, gjson.Get(respBody, "value.0.title").String()) require.Equal(t, *testData.appInput.Description, gjson.Get(respBody, "value.0.description").String()) @@ -378,7 +438,10 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting Bundles for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/consumptionBundles?$format=json", testData.headers) + params := url.Values{} + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) require.Equal(t, len(testData.appInput.Bundles), len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Bundles[0].Name, gjson.Get(respBody, "value.0.title").String()) @@ -386,13 +449,19 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting APIs and their specs for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/apis?$format=json", testData.headers) + params := url.Values{} + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/apis?", testData.headers, params) assertEqualAPIDefinitions(t, testData.appInput.Bundles[0].APIDefinitions, gjson.Get(respBody, "value").String(), testData.apisMap, testData.client, testData.headers) }) t.Run(fmt.Sprintf("Requesting Events and their specs for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/events?$format=json", testData.headers) + params := url.Values{} + params.Add("$format", "json") + + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/events?", testData.headers, params) assertEqualEventDefinitions(t, testData.appInput.Bundles[0].EventDefinitions, gjson.Get(respBody, "value").String(), testData.eventsMap, testData.client, testData.headers) }) @@ -400,11 +469,20 @@ func TestORDService(t *testing.T) { // Paging: t.Run(fmt.Sprintf("Requesting paging of Bundles for tenant %s returns them as expected", testData.msg), func(t *testing.T) { totalCount := len(testData.appInput.Bundles) + params := url.Values{} + params.Add("$top", "10") + params.Add("$skip", "0") + params.Add("$format", "json") - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/consumptionBundles?$top=10&$skip=0&$format=json", testData.headers) + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) require.Equal(t, totalCount, len(gjson.Get(respBody, "value").Array())) - respBody = makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$top=10&$skip=%d&$format=json", testData.url, totalCount), testData.headers) + params = url.Values{} + params.Add("$top", "10") + params.Add("$skip", fmt.Sprintf("%d", totalCount)) + params.Add("$format", "json") + + respBody = makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) require.Equal(t, 0, len(gjson.Get(respBody, "value").Array())) }) @@ -442,12 +520,16 @@ func TestORDService(t *testing.T) { t.Run(fmt.Sprintf("Requesting filtering of Bundles for tenant %s returns them as expected", testData.msg), func(t *testing.T) { bndlName := testData.appInput.Bundles[0].Name - escapedFilterValue := urlpkg.PathEscape(fmt.Sprintf("title eq '%s'", bndlName)) - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$filter=(%s)&$format=json", testData.url, escapedFilterValue), testData.headers) + params := urlpkg.Values{} + params.Add("$filter", fmt.Sprintf("(title eq '%s')", bndlName)) + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) - escapedFilterValue = urlpkg.PathEscape(fmt.Sprintf("title ne '%s'", bndlName)) - respBody = makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$filter=(%s)&$format=json", testData.url, escapedFilterValue), testData.headers) + params.Set("$filter", fmt.Sprintf("(title ne '%s')", bndlName)) + serviceURL = testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody = makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, 0, len(gjson.Get(respBody, "value").Array())) }) @@ -455,12 +537,17 @@ func TestORDService(t *testing.T) { totalCount := len(testData.appInput.Bundles[0].APIDefinitions) apiName := testData.appInput.Bundles[0].APIDefinitions[0].Name - escapedFilterValue := urlpkg.PathEscape(fmt.Sprintf("title eq '%s'", apiName)) - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=apis($filter=(%s))&$format=json", testData.url, escapedFilterValue), testData.headers) + params := urlpkg.Values{} + + params.Add("$expand", fmt.Sprintf("apis($filter=(title eq '%s'))", apiName)) + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) - escapedFilterValue = urlpkg.PathEscape(fmt.Sprintf("title ne '%s'", apiName)) - respBody = makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=apis($filter=(%s))&$format=json", testData.url, escapedFilterValue), testData.headers) + params.Set("$expand", fmt.Sprintf("apis($filter=(title ne '%s'))", apiName)) + serviceURL = testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody = makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, totalCount-1, len(gjson.Get(respBody, "value.0.apis").Array())) }) @@ -468,25 +555,37 @@ func TestORDService(t *testing.T) { totalCount := len(testData.appInput.Bundles[0].EventDefinitions) eventName := testData.appInput.Bundles[0].EventDefinitions[0].Name - escapedFilterValue := urlpkg.PathEscape(fmt.Sprintf("title eq '%s'", eventName)) - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=events($filter=(%s))&$format=json", testData.url, escapedFilterValue), testData.headers) + params := urlpkg.Values{} + params.Add("$expand", fmt.Sprintf("events($filter=(title eq '%s'))", eventName)) + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) - escapedFilterValue = urlpkg.PathEscape(fmt.Sprintf("title ne '%s'", eventName)) - respBody = makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=events($filter=(%s))&$format=json", testData.url, escapedFilterValue), testData.headers) + params.Set("$expand", fmt.Sprintf("events($filter=(title ne '%s'))", eventName)) + serviceURL = testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody = makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, totalCount-1, len(gjson.Get(respBody, "value.0.events").Array())) }) // Projection: t.Run(fmt.Sprintf("Requesting projection of Bundles for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/consumptionBundles?$select=title&$format=json", testData.headers) + params := urlpkg.Values{} + + params.Add("$select", "title") + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Bundles[0].Name, gjson.Get(respBody, "value.0.title").String()) require.Equal(t, false, gjson.Get(respBody, "value.0.description").Exists()) }) t.Run(fmt.Sprintf("Requesting projection of Bundle APIs for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/consumptionBundles?$expand=apis($select=title)&$format=json", testData.headers) + params := urlpkg.Values{} + + params.Add("$expand", "apis($select=title)") + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) apis := gjson.Get(respBody, "value.0.apis").Array() require.Len(t, apis, len(testData.appInput.Bundles[0].APIDefinitions)) @@ -502,7 +601,11 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting projection of Bundle Events for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - respBody := makeRequestWithHeaders(t, testData.client, testData.url+"/consumptionBundles?$expand=events($select=title)&$format=json", testData.headers) + params := urlpkg.Values{} + + params.Add("$expand", "events($select=title)") + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, testData.client, testData.url+"/consumptionBundles?", testData.headers, params) events := gjson.Get(respBody, "value.0.events").Array() require.Len(t, events, len(testData.appInput.Bundles[0].EventDefinitions)) @@ -519,16 +622,22 @@ func TestORDService(t *testing.T) { //Ordering: t.Run(fmt.Sprintf("Requesting ordering of Bundles for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - escapedOrderByValue := urlpkg.PathEscape("title asc,description desc") - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$orderby=%s&$format=json", testData.url, escapedOrderByValue), testData.headers) + params := urlpkg.Values{} + params.Add("$orderby", "title asc,description desc") + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) require.Equal(t, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(t, testData.appInput.Bundles[0].Name, gjson.Get(respBody, "value.0.title").String()) require.Equal(t, *testData.appInput.Bundles[0].Description, gjson.Get(respBody, "value.0.description").String()) }) t.Run(fmt.Sprintf("Requesting ordering of Bundle APIs for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - escapedOrderByValue := urlpkg.PathEscape("title asc,description desc") - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=apis($orderby=%s)&$format=json", testData.url, escapedOrderByValue), testData.headers) + params := urlpkg.Values{} + params.Add("$expand", fmt.Sprintf("apis($orderby=%s)", "title asc,description desc")) + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) apis := gjson.Get(respBody, "value.0.apis").Array() require.Len(t, apis, len(testData.appInput.Bundles[0].APIDefinitions)) @@ -545,8 +654,12 @@ func TestORDService(t *testing.T) { }) t.Run(fmt.Sprintf("Requesting ordering of Bundle Events for tenant %s returns them as expected", testData.msg), func(t *testing.T) { - escapedOrderByValue := urlpkg.PathEscape("title asc,description desc") - respBody := makeRequestWithHeaders(t, testData.client, fmt.Sprintf("%s/consumptionBundles?$expand=events($orderby=%s)&$format=json", testData.url, escapedOrderByValue), testData.headers) + params := urlpkg.Values{} + + params.Add("$expand", fmt.Sprintf("events($orderby=%s)", "title asc,description desc")) + params.Add("$format", "json") + serviceURL := testData.url + "/consumptionBundles?" + strings.ReplaceAll(params.Encode(), "+", "%20") + respBody := makeRequestWithHeaders(t, testData.client, serviceURL, testData.headers) events := gjson.Get(respBody, "value.0.events").Array() require.Len(t, events, len(testData.appInput.Bundles[0].EventDefinitions)) @@ -564,7 +677,10 @@ func TestORDService(t *testing.T) { } t.Run("404 when request to ORD Service for api spec have another tenant header value", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/apis?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := urlpkg.Values{} + + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, intSystemHttpClient, conf.ORDServiceURL+"/apis?", map[string][]string{tenantHeader: {defaultTestTenant}}, params) require.Equal(t, len(appInput.Bundles[0].APIDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -577,7 +693,10 @@ func TestORDService(t *testing.T) { }) t.Run("404 when request to ORD Service for event spec have another tenant header value", func(t *testing.T) { - respBody := makeRequestWithHeaders(t, intSystemHttpClient, conf.ORDServiceURL+"/events?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}) + params := urlpkg.Values{} + + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, intSystemHttpClient, conf.ORDServiceURL+"/events?", map[string][]string{tenantHeader: {defaultTestTenant}}, params) require.Equal(t, len(appInput.Bundles[0].EventDefinitions), len(gjson.Get(respBody, "value").Array())) specs := gjson.Get(respBody, fmt.Sprintf("value.%d.resourceDefinitions", 0)).Array() @@ -590,7 +709,11 @@ func TestORDService(t *testing.T) { }) t.Run("Errors generate user-friendly message", func(t *testing.T) { - respBody := request.MakeRequestWithHeadersAndStatusExpect(t, intSystemHttpClient, conf.ORDServiceURL+"/test?$format=json", map[string][]string{tenantHeader: {defaultTestTenant}}, http.StatusNotFound, conf.ORDServiceDefaultResponseType) + params := urlpkg.Values{} + + params.Add("$format", "json") + serviceURL := conf.ORDServiceURL + "/test?" + params.Encode() + respBody := request.MakeRequestWithHeadersAndStatusExpect(t, intSystemHttpClient, serviceURL, map[string][]string{tenantHeader: {defaultTestTenant}}, http.StatusNotFound, conf.ORDServiceDefaultResponseType) require.Contains(t, gjson.Get(respBody, "error.message").String(), "Use odata-debug query parameter with value one of the following formats: json,html,download for more information") }) @@ -626,9 +749,12 @@ func TestORDService(t *testing.T) { defer fixtures.CleanupApplication(t, ctx, certSecuredGraphQLClient, defaultTestTenant, &outputApp) require.NoError(t, err) - getSystemInstanceURL := fmt.Sprintf("%s/systemInstances(%s)?$format=json", conf.ORDServiceURL, outputApp.ID) + params := urlpkg.Values{} + + params.Add("$format", "json") + getSystemInstanceURL := fmt.Sprintf("%s/systemInstances(%s)?", conf.ORDServiceURL, outputApp.ID) - respBody := makeRequestWithHeaders(t, intSystemHttpClient, getSystemInstanceURL, map[string][]string{tenantHeader: {defaultTestTenant}}) + respBody := makeRequestWithHeadersAndQueryParams(t, intSystemHttpClient, getSystemInstanceURL, map[string][]string{tenantHeader: {defaultTestTenant}}, params) require.Equal(t, outputApp.Name, gjson.Get(respBody, "title").String()) @@ -727,7 +853,10 @@ func TestORDServiceSystemDiscoveryByApplicationTenantID(t *testing.T) { headers := map[string][]string{applicationTenantIDHeaderKey: {localTenantID}} // Make a request to the ORD service with http client containing custom certificate and application tenant ID header t.Log("Getting application using custom certificate and appplicationTenantId header before a formation is created...") - respBody := makeRequestWithHeaders(t, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params := urlpkg.Values{} + + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(t, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Empty(t, gjson.Get(respBody, "value").Array()) t.Log("No system instance details are returned due to missing formation") @@ -754,7 +883,7 @@ func TestORDServiceSystemDiscoveryByApplicationTenantID(t *testing.T) { defer unassignFromFormation(t, ctx, consumerApp.ID, string(directorSchema.FormationObjectTypeApplication), systemDiscoveryFormationName, tenantID) t.Log("Getting application using custom certificate and appplicationTenantId header after formation is created...") - respBody = makeRequestWithHeaders(t, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + respBody = makeRequestWithHeadersAndQueryParams(t, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Len(t, gjson.Get(respBody, "value").Array(), 2) isSystemFound := false diff --git a/tests/ord-service/tests/subscription_flow_test.go b/tests/ord-service/tests/subscription_flow_test.go index 2de1bb830d..40ac9d6e89 100644 --- a/tests/ord-service/tests/subscription_flow_test.go +++ b/tests/ord-service/tests/subscription_flow_test.go @@ -24,6 +24,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strings" "testing" "time" @@ -361,7 +362,9 @@ func TestConsumerProviderFlow(stdT *testing.T) { // Make a request to the ORD service with http client containing certificate with provider information and token with the consumer data. stdT.Log("Getting consumer application using both provider and consumer credentials...") - respBody := makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params := url.Values{} + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Len(stdT, gjson.Get(respBody, "value").Array(), 1) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "value.0.title").String()) expectedFormationDetailsAssignmentID := getExpectedFormationDetailsAssignmentID(stdT, ctx, secondaryTenant, consumerApp.ID, rtCtx.ID, formation.ID) // find the assignment where the consumer app is source and the subscription is target @@ -372,7 +375,10 @@ func TestConsumerProviderFlow(stdT *testing.T) { // With destinations - waiting for the synchronization job stdT.Log("Getting system with bundles and destinations - waiting for the synchronization job") require.Eventually(stdT, func() bool { - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$expand=consumptionBundles($expand=destinations)&$format=json", headers) + params := url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) appsLen := len(gjson.Get(respBody, "value").Array()) if appsLen != 1 { @@ -416,8 +422,12 @@ func TestConsumerProviderFlow(stdT *testing.T) { // With destinations - reload stdT.Log("Getting system with bundles and destinations - reloading the destination") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+ - "/systemInstances?$expand=consumptionBundles($expand=destinations)&$format=json&reload=true", headers) + params = url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + params.Add("reload", "true") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+ + "/systemInstances?", headers, params) require.Equal(stdT, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "value.0.title").String()) require.NotEmpty(stdT, gjson.Get(respBody, "value.0.consumptionBundles.0.destinations").Raw) @@ -432,12 +442,14 @@ func TestConsumerProviderFlow(stdT *testing.T) { subscription.BuildAndExecuteUnsubscribeRequest(stdT, runtime.ID, runtime.Name, httpClient, conf.SubscriptionConfig.URL, apiPath, subscriptionToken, conf.SubscriptionConfig.PropagatedProviderSubaccountHeader, subscriptionConsumerSubaccountID, "", subscriptionProviderSubaccountID, conf.SubscriptionConfig.StandardFlow, conf.SubscriptionConfig.SubscriptionFlowHeaderKey) stdT.Log("Validating no application is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params = url.Values{} + params.Add("$format", "json") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no application is returned after successful unsubscription request") stdT.Log("Validating no destination is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?$format=json", headers) + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no destination is returned after successful unsubscription request") @@ -666,7 +678,9 @@ func TestConsumerProviderFlow(stdT *testing.T) { // Make a request to the ORD service with http client containing certificate with provider information and token with the consumer data. stdT.Log("Getting consumer application using both provider and consumer credentials...") - respBody := makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?$format=json", consumerApp.ID), headers) + params := url.Values{} + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?", consumerApp.ID), headers, params) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "title").String()) expectedFormationDetailsAssignmentID := getExpectedFormationDetailsAssignmentID(stdT, ctx, secondaryTenant, consumerApp.ID, providerApp.ID, formation.ID) // find the assignment where the consumer app is source and the subscription is target verifyFormationDetails(stdT, gjson.Result{Raw: respBody}, formation.ID, expectedFormationDetailsAssignmentID, ft.ID) @@ -676,7 +690,10 @@ func TestConsumerProviderFlow(stdT *testing.T) { // With destinations - waiting for the synchronization job stdT.Log("Getting system with bundles and destinations - waiting for the synchronization job") require.Eventually(stdT, func() bool { - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?$expand=consumptionBundles($expand=destinations)&$format=json", consumerApp.ID), headers) + params := url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?", consumerApp.ID), headers, params) appName := gjson.Get(respBody, "title").String() if appName != consumerApp.Name { @@ -713,7 +730,11 @@ func TestConsumerProviderFlow(stdT *testing.T) { defer client.DeleteDestination(stdT, destinationSecond.Name) // With destinations - reload - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?$expand=consumptionBundles($expand=destinations)&$format=json&reload=true", consumerApp.ID), headers) + params = url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + params.Add("reload", "true") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+fmt.Sprintf("/systemInstances(%s)?", consumerApp.ID), headers, params) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "title").String()) require.NotEmpty(stdT, gjson.Get(respBody, "consumptionBundles.0.destinations").Raw) destinationsFromResponse := gjson.Get(respBody, "consumptionBundles.0.destinations").Array() @@ -726,12 +747,14 @@ func TestConsumerProviderFlow(stdT *testing.T) { subscription.BuildAndExecuteUnsubscribeRequest(stdT, appTmpl.ID, appTmpl.Name, httpClient, conf.SubscriptionConfig.URL, apiPath, subscriptionToken, conf.SubscriptionConfig.PropagatedProviderSubaccountHeader, subscriptionConsumerSubaccountID, "", subscriptionProviderSubaccountID, conf.SubscriptionConfig.StandardFlow, conf.SubscriptionConfig.SubscriptionFlowHeaderKey) stdT.Log("Validating no application is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params = url.Values{} + params.Add("$format", "json") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no application is returned after successful unsubscription request") stdT.Log("Validating no destination is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?$format=json", headers) + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no destination is returned after successful unsubscription request") @@ -962,7 +985,9 @@ func TestConsumerProviderFlow(stdT *testing.T) { // Make a request to the ORD service with http client containing certificate with provider information and token with the consumer data. stdT.Log("Getting consumer application using both provider and consumer credentials...") - respBody := makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params := url.Values{} + params.Add("$format", "json") + respBody := makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Len(stdT, gjson.Get(respBody, "value").Array(), 1) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "value.0.title").String()) expectedFormationDetailsAssignmentID := getExpectedFormationDetailsAssignmentID(stdT, ctx, secondaryTenant, consumerApp.ID, rtCtx.ID, formation.ID) // find the assignment where the consumer app is source and the subscription is target @@ -973,7 +998,10 @@ func TestConsumerProviderFlow(stdT *testing.T) { // With destinations - waiting for the synchronization job stdT.Log("Getting system with bundles and destinations - waiting for the synchronization job") require.Eventually(stdT, func() bool { - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$expand=consumptionBundles($expand=destinations)&$format=json", headers) + params = url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) appsLen := len(gjson.Get(respBody, "value").Array()) if appsLen != 1 { @@ -1017,8 +1045,12 @@ func TestConsumerProviderFlow(stdT *testing.T) { // With destinations - reload stdT.Log("Getting system with bundles and destinations - reloading the destination") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+ - "/systemInstances?$expand=consumptionBundles($expand=destinations)&$format=json&reload=true", headers) + params = url.Values{} + params.Add("$format", "json") + params.Add("$expand", "consumptionBundles($expand=destinations)") + params.Add("reload", "true") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+ + "/systemInstances?", headers, params) require.Equal(stdT, 1, len(gjson.Get(respBody, "value").Array())) require.Equal(stdT, consumerApp.Name, gjson.Get(respBody, "value.0.title").String()) require.NotEmpty(stdT, gjson.Get(respBody, "value.0.consumptionBundles.0.destinations").Raw) @@ -1033,12 +1065,14 @@ func TestConsumerProviderFlow(stdT *testing.T) { subscription.BuildAndExecuteUnsubscribeRequest(stdT, runtime.ID, runtime.Name, httpClient, conf.SubscriptionConfig.URL, apiPath, subscriptionToken, conf.SubscriptionConfig.PropagatedProviderSubaccountHeader, subscriptionConsumerSubaccountID, "", subscriptionProviderSubaccountID, conf.SubscriptionConfig.StandardFlow, conf.SubscriptionConfig.SubscriptionFlowHeaderKey) stdT.Log("Validating no application is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?$format=json", headers) + params = url.Values{} + params.Add("$format", "json") + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/systemInstances?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no application is returned after successful unsubscription request") stdT.Log("Validating no destination is returned after successful unsubscription request...") - respBody = makeRequestWithHeaders(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?$format=json", headers) + respBody = makeRequestWithHeadersAndQueryParams(stdT, certHttpClient, conf.ORDExternalCertSecuredServiceURL+"/destinations?", headers, params) require.Empty(stdT, gjson.Get(respBody, "value").Array()) stdT.Log("Successfully validated no destination is returned after successful unsubscription request") diff --git a/tests/pkg/fixtures/application_template_queries.go b/tests/pkg/fixtures/application_template_queries.go index c93f5ef585..b6ce35ceb6 100644 --- a/tests/pkg/fixtures/application_template_queries.go +++ b/tests/pkg/fixtures/application_template_queries.go @@ -2,6 +2,7 @@ package fixtures import ( "context" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/tests/pkg/assertions" "github.com/kyma-incubator/compass/tests/pkg/testctx" diff --git a/tests/pkg/fixtures/formation_queries.go b/tests/pkg/fixtures/formation_queries.go index 3c8558a7a5..c5f2ea6bf8 100644 --- a/tests/pkg/fixtures/formation_queries.go +++ b/tests/pkg/fixtures/formation_queries.go @@ -79,7 +79,7 @@ func CreateFormationFromTemplateWithinTenant(t *testing.T, ctx context.Context, formationInput := FixFormationInput(formationName, formationTemplateName) formationInputGQL, err := testctx.Tc.Graphqlizer.FormationInputToGQL(formationInput) require.NoError(t, err) - return CreateFormationFromTemplateWithInputWithinTenant(t, ctx, gqlClient, tenantID,formationName, formationInputGQL) + return CreateFormationFromTemplateWithInputWithinTenant(t, ctx, gqlClient, tenantID, formationName, formationInputGQL) } func CreateFormationFromTemplateWithStateWithinTenant(t *testing.T, ctx context.Context, gqlClient *gcli.Client, tenantID, formationName string, formationTemplateName, formationState *string) graphql.FormationExt { @@ -267,4 +267,4 @@ func FinalizeFormation(t require.TestingT, ctx context.Context, gqlClient *gcli. require.NoError(t, err) require.Equal(t, formationName, assignedFormation.Name) return assignedFormation -} \ No newline at end of file +} diff --git a/tests/pkg/notifications/asserters/formation_assigments_async_custom_config_matcher.go b/tests/pkg/notifications/asserters/formation_assigments_async_custom_config_matcher.go index 831e4598f9..9498858b8e 100644 --- a/tests/pkg/notifications/asserters/formation_assigments_async_custom_config_matcher.go +++ b/tests/pkg/notifications/asserters/formation_assigments_async_custom_config_matcher.go @@ -2,12 +2,13 @@ package asserters import ( "context" + "testing" + "time" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" "github.com/machinebox/graphql" "github.com/stretchr/testify/require" - "testing" - "time" ) type FormationAssignmentsAsyncCustomConfigMatcherAsserter struct { diff --git a/tests/pkg/notifications/asserters/formation_is_deleted.go b/tests/pkg/notifications/asserters/formation_is_deleted.go index 9792a14f75..94c470fe02 100644 --- a/tests/pkg/notifications/asserters/formation_is_deleted.go +++ b/tests/pkg/notifications/asserters/formation_is_deleted.go @@ -2,11 +2,12 @@ package asserters import ( "context" - context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" - testingx "github.com/kyma-incubator/compass/tests/pkg/testing" "testing" "time" + context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" + testingx "github.com/kyma-incubator/compass/tests/pkg/testing" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/machinebox/graphql" "github.com/stretchr/testify/require" diff --git a/tests/pkg/notifications/asserters/lifecycle_notification.go b/tests/pkg/notifications/asserters/lifecycle_notification.go index 6dfbfbed44..bd3b3a22e8 100644 --- a/tests/pkg/notifications/asserters/lifecycle_notification.go +++ b/tests/pkg/notifications/asserters/lifecycle_notification.go @@ -2,6 +2,10 @@ package asserters import ( "context" + "net/http" + "testing" + "time" + gql "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/tests/pkg/fixtures" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" @@ -9,9 +13,6 @@ import ( "github.com/machinebox/graphql" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" - "net/http" - "testing" - "time" ) type LifecycleNotificationsAsserter struct { diff --git a/tests/pkg/notifications/asserters/notifications.go b/tests/pkg/notifications/asserters/notifications.go index 36ac97198f..805ecf7260 100644 --- a/tests/pkg/notifications/asserters/notifications.go +++ b/tests/pkg/notifications/asserters/notifications.go @@ -3,13 +3,14 @@ package asserters import ( "context" "fmt" - "github.com/kyma-incubator/compass/tests/pkg/certs" - "github.com/kyma-incubator/compass/tests/pkg/fixtures" - "github.com/machinebox/graphql" "io" "net/http" "testing" + "github.com/kyma-incubator/compass/tests/pkg/certs" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" + "github.com/machinebox/graphql" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/tidwall/sjson" diff --git a/tests/pkg/notifications/asserters/notifications_unassign.go b/tests/pkg/notifications/asserters/notifications_unassign.go index f6df97bffc..ad30682488 100644 --- a/tests/pkg/notifications/asserters/notifications_unassign.go +++ b/tests/pkg/notifications/asserters/notifications_unassign.go @@ -2,11 +2,12 @@ package asserters import ( "context" - "github.com/kyma-incubator/compass/tests/pkg/fixtures" - "github.com/machinebox/graphql" "net/http" "testing" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" + "github.com/machinebox/graphql" + context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/tests/pkg/notifications/operations/assign_application.go b/tests/pkg/notifications/operations/assign_application.go index a9df2baaa8..079a95fc7c 100644 --- a/tests/pkg/notifications/operations/assign_application.go +++ b/tests/pkg/notifications/operations/assign_application.go @@ -2,12 +2,13 @@ package operations import ( "context" + "testing" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" gcli "github.com/machinebox/graphql" - "testing" ) type AssignAppToFormationOperation struct { diff --git a/tests/pkg/notifications/operations/create_formation.go b/tests/pkg/notifications/operations/create_formation.go index 0fa0c7051f..75e9dae4f5 100644 --- a/tests/pkg/notifications/operations/create_formation.go +++ b/tests/pkg/notifications/operations/create_formation.go @@ -2,9 +2,10 @@ package operations import ( "context" + "testing" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" - "testing" "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" diff --git a/tests/pkg/notifications/operations/delete_formation.go b/tests/pkg/notifications/operations/delete_formation.go index 3a0d173048..4159402b84 100644 --- a/tests/pkg/notifications/operations/delete_formation.go +++ b/tests/pkg/notifications/operations/delete_formation.go @@ -2,9 +2,10 @@ package operations import ( "context" - context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" "testing" + context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" gcli "github.com/machinebox/graphql" diff --git a/tests/pkg/notifications/operations/update_webhook.go b/tests/pkg/notifications/operations/update_webhook.go index c76da8aca9..63af562790 100644 --- a/tests/pkg/notifications/operations/update_webhook.go +++ b/tests/pkg/notifications/operations/update_webhook.go @@ -2,9 +2,10 @@ package operations import ( "context" - context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" "testing" + context_keys "github.com/kyma-incubator/compass/tests/pkg/notifications/context-keys" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/tests/pkg/fixtures" "github.com/kyma-incubator/compass/tests/pkg/notifications/asserters" diff --git a/tests/pkg/notifications/resource-providers/formation.go b/tests/pkg/notifications/resource-providers/formation.go index a070aeaf26..decdbd9969 100644 --- a/tests/pkg/notifications/resource-providers/formation.go +++ b/tests/pkg/notifications/resource-providers/formation.go @@ -2,9 +2,10 @@ package resource_providers import ( "context" - "github.com/kyma-incubator/compass/components/director/pkg/graphql" "testing" + "github.com/kyma-incubator/compass/components/director/pkg/graphql" + "github.com/kyma-incubator/compass/tests/pkg/fixtures" gcli "github.com/machinebox/graphql" ) From 667eacd03a2f3755642589a32c8f7590b79b14a8 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Thu, 11 Apr 2024 12:14:38 +0300 Subject: [PATCH 11/19] Update IAS adapter version (#3800) * update ias adapter version * set PR version * fix ias adapter version --- chart/compass/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 39e3a0dd30..950c064793 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -183,7 +183,7 @@ global: name: compass-hydrator ias_adapter: dir: dev/incubator/ - version: "PR-3797" + version: "PR-3798" name: compass-ias-adapter kyma_adapter: dir: dev/incubator/ From dbb628e9a6822889d4266762bd48294104869d54 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:50:40 +0300 Subject: [PATCH 12/19] Fix strict IAS adapter validation (#3807) * fix strict ias adapter validation * update ias adapter version --- chart/compass/values.yaml | 2 +- components/ias-adapter/internal/types/tenant_mapping.go | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 950c064793..b90ff466d3 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -183,7 +183,7 @@ global: name: compass-hydrator ias_adapter: dir: dev/incubator/ - version: "PR-3798" + version: "PR-3807" name: compass-ias-adapter kyma_adapter: dir: dev/incubator/ diff --git a/components/ias-adapter/internal/types/tenant_mapping.go b/components/ias-adapter/internal/types/tenant_mapping.go index d72c9666db..ae9693e8ea 100644 --- a/components/ias-adapter/internal/types/tenant_mapping.go +++ b/components/ias-adapter/internal/types/tenant_mapping.go @@ -135,9 +135,6 @@ func (tm TenantMapping) Validate() error { if tm.AssignedTenant.LocalTenantID == "" { return errors.New("$.assignedTenant.applicationTenantId is required") } - if tm.AssignedTenant.AppNamespace == "" { - return errors.New("$.assignedTenant.applicationNamespace is required") - } // S/4 applications are created by the IAS adapter and therefore the tenant mapping does not contain its clientID if tm.AssignedTenant.AppNamespace != S4ApplicationNamespace && tm.AssignedTenant.Parameters.ClientID == "" { return errors.New("$.assignedTenant.parameters.technicalIntegrationId is required") From 81606c1ea51a8bd7d57a838eb6cfa32f8b38987c Mon Sep 17 00:00:00 2001 From: Lachezar Bogomilov <64094901+la4ezar@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:04:40 +0300 Subject: [PATCH 13/19] Fix assign formation not found errors (#3804) * Initial implementation and unit tests. * Update values.yaml * Add missing unti test. --- chart/compass/values.yaml | 2 +- .../domain/formation/assign_formation_test.go | 112 +++++++++++++++++- .../internal/domain/formation/service.go | 20 ++-- 3 files changed, 122 insertions(+), 12 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index b90ff466d3..783179f39d 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3802" + version: "PR-3804" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formation/assign_formation_test.go b/components/director/internal/domain/formation/assign_formation_test.go index f1af4c9613..5f07d0203e 100644 --- a/components/director/internal/domain/formation/assign_formation_test.go +++ b/components/director/internal/domain/formation/assign_formation_test.go @@ -240,6 +240,8 @@ func TestServiceAssignFormation(t *testing.T) { TargetTenantID: TargetTenant, } + runtime := &model.Runtime{ID: RuntimeID} + testCases := []struct { Name string TxFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner) @@ -251,6 +253,7 @@ func TestServiceAssignFormation(t *testing.T) { AsaRepoFn func() *automock.AutomaticFormationAssignmentRepository AsaServiceFN func() *automock.AutomaticFormationAssignmentService RuntimeContextRepoFn func() *automock.RuntimeContextRepository + RuntimeRepoFn func() *automock.RuntimeRepository FormationRepositoryFn func() *automock.FormationRepository NotificationServiceFN func() *automock.NotificationsService FormationTemplateRepositoryFn func() *automock.FormationTemplateRepository @@ -503,6 +506,11 @@ func TestServiceAssignFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() return repo }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + runtimeRepo := &automock.RuntimeRepository{} + runtimeRepo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return runtimeRepo + }, NotificationServiceFN: func() *automock.NotificationsService { notificationSvc := &automock.NotificationsService{} notificationSvc.On("GenerateFormationAssignmentNotifications", ctx, TntInternalID, RuntimeID, expectedFormation, model.AssignFormation, graphql.FormationObjectTypeRuntime).Return(notifications, nil).Once() @@ -546,6 +554,11 @@ func TestServiceAssignFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() return repo }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + runtimeRepo := &automock.RuntimeRepository{} + runtimeRepo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return runtimeRepo + }, NotificationServiceFN: func() *automock.NotificationsService { notificationSvc := &automock.NotificationsService{} notificationSvc.On("GenerateFormationAssignmentNotifications", ctx, TntInternalID, RuntimeID, expectedFormation, model.AssignFormation, graphql.FormationObjectTypeRuntime).Return(notifications, nil).Once() @@ -596,6 +609,11 @@ func TestServiceAssignFormation(t *testing.T) { notificationSvc.On("GenerateFormationAssignmentNotifications", ctx, TntInternalID, RuntimeID, expectedSecondFormation, model.AssignFormation, graphql.FormationObjectTypeRuntime).Return(notifications, nil).Once() return notificationSvc }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + runtimeRepo := &automock.RuntimeRepository{} + runtimeRepo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return runtimeRepo + }, FormationAssignmentServiceFn: func() *automock.FormationAssignmentService { formationAssignmentSvc := &automock.FormationAssignmentService{} formationAssignmentSvc.On("GenerateAssignments", ctx, TntInternalID, RuntimeID, graphql.FormationObjectTypeRuntime, expectedSecondFormation).Return(formationAssignmentInputs, nil).Once() @@ -969,6 +987,7 @@ func TestServiceAssignFormation(t *testing.T) { repo := &automock.ApplicationRepository{} repo.On("GetByID", mock.Anything, TntInternalID, ApplicationID).Return(&model.Application{ BaseEntity: &model.BaseEntity{ + ID: ApplicationID, Ready: false, }, }, nil).Once() @@ -1006,6 +1025,7 @@ func TestServiceAssignFormation(t *testing.T) { repo := &automock.ApplicationRepository{} repo.On("GetByID", mock.Anything, TntInternalID, ApplicationID).Return(&model.Application{ BaseEntity: &model.BaseEntity{ + ID: ApplicationID, DeletedAt: &time.Time{}, Ready: true, }, @@ -1126,6 +1146,7 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &applicationTypeLblInput).Return(emptyApplicationType, nil).Once() return labelService }, + ApplicationRepoFn: expectEmptySliceApplicationAndReadyApplicationRepo, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedSecondFormation, nil).Once() @@ -1164,6 +1185,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &applicationTypeLblInput).Return(emptyApplicationType, nil).Once() return labelService }, + ApplicationRepoFn: func() *automock.ApplicationRepository { + repo := &automock.ApplicationRepository{} + repo.On("GetByID", mock.Anything, TntInternalID, ApplicationID).Return(existingApp, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedSecondFormation, nil).Once() @@ -1193,6 +1219,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &applicationTypeLblInput).Return(nil, testErr).Once() return labelService }, + ApplicationRepoFn: func() *automock.ApplicationRepository { + repo := &automock.ApplicationRepository{} + repo.On("GetByID", mock.Anything, TntInternalID, ApplicationID).Return(existingApp, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedSecondFormation, nil).Once() @@ -1244,6 +1275,39 @@ func TestServiceAssignFormation(t *testing.T) { InputFormation: inputFormation, ExpectedErrMessage: testErr.Error(), }, + { + Name: "error for runtime when runtime does not exist", + TxFn: txGen.ThatDoesntExpectCommit, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(nil, testErr).Once() + return repo + }, + FormationRepositoryFn: func() *automock.FormationRepository { + formationRepo := &automock.FormationRepository{} + formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation, nil).Once() + return formationRepo + }, + FormationTemplateRepositoryFn: func() *automock.FormationTemplateRepository { + repo := &automock.FormationTemplateRepository{} + repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() + return repo + }, + LabelServiceFn: func() *automock.LabelService { + labelService := &automock.LabelService{} + labelService.On("GetLabel", ctx, TntInternalID, &runtimeTypeLblInput).Return(runtimeTypeLbl, nil).Once() + return labelService + }, + ConstraintEngineFn: func() *automock.ConstraintEngine { + engine := &automock.ConstraintEngine{} + engine.On("EnforceConstraints", ctx, preAssignLocation, fixAssignRuntimeDetails(testFormationName), FormationTemplateID).Return(nil).Once() + return engine + }, + ObjectType: graphql.FormationObjectTypeRuntime, + ObjectID: RuntimeID, + InputFormation: inputFormation, + ExpectedErrMessage: testErr.Error(), + }, { Name: "error for runtime when label does not exist and can't create it", TxFn: txGen.ThatDoesntExpectCommit, @@ -1252,6 +1316,11 @@ func TestServiceAssignFormation(t *testing.T) { uidService.On("Generate").Return(fixUUID()).Once() return uidService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation, nil).Once() @@ -1288,6 +1357,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &runtimeLblInput).Return(nil, testErr).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationTemplateRepositoryFn: func() *automock.FormationTemplateRepository { repo := &automock.FormationTemplateRepository{} repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() @@ -1331,6 +1405,11 @@ func TestServiceAssignFormation(t *testing.T) { }, nil).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationTemplateRepositoryFn: func() *automock.FormationTemplateRepository { repo := &automock.FormationTemplateRepository{} repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() @@ -1368,6 +1447,11 @@ func TestServiceAssignFormation(t *testing.T) { }, nil).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationTemplateRepositoryFn: func() *automock.FormationTemplateRepository { repo := &automock.FormationTemplateRepository{} repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() @@ -1398,6 +1482,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("UpdateLabel", ctx, TntInternalID, runtimeLbl.ID, &runtimeLblInput).Return(testErr).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationTemplateRepositoryFn: func() *automock.FormationTemplateRepository { repo := &automock.FormationTemplateRepository{} repo.On("Get", ctx, FormationTemplateID).Return(expectedFormationTemplate, nil).Once() @@ -1547,6 +1636,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &runtimeTypeLblInput).Return(runtimeTypeLbl, nil).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation, nil).Once() @@ -1590,6 +1684,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &runtimeTypeLblInput).Return(emptyRuntimeType, nil).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation, nil).Once() @@ -1619,6 +1718,11 @@ func TestServiceAssignFormation(t *testing.T) { labelService.On("GetLabel", ctx, TntInternalID, &runtimeTypeLblInput).Return(nil, testErr).Once() return labelService }, + RuntimeRepoFn: func() *automock.RuntimeRepository { + repo := &automock.RuntimeRepository{} + repo.On("GetByID", ctx, TntInternalID, RuntimeID).Return(runtime, nil).Once() + return repo + }, FormationRepositoryFn: func() *automock.FormationRepository { formationRepo := &automock.FormationRepository{} formationRepo.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation, nil).Once() @@ -2729,6 +2833,10 @@ func TestServiceAssignFormation(t *testing.T) { if testCase.RuntimeContextRepoFn != nil { runtimeContextRepo = testCase.RuntimeContextRepoFn() } + runtimeRepo := unusedRuntimeRepo() + if testCase.RuntimeRepoFn != nil { + runtimeRepo = testCase.RuntimeRepoFn() + } formationRepo := unusedFormationRepo() if testCase.FormationRepositoryFn != nil { formationRepo = testCase.FormationRepositoryFn() @@ -2756,7 +2864,7 @@ func TestServiceAssignFormation(t *testing.T) { asaEngine = testCase.ASAEngineFn() } - svc := formation.NewServiceWithAsaEngine(transact, applicationRepository, nil, nil, formationRepo, formationTemplateRepo, labelService, uidService, labelDefService, asaRepo, asaService, tenantSvc, nil, runtimeContextRepo, formationAssignmentSvc, nil, nil, notificationSvc, constraintEngine, runtimeType, applicationType, asaEngine, nil) + svc := formation.NewServiceWithAsaEngine(transact, applicationRepository, nil, nil, formationRepo, formationTemplateRepo, labelService, uidService, labelDefService, asaRepo, asaService, tenantSvc, runtimeRepo, runtimeContextRepo, formationAssignmentSvc, nil, nil, notificationSvc, constraintEngine, runtimeType, applicationType, asaEngine, nil) // WHEN actual, err := svc.AssignFormation(ctx, TntInternalID, testCase.ObjectID, testCase.ObjectType, testCase.InputFormation) @@ -2771,7 +2879,7 @@ func TestServiceAssignFormation(t *testing.T) { require.Nil(t, actual) } - mock.AssertExpectationsForObjects(t, persist, uidService, applicationRepository, labelService, asaRepo, asaService, tenantSvc, labelDefService, runtimeContextRepo, formationRepo, formationTemplateRepo, webhookClient, notificationSvc, formationAssignmentSvc, constraintEngine, asaEngine) + mock.AssertExpectationsForObjects(t, persist, uidService, applicationRepository, labelService, asaRepo, asaService, tenantSvc, labelDefService, runtimeContextRepo, runtimeRepo, formationRepo, formationTemplateRepo, webhookClient, notificationSvc, formationAssignmentSvc, constraintEngine, asaEngine) }) } } diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index 02730dad95..6e42dbd2c6 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -808,13 +808,20 @@ func (s *service) UnassignFromScenarioLabel(ctx context.Context, tnt, objectID s func (s *service) checkFormationTemplateTypes(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formationTemplate *model.FormationTemplate) error { switch objectType { case graphql.FormationObjectTypeApplication: + app, err := s.applicationRepository.GetByID(ctx, tnt, objectID) + if err != nil { + return errors.Wrapf(err, "while getting application with ID: %q", objectID) + } if err := s.isValidApplicationType(ctx, tnt, objectID, formationTemplate); err != nil { return errors.Wrapf(err, "while validating application type for application %q", objectID) } - if err := s.isValidApplication(ctx, tnt, objectID); err != nil { + if err := s.isValidApplication(app); err != nil { return errors.Wrapf(err, "while validating application with ID: %q", objectID) } case graphql.FormationObjectTypeRuntime: + if _, err := s.runtimeRepo.GetByID(ctx, tnt, objectID); err != nil { + return errors.Wrapf(err, "while getting runtime with ID: %q", objectID) + } if err := s.isValidRuntimeType(ctx, tnt, objectID, formationTemplate); err != nil { return errors.Wrapf(err, "while validating runtime type") } @@ -1820,17 +1827,12 @@ func (s *service) isValidRuntimeType(ctx context.Context, tnt string, runtimeID return nil } -func (s *service) isValidApplication(ctx context.Context, tnt string, applicationID string) error { - application, err := s.applicationRepository.GetByID(ctx, tnt, applicationID) - if err != nil { - return errors.Wrapf(err, "while getting application with ID %q", applicationID) - } - +func (s *service) isValidApplication(application *model.Application) error { if application.DeletedAt != nil { - return apperrors.NewInvalidOperationError(fmt.Sprintf("application with ID %q is currently being deleted", applicationID)) + return apperrors.NewInvalidOperationError(fmt.Sprintf("application with ID %q is currently being deleted", application.ID)) } if !application.Ready { - return apperrors.NewInvalidOperationError(fmt.Sprintf("application with ID %q is not ready", applicationID)) + return apperrors.NewInvalidOperationError(fmt.Sprintf("application with ID %q is not ready", application.ID)) } return nil } From e8e45ce4180591a7a15257a351605249a63620ef Mon Sep 17 00:00:00 2001 From: ivantenevvasilev <48180084+ivantenevvasilev@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:12:39 +0300 Subject: [PATCH 14/19] Handle race condition in async flow control operator (#3808) * fix race condition * bump director version * fix unit test --- chart/compass/values.yaml | 2 +- .../asynchronous_flow_control_operator.go | 15 ++++ ...asynchronous_flow_control_operator_test.go | 77 ++++++++++++++++--- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 783179f39d..2e325e9b03 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3804" + version: "PR-3808" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go index d32b108833..338060e11d 100644 --- a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go +++ b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator.go @@ -6,6 +6,7 @@ import ( "runtime/debug" "github.com/kyma-incubator/compass/components/director/internal/model" + "github.com/kyma-incubator/compass/components/director/pkg/consumer" "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" "github.com/kyma-incubator/compass/components/director/pkg/log" "github.com/pkg/errors" @@ -83,6 +84,20 @@ func (e *ConstraintEngine) AsynchronousFlowControlOperator(ctx context.Context, } if ri.Operation == model.UnassignFormation { if formationAssignment.State == string(model.DeletingAssignmentState) && statusReport.State == string(model.ReadyAssignmentState) { + consumerInfo, err := consumer.LoadFromContext(ctx) + if err != nil { + return false, errors.Wrap(err, "while fetching consumer info from context") + } + + // This handles the case when there is the following race condition: + // 1. First unassign sends a notification to the participant, the participant responds with READY and the assignment is in INSTANCE_CREATOR_DELETING + // 2. The second is started after the first and unassign sees the assignment as READY initially, but upon update it sees INSTANCE_CREATOR_DELETING and reverts it back to DELETING. + // Theoretically the instance creator won't respond unless it has been notified. + if consumerInfo.Type == consumer.InstanceCreator { + log.C(ctx).Infof("Instance creator reported %q, proceeding with deletion of formation assignment with ID %q", statusReport.State, formationAssignment.ID) + return true, nil + } + reverseAssignment, err := RetrieveFormationAssignmentPointer(ctx, ri.ReverseFAMemoryAddress) if err != nil { log.C(ctx).Warnf(errors.Wrapf(err, "Reverse assignment not found").Error()) diff --git a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go index da8bd80359..4f22a55bc3 100644 --- a/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go +++ b/components/director/internal/domain/formationconstraint/operators/asynchronous_flow_control_operator_test.go @@ -1,6 +1,7 @@ package operators_test import ( + "context" "testing" "github.com/kyma-incubator/compass/components/director/internal/domain/formationassignment" @@ -8,6 +9,7 @@ import ( "github.com/kyma-incubator/compass/components/director/internal/domain/formationconstraint/operators/automock" "github.com/kyma-incubator/compass/components/director/internal/domain/statusreport" "github.com/kyma-incubator/compass/components/director/internal/model" + "github.com/kyma-incubator/compass/components/director/pkg/consumer" formationconstraintpkg "github.com/kyma-incubator/compass/components/director/pkg/formationconstraint" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -15,6 +17,15 @@ import ( ) func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { + certConsumer := consumer.Consumer{ + Type: consumer.ExternalCertificate, + } + instanceCreatorConsumer := consumer.Consumer{ + Type: consumer.InstanceCreator, + } + ctxWithCertConsumer := consumer.SaveToContext(context.TODO(), certConsumer) + ctxWithInstanceCreatorConsumer := consumer.SaveToContext(context.TODO(), instanceCreatorConsumer) + inputForNotificationStatusReturnedAssign := fixAsynchronousFlowControlOperatorInputWithAssignmentAndReverseFAMemoryAddress(model.AssignFormation, fixWebhook(), preNotificationStatusReturnedLocation) inputForNotificationStatusReturnedUnassign := fixAsynchronousFlowControlOperatorInputWithAssignmentAndReverseFAMemoryAddress(model.UnassignFormation, fixWebhook(), preNotificationStatusReturnedLocation) inputForSendNotificationAssign := fixAsynchronousFlowControlOperatorInputWithAssignmentAndReverseFAMemoryAddressShouldRedirect(true, model.AssignFormation, fixWebhook(), preSendNotificationLocation) @@ -31,6 +42,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { testCases := []struct { Name string + Context context.Context Input *formationconstraintpkg.AsynchronousFlowControlOperatorInput Assignment *model.FormationAssignment ReverseAssignment *model.FormationAssignment @@ -48,6 +60,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when sending notification and state is READY", Input: inputForSendNotificationAssign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.ReadyAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.ReadyAssignmentState), ExpectShouldRedirect: true, @@ -57,6 +70,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when sending notification and state is INSTANCE_CREATOR_DELETING state", Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectShouldRedirect: true, @@ -65,6 +79,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when sending notification and state is INSTANCE_CREATOR_DELETE_ERROR state", Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeleteErrorAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectShouldRedirect: true, @@ -73,6 +88,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when sending notification and state is READY state", Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.ReadyAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectShouldRedirect: false, @@ -81,6 +97,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error when retrieving formation assignment pointer fails during send notification", Input: inputForSendNotificationUnassign, + Context: ctxWithCertConsumer, ExpectedFormationAssignmentState: string(model.InitialAssignmentState), StatusReport: fixNotificationStatusReportWithStateAndConfig(string(model.ReadyAssignmentState), configWithDifferentStructure), ExpectedErrorMsg: "The join point details' assignment memory address cannot be 0", @@ -89,6 +106,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error when formation assignment config is invalid", Input: inputForNotificationStatusReturnedAssign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ExpectedFormationAssignmentState: string(model.InitialAssignmentState), @@ -99,6 +117,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning to READY state with no configuration", Input: inputForNotificationStatusReturnedAssign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ExpectedFormationAssignmentState: string(model.InitialAssignmentState), @@ -109,6 +128,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning to READY state with inbound credentials", Input: inputForNotificationStatusReturnedAssign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ExpectedFormationAssignmentState: string(model.InitialAssignmentState), @@ -119,6 +139,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning to READY state without inbound credentials", Input: inputForNotificationStatusReturnedAssign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.InitialAssignmentState), ExpectedFormationAssignmentState: string(model.InitialAssignmentState), @@ -130,6 +151,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning from DELETING to READY", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -137,24 +159,48 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() + repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() return repo }, FormationAssignmentService: func() *automock.FormationAssignmentService { svc := &automock.FormationAssignmentService{} - svc.On("CleanupFormationAssignment", ctx, testAssignmentPair).Return(false, nil) + svc.On("CleanupFormationAssignment", ctxWithCertConsumer, testAssignmentPair).Return(false, nil) return svc }, FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() return notificationSvc }, ExpectedResult: true, }, + { + Name: "Success when transitioning from DELETING to READY but consumer type is instance creator", + Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithInstanceCreatorConsumer, + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), + StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), + ExpectedStatusReportState: string(model.ReadyAssignmentState), + ExpectedResult: true, + }, + { + Name: "Error when transitioning from DELETING to READY but there is no consumer in context", + Input: inputForNotificationStatusReturnedUnassign, + Context: ctx, + Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), + ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), + StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), + ExpectedStatusReportState: string(model.ReadyAssignmentState), + ExpectedResult: false, + ExpectedErrorMsg: "while fetching consumer info from context", + }, { Name: "Error when cleanup formation assignment fails", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -162,17 +208,17 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() + repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() return repo }, FormationAssignmentService: func() *automock.FormationAssignmentService { svc := &automock.FormationAssignmentService{} - svc.On("CleanupFormationAssignment", ctx, testAssignmentPair).Return(false, testErr) + svc.On("CleanupFormationAssignment", ctxWithCertConsumer, testAssignmentPair).Return(false, testErr) return svc }, FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(testAssignmentPair, nil).Once() return notificationSvc }, ExpectedResult: false, @@ -181,6 +227,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error during generating formation assignment pair", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -188,12 +235,12 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() + repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(nil).Once() return repo }, FormationAssignmentNotificationService: func() *automock.FormationAssignmentNotificationService { notificationSvc := &automock.FormationAssignmentNotificationService{} - notificationSvc.On("GenerateFormationAssignmentPair", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(nil, testErr).Once() + notificationSvc.On("GenerateFormationAssignmentPair", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), fixFormationAssignmentWithState(model.DeletingAssignmentState), model.UnassignFormation).Return(nil, testErr).Once() return notificationSvc }, ExpectedResult: false, @@ -202,6 +249,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error during formation assignment update to INSTANCE_CREATOR_DELETING state", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -209,7 +257,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { ExpectedStatusReportState: string(model.InstanceCreatorDeletingAssignmentState), FormationAssignmentRepository: func() *automock.FormationAssignmentRepository { repo := &automock.FormationAssignmentRepository{} - repo.On("Update", ctx, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(testErr).Once() + repo.On("Update", ctxWithCertConsumer, fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState)).Return(testErr).Once() return repo }, ExpectedResult: false, @@ -218,6 +266,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning from DELETING to DELETE_ERROR", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.DeletingAssignmentState), @@ -228,6 +277,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to DELETE_ERROR", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -238,6 +288,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to READY", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedFormationAssignmentState: string(model.InstanceCreatorDeletingAssignmentState), @@ -248,6 +299,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Success when transitioning from INSTANCE_CREATOR_DELETING to READY without reverse assignment", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), StatusReport: fixNotificationStatusReportWithState(model.ReadyAssignmentState), ExpectedStatusReportState: string(model.ReadyAssignmentState), @@ -256,6 +308,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error when retrieving status report pointer fails", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, Assignment: fixFormationAssignmentWithState(model.InstanceCreatorDeletingAssignmentState), ReverseAssignment: fixFormationAssignmentWithState(model.DeletingAssignmentState), ExpectedErrorMsg: "The join point details' notification status report memory address cannot be 0", @@ -263,11 +316,13 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { { Name: "Error when retrieving formation assignment pointer fails", Input: inputForNotificationStatusReturnedUnassign, + Context: ctxWithCertConsumer, ExpectedErrorMsg: "The join point details' assignment memory address cannot be 0", }, { Name: "Error when retrieving formation assignment pointer fails", Input: inputForPreAssign, + Context: ctxWithCertConsumer, ExpectedResult: true, }, } @@ -299,7 +354,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { setStatusReportToAsynchronousFlowControlInput(inputClone, testCase.StatusReport) } - result, err := engine.AsynchronousFlowControlOperator(ctx, inputClone) + result, err := engine.AsynchronousFlowControlOperator(testCase.Context, inputClone) if testCase.ExpectedErrorMsg != "" { require.Error(t, err) @@ -327,7 +382,7 @@ func TestConstraintOperators_AsynchronousFlowControlOperator(t *testing.T) { // WHEN input := "wrong input" - result, err := engine.AsynchronousFlowControlOperator(ctx, input) + result, err := engine.AsynchronousFlowControlOperator(ctxWithCertConsumer, input) // THEN assert.Equal(t, false, result) From 6ef36cc919d76811699d1845c1d7f5354dfe0cd7 Mon Sep 17 00:00:00 2001 From: Nikolay Yordanov Date: Fri, 12 Apr 2024 11:34:41 +0300 Subject: [PATCH 15/19] Add env var and its value (#3805) --- chart/compass/charts/ord-service/templates/deployment.yaml | 2 ++ chart/compass/values.yaml | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/chart/compass/charts/ord-service/templates/deployment.yaml b/chart/compass/charts/ord-service/templates/deployment.yaml index 4829ea848f..855f55089f 100644 --- a/chart/compass/charts/ord-service/templates/deployment.yaml +++ b/chart/compass/charts/ord-service/templates/deployment.yaml @@ -109,6 +109,8 @@ spec: value: {{ .Values.global.ordService.authTokenPath | quote }} - name: DESTINATION_FETCHER_SKIP_SSL_VALIDATION value: {{ .Values.global.ordService.skipSSLValidation | quote }} + - name: SPECIFICATION_PROTOCOL + value: {{ .Values.global.ordService.specification.protocol }} livenessProbe: httpGet: port: {{.Values.deployment.args.containerPort }} diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 2e325e9b03..3cef5bc2a1 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -207,7 +207,7 @@ global: name: compass-operations-controller ord_service: dir: dev/incubator/ - version: "PR-128" + version: "PR-130" name: compass-ord-service schema_migrator: dir: dev/incubator/ @@ -1053,6 +1053,8 @@ global: userContextHeader: "user_context" authTokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token" skipSSLValidation: false + specification: + protocol: "https" ordAggregator: port: 3000 prefix: /ord-aggregator From b6aa5380e7720de9ecf116d8add2fcbf6ca44be9 Mon Sep 17 00:00:00 2001 From: Lachezar Bogomilov <64094901+la4ezar@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:44:43 +0300 Subject: [PATCH 16/19] Fix non compliant log (#3810) * Initial implementation. * Update values.yaml --- chart/compass/values.yaml | 2 +- .../director/internal/formationmapping/auth_middleware.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 3cef5bc2a1..d0dfa204e7 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3808" + version: "PR-3810" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/formationmapping/auth_middleware.go b/components/director/internal/formationmapping/auth_middleware.go index ca87aad203..7ebfe10720 100644 --- a/components/director/internal/formationmapping/auth_middleware.go +++ b/components/director/internal/formationmapping/auth_middleware.go @@ -368,7 +368,7 @@ func (a *Authenticator) isFormationAssignmentAuthorized(ctx context.Context, for return false, http.StatusInternalServerError, errors.Wrap(err, "while closing database transaction") } - log.C(ctx).Infof("The caller with ID: %s and type: %s is allowed to update formation assignments in tenants of type %s", consumerID, consumerType, tnt.Type) + log.C(ctx).Infof("The caller with type: %s is allowed to update formation assignments in tenants of type %s", consumerType, tnt.Type) return true, http.StatusOK, nil } From f07c91f5643adbb8ff2447f40a9e7a4eaac68d50 Mon Sep 17 00:00:00 2001 From: ZdravkoGyurov <37419999+ZdravkoGyurov@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:34:40 +0300 Subject: [PATCH 17/19] Add IAS adapter status report logs (#3811) * add IAS adapter status report logs * update ias adapter version according to PR * fix err log send --- chart/compass/values.yaml | 2 +- .../internal/service/processor/processor.go | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index d0dfa204e7..21c777bccc 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -183,7 +183,7 @@ global: name: compass-hydrator ias_adapter: dir: dev/incubator/ - version: "PR-3807" + version: "PR-3811" name: compass-ias-adapter kyma_adapter: dir: dev/incubator/ diff --git a/components/ias-adapter/internal/service/processor/processor.go b/components/ias-adapter/internal/service/processor/processor.go index fc1ff1bca0..3b04c7a639 100644 --- a/components/ias-adapter/internal/service/processor/processor.go +++ b/components/ias-adapter/internal/service/processor/processor.go @@ -40,10 +40,11 @@ var s4Config = &types.TenantMappingConfiguration{ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping types.TenantMapping) { log := logger.FromContext(ctx) + reverseAssignmentState := tenantMapping.AssignedTenant.ReverseAssignmentState if tenantMapping.Operation == types.OperationAssign { if reverseAssignmentState != types.StateInitial && reverseAssignmentState != types.StateReady { - log.Warn().Msgf("skipping processing tenant mapping notification with $.assignedTenant.state '%s'", + log.Warn().Msgf("Skipping processing tenant mapping notification with $.assignedTenant.state '%s'", reverseAssignmentState) p.ReportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending}) return @@ -54,6 +55,7 @@ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping type if err := p.TenantMappingsService.ProcessTenantMapping(ctx, tenantMapping); err != nil { err = errors.Newf("failed to process tenant mapping notification: %w", err) + log.Err(err).Send() if operation == types.OperationAssign { if errors.Is(err, errors.IASApplicationNotFound) { @@ -62,7 +64,7 @@ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping type } if errors.Is(err, errors.S4CertificateNotFound) { - log.Info().Msgf("S/4 certificate not provided. Responding with CONFIG_PENDING.") + log.Info().Msgf("S/4 certificate not provided") p.ReportStatus(ctx, ucl.StatusReport{State: types.StateConfigPending, Configuration: s4Config}) return } @@ -76,8 +78,12 @@ func (p AsyncProcessor) ProcessTMRequest(ctx context.Context, tenantMapping type } func (p AsyncProcessor) ReportStatus(ctx context.Context, statusReport ucl.StatusReport) { + log := logger.FromContext(ctx) + statusReportURL := ctx.Value(locationHeader).(string) + log.Info().Msgf("Reporting status '%s' to '%s'", statusReport.State, statusReportURL) + if err := p.UCLService.ReportStatus(ctx, statusReportURL, statusReport); err != nil { - logger.FromContext(ctx).Error().Msgf("failed to report status to '%s': %s", statusReportURL, err) + log.Err(err).Msgf("Failed to report status to '%s'", statusReportURL) } } From 597ebcfe6decddd3fb1325f0505f475f85e51229 Mon Sep 17 00:00:00 2001 From: StanislavStefanov <39959090+StanislavStefanov@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:26:40 +0300 Subject: [PATCH 18/19] Do not delete formation if there is Subsccount assigned to it (#3796) * check if there are ASAs before deleting a formation * adjust PR version --- chart/compass/values.yaml | 2 +- .../internal/domain/formation/asa_engine.go | 6 +- .../domain/formation/asa_engine_test.go | 4 +- .../domain/formation/assign_formation_test.go | 5 +- .../domain/formation/automock/asa_engine.go | 28 ++- ...tomatic_formation_assignment_repository.go | 20 +- .../automatic_formation_assignment_service.go | 16 +- .../formation/automock/constraint_engine.go | 4 + ...mation_assignment_notifications_service.go | 4 + .../automock/formation_repository.go | 32 +++ .../automock/formation_template_repository.go | 8 + .../automock/label_def_repository.go | 8 + .../formation/automock/label_def_service.go | 16 ++ .../formation/automock/label_repository.go | 12 + .../formation/automock/label_service.go | 12 + .../automock/notifications_service.go | 16 ++ .../domain/formation/automock/process_func.go | 4 + .../automock/runtime_context_repository.go | 24 ++ .../formation/automock/runtime_repository.go | 36 +++ .../formation/automock/status_service.go | 8 + .../formation/automock/tenant_service.go | 12 + .../domain/formation/automock/uuid_service.go | 4 + .../domain/formation/fixtures_test.go | 6 +- .../internal/domain/formation/service.go | 38 ++-- .../internal/domain/formation/service_test.go | 209 +++++++++++++++--- .../formation/unassign_formation_test.go | 4 +- .../automock/bundle_instance_auth_service.go | 8 + .../runtime/automock/eventing_service.go | 4 + .../runtime/automock/formation_service.go | 20 +- .../runtime/automock/o_auth20_service.go | 4 + .../automock/runtime_context_converter.go | 8 + .../automock/runtime_context_service.go | 16 ++ .../runtime/automock/runtime_converter.go | 16 ++ .../runtime/automock/runtime_service.go | 48 ++++ .../automock/scenario_assignment_service.go | 16 +- .../runtime/automock/self_register_manager.go | 12 + .../runtime/automock/subscription_service.go | 8 + .../runtime/automock/system_auth_converter.go | 4 + .../runtime/automock/system_auth_service.go | 4 + .../domain/runtime/automock/tenant_fetcher.go | 4 + .../domain/runtime/automock/tenant_svc.go | 18 +- .../runtime/automock/webhook_converter.go | 8 + .../runtime/automock/webhook_service.go | 8 + .../internal/domain/runtime/fixtures_test.go | 2 + .../internal/domain/runtime/resolver.go | 4 +- .../internal/domain/runtime/resolver_test.go | 43 ++-- .../automock/asa_service.go | 24 +- .../automock/entity_converter.go | 22 +- .../automock/gql_converter.go | 8 +- .../scenarioassignment/automock/repository.go | 24 +- .../automock/scenarios_def_service.go | 4 + .../automock/tenant_service.go | 8 + .../domain/scenarioassignment/converter.go | 8 +- .../scenarioassignment/converter_test.go | 6 +- .../scenarioassignment/fixtures_test.go | 6 +- .../domain/scenarioassignment/repository.go | 16 +- .../scenarioassignment/repository_test.go | 12 +- .../domain/scenarioassignment/resolver.go | 8 +- .../scenarioassignment/resolver_test.go | 8 +- .../domain/scenarioassignment/service.go | 8 +- .../domain/scenarioassignment/service_test.go | 6 +- 61 files changed, 796 insertions(+), 165 deletions(-) diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index 21c777bccc..e09e6e8893 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3810" + version: "PR-3796" name: compass-director hydrator: dir: dev/incubator/ diff --git a/components/director/internal/domain/formation/asa_engine.go b/components/director/internal/domain/formation/asa_engine.go index 4d737127e5..14605a2421 100644 --- a/components/director/internal/domain/formation/asa_engine.go +++ b/components/director/internal/domain/formation/asa_engine.go @@ -38,16 +38,16 @@ func NewASAEngine(asaRepository automaticFormationAssignmentRepository, runtimeR } // EnsureScenarioAssigned ensures that the scenario is assigned to all the runtimes and runtimeContexts that are in the ASAs target_tenant_id -func (s *ASAEngine) EnsureScenarioAssigned(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error { +func (s *ASAEngine) EnsureScenarioAssigned(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error { return s.processScenario(ctx, in, processScenarioFunc, model.AssignFormation) } // RemoveAssignedScenario removes all the scenarios that are coming from the provided ASA -func (s *ASAEngine) RemoveAssignedScenario(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error { +func (s *ASAEngine) RemoveAssignedScenario(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error { return s.processScenario(ctx, in, processScenarioFunc, model.UnassignFormation) } -func (s *ASAEngine) processScenario(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc, processingType model.FormationOperation) error { +func (s *ASAEngine) processScenario(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc, processingType model.FormationOperation) error { runtimeTypes, err := s.getFormationTemplateRuntimeTypes(ctx, in.ScenarioName, in.Tenant) if err != nil { return err diff --git a/components/director/internal/domain/formation/asa_engine_test.go b/components/director/internal/domain/formation/asa_engine_test.go index 5fc4e84bd6..c7e658f74a 100644 --- a/components/director/internal/domain/formation/asa_engine_test.go +++ b/components/director/internal/domain/formation/asa_engine_test.go @@ -64,7 +64,7 @@ func TestService_EnsureScenarioAssigned(t *testing.T) { FormationRepositoryFn func() *automock.FormationRepository FormationTemplateRepositoryFn func() *automock.FormationTemplateRepository ProcessFunc func() *automock.ProcessFunc - InputASA model.AutomaticScenarioAssignment + InputASA *model.AutomaticScenarioAssignment ExpectedErrMessage string }{ { @@ -408,7 +408,7 @@ func TestService_RemoveAssignedScenario(t *testing.T) { FormationRepositoryFn func() *automock.FormationRepository FormationTemplateRepositoryFn func() *automock.FormationTemplateRepository ProcessFunc func() *automock.ProcessFunc - InputASA model.AutomaticScenarioAssignment + InputASA *model.AutomaticScenarioAssignment ExpectedErrMessage string }{ { diff --git a/components/director/internal/domain/formation/assign_formation_test.go b/components/director/internal/domain/formation/assign_formation_test.go index 5f07d0203e..6be71ff2be 100644 --- a/components/director/internal/domain/formation/assign_formation_test.go +++ b/components/director/internal/domain/formation/assign_formation_test.go @@ -233,8 +233,7 @@ func TestServiceAssignFormation(t *testing.T) { TargetType: model.FormationAssignmentTypeRuntimeContext, }, } - - asa := model.AutomaticScenarioAssignment{ + asa := &model.AutomaticScenarioAssignment{ ScenarioName: testFormationName, Tenant: TntInternalID, TargetTenantID: TargetTenant, @@ -1547,7 +1546,7 @@ func TestServiceAssignFormation(t *testing.T) { }, AsaRepoFn: func() *automock.AutomaticFormationAssignmentRepository { asaRepo := &automock.AutomaticFormationAssignmentRepository{} - asaRepo.On("Create", ctx, model.AutomaticScenarioAssignment{ScenarioName: testFormationName, Tenant: TntInternalID, TargetTenantID: TargetTenant}).Return(testErr).Once() + asaRepo.On("Create", ctx, asa).Return(testErr).Once() return asaRepo }, diff --git a/components/director/internal/domain/formation/automock/asa_engine.go b/components/director/internal/domain/formation/automock/asa_engine.go index 90625f74a8..352c0fdd21 100644 --- a/components/director/internal/domain/formation/automock/asa_engine.go +++ b/components/director/internal/domain/formation/automock/asa_engine.go @@ -19,11 +19,15 @@ type AsaEngine struct { } // EnsureScenarioAssigned provides a mock function with given fields: ctx, in, processScenarioFunc -func (_m *AsaEngine) EnsureScenarioAssigned(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc formation.ProcessScenarioFunc) error { +func (_m *AsaEngine) EnsureScenarioAssigned(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc formation.ProcessScenarioFunc) error { ret := _m.Called(ctx, in, processScenarioFunc) + if len(ret) == 0 { + panic("no return value specified for EnsureScenarioAssigned") + } + var r0 error - if rf, ok := ret.Get(0).(func(context.Context, model.AutomaticScenarioAssignment, formation.ProcessScenarioFunc) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *model.AutomaticScenarioAssignment, formation.ProcessScenarioFunc) error); ok { r0 = rf(ctx, in, processScenarioFunc) } else { r0 = ret.Error(0) @@ -36,6 +40,10 @@ func (_m *AsaEngine) EnsureScenarioAssigned(ctx context.Context, in model.Automa func (_m *AsaEngine) GetMatchingFuncByFormationObjectType(objType graphql.FormationObjectType) (formation.MatchingFunc, error) { ret := _m.Called(objType) + if len(ret) == 0 { + panic("no return value specified for GetMatchingFuncByFormationObjectType") + } + var r0 formation.MatchingFunc var r1 error if rf, ok := ret.Get(0).(func(graphql.FormationObjectType) (formation.MatchingFunc, error)); ok { @@ -62,6 +70,10 @@ func (_m *AsaEngine) GetMatchingFuncByFormationObjectType(objType graphql.Format func (_m *AsaEngine) GetScenariosFromMatchingASAs(ctx context.Context, objectID string, objType graphql.FormationObjectType) ([]string, error) { ret := _m.Called(ctx, objectID, objType) + if len(ret) == 0 { + panic("no return value specified for GetScenariosFromMatchingASAs") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, graphql.FormationObjectType) ([]string, error)); ok { @@ -88,6 +100,10 @@ func (_m *AsaEngine) GetScenariosFromMatchingASAs(ctx context.Context, objectID func (_m *AsaEngine) IsFormationComingFromASA(ctx context.Context, objectID string, _a2 string, objectType graphql.FormationObjectType) (bool, error) { ret := _m.Called(ctx, objectID, _a2, objectType) + if len(ret) == 0 { + panic("no return value specified for IsFormationComingFromASA") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, graphql.FormationObjectType) (bool, error)); ok { @@ -109,11 +125,15 @@ func (_m *AsaEngine) IsFormationComingFromASA(ctx context.Context, objectID stri } // RemoveAssignedScenario provides a mock function with given fields: ctx, in, processScenarioFunc -func (_m *AsaEngine) RemoveAssignedScenario(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc formation.ProcessScenarioFunc) error { +func (_m *AsaEngine) RemoveAssignedScenario(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc formation.ProcessScenarioFunc) error { ret := _m.Called(ctx, in, processScenarioFunc) + if len(ret) == 0 { + panic("no return value specified for RemoveAssignedScenario") + } + var r0 error - if rf, ok := ret.Get(0).(func(context.Context, model.AutomaticScenarioAssignment, formation.ProcessScenarioFunc) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *model.AutomaticScenarioAssignment, formation.ProcessScenarioFunc) error); ok { r0 = rf(ctx, in, processScenarioFunc) } else { r0 = ret.Error(0) diff --git a/components/director/internal/domain/formation/automock/automatic_formation_assignment_repository.go b/components/director/internal/domain/formation/automock/automatic_formation_assignment_repository.go index 83221c6d10..737fbd268c 100644 --- a/components/director/internal/domain/formation/automock/automatic_formation_assignment_repository.go +++ b/components/director/internal/domain/formation/automock/automatic_formation_assignment_repository.go @@ -16,11 +16,15 @@ type AutomaticFormationAssignmentRepository struct { } // Create provides a mock function with given fields: ctx, _a1 -func (_m *AutomaticFormationAssignmentRepository) Create(ctx context.Context, _a1 model.AutomaticScenarioAssignment) error { +func (_m *AutomaticFormationAssignmentRepository) Create(ctx context.Context, _a1 *model.AutomaticScenarioAssignment) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 error - if rf, ok := ret.Get(0).(func(context.Context, model.AutomaticScenarioAssignment) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *model.AutomaticScenarioAssignment) error); ok { r0 = rf(ctx, _a1) } else { r0 = ret.Error(0) @@ -33,6 +37,10 @@ func (_m *AutomaticFormationAssignmentRepository) Create(ctx context.Context, _a func (_m *AutomaticFormationAssignmentRepository) DeleteForScenarioName(ctx context.Context, tenantID string, scenarioName string) error { ret := _m.Called(ctx, tenantID, scenarioName) + if len(ret) == 0 { + panic("no return value specified for DeleteForScenarioName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenantID, scenarioName) @@ -47,6 +55,10 @@ func (_m *AutomaticFormationAssignmentRepository) DeleteForScenarioName(ctx cont func (_m *AutomaticFormationAssignmentRepository) DeleteForTargetTenant(ctx context.Context, tenantID string, targetTenantID string) error { ret := _m.Called(ctx, tenantID, targetTenantID) + if len(ret) == 0 { + panic("no return value specified for DeleteForTargetTenant") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenantID, targetTenantID) @@ -61,6 +73,10 @@ func (_m *AutomaticFormationAssignmentRepository) DeleteForTargetTenant(ctx cont func (_m *AutomaticFormationAssignmentRepository) ListAll(ctx context.Context, tenantID string) ([]*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for ListAll") + } + var r0 []*model.AutomaticScenarioAssignment var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.AutomaticScenarioAssignment, error)); ok { diff --git a/components/director/internal/domain/formation/automock/automatic_formation_assignment_service.go b/components/director/internal/domain/formation/automock/automatic_formation_assignment_service.go index 010731f6ae..9be31f9653 100644 --- a/components/director/internal/domain/formation/automock/automatic_formation_assignment_service.go +++ b/components/director/internal/domain/formation/automock/automatic_formation_assignment_service.go @@ -16,18 +16,24 @@ type AutomaticFormationAssignmentService struct { } // GetForScenarioName provides a mock function with given fields: ctx, scenarioName -func (_m *AutomaticFormationAssignmentService) GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (_m *AutomaticFormationAssignmentService) GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, scenarioName) - var r0 model.AutomaticScenarioAssignment + if len(ret) == 0 { + panic("no return value specified for GetForScenarioName") + } + + var r0 *model.AutomaticScenarioAssignment var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (model.AutomaticScenarioAssignment, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) (*model.AutomaticScenarioAssignment, error)); ok { return rf(ctx, scenarioName) } - if rf, ok := ret.Get(0).(func(context.Context, string) model.AutomaticScenarioAssignment); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) *model.AutomaticScenarioAssignment); ok { r0 = rf(ctx, scenarioName) } else { - r0 = ret.Get(0).(model.AutomaticScenarioAssignment) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AutomaticScenarioAssignment) + } } if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { diff --git a/components/director/internal/domain/formation/automock/constraint_engine.go b/components/director/internal/domain/formation/automock/constraint_engine.go index de84b8cd30..0de1842038 100644 --- a/components/director/internal/domain/formation/automock/constraint_engine.go +++ b/components/director/internal/domain/formation/automock/constraint_engine.go @@ -19,6 +19,10 @@ type ConstraintEngine struct { func (_m *ConstraintEngine) EnforceConstraints(ctx context.Context, location formationconstraint.JoinPointLocation, details formationconstraint.JoinPointDetails, formationTemplateID string) error { ret := _m.Called(ctx, location, details, formationTemplateID) + if len(ret) == 0 { + panic("no return value specified for EnforceConstraints") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, formationconstraint.JoinPointLocation, formationconstraint.JoinPointDetails, string) error); ok { r0 = rf(ctx, location, details, formationTemplateID) diff --git a/components/director/internal/domain/formation/automock/formation_assignment_notifications_service.go b/components/director/internal/domain/formation/automock/formation_assignment_notifications_service.go index 13095b87c0..65a3e744d9 100644 --- a/components/director/internal/domain/formation/automock/formation_assignment_notifications_service.go +++ b/components/director/internal/domain/formation/automock/formation_assignment_notifications_service.go @@ -21,6 +21,10 @@ type FormationAssignmentNotificationsService struct { func (_m *FormationAssignmentNotificationsService) GenerateFormationAssignmentNotification(ctx context.Context, formationAssignment *model.FormationAssignment, operation model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error) { ret := _m.Called(ctx, formationAssignment, operation) + if len(ret) == 0 { + panic("no return value specified for GenerateFormationAssignmentNotification") + } + var r0 *webhookclient.FormationAssignmentNotificationRequest var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.FormationAssignment, model.FormationOperation) (*webhookclient.FormationAssignmentNotificationRequest, error)); ok { diff --git a/components/director/internal/domain/formation/automock/formation_repository.go b/components/director/internal/domain/formation/automock/formation_repository.go index 4a33f85b61..7b97141fdb 100644 --- a/components/director/internal/domain/formation/automock/formation_repository.go +++ b/components/director/internal/domain/formation/automock/formation_repository.go @@ -19,6 +19,10 @@ type FormationRepository struct { func (_m *FormationRepository) Create(ctx context.Context, item *model.Formation) error { ret := _m.Called(ctx, item) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Formation) error); ok { r0 = rf(ctx, item) @@ -33,6 +37,10 @@ func (_m *FormationRepository) Create(ctx context.Context, item *model.Formation func (_m *FormationRepository) DeleteByName(ctx context.Context, tenantID string, name string) error { ret := _m.Called(ctx, tenantID, name) + if len(ret) == 0 { + panic("no return value specified for DeleteByName") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenantID, name) @@ -47,6 +55,10 @@ func (_m *FormationRepository) DeleteByName(ctx context.Context, tenantID string func (_m *FormationRepository) Get(ctx context.Context, id string, tenantID string) (*model.Formation, error) { ret := _m.Called(ctx, id, tenantID) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.Formation, error)); ok { @@ -73,6 +85,10 @@ func (_m *FormationRepository) Get(ctx context.Context, id string, tenantID stri func (_m *FormationRepository) GetByName(ctx context.Context, name string, tenantID string) (*model.Formation, error) { ret := _m.Called(ctx, name, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetByName") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.Formation, error)); ok { @@ -99,6 +115,10 @@ func (_m *FormationRepository) GetByName(ctx context.Context, name string, tenan func (_m *FormationRepository) GetGlobalByID(ctx context.Context, id string) (*model.Formation, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetGlobalByID") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.Formation, error)); ok { @@ -125,6 +145,10 @@ func (_m *FormationRepository) GetGlobalByID(ctx context.Context, id string) (*m func (_m *FormationRepository) List(ctx context.Context, tenant string, pageSize int, cursor string) (*model.FormationPage, error) { ret := _m.Called(ctx, tenant, pageSize, cursor) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 *model.FormationPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int, string) (*model.FormationPage, error)); ok { @@ -151,6 +175,10 @@ func (_m *FormationRepository) List(ctx context.Context, tenant string, pageSize func (_m *FormationRepository) ListByIDsGlobal(ctx context.Context, formationIDs []string) ([]*model.Formation, error) { ret := _m.Called(ctx, formationIDs) + if len(ret) == 0 { + panic("no return value specified for ListByIDsGlobal") + } + var r0 []*model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.Formation, error)); ok { @@ -177,6 +205,10 @@ func (_m *FormationRepository) ListByIDsGlobal(ctx context.Context, formationIDs func (_m *FormationRepository) Update(ctx context.Context, _a1 *model.Formation) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Formation) error); ok { r0 = rf(ctx, _a1) diff --git a/components/director/internal/domain/formation/automock/formation_template_repository.go b/components/director/internal/domain/formation/automock/formation_template_repository.go index bd25184ac2..f7d056de3d 100644 --- a/components/director/internal/domain/formation/automock/formation_template_repository.go +++ b/components/director/internal/domain/formation/automock/formation_template_repository.go @@ -19,6 +19,10 @@ type FormationTemplateRepository struct { func (_m *FormationTemplateRepository) Get(ctx context.Context, id string) (*model.FormationTemplate, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.FormationTemplate var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.FormationTemplate, error)); ok { @@ -45,6 +49,10 @@ func (_m *FormationTemplateRepository) Get(ctx context.Context, id string) (*mod func (_m *FormationTemplateRepository) GetByNameAndTenant(ctx context.Context, templateName string, tenantID string) (*model.FormationTemplate, error) { ret := _m.Called(ctx, templateName, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetByNameAndTenant") + } + var r0 *model.FormationTemplate var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.FormationTemplate, error)); ok { diff --git a/components/director/internal/domain/formation/automock/label_def_repository.go b/components/director/internal/domain/formation/automock/label_def_repository.go index 132217bed1..01285ec5d9 100644 --- a/components/director/internal/domain/formation/automock/label_def_repository.go +++ b/components/director/internal/domain/formation/automock/label_def_repository.go @@ -19,6 +19,10 @@ type LabelDefRepository struct { func (_m *LabelDefRepository) GetByKey(ctx context.Context, tenant string, key string) (*model.LabelDefinition, error) { ret := _m.Called(ctx, tenant, key) + if len(ret) == 0 { + panic("no return value specified for GetByKey") + } + var r0 *model.LabelDefinition var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.LabelDefinition, error)); ok { @@ -45,6 +49,10 @@ func (_m *LabelDefRepository) GetByKey(ctx context.Context, tenant string, key s func (_m *LabelDefRepository) UpdateWithVersion(ctx context.Context, def model.LabelDefinition) error { ret := _m.Called(ctx, def) + if len(ret) == 0 { + panic("no return value specified for UpdateWithVersion") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, model.LabelDefinition) error); ok { r0 = rf(ctx, def) diff --git a/components/director/internal/domain/formation/automock/label_def_service.go b/components/director/internal/domain/formation/automock/label_def_service.go index 08047effc8..08bc537d08 100644 --- a/components/director/internal/domain/formation/automock/label_def_service.go +++ b/components/director/internal/domain/formation/automock/label_def_service.go @@ -17,6 +17,10 @@ type LabelDefService struct { func (_m *LabelDefService) CreateWithFormations(ctx context.Context, tnt string, formations []string) error { ret := _m.Called(ctx, tnt, formations) + if len(ret) == 0 { + panic("no return value specified for CreateWithFormations") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, []string) error); ok { r0 = rf(ctx, tnt, formations) @@ -31,6 +35,10 @@ func (_m *LabelDefService) CreateWithFormations(ctx context.Context, tnt string, func (_m *LabelDefService) GetAvailableScenarios(ctx context.Context, tenantID string) ([]string, error) { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetAvailableScenarios") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { @@ -57,6 +65,10 @@ func (_m *LabelDefService) GetAvailableScenarios(ctx context.Context, tenantID s func (_m *LabelDefService) ValidateAutomaticScenarioAssignmentAgainstSchema(ctx context.Context, schema interface{}, tenantID string, key string) error { ret := _m.Called(ctx, schema, tenantID, key) + if len(ret) == 0 { + panic("no return value specified for ValidateAutomaticScenarioAssignmentAgainstSchema") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, interface{}, string, string) error); ok { r0 = rf(ctx, schema, tenantID, key) @@ -71,6 +83,10 @@ func (_m *LabelDefService) ValidateAutomaticScenarioAssignmentAgainstSchema(ctx func (_m *LabelDefService) ValidateExistingLabelsAgainstSchema(ctx context.Context, schema interface{}, tenant string, key string) error { ret := _m.Called(ctx, schema, tenant, key) + if len(ret) == 0 { + panic("no return value specified for ValidateExistingLabelsAgainstSchema") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, interface{}, string, string) error); ok { r0 = rf(ctx, schema, tenant, key) diff --git a/components/director/internal/domain/formation/automock/label_repository.go b/components/director/internal/domain/formation/automock/label_repository.go index 44887fcddd..7c9586d6fe 100644 --- a/components/director/internal/domain/formation/automock/label_repository.go +++ b/components/director/internal/domain/formation/automock/label_repository.go @@ -19,6 +19,10 @@ type LabelRepository struct { func (_m *LabelRepository) Delete(_a0 context.Context, _a1 string, _a2 model.LabelableObject, _a3 string, _a4 string) error { ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, model.LabelableObject, string, string) error); ok { r0 = rf(_a0, _a1, _a2, _a3, _a4) @@ -33,6 +37,10 @@ func (_m *LabelRepository) Delete(_a0 context.Context, _a1 string, _a2 model.Lab func (_m *LabelRepository) ListForObject(ctx context.Context, tenant string, objectType model.LabelableObject, objectID string) (map[string]*model.Label, error) { ret := _m.Called(ctx, tenant, objectType, objectID) + if len(ret) == 0 { + panic("no return value specified for ListForObject") + } + var r0 map[string]*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, model.LabelableObject, string) (map[string]*model.Label, error)); ok { @@ -59,6 +67,10 @@ func (_m *LabelRepository) ListForObject(ctx context.Context, tenant string, obj func (_m *LabelRepository) ListForObjectIDs(ctx context.Context, tenant string, objectType model.LabelableObject, objectIDs []string) (map[string]map[string]interface{}, error) { ret := _m.Called(ctx, tenant, objectType, objectIDs) + if len(ret) == 0 { + panic("no return value specified for ListForObjectIDs") + } + var r0 map[string]map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, model.LabelableObject, []string) (map[string]map[string]interface{}, error)); ok { diff --git a/components/director/internal/domain/formation/automock/label_service.go b/components/director/internal/domain/formation/automock/label_service.go index 4c0740a546..9821364ccf 100644 --- a/components/director/internal/domain/formation/automock/label_service.go +++ b/components/director/internal/domain/formation/automock/label_service.go @@ -19,6 +19,10 @@ type LabelService struct { func (_m *LabelService) CreateLabel(ctx context.Context, tenant string, id string, labelInput *model.LabelInput) error { ret := _m.Called(ctx, tenant, id, labelInput) + if len(ret) == 0 { + panic("no return value specified for CreateLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, *model.LabelInput) error); ok { r0 = rf(ctx, tenant, id, labelInput) @@ -33,6 +37,10 @@ func (_m *LabelService) CreateLabel(ctx context.Context, tenant string, id strin func (_m *LabelService) GetLabel(ctx context.Context, tenant string, labelInput *model.LabelInput) (*model.Label, error) { ret := _m.Called(ctx, tenant, labelInput) + if len(ret) == 0 { + panic("no return value specified for GetLabel") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, *model.LabelInput) (*model.Label, error)); ok { @@ -59,6 +67,10 @@ func (_m *LabelService) GetLabel(ctx context.Context, tenant string, labelInput func (_m *LabelService) UpdateLabel(ctx context.Context, tenant string, id string, labelInput *model.LabelInput) error { ret := _m.Called(ctx, tenant, id, labelInput) + if len(ret) == 0 { + panic("no return value specified for UpdateLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string, *model.LabelInput) error); ok { r0 = rf(ctx, tenant, id, labelInput) diff --git a/components/director/internal/domain/formation/automock/notifications_service.go b/components/director/internal/domain/formation/automock/notifications_service.go index 7d5f739fda..8df1756cb0 100644 --- a/components/director/internal/domain/formation/automock/notifications_service.go +++ b/components/director/internal/domain/formation/automock/notifications_service.go @@ -27,6 +27,10 @@ type NotificationsService struct { func (_m *NotificationsService) GenerateFormationAssignmentNotifications(ctx context.Context, tenant string, objectID string, _a3 *model.Formation, operation model.FormationOperation, objectType graphql.FormationObjectType) ([]*webhookclient.FormationAssignmentNotificationRequestTargetMapping, error) { ret := _m.Called(ctx, tenant, objectID, _a3, operation, objectType) + if len(ret) == 0 { + panic("no return value specified for GenerateFormationAssignmentNotifications") + } + var r0 []*webhookclient.FormationAssignmentNotificationRequestTargetMapping var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, *model.Formation, model.FormationOperation, graphql.FormationObjectType) ([]*webhookclient.FormationAssignmentNotificationRequestTargetMapping, error)); ok { @@ -53,6 +57,10 @@ func (_m *NotificationsService) GenerateFormationAssignmentNotifications(ctx con func (_m *NotificationsService) GenerateFormationNotifications(ctx context.Context, formationTemplateWebhooks []*model.Webhook, tenantID string, _a3 *model.Formation, formationTemplateName string, formationTemplateID string, formationOperation model.FormationOperation) ([]*webhookclient.FormationNotificationRequest, error) { ret := _m.Called(ctx, formationTemplateWebhooks, tenantID, _a3, formationTemplateName, formationTemplateID, formationOperation) + if len(ret) == 0 { + panic("no return value specified for GenerateFormationNotifications") + } + var r0 []*webhookclient.FormationNotificationRequest var r1 error if rf, ok := ret.Get(0).(func(context.Context, []*model.Webhook, string, *model.Formation, string, string, model.FormationOperation) ([]*webhookclient.FormationNotificationRequest, error)); ok { @@ -79,6 +87,10 @@ func (_m *NotificationsService) GenerateFormationNotifications(ctx context.Conte func (_m *NotificationsService) PrepareDetailsForNotificationStatusReturned(ctx context.Context, _a1 *model.Formation, operation model.FormationOperation) (*formationconstraint.NotificationStatusReturnedOperationDetails, error) { ret := _m.Called(ctx, _a1, operation) + if len(ret) == 0 { + panic("no return value specified for PrepareDetailsForNotificationStatusReturned") + } + var r0 *formationconstraint.NotificationStatusReturnedOperationDetails var r1 error if rf, ok := ret.Get(0).(func(context.Context, *model.Formation, model.FormationOperation) (*formationconstraint.NotificationStatusReturnedOperationDetails, error)); ok { @@ -105,6 +117,10 @@ func (_m *NotificationsService) PrepareDetailsForNotificationStatusReturned(ctx func (_m *NotificationsService) SendNotification(ctx context.Context, webhookNotificationReq webhookclient.WebhookExtRequest) (*webhook.Response, error) { ret := _m.Called(ctx, webhookNotificationReq) + if len(ret) == 0 { + panic("no return value specified for SendNotification") + } + var r0 *webhook.Response var r1 error if rf, ok := ret.Get(0).(func(context.Context, webhookclient.WebhookExtRequest) (*webhook.Response, error)); ok { diff --git a/components/director/internal/domain/formation/automock/process_func.go b/components/director/internal/domain/formation/automock/process_func.go index 604e5718f0..f2aad15842 100644 --- a/components/director/internal/domain/formation/automock/process_func.go +++ b/components/director/internal/domain/formation/automock/process_func.go @@ -21,6 +21,10 @@ type ProcessFunc struct { func (_m *ProcessFunc) ProcessScenarioFunc(_a0 context.Context, _a1 string, _a2 string, _a3 graphql.FormationObjectType, _a4 model.Formation) (*model.Formation, error) { ret := _m.Called(_a0, _a1, _a2, _a3, _a4) + if len(ret) == 0 { + panic("no return value specified for ProcessScenarioFunc") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, graphql.FormationObjectType, model.Formation) (*model.Formation, error)); ok { diff --git a/components/director/internal/domain/formation/automock/runtime_context_repository.go b/components/director/internal/domain/formation/automock/runtime_context_repository.go index 76751bb627..94a8eab478 100644 --- a/components/director/internal/domain/formation/automock/runtime_context_repository.go +++ b/components/director/internal/domain/formation/automock/runtime_context_repository.go @@ -19,6 +19,10 @@ type RuntimeContextRepository struct { func (_m *RuntimeContextRepository) ExistsByRuntimeID(ctx context.Context, tenant string, rtmID string) (bool, error) { ret := _m.Called(ctx, tenant, rtmID) + if len(ret) == 0 { + panic("no return value specified for ExistsByRuntimeID") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (bool, error)); ok { @@ -43,6 +47,10 @@ func (_m *RuntimeContextRepository) ExistsByRuntimeID(ctx context.Context, tenan func (_m *RuntimeContextRepository) GetByID(ctx context.Context, tenant string, id string) (*model.RuntimeContext, error) { ret := _m.Called(ctx, tenant, id) + if len(ret) == 0 { + panic("no return value specified for GetByID") + } + var r0 *model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.RuntimeContext, error)); ok { @@ -69,6 +77,10 @@ func (_m *RuntimeContextRepository) GetByID(ctx context.Context, tenant string, func (_m *RuntimeContextRepository) GetByRuntimeID(ctx context.Context, tenant string, runtimeID string) (*model.RuntimeContext, error) { ret := _m.Called(ctx, tenant, runtimeID) + if len(ret) == 0 { + panic("no return value specified for GetByRuntimeID") + } + var r0 *model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.RuntimeContext, error)); ok { @@ -95,6 +107,10 @@ func (_m *RuntimeContextRepository) GetByRuntimeID(ctx context.Context, tenant s func (_m *RuntimeContextRepository) ListByIDs(ctx context.Context, tenant string, ids []string) ([]*model.RuntimeContext, error) { ret := _m.Called(ctx, tenant, ids) + if len(ret) == 0 { + panic("no return value specified for ListByIDs") + } + var r0 []*model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string) ([]*model.RuntimeContext, error)); ok { @@ -121,6 +137,10 @@ func (_m *RuntimeContextRepository) ListByIDs(ctx context.Context, tenant string func (_m *RuntimeContextRepository) ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.RuntimeContext, error) { ret := _m.Called(ctx, tenant, scenarios) + if len(ret) == 0 { + panic("no return value specified for ListByScenarios") + } + var r0 []*model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string) ([]*model.RuntimeContext, error)); ok { @@ -147,6 +167,10 @@ func (_m *RuntimeContextRepository) ListByScenarios(ctx context.Context, tenant func (_m *RuntimeContextRepository) ListByScenariosAndRuntimeIDs(ctx context.Context, tenant string, scenarios []string, runtimeIDs []string) ([]*model.RuntimeContext, error) { ret := _m.Called(ctx, tenant, scenarios, runtimeIDs) + if len(ret) == 0 { + panic("no return value specified for ListByScenariosAndRuntimeIDs") + } + var r0 []*model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string, []string) ([]*model.RuntimeContext, error)); ok { diff --git a/components/director/internal/domain/formation/automock/runtime_repository.go b/components/director/internal/domain/formation/automock/runtime_repository.go index 761cd34b22..95567320c1 100644 --- a/components/director/internal/domain/formation/automock/runtime_repository.go +++ b/components/director/internal/domain/formation/automock/runtime_repository.go @@ -21,6 +21,10 @@ type RuntimeRepository struct { func (_m *RuntimeRepository) GetByFiltersAndIDUsingUnion(ctx context.Context, tenant string, id string, filter []*labelfilter.LabelFilter) (*model.Runtime, error) { ret := _m.Called(ctx, tenant, id, filter) + if len(ret) == 0 { + panic("no return value specified for GetByFiltersAndIDUsingUnion") + } + var r0 *model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []*labelfilter.LabelFilter) (*model.Runtime, error)); ok { @@ -47,6 +51,10 @@ func (_m *RuntimeRepository) GetByFiltersAndIDUsingUnion(ctx context.Context, te func (_m *RuntimeRepository) GetByID(ctx context.Context, tenant string, id string) (*model.Runtime, error) { ret := _m.Called(ctx, tenant, id) + if len(ret) == 0 { + panic("no return value specified for GetByID") + } + var r0 *model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.Runtime, error)); ok { @@ -73,6 +81,10 @@ func (_m *RuntimeRepository) GetByID(ctx context.Context, tenant string, id stri func (_m *RuntimeRepository) ListAll(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, filter) + if len(ret) == 0 { + panic("no return value specified for ListAll") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []*labelfilter.LabelFilter) ([]*model.Runtime, error)); ok { @@ -99,6 +111,10 @@ func (_m *RuntimeRepository) ListAll(ctx context.Context, tenant string, filter func (_m *RuntimeRepository) ListAllWithUnionSetCombination(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, filter) + if len(ret) == 0 { + panic("no return value specified for ListAllWithUnionSetCombination") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []*labelfilter.LabelFilter) ([]*model.Runtime, error)); ok { @@ -125,6 +141,10 @@ func (_m *RuntimeRepository) ListAllWithUnionSetCombination(ctx context.Context, func (_m *RuntimeRepository) ListByIDs(ctx context.Context, tenant string, ids []string) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, ids) + if len(ret) == 0 { + panic("no return value specified for ListByIDs") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string) ([]*model.Runtime, error)); ok { @@ -151,6 +171,10 @@ func (_m *RuntimeRepository) ListByIDs(ctx context.Context, tenant string, ids [ func (_m *RuntimeRepository) ListByScenarios(ctx context.Context, tenant string, scenarios []string) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, scenarios) + if len(ret) == 0 { + panic("no return value specified for ListByScenarios") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string) ([]*model.Runtime, error)); ok { @@ -177,6 +201,10 @@ func (_m *RuntimeRepository) ListByScenarios(ctx context.Context, tenant string, func (_m *RuntimeRepository) ListByScenariosAndIDs(ctx context.Context, tenant string, scenarios []string, ids []string) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, scenarios, ids) + if len(ret) == 0 { + panic("no return value specified for ListByScenariosAndIDs") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []string, []string) ([]*model.Runtime, error)); ok { @@ -203,6 +231,10 @@ func (_m *RuntimeRepository) ListByScenariosAndIDs(ctx context.Context, tenant s func (_m *RuntimeRepository) ListOwnedRuntimes(ctx context.Context, tenant string, filter []*labelfilter.LabelFilter) ([]*model.Runtime, error) { ret := _m.Called(ctx, tenant, filter) + if len(ret) == 0 { + panic("no return value specified for ListOwnedRuntimes") + } + var r0 []*model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, []*labelfilter.LabelFilter) ([]*model.Runtime, error)); ok { @@ -229,6 +261,10 @@ func (_m *RuntimeRepository) ListOwnedRuntimes(ctx context.Context, tenant strin func (_m *RuntimeRepository) OwnerExistsByFiltersAndID(ctx context.Context, tenant string, id string, filter []*labelfilter.LabelFilter) (bool, error) { ret := _m.Called(ctx, tenant, id, filter) + if len(ret) == 0 { + panic("no return value specified for OwnerExistsByFiltersAndID") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, []*labelfilter.LabelFilter) (bool, error)); ok { diff --git a/components/director/internal/domain/formation/automock/status_service.go b/components/director/internal/domain/formation/automock/status_service.go index aafeaf8fd6..72c86d0a2c 100644 --- a/components/director/internal/domain/formation/automock/status_service.go +++ b/components/director/internal/domain/formation/automock/status_service.go @@ -21,6 +21,10 @@ type StatusService struct { func (_m *StatusService) SetFormationToErrorStateWithConstraints(ctx context.Context, _a1 *model.Formation, errorMessage string, errorCode formationassignment.AssignmentErrorCode, state model.FormationState, operation model.FormationOperation) error { ret := _m.Called(ctx, _a1, errorMessage, errorCode, state, operation) + if len(ret) == 0 { + panic("no return value specified for SetFormationToErrorStateWithConstraints") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Formation, string, formationassignment.AssignmentErrorCode, model.FormationState, model.FormationOperation) error); ok { r0 = rf(ctx, _a1, errorMessage, errorCode, state, operation) @@ -35,6 +39,10 @@ func (_m *StatusService) SetFormationToErrorStateWithConstraints(ctx context.Con func (_m *StatusService) UpdateWithConstraints(ctx context.Context, _a1 *model.Formation, operation model.FormationOperation) error { ret := _m.Called(ctx, _a1, operation) + if len(ret) == 0 { + panic("no return value specified for UpdateWithConstraints") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.Formation, model.FormationOperation) error); ok { r0 = rf(ctx, _a1, operation) diff --git a/components/director/internal/domain/formation/automock/tenant_service.go b/components/director/internal/domain/formation/automock/tenant_service.go index 8f6792fc65..ec9a61481b 100644 --- a/components/director/internal/domain/formation/automock/tenant_service.go +++ b/components/director/internal/domain/formation/automock/tenant_service.go @@ -19,6 +19,10 @@ type TenantService struct { func (_m *TenantService) GetInternalTenant(ctx context.Context, externalTenant string) (string, error) { ret := _m.Called(ctx, externalTenant) + if len(ret) == 0 { + panic("no return value specified for GetInternalTenant") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { @@ -43,6 +47,10 @@ func (_m *TenantService) GetInternalTenant(ctx context.Context, externalTenant s func (_m *TenantService) GetTenantByExternalID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetTenantByExternalID") + } + var r0 *model.BusinessTenantMapping var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.BusinessTenantMapping, error)); ok { @@ -69,6 +77,10 @@ func (_m *TenantService) GetTenantByExternalID(ctx context.Context, id string) ( func (_m *TenantService) GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetTenantByID") + } + var r0 *model.BusinessTenantMapping var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.BusinessTenantMapping, error)); ok { diff --git a/components/director/internal/domain/formation/automock/uuid_service.go b/components/director/internal/domain/formation/automock/uuid_service.go index ea361d0af7..df1c465807 100644 --- a/components/director/internal/domain/formation/automock/uuid_service.go +++ b/components/director/internal/domain/formation/automock/uuid_service.go @@ -13,6 +13,10 @@ type UuidService struct { func (_m *UuidService) Generate() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Generate") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/components/director/internal/domain/formation/fixtures_test.go b/components/director/internal/domain/formation/fixtures_test.go index 5416f66470..043587c5f5 100644 --- a/components/director/internal/domain/formation/fixtures_test.go +++ b/components/director/internal/domain/formation/fixtures_test.go @@ -1047,12 +1047,12 @@ func fixCtxWithTenant() context.Context { return ctx } -func fixModel(scenarioName string) model.AutomaticScenarioAssignment { +func fixModel(scenarioName string) *model.AutomaticScenarioAssignment { return fixModelWithScenarioName(scenarioName) } -func fixModelWithScenarioName(scenario string) model.AutomaticScenarioAssignment { - return model.AutomaticScenarioAssignment{ +func fixModelWithScenarioName(scenario string) *model.AutomaticScenarioAssignment { + return &model.AutomaticScenarioAssignment{ ScenarioName: scenario, Tenant: tenantID.String(), TargetTenantID: TargetTenantID, diff --git a/components/director/internal/domain/formation/service.go b/components/director/internal/domain/formation/service.go index 6e42dbd2c6..9d2f203038 100644 --- a/components/director/internal/domain/formation/service.go +++ b/components/director/internal/domain/formation/service.go @@ -129,12 +129,12 @@ type uuidService interface { //go:generate mockery --exported --name=automaticFormationAssignmentService --output=automock --outpkg=automock --case=underscore --disable-version-string type automaticFormationAssignmentService interface { - GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) + GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) } //go:generate mockery --exported --name=automaticFormationAssignmentRepository --output=automock --outpkg=automock --case=underscore --disable-version-string type automaticFormationAssignmentRepository interface { - Create(ctx context.Context, model model.AutomaticScenarioAssignment) error + Create(ctx context.Context, model *model.AutomaticScenarioAssignment) error DeleteForTargetTenant(ctx context.Context, tenantID string, targetTenantID string) error DeleteForScenarioName(ctx context.Context, tenantID string, scenarioName string) error ListAll(ctx context.Context, tenantID string) ([]*model.AutomaticScenarioAssignment, error) @@ -154,8 +154,8 @@ type constraintEngine interface { //go:generate mockery --exported --name=asaEngine --output=automock --outpkg=automock --case=underscore --disable-version-string type asaEngine interface { - EnsureScenarioAssigned(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error - RemoveAssignedScenario(ctx context.Context, in model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error + EnsureScenarioAssigned(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error + RemoveAssignedScenario(ctx context.Context, in *model.AutomaticScenarioAssignment, processScenarioFunc ProcessScenarioFunc) error GetMatchingFuncByFormationObjectType(objType graphql.FormationObjectType) (MatchingFunc, error) GetScenariosFromMatchingASAs(ctx context.Context, objectID string, objType graphql.FormationObjectType) ([]string, error) IsFormationComingFromASA(ctx context.Context, objectID, formation string, objectType graphql.FormationObjectType) (bool, error) @@ -448,6 +448,14 @@ func (s *service) DeleteFormation(ctx context.Context, tnt string, formation mod return nil, errors.Errorf("cannot delete formation with ID %q, because it is not empty", formationID) } + asa, err := s.asaService.GetForScenarioName(ctx, formationName) + if err != nil && !apperrors.IsNotFoundError(err) { + return nil, errors.Wrapf(err, "while getting automatic scenario assignment for formation with name %q", formationName) + } + if asa != nil { + return nil, errors.Errorf("cannot delete formation with ID %q, because there is still a subaccount part of it", formationID) + } + if err = s.constraintEngine.EnforceConstraints(ctx, formationconstraint.PreDelete, joinPointDetails, formationTemplateID); err != nil { return nil, errors.Wrapf(err, "while enforcing constraints for target operation %q and constraint type %q", model.DeleteFormationOperation, model.PreOperation) } @@ -1434,27 +1442,27 @@ func (s *service) resynchronizeFormationNotifications(ctx context.Context, tenan // CreateAutomaticScenarioAssignment creates a new AutomaticScenarioAssignment for a given ScenarioName, Tenant and TargetTenantID // It also ensures that all runtimes(or/and runtime contexts) with given scenarios are assigned for the TargetTenantID -func (s *service) CreateAutomaticScenarioAssignment(ctx context.Context, in model.AutomaticScenarioAssignment) (model.AutomaticScenarioAssignment, error) { +func (s *service) CreateAutomaticScenarioAssignment(ctx context.Context, in *model.AutomaticScenarioAssignment) (*model.AutomaticScenarioAssignment, error) { tenantID, err := tenant.LoadFromContext(ctx) if err != nil { - return model.AutomaticScenarioAssignment{}, err + return nil, err } in.Tenant = tenantID if err := s.validateThatScenarioExists(ctx, in); err != nil { - return model.AutomaticScenarioAssignment{}, err + return nil, err } if err = s.repo.Create(ctx, in); err != nil { if apperrors.IsNotUniqueError(err) { - return model.AutomaticScenarioAssignment{}, apperrors.NewInvalidOperationError("a given scenario already has an assignment") + return nil, apperrors.NewInvalidOperationError("a given scenario already has an assignment") } - return model.AutomaticScenarioAssignment{}, errors.Wrap(err, "while persisting Assignment") + return nil, errors.Wrap(err, "while persisting Assignment") } if err = s.asaEngine.EnsureScenarioAssigned(ctx, in, s.AssignFormation); err != nil { - return model.AutomaticScenarioAssignment{}, errors.Wrap(err, "while assigning scenario to runtimes matching selector") + return nil, errors.Wrap(err, "while assigning scenario to runtimes matching selector") } return in, nil @@ -1462,7 +1470,7 @@ func (s *service) CreateAutomaticScenarioAssignment(ctx context.Context, in mode // DeleteAutomaticScenarioAssignment deletes the assignment for a given scenario in a scope of a tenant // It also removes corresponding assigned scenarios for the ASA -func (s *service) DeleteAutomaticScenarioAssignment(ctx context.Context, in model.AutomaticScenarioAssignment) error { +func (s *service) DeleteAutomaticScenarioAssignment(ctx context.Context, in *model.AutomaticScenarioAssignment) error { tenantID, err := tenant.LoadFromContext(ctx) if err != nil { return errors.Wrap(err, "while loading tenant from context") @@ -1482,7 +1490,7 @@ func (s *service) DeleteAutomaticScenarioAssignment(ctx context.Context, in mode // RemoveAssignedScenarios removes all the scenarios that are coming from any of the provided ASAs func (s *service) RemoveAssignedScenarios(ctx context.Context, in []*model.AutomaticScenarioAssignment) error { for _, asa := range in { - if err := s.asaEngine.RemoveAssignedScenario(ctx, *asa, s.UnassignFormation); err != nil { + if err := s.asaEngine.RemoveAssignedScenario(ctx, asa, s.UnassignFormation); err != nil { return errors.Wrapf(err, "while deleting automatic scenario assigment: %s", asa.ScenarioName) } } @@ -1688,8 +1696,8 @@ func newLabelInput(formation, objectID string, objectType model.LabelableObject) } } -func newAutomaticScenarioAssignmentModel(formation, callerTenant, targetTenant string) model.AutomaticScenarioAssignment { - return model.AutomaticScenarioAssignment{ +func newAutomaticScenarioAssignmentModel(formation, callerTenant, targetTenant string) *model.AutomaticScenarioAssignment { + return &model.AutomaticScenarioAssignment{ ScenarioName: formation, Tenant: callerTenant, TargetTenantID: targetTenant, @@ -1738,7 +1746,7 @@ func (s *service) ensureSameTargetTenant(in []*model.AutomaticScenarioAssignment return targetTenant, nil } -func (s *service) validateThatScenarioExists(ctx context.Context, in model.AutomaticScenarioAssignment) error { +func (s *service) validateThatScenarioExists(ctx context.Context, in *model.AutomaticScenarioAssignment) error { availableScenarios, err := s.getAvailableScenarios(ctx, in.Tenant) if err != nil { return err diff --git a/components/director/internal/domain/formation/service_test.go b/components/director/internal/domain/formation/service_test.go index 9fc51326bc..30014ba7de 100644 --- a/components/director/internal/domain/formation/service_test.go +++ b/components/director/internal/domain/formation/service_test.go @@ -1582,18 +1582,19 @@ func TestServiceDeleteFormation(t *testing.T) { nilSchemaLblDef.Schema = nil testCases := []struct { - Name string - LabelDefRepositoryFn func() *automock.LabelDefRepository - LabelDefServiceFn func() *automock.LabelDefService - NotificationsSvcFn func() *automock.NotificationsService - FormationRepoFn func() *automock.FormationRepository - FormationAssignmentSvcFn func() *automock.FormationAssignmentService - FormationTemplateRepoFn func() *automock.FormationTemplateRepository - ConstraintEngineFn func() *automock.ConstraintEngine - webhookRepoFn func() *automock.WebhookRepository - InputFormation model.Formation - ExpectedFormation *model.Formation - ExpectedErrMessage string + Name string + LabelDefRepositoryFn func() *automock.LabelDefRepository + LabelDefServiceFn func() *automock.LabelDefService + NotificationsSvcFn func() *automock.NotificationsService + FormationRepoFn func() *automock.FormationRepository + FormationAssignmentSvcFn func() *automock.FormationAssignmentService + FormationTemplateRepoFn func() *automock.FormationTemplateRepository + AutomaticScenarioAssignmentSvcFn func() *automock.AutomaticFormationAssignmentService + ConstraintEngineFn func() *automock.ConstraintEngine + webhookRepoFn func() *automock.WebhookRepository + InputFormation model.Formation + ExpectedFormation *model.Formation + ExpectedErrMessage string }{ { Name: "success", @@ -1630,6 +1631,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1679,6 +1685,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1728,6 +1739,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1766,6 +1782,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1803,6 +1824,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1840,6 +1866,11 @@ func TestServiceDeleteFormation(t *testing.T) { formationAssignmentSvc.On("GetAssignmentsForFormation", ctx, TntInternalID, FormationID).Return(emptyFormationAssignments, nil) return formationAssignmentSvc }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1880,6 +1911,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1920,6 +1956,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -1965,6 +2006,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2011,6 +2057,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2058,6 +2109,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2117,6 +2173,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2147,6 +2208,11 @@ func TestServiceDeleteFormation(t *testing.T) { formationAssignmentSvc.On("GetAssignmentsForFormation", ctx, TntInternalID, FormationID).Return(emptyFormationAssignments, nil) return formationAssignmentSvc }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(testErr).Once() @@ -2234,6 +2300,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2265,6 +2336,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2301,6 +2377,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2339,6 +2420,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2377,6 +2463,11 @@ func TestServiceDeleteFormation(t *testing.T) { repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() return repo }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, nil).Once() + return svc + }, ConstraintEngineFn: func() *automock.ConstraintEngine { engine := &automock.ConstraintEngine{} engine.On("EnforceConstraints", ctx, preDeleteLocation, deleteFormationDetails, FormationTemplateID).Return(nil).Once() @@ -2391,6 +2482,68 @@ func TestServiceDeleteFormation(t *testing.T) { ExpectedFormation: nil, ExpectedErrMessage: testErr.Error(), }, + { + Name: "Error when there is subaccount assigned to the formation", + FormationRepoFn: func() *automock.FormationRepository { + formationRepoMock := &automock.FormationRepository{} + formationRepoMock.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation2, nil).Once() + return formationRepoMock + }, + FormationAssignmentSvcFn: func() *automock.FormationAssignmentService { + formationAssignmentSvc := &automock.FormationAssignmentService{} + formationAssignmentSvc.On("GetAssignmentsForFormation", ctx, TntInternalID, FormationID).Return(emptyFormationAssignments, nil) + return formationAssignmentSvc + }, + FormationTemplateRepoFn: func() *automock.FormationTemplateRepository { + repo := &automock.FormationTemplateRepository{} + repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() + return repo + }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(fixModel(in.Name), nil).Once() + return svc + }, + webhookRepoFn: func() *automock.WebhookRepository { + webhookRepo := &automock.WebhookRepository{} + webhookRepo.On("ListByReferenceObjectIDGlobal", ctx, FormationTemplateID, model.FormationTemplateWebhookReference).Return(emptyFormationLifecycleWebhooks, nil).Once() + return webhookRepo + }, + InputFormation: in, + ExpectedFormation: nil, + ExpectedErrMessage: fmt.Sprintf("cannot delete formation with ID %q, because there is still a subaccount part of it", expectedFormation2.ID), + }, + { + Name: "Error when checking for assigned subaccount", + FormationRepoFn: func() *automock.FormationRepository { + formationRepoMock := &automock.FormationRepository{} + formationRepoMock.On("GetByName", ctx, testFormationName, TntInternalID).Return(expectedFormation2, nil).Once() + return formationRepoMock + }, + FormationAssignmentSvcFn: func() *automock.FormationAssignmentService { + formationAssignmentSvc := &automock.FormationAssignmentService{} + formationAssignmentSvc.On("GetAssignmentsForFormation", ctx, TntInternalID, FormationID).Return(emptyFormationAssignments, nil) + return formationAssignmentSvc + }, + FormationTemplateRepoFn: func() *automock.FormationTemplateRepository { + repo := &automock.FormationTemplateRepository{} + repo.On("Get", ctx, FormationTemplateID).Return(fixFormationTemplateModel(), nil).Once() + return repo + }, + AutomaticScenarioAssignmentSvcFn: func() *automock.AutomaticFormationAssignmentService { + svc := &automock.AutomaticFormationAssignmentService{} + svc.On("GetForScenarioName", ctx, expectedFormation.Name).Return(nil, testErr).Once() + return svc + }, + webhookRepoFn: func() *automock.WebhookRepository { + webhookRepo := &automock.WebhookRepository{} + webhookRepo.On("ListByReferenceObjectIDGlobal", ctx, FormationTemplateID, model.FormationTemplateWebhookReference).Return(emptyFormationLifecycleWebhooks, nil).Once() + return webhookRepo + }, + InputFormation: in, + ExpectedFormation: nil, + ExpectedErrMessage: fmt.Sprintf("while getting automatic scenario assignment for formation with name %q", expectedFormation2.Name), + }, } for _, testCase := range testCases { @@ -2400,17 +2553,14 @@ func TestServiceDeleteFormation(t *testing.T) { if testCase.LabelDefRepositoryFn != nil { labelDefRepo = testCase.LabelDefRepositoryFn() } - labelDefService := unusedLabelDefService() if testCase.LabelDefServiceFn != nil { labelDefService = testCase.LabelDefServiceFn() } - notificationsService := unusedNotificationsService() if testCase.NotificationsSvcFn != nil { notificationsService = testCase.NotificationsSvcFn() } - formationRepo := unusedFormationRepo() if testCase.FormationRepoFn != nil { formationRepo = testCase.FormationRepoFn() @@ -2419,7 +2569,10 @@ func TestServiceDeleteFormation(t *testing.T) { if testCase.FormationTemplateRepoFn != nil { formationTemplateRepo = testCase.FormationTemplateRepoFn() } - + asaService := unusedASAService() + if testCase.AutomaticScenarioAssignmentSvcFn != nil { + asaService = testCase.AutomaticScenarioAssignmentSvcFn() + } constraintEngine := unusedConstraintEngine() if testCase.ConstraintEngineFn != nil { constraintEngine = testCase.ConstraintEngineFn() @@ -2433,7 +2586,7 @@ func TestServiceDeleteFormation(t *testing.T) { formationAssignmentService = testCase.FormationAssignmentSvcFn() } - svc := formation.NewService(nil, nil, labelDefRepo, nil, formationRepo, formationTemplateRepo, nil, nil, labelDefService, nil, nil, nil, nil, nil, formationAssignmentService, nil, notificationsService, constraintEngine, webhookRepo, nil, runtimeType, applicationType) + svc := formation.NewService(nil, nil, labelDefRepo, nil, formationRepo, formationTemplateRepo, nil, nil, labelDefService, nil, asaService, nil, nil, nil, formationAssignmentService, nil, notificationsService, constraintEngine, webhookRepo, nil, runtimeType, applicationType) // WHEN actual, err := svc.DeleteFormation(ctx, TntInternalID, testCase.InputFormation) @@ -2448,7 +2601,7 @@ func TestServiceDeleteFormation(t *testing.T) { require.Nil(t, actual) } - mock.AssertExpectationsForObjects(t, labelDefRepo, labelDefService, notificationsService, formationRepo, formationTemplateRepo, constraintEngine) + mock.AssertExpectationsForObjects(t, labelDefRepo, labelDefService, notificationsService, formationRepo, formationTemplateRepo, asaService, constraintEngine) }) } } @@ -2605,8 +2758,8 @@ func TestService_CreateAutomaticScenarioAssignment(t *testing.T) { LabelDefServiceFn func() *automock.LabelDefService AsaRepoFn func() *automock.AutomaticFormationAssignmentRepository AsaEngineFN func() *automock.AsaEngine - InputASA model.AutomaticScenarioAssignment - ExpectedASA model.AutomaticScenarioAssignment + InputASA *model.AutomaticScenarioAssignment + ExpectedASA *model.AutomaticScenarioAssignment ExpectedErrMessage string }{ { @@ -2637,7 +2790,7 @@ func TestService_CreateAutomaticScenarioAssignment(t *testing.T) { AsaRepoFn: unusedASARepo, AsaEngineFN: unusedASAEngine, InputASA: fixModel(ScenarioName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "while getting available scenarios: some error", }, { @@ -2652,7 +2805,7 @@ func TestService_CreateAutomaticScenarioAssignment(t *testing.T) { }, AsaEngineFN: unusedASAEngine, InputASA: fixModel(testFormationName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "while persisting Assignment", }, @@ -2667,7 +2820,7 @@ func TestService_CreateAutomaticScenarioAssignment(t *testing.T) { }, AsaEngineFN: unusedASAEngine, InputASA: fixModel(testFormationName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "a given scenario already has an assignment", }, { @@ -2686,7 +2839,7 @@ func TestService_CreateAutomaticScenarioAssignment(t *testing.T) { return engine }, InputASA: fixModel(testFormationName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "while assigning scenario to runtimes matching selector", }, } @@ -2738,8 +2891,8 @@ func TestService_DeleteAutomaticScenarioAssignment(t *testing.T) { Name string AsaRepoFn func() *automock.AutomaticFormationAssignmentRepository AsaEngineFN func() *automock.AsaEngine - InputASA model.AutomaticScenarioAssignment - ExpectedASA model.AutomaticScenarioAssignment + InputASA *model.AutomaticScenarioAssignment + ExpectedASA *model.AutomaticScenarioAssignment ExpectedErrMessage string }{ { @@ -2766,7 +2919,7 @@ func TestService_DeleteAutomaticScenarioAssignment(t *testing.T) { }, AsaEngineFN: unusedASAEngine, InputASA: fixModel(testFormationName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "while deleting the Assignment", }, { @@ -2782,7 +2935,7 @@ func TestService_DeleteAutomaticScenarioAssignment(t *testing.T) { return engine }, InputASA: fixModel(testFormationName), - ExpectedASA: model.AutomaticScenarioAssignment{}, + ExpectedASA: nil, ExpectedErrMessage: "while unassigning scenario from runtimes", }, } diff --git a/components/director/internal/domain/formation/unassign_formation_test.go b/components/director/internal/domain/formation/unassign_formation_test.go index 2e4be91ece..0303a61e1b 100644 --- a/components/director/internal/domain/formation/unassign_formation_test.go +++ b/components/director/internal/domain/formation/unassign_formation_test.go @@ -2264,7 +2264,7 @@ func TestServiceUnassignFormation_Tenant(t *testing.T) { TenantID: TntInternalID, State: model.ReadyFormationState, } - asa := model.AutomaticScenarioAssignment{ + asa := &model.AutomaticScenarioAssignment{ ScenarioName: testFormationName, Tenant: TntInternalID, TargetTenantID: TargetTenant, @@ -2424,7 +2424,7 @@ func TestServiceUnassignFormation_Tenant(t *testing.T) { Name: "error for tenant when getting asa fails", AsaServiceFN: func() *automock.AutomaticFormationAssignmentService { asaService := &automock.AutomaticFormationAssignmentService{} - asaService.On("GetForScenarioName", ctx, testFormationName).Return(model.AutomaticScenarioAssignment{}, testErr).Once() + asaService.On("GetForScenarioName", ctx, testFormationName).Return(nil, testErr).Once() return asaService }, TenantServiceFn: func() *automock.TenantService { diff --git a/components/director/internal/domain/runtime/automock/bundle_instance_auth_service.go b/components/director/internal/domain/runtime/automock/bundle_instance_auth_service.go index 45e2b33dc0..d99ddc75ce 100644 --- a/components/director/internal/domain/runtime/automock/bundle_instance_auth_service.go +++ b/components/director/internal/domain/runtime/automock/bundle_instance_auth_service.go @@ -18,6 +18,10 @@ type BundleInstanceAuthService struct { func (_m *BundleInstanceAuthService) ListByRuntimeID(ctx context.Context, runtimeID string) ([]*model.BundleInstanceAuth, error) { ret := _m.Called(ctx, runtimeID) + if len(ret) == 0 { + panic("no return value specified for ListByRuntimeID") + } + var r0 []*model.BundleInstanceAuth var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.BundleInstanceAuth, error)); ok { @@ -44,6 +48,10 @@ func (_m *BundleInstanceAuthService) ListByRuntimeID(ctx context.Context, runtim func (_m *BundleInstanceAuthService) Update(ctx context.Context, instanceAuth *model.BundleInstanceAuth) error { ret := _m.Called(ctx, instanceAuth) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.BundleInstanceAuth) error); ok { r0 = rf(ctx, instanceAuth) diff --git a/components/director/internal/domain/runtime/automock/eventing_service.go b/components/director/internal/domain/runtime/automock/eventing_service.go index 1e3928aeca..5d8796452e 100644 --- a/components/director/internal/domain/runtime/automock/eventing_service.go +++ b/components/director/internal/domain/runtime/automock/eventing_service.go @@ -20,6 +20,10 @@ type EventingService struct { func (_m *EventingService) GetForRuntime(ctx context.Context, runtimeID uuid.UUID) (*model.RuntimeEventingConfiguration, error) { ret := _m.Called(ctx, runtimeID) + if len(ret) == 0 { + panic("no return value specified for GetForRuntime") + } + var r0 *model.RuntimeEventingConfiguration var r1 error if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (*model.RuntimeEventingConfiguration, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/formation_service.go b/components/director/internal/domain/runtime/automock/formation_service.go index 6c8d851f59..5921c126e6 100644 --- a/components/director/internal/domain/runtime/automock/formation_service.go +++ b/components/director/internal/domain/runtime/automock/formation_service.go @@ -20,6 +20,10 @@ type FormationService struct { func (_m *FormationService) AssignFormation(ctx context.Context, tnt string, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) { ret := _m.Called(ctx, tnt, objectID, objectType, formation) + if len(ret) == 0 { + panic("no return value specified for AssignFormation") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, graphql.FormationObjectType, model.Formation) (*model.Formation, error)); ok { @@ -43,11 +47,15 @@ func (_m *FormationService) AssignFormation(ctx context.Context, tnt string, obj } // DeleteAutomaticScenarioAssignment provides a mock function with given fields: ctx, in -func (_m *FormationService) DeleteAutomaticScenarioAssignment(ctx context.Context, in model.AutomaticScenarioAssignment) error { +func (_m *FormationService) DeleteAutomaticScenarioAssignment(ctx context.Context, in *model.AutomaticScenarioAssignment) error { ret := _m.Called(ctx, in) + if len(ret) == 0 { + panic("no return value specified for DeleteAutomaticScenarioAssignment") + } + var r0 error - if rf, ok := ret.Get(0).(func(context.Context, model.AutomaticScenarioAssignment) error); ok { + if rf, ok := ret.Get(0).(func(context.Context, *model.AutomaticScenarioAssignment) error); ok { r0 = rf(ctx, in) } else { r0 = ret.Error(0) @@ -60,6 +68,10 @@ func (_m *FormationService) DeleteAutomaticScenarioAssignment(ctx context.Contex func (_m *FormationService) MergeScenariosFromInputLabelsAndAssignments(ctx context.Context, inputLabels map[string]interface{}, runtimeID string) ([]interface{}, error) { ret := _m.Called(ctx, inputLabels, runtimeID) + if len(ret) == 0 { + panic("no return value specified for MergeScenariosFromInputLabelsAndAssignments") + } + var r0 []interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context, map[string]interface{}, string) ([]interface{}, error)); ok { @@ -86,6 +98,10 @@ func (_m *FormationService) MergeScenariosFromInputLabelsAndAssignments(ctx cont func (_m *FormationService) UnassignFormation(ctx context.Context, tnt string, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) { ret := _m.Called(ctx, tnt, objectID, objectType, formation) + if len(ret) == 0 { + panic("no return value specified for UnassignFormation") + } + var r0 *model.Formation var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, graphql.FormationObjectType, model.Formation) (*model.Formation, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/o_auth20_service.go b/components/director/internal/domain/runtime/automock/o_auth20_service.go index ce3af6c6c7..0e6dbab541 100644 --- a/components/director/internal/domain/runtime/automock/o_auth20_service.go +++ b/components/director/internal/domain/runtime/automock/o_auth20_service.go @@ -18,6 +18,10 @@ type OAuth20Service struct { func (_m *OAuth20Service) DeleteMultipleClientCredentials(ctx context.Context, auths []model.SystemAuth) error { ret := _m.Called(ctx, auths) + if len(ret) == 0 { + panic("no return value specified for DeleteMultipleClientCredentials") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, []model.SystemAuth) error); ok { r0 = rf(ctx, auths) diff --git a/components/director/internal/domain/runtime/automock/runtime_context_converter.go b/components/director/internal/domain/runtime/automock/runtime_context_converter.go index 32cb88db4d..f07297cd41 100644 --- a/components/director/internal/domain/runtime/automock/runtime_context_converter.go +++ b/components/director/internal/domain/runtime/automock/runtime_context_converter.go @@ -18,6 +18,10 @@ type RuntimeContextConverter struct { func (_m *RuntimeContextConverter) MultipleToGraphQL(in []*model.RuntimeContext) []*graphql.RuntimeContext { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for MultipleToGraphQL") + } + var r0 []*graphql.RuntimeContext if rf, ok := ret.Get(0).(func([]*model.RuntimeContext) []*graphql.RuntimeContext); ok { r0 = rf(in) @@ -34,6 +38,10 @@ func (_m *RuntimeContextConverter) MultipleToGraphQL(in []*model.RuntimeContext) func (_m *RuntimeContextConverter) ToGraphQL(in *model.RuntimeContext) *graphql.RuntimeContext { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for ToGraphQL") + } + var r0 *graphql.RuntimeContext if rf, ok := ret.Get(0).(func(*model.RuntimeContext) *graphql.RuntimeContext); ok { r0 = rf(in) diff --git a/components/director/internal/domain/runtime/automock/runtime_context_service.go b/components/director/internal/domain/runtime/automock/runtime_context_service.go index 2f8b752c53..9b4cc6c26f 100644 --- a/components/director/internal/domain/runtime/automock/runtime_context_service.go +++ b/components/director/internal/domain/runtime/automock/runtime_context_service.go @@ -18,6 +18,10 @@ type RuntimeContextService struct { func (_m *RuntimeContextService) Delete(ctx context.Context, id string) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, id) @@ -32,6 +36,10 @@ func (_m *RuntimeContextService) Delete(ctx context.Context, id string) error { func (_m *RuntimeContextService) GetForRuntime(ctx context.Context, id string, runtimeID string) (*model.RuntimeContext, error) { ret := _m.Called(ctx, id, runtimeID) + if len(ret) == 0 { + panic("no return value specified for GetForRuntime") + } + var r0 *model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.RuntimeContext, error)); ok { @@ -58,6 +66,10 @@ func (_m *RuntimeContextService) GetForRuntime(ctx context.Context, id string, r func (_m *RuntimeContextService) ListAllForRuntime(ctx context.Context, runtimeID string) ([]*model.RuntimeContext, error) { ret := _m.Called(ctx, runtimeID) + if len(ret) == 0 { + panic("no return value specified for ListAllForRuntime") + } + var r0 []*model.RuntimeContext var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.RuntimeContext, error)); ok { @@ -84,6 +96,10 @@ func (_m *RuntimeContextService) ListAllForRuntime(ctx context.Context, runtimeI func (_m *RuntimeContextService) ListByRuntimeIDs(ctx context.Context, runtimeIDs []string, pageSize int, cursor string) ([]*model.RuntimeContextPage, error) { ret := _m.Called(ctx, runtimeIDs, pageSize, cursor) + if len(ret) == 0 { + panic("no return value specified for ListByRuntimeIDs") + } + var r0 []*model.RuntimeContextPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string, int, string) ([]*model.RuntimeContextPage, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/runtime_converter.go b/components/director/internal/domain/runtime/automock/runtime_converter.go index 1a6d926b58..71ae633dec 100644 --- a/components/director/internal/domain/runtime/automock/runtime_converter.go +++ b/components/director/internal/domain/runtime/automock/runtime_converter.go @@ -18,6 +18,10 @@ type RuntimeConverter struct { func (_m *RuntimeConverter) MultipleToGraphQL(in []*model.Runtime) []*graphql.Runtime { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for MultipleToGraphQL") + } + var r0 []*graphql.Runtime if rf, ok := ret.Get(0).(func([]*model.Runtime) []*graphql.Runtime); ok { r0 = rf(in) @@ -34,6 +38,10 @@ func (_m *RuntimeConverter) MultipleToGraphQL(in []*model.Runtime) []*graphql.Ru func (_m *RuntimeConverter) RegisterInputFromGraphQL(in graphql.RuntimeRegisterInput) (model.RuntimeRegisterInput, error) { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for RegisterInputFromGraphQL") + } + var r0 model.RuntimeRegisterInput var r1 error if rf, ok := ret.Get(0).(func(graphql.RuntimeRegisterInput) (model.RuntimeRegisterInput, error)); ok { @@ -58,6 +66,10 @@ func (_m *RuntimeConverter) RegisterInputFromGraphQL(in graphql.RuntimeRegisterI func (_m *RuntimeConverter) ToGraphQL(in *model.Runtime) *graphql.Runtime { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for ToGraphQL") + } + var r0 *graphql.Runtime if rf, ok := ret.Get(0).(func(*model.Runtime) *graphql.Runtime); ok { r0 = rf(in) @@ -74,6 +86,10 @@ func (_m *RuntimeConverter) ToGraphQL(in *model.Runtime) *graphql.Runtime { func (_m *RuntimeConverter) UpdateInputFromGraphQL(in graphql.RuntimeUpdateInput) model.RuntimeUpdateInput { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for UpdateInputFromGraphQL") + } + var r0 model.RuntimeUpdateInput if rf, ok := ret.Get(0).(func(graphql.RuntimeUpdateInput) model.RuntimeUpdateInput); ok { r0 = rf(in) diff --git a/components/director/internal/domain/runtime/automock/runtime_service.go b/components/director/internal/domain/runtime/automock/runtime_service.go index 2851dc1849..4e706116ca 100644 --- a/components/director/internal/domain/runtime/automock/runtime_service.go +++ b/components/director/internal/domain/runtime/automock/runtime_service.go @@ -20,6 +20,10 @@ type RuntimeService struct { func (_m *RuntimeService) CreateWithMandatoryLabels(ctx context.Context, in model.RuntimeRegisterInput, id string, mandatoryLabels map[string]interface{}) error { ret := _m.Called(ctx, in, id, mandatoryLabels) + if len(ret) == 0 { + panic("no return value specified for CreateWithMandatoryLabels") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, model.RuntimeRegisterInput, string, map[string]interface{}) error); ok { r0 = rf(ctx, in, id, mandatoryLabels) @@ -34,6 +38,10 @@ func (_m *RuntimeService) CreateWithMandatoryLabels(ctx context.Context, in mode func (_m *RuntimeService) Delete(ctx context.Context, id string) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, id) @@ -48,6 +56,10 @@ func (_m *RuntimeService) Delete(ctx context.Context, id string) error { func (_m *RuntimeService) DeleteLabel(ctx context.Context, runtimeID string, key string) error { ret := _m.Called(ctx, runtimeID, key) + if len(ret) == 0 { + panic("no return value specified for DeleteLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, runtimeID, key) @@ -62,6 +74,10 @@ func (_m *RuntimeService) DeleteLabel(ctx context.Context, runtimeID string, key func (_m *RuntimeService) Get(ctx context.Context, id string) (*model.Runtime, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.Runtime, error)); ok { @@ -88,6 +104,10 @@ func (_m *RuntimeService) Get(ctx context.Context, id string) (*model.Runtime, e func (_m *RuntimeService) GetByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error) { ret := _m.Called(ctx, filters) + if len(ret) == 0 { + panic("no return value specified for GetByFilters") + } + var r0 *model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, []*labelfilter.LabelFilter) (*model.Runtime, error)); ok { @@ -114,6 +134,10 @@ func (_m *RuntimeService) GetByFilters(ctx context.Context, filters []*labelfilt func (_m *RuntimeService) GetByTokenIssuer(ctx context.Context, issuer string) (*model.Runtime, error) { ret := _m.Called(ctx, issuer) + if len(ret) == 0 { + panic("no return value specified for GetByTokenIssuer") + } + var r0 *model.Runtime var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.Runtime, error)); ok { @@ -140,6 +164,10 @@ func (_m *RuntimeService) GetByTokenIssuer(ctx context.Context, issuer string) ( func (_m *RuntimeService) GetLabel(ctx context.Context, runtimeID string, key string) (*model.Label, error) { ret := _m.Called(ctx, runtimeID, key) + if len(ret) == 0 { + panic("no return value specified for GetLabel") + } + var r0 *model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.Label, error)); ok { @@ -166,6 +194,10 @@ func (_m *RuntimeService) GetLabel(ctx context.Context, runtimeID string, key st func (_m *RuntimeService) List(ctx context.Context, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimePage, error) { ret := _m.Called(ctx, filter, pageSize, cursor) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 *model.RuntimePage var r1 error if rf, ok := ret.Get(0).(func(context.Context, []*labelfilter.LabelFilter, int, string) (*model.RuntimePage, error)); ok { @@ -192,6 +224,10 @@ func (_m *RuntimeService) List(ctx context.Context, filter []*labelfilter.LabelF func (_m *RuntimeService) ListLabels(ctx context.Context, runtimeID string) (map[string]*model.Label, error) { ret := _m.Called(ctx, runtimeID) + if len(ret) == 0 { + panic("no return value specified for ListLabels") + } + var r0 map[string]*model.Label var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (map[string]*model.Label, error)); ok { @@ -218,6 +254,10 @@ func (_m *RuntimeService) ListLabels(ctx context.Context, runtimeID string) (map func (_m *RuntimeService) SetLabel(ctx context.Context, label *model.LabelInput) error { ret := _m.Called(ctx, label) + if len(ret) == 0 { + panic("no return value specified for SetLabel") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.LabelInput) error); ok { r0 = rf(ctx, label) @@ -232,6 +272,10 @@ func (_m *RuntimeService) SetLabel(ctx context.Context, label *model.LabelInput) func (_m *RuntimeService) UnsafeExtractModifiableLabels(labels map[string]interface{}) (map[string]interface{}, error) { ret := _m.Called(labels) + if len(ret) == 0 { + panic("no return value specified for UnsafeExtractModifiableLabels") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(map[string]interface{}) (map[string]interface{}, error)); ok { @@ -258,6 +302,10 @@ func (_m *RuntimeService) UnsafeExtractModifiableLabels(labels map[string]interf func (_m *RuntimeService) Update(ctx context.Context, id string, in model.RuntimeUpdateInput) error { ret := _m.Called(ctx, id, in) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, model.RuntimeUpdateInput) error); ok { r0 = rf(ctx, id, in) diff --git a/components/director/internal/domain/runtime/automock/scenario_assignment_service.go b/components/director/internal/domain/runtime/automock/scenario_assignment_service.go index 42fb77c385..e25225c4f5 100644 --- a/components/director/internal/domain/runtime/automock/scenario_assignment_service.go +++ b/components/director/internal/domain/runtime/automock/scenario_assignment_service.go @@ -15,18 +15,24 @@ type ScenarioAssignmentService struct { } // GetForScenarioName provides a mock function with given fields: ctx, scenarioName -func (_m *ScenarioAssignmentService) GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (_m *ScenarioAssignmentService) GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, scenarioName) - var r0 model.AutomaticScenarioAssignment + if len(ret) == 0 { + panic("no return value specified for GetForScenarioName") + } + + var r0 *model.AutomaticScenarioAssignment var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (model.AutomaticScenarioAssignment, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) (*model.AutomaticScenarioAssignment, error)); ok { return rf(ctx, scenarioName) } - if rf, ok := ret.Get(0).(func(context.Context, string) model.AutomaticScenarioAssignment); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) *model.AutomaticScenarioAssignment); ok { r0 = rf(ctx, scenarioName) } else { - r0 = ret.Get(0).(model.AutomaticScenarioAssignment) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AutomaticScenarioAssignment) + } } if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { diff --git a/components/director/internal/domain/runtime/automock/self_register_manager.go b/components/director/internal/domain/runtime/automock/self_register_manager.go index 5988bb2939..a1e2d0f3f1 100644 --- a/components/director/internal/domain/runtime/automock/self_register_manager.go +++ b/components/director/internal/domain/runtime/automock/self_register_manager.go @@ -18,6 +18,10 @@ type SelfRegisterManager struct { func (_m *SelfRegisterManager) CleanupSelfRegistration(ctx context.Context, selfRegisterLabelValue string, region string) error { ret := _m.Called(ctx, selfRegisterLabelValue, region) + if len(ret) == 0 { + panic("no return value specified for CleanupSelfRegistration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, selfRegisterLabelValue, region) @@ -32,6 +36,10 @@ func (_m *SelfRegisterManager) CleanupSelfRegistration(ctx context.Context, self func (_m *SelfRegisterManager) GetSelfRegDistinguishingLabelKey() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for GetSelfRegDistinguishingLabelKey") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() @@ -46,6 +54,10 @@ func (_m *SelfRegisterManager) GetSelfRegDistinguishingLabelKey() string { func (_m *SelfRegisterManager) PrepareForSelfRegistration(ctx context.Context, resourceType resource.Type, labels map[string]interface{}, id string, validate func() error) (map[string]interface{}, error) { ret := _m.Called(ctx, resourceType, labels, id, validate) + if len(ret) == 0 { + panic("no return value specified for PrepareForSelfRegistration") + } + var r0 map[string]interface{} var r1 error if rf, ok := ret.Get(0).(func(context.Context, resource.Type, map[string]interface{}, string, func() error) (map[string]interface{}, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/subscription_service.go b/components/director/internal/domain/runtime/automock/subscription_service.go index adf0e9e6f9..4e0fd1687f 100644 --- a/components/director/internal/domain/runtime/automock/subscription_service.go +++ b/components/director/internal/domain/runtime/automock/subscription_service.go @@ -17,6 +17,10 @@ type SubscriptionService struct { func (_m *SubscriptionService) SubscribeTenantToRuntime(ctx context.Context, providerID string, subaccountTenantID string, providerSubaccountID string, consumerTenantID string, region string, subscriptionAppName string, subscriptionID string) (bool, error) { ret := _m.Called(ctx, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region, subscriptionAppName, subscriptionID) + if len(ret) == 0 { + panic("no return value specified for SubscribeTenantToRuntime") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string, string) (bool, error)); ok { @@ -41,6 +45,10 @@ func (_m *SubscriptionService) SubscribeTenantToRuntime(ctx context.Context, pro func (_m *SubscriptionService) UnsubscribeTenantFromRuntime(ctx context.Context, providerID string, subaccountTenantID string, providerSubaccountID string, consumerTenantID string, region string, subscriptionID string) (bool, error) { ret := _m.Called(ctx, providerID, subaccountTenantID, providerSubaccountID, consumerTenantID, region, subscriptionID) + if len(ret) == 0 { + panic("no return value specified for UnsubscribeTenantFromRuntime") + } + var r0 bool var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, string) (bool, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/system_auth_converter.go b/components/director/internal/domain/runtime/automock/system_auth_converter.go index 136a4b2aa2..9221641505 100644 --- a/components/director/internal/domain/runtime/automock/system_auth_converter.go +++ b/components/director/internal/domain/runtime/automock/system_auth_converter.go @@ -18,6 +18,10 @@ type SystemAuthConverter struct { func (_m *SystemAuthConverter) ToGraphQL(in *model.SystemAuth) (graphql.SystemAuth, error) { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for ToGraphQL") + } + var r0 graphql.SystemAuth var r1 error if rf, ok := ret.Get(0).(func(*model.SystemAuth) (graphql.SystemAuth, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/system_auth_service.go b/components/director/internal/domain/runtime/automock/system_auth_service.go index 169e36a8bd..d034426ce5 100644 --- a/components/director/internal/domain/runtime/automock/system_auth_service.go +++ b/components/director/internal/domain/runtime/automock/system_auth_service.go @@ -18,6 +18,10 @@ type SystemAuthService struct { func (_m *SystemAuthService) ListForObject(ctx context.Context, objectType model.SystemAuthReferenceObjectType, objectID string) ([]model.SystemAuth, error) { ret := _m.Called(ctx, objectType, objectID) + if len(ret) == 0 { + panic("no return value specified for ListForObject") + } + var r0 []model.SystemAuth var r1 error if rf, ok := ret.Get(0).(func(context.Context, model.SystemAuthReferenceObjectType, string) ([]model.SystemAuth, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/tenant_fetcher.go b/components/director/internal/domain/runtime/automock/tenant_fetcher.go index c776f01a94..141855152c 100644 --- a/components/director/internal/domain/runtime/automock/tenant_fetcher.go +++ b/components/director/internal/domain/runtime/automock/tenant_fetcher.go @@ -17,6 +17,10 @@ type TenantFetcher struct { func (_m *TenantFetcher) FetchOnDemand(ctx context.Context, tenant string, parentTenant string) error { ret := _m.Called(ctx, tenant, parentTenant) + if len(ret) == 0 { + panic("no return value specified for FetchOnDemand") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, tenant, parentTenant) diff --git a/components/director/internal/domain/runtime/automock/tenant_svc.go b/components/director/internal/domain/runtime/automock/tenant_svc.go index 352100229d..c423104475 100644 --- a/components/director/internal/domain/runtime/automock/tenant_svc.go +++ b/components/director/internal/domain/runtime/automock/tenant_svc.go @@ -18,7 +18,15 @@ type TenantSvc struct { func (_m *TenantSvc) GetTenantByID(ctx context.Context, id string) (*model.BusinessTenantMapping, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetTenantByID") + } + var r0 *model.BusinessTenantMapping + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*model.BusinessTenantMapping, error)); ok { + return rf(ctx, id) + } if rf, ok := ret.Get(0).(func(context.Context, string) *model.BusinessTenantMapping); ok { r0 = rf(ctx, id) } else { @@ -27,7 +35,6 @@ func (_m *TenantSvc) GetTenantByID(ctx context.Context, id string) (*model.Busin } } - var r1 error if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { r1 = rf(ctx, id) } else { @@ -37,13 +44,12 @@ func (_m *TenantSvc) GetTenantByID(ctx context.Context, id string) (*model.Busin return r0, r1 } -type mockConstructorTestingTNewTenantSvc interface { +// NewTenantSvc creates a new instance of TenantSvc. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTenantSvc(t interface { mock.TestingT Cleanup(func()) -} - -// NewTenantSvc creates a new instance of TenantSvc. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewTenantSvc(t mockConstructorTestingTNewTenantSvc) *TenantSvc { +}) *TenantSvc { mock := &TenantSvc{} mock.Mock.Test(t) diff --git a/components/director/internal/domain/runtime/automock/webhook_converter.go b/components/director/internal/domain/runtime/automock/webhook_converter.go index adb0cd7979..c799fff389 100644 --- a/components/director/internal/domain/runtime/automock/webhook_converter.go +++ b/components/director/internal/domain/runtime/automock/webhook_converter.go @@ -18,6 +18,10 @@ type WebhookConverter struct { func (_m *WebhookConverter) MultipleInputFromGraphQL(in []*graphql.WebhookInput) ([]*model.WebhookInput, error) { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for MultipleInputFromGraphQL") + } + var r0 []*model.WebhookInput var r1 error if rf, ok := ret.Get(0).(func([]*graphql.WebhookInput) ([]*model.WebhookInput, error)); ok { @@ -44,6 +48,10 @@ func (_m *WebhookConverter) MultipleInputFromGraphQL(in []*graphql.WebhookInput) func (_m *WebhookConverter) MultipleToGraphQL(in []*model.Webhook) ([]*graphql.Webhook, error) { ret := _m.Called(in) + if len(ret) == 0 { + panic("no return value specified for MultipleToGraphQL") + } + var r0 []*graphql.Webhook var r1 error if rf, ok := ret.Get(0).(func([]*model.Webhook) ([]*graphql.Webhook, error)); ok { diff --git a/components/director/internal/domain/runtime/automock/webhook_service.go b/components/director/internal/domain/runtime/automock/webhook_service.go index 9c1453b4da..f67ac189cf 100644 --- a/components/director/internal/domain/runtime/automock/webhook_service.go +++ b/components/director/internal/domain/runtime/automock/webhook_service.go @@ -18,6 +18,10 @@ type WebhookService struct { func (_m *WebhookService) Create(ctx context.Context, owningResourceID string, in model.WebhookInput, objectType model.WebhookReferenceObjectType) (string, error) { ret := _m.Called(ctx, owningResourceID, in, objectType) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, model.WebhookInput, model.WebhookReferenceObjectType) (string, error)); ok { @@ -42,6 +46,10 @@ func (_m *WebhookService) Create(ctx context.Context, owningResourceID string, i func (_m *WebhookService) ListForRuntime(ctx context.Context, runtimeID string) ([]*model.Webhook, error) { ret := _m.Called(ctx, runtimeID) + if len(ret) == 0 { + panic("no return value specified for ListForRuntime") + } + var r0 []*model.Webhook var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.Webhook, error)); ok { diff --git a/components/director/internal/domain/runtime/fixtures_test.go b/components/director/internal/domain/runtime/fixtures_test.go index 3d688a3ca1..bb3c71ea9f 100644 --- a/components/director/internal/domain/runtime/fixtures_test.go +++ b/components/director/internal/domain/runtime/fixtures_test.go @@ -33,6 +33,8 @@ var ( inputTemplate = "{\"context\":{\"platform\":\"{{if .CustomerTenantContext.AccountID}}btp{{else}}unified-services{{end}}\",\"uclFormationId\":\"{{.FormationID}}\",\"accountId\":\"{{if .CustomerTenantContext.AccountID}}{{.CustomerTenantContext.AccountID}}{{else}}{{.CustomerTenantContext.Path}}{{end}}\",\"crmId\":\"{{.CustomerTenantContext.CustomerID}}\",\"operation\":\"{{.Operation}}\"},\"assignedTenant\":{\"state\":\"{{.Assignment.State}}\",\"uclAssignmentId\":\"{{.Assignment.ID}}\",\"deploymentRegion\":\"{{if .Application.Labels.region}}{{.Application.Labels.region}}{{else}}{{.ApplicationTemplate.Labels.region}}{{end}}\",\"applicationNamespace\":\"{{if .Application.ApplicationNamespace}}{{.Application.ApplicationNamespace}}{{else}}{{.ApplicationTemplate.ApplicationNamespace}}{{end}}\",\"applicationUrl\":\"{{.Application.BaseURL}}\",\"applicationTenantId\":\"{{.Application.LocalTenantID}}\",\"uclSystemName\":\"{{.Application.Name}}\",\"uclSystemTenantId\":\"{{.Application.ID}}\",{{if .ApplicationTemplate.Labels.parameters}}\"parameters\":{{.ApplicationTemplate.Labels.parameters}},{{end}}\"configuration\":{{.ReverseAssignment.Value}}},\"receiverTenant\":{\"ownerTenant\":\"{{.Runtime.Tenant.Parent}}\",\"state\":\"{{.ReverseAssignment.State}}\",\"uclAssignmentId\":\"{{.ReverseAssignment.ID}}\",\"deploymentRegion\":\"{{if and .RuntimeContext .RuntimeContext.Labels.region}}{{.RuntimeContext.Labels.region}}{{else}}{{.Runtime.Labels.region}}{{end}}\",\"applicationNamespace\":\"{{.Runtime.ApplicationNamespace}}\",\"applicationTenantId\":\"{{if .RuntimeContext}}{{.RuntimeContext.Value}}{{else}}{{.Runtime.Labels.global_subaccount_id}}{{end}}\",\"uclSystemTenantId\":\"{{if .RuntimeContext}}{{.RuntimeContext.ID}}{{else}}{{.Runtime.ID}}{{end}}\",{{if .Runtime.Labels.parameters}}\"parameters\":{{.Runtime.Labels.parameters}},{{end}}\"configuration\":{{.Assignment.Value}}}}" headerTemplate = "{\"Content-Type\": [\"application/json\"]}" outputTemplate = "{\"error\":\"{{.Body.error}}\",\"state\":\"{{.Body.state}}\",\"success_status_code\": 200,\"incomplete_status_code\": 422}" + + emptyAssignment = &model.AutomaticScenarioAssignment{} ) func fixRuntimePage(runtimes []*model.Runtime) *model.RuntimePage { diff --git a/components/director/internal/domain/runtime/resolver.go b/components/director/internal/domain/runtime/resolver.go index 7d458bcccb..4481397bed 100644 --- a/components/director/internal/domain/runtime/resolver.go +++ b/components/director/internal/domain/runtime/resolver.go @@ -64,7 +64,7 @@ type RuntimeService interface { // //go:generate mockery --name=ScenarioAssignmentService --output=automock --outpkg=automock --case=underscore --disable-version-string type ScenarioAssignmentService interface { - GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) + GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) } //go:generate mockery --exported --name=formationService --output=automock --outpkg=automock --case=underscore --disable-version-string @@ -72,7 +72,7 @@ type formationService interface { MergeScenariosFromInputLabelsAndAssignments(ctx context.Context, inputLabels map[string]interface{}, runtimeID string) ([]interface{}, error) AssignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) UnassignFormation(ctx context.Context, tnt, objectID string, objectType graphql.FormationObjectType, formation model.Formation) (*model.Formation, error) - DeleteAutomaticScenarioAssignment(ctx context.Context, in model.AutomaticScenarioAssignment) error + DeleteAutomaticScenarioAssignment(ctx context.Context, in *model.AutomaticScenarioAssignment) error } // RuntimeConverter missing godoc diff --git a/components/director/internal/domain/runtime/resolver_test.go b/components/director/internal/domain/runtime/resolver_test.go index 7a298d144a..1a5bcdd1e9 100644 --- a/components/director/internal/domain/runtime/resolver_test.go +++ b/components/director/internal/domain/runtime/resolver_test.go @@ -1332,7 +1332,7 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(singleScenarioLabel.Value) assert.NoError(t, err) - svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(model.AutomaticScenarioAssignment{}, testErr) + svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(nil, testErr) return svc }, ConverterFn: UnusedRuntimeConverter, @@ -1368,7 +1368,7 @@ func TestResolver_DeleteRuntime(t *testing.T) { svc := &automock.ScenarioAssignmentService{} scenarios, err := label.ValueToStringsSlice(singleScenarioLabel.Value) assert.NoError(t, err) - svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(model.AutomaticScenarioAssignment{}, scenarioAssignmentNotFoundErr) + svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(nil, scenarioAssignmentNotFoundErr) return svc }, ConverterFn: func() *automock.RuntimeConverter { @@ -1411,15 +1411,14 @@ func TestResolver_DeleteRuntime(t *testing.T) { svc := &automock.ScenarioAssignmentService{} scenarios, err := label.ValueToStringsSlice(singleScenarioLabel.Value) assert.NoError(t, err) - scenarioAssignment := model.AutomaticScenarioAssignment{} + scenarioAssignment := &model.AutomaticScenarioAssignment{} svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(scenarioAssignment, nil) return svc }, FormationsSvcFn: func() *automock.FormationService { svc := &automock.FormationService{} - scenarioAssignment := model.AutomaticScenarioAssignment{} - svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment).Return(testErr) + svc.On("DeleteAutomaticScenarioAssignment", contextParam, emptyAssignment).Return(testErr) return svc }, ConverterFn: UnusedRuntimeConverter, @@ -1454,15 +1453,14 @@ func TestResolver_DeleteRuntime(t *testing.T) { svc := &automock.ScenarioAssignmentService{} scenarios, err := label.ValueToStringsSlice(singleScenarioLabel.Value) assert.NoError(t, err) - scenarioAssignment := model.AutomaticScenarioAssignment{} + scenarioAssignment := &model.AutomaticScenarioAssignment{} svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(scenarioAssignment, nil) return svc }, FormationsSvcFn: func() *automock.FormationService { svc := &automock.FormationService{} - scenarioAssignment := model.AutomaticScenarioAssignment{} - svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment).Return(nil) + svc.On("DeleteAutomaticScenarioAssignment", contextParam, emptyAssignment).Return(nil) return svc }, ConverterFn: func() *automock.RuntimeConverter { @@ -1506,9 +1504,8 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - emptyAssignment := model.AutomaticScenarioAssignment{} - scenarioAssignment1 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} - scenarioAssignment2 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} + scenarioAssignment1 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} + scenarioAssignment2 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) svc.On("GetForScenarioName", contextParam, scenarios[1]).Return(scenarioAssignment1, nil) @@ -1522,8 +1519,8 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - scenarioAssignment1 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} - scenarioAssignment2 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} + scenarioAssignment1 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} + scenarioAssignment2 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment1).Return(nil).Once() svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment2).Return(nil).Once() return svc @@ -1569,10 +1566,10 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - scenarioAssignment0 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[0]} - scenarioAssignment1 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} - scenarioAssignment2 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} - scenarioAssignment3 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[3]} + scenarioAssignment0 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[0]} + scenarioAssignment1 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} + scenarioAssignment2 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} + scenarioAssignment3 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[3]} svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(scenarioAssignment0, nil) svc.On("GetForScenarioName", contextParam, scenarios[1]).Return(scenarioAssignment1, nil) @@ -1607,10 +1604,10 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - scenarioAssignment0 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[0]} - scenarioAssignment1 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} - scenarioAssignment2 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} - scenarioAssignment3 := model.AutomaticScenarioAssignment{ScenarioName: scenarios[3]} + scenarioAssignment0 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[0]} + scenarioAssignment1 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[1]} + scenarioAssignment2 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[2]} + scenarioAssignment3 := &model.AutomaticScenarioAssignment{ScenarioName: scenarios[3]} svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment0).Return(nil).Once() svc.On("DeleteAutomaticScenarioAssignment", contextParam, scenarioAssignment1).Return(nil).Once() @@ -1639,8 +1636,6 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - emptyAssignment := model.AutomaticScenarioAssignment{} - svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) svc.On("GetForScenarioName", contextParam, scenarios[1]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) svc.On("GetForScenarioName", contextParam, scenarios[2]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) @@ -1690,8 +1685,6 @@ func TestResolver_DeleteRuntime(t *testing.T) { scenarios, err := label.ValueToStringsSlice(multiScenariosLabel.Value) assert.NoError(t, err) - emptyAssignment := model.AutomaticScenarioAssignment{} - svc.On("GetForScenarioName", contextParam, scenarios[0]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) svc.On("GetForScenarioName", contextParam, scenarios[1]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) svc.On("GetForScenarioName", contextParam, scenarios[2]).Return(emptyAssignment, scenarioAssignmentNotFoundErr) diff --git a/components/director/internal/domain/scenarioassignment/automock/asa_service.go b/components/director/internal/domain/scenarioassignment/automock/asa_service.go index 472e5f0e49..45b18c6675 100644 --- a/components/director/internal/domain/scenarioassignment/automock/asa_service.go +++ b/components/director/internal/domain/scenarioassignment/automock/asa_service.go @@ -15,18 +15,24 @@ type AsaService struct { } // GetForScenarioName provides a mock function with given fields: ctx, scenarioName -func (_m *AsaService) GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (_m *AsaService) GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, scenarioName) - var r0 model.AutomaticScenarioAssignment + if len(ret) == 0 { + panic("no return value specified for GetForScenarioName") + } + + var r0 *model.AutomaticScenarioAssignment var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) (model.AutomaticScenarioAssignment, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) (*model.AutomaticScenarioAssignment, error)); ok { return rf(ctx, scenarioName) } - if rf, ok := ret.Get(0).(func(context.Context, string) model.AutomaticScenarioAssignment); ok { + if rf, ok := ret.Get(0).(func(context.Context, string) *model.AutomaticScenarioAssignment); ok { r0 = rf(ctx, scenarioName) } else { - r0 = ret.Get(0).(model.AutomaticScenarioAssignment) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AutomaticScenarioAssignment) + } } if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { @@ -42,6 +48,10 @@ func (_m *AsaService) GetForScenarioName(ctx context.Context, scenarioName strin func (_m *AsaService) List(ctx context.Context, pageSize int, cursor string) (*model.AutomaticScenarioAssignmentPage, error) { ret := _m.Called(ctx, pageSize, cursor) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 *model.AutomaticScenarioAssignmentPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, int, string) (*model.AutomaticScenarioAssignmentPage, error)); ok { @@ -68,6 +78,10 @@ func (_m *AsaService) List(ctx context.Context, pageSize int, cursor string) (*m func (_m *AsaService) ListForTargetTenant(ctx context.Context, targetTenantInternalID string) ([]*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, targetTenantInternalID) + if len(ret) == 0 { + panic("no return value specified for ListForTargetTenant") + } + var r0 []*model.AutomaticScenarioAssignment var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.AutomaticScenarioAssignment, error)); ok { diff --git a/components/director/internal/domain/scenarioassignment/automock/entity_converter.go b/components/director/internal/domain/scenarioassignment/automock/entity_converter.go index 5bece1c0ac..2476adca35 100644 --- a/components/director/internal/domain/scenarioassignment/automock/entity_converter.go +++ b/components/director/internal/domain/scenarioassignment/automock/entity_converter.go @@ -14,25 +14,35 @@ type EntityConverter struct { } // FromEntity provides a mock function with given fields: assignment -func (_m *EntityConverter) FromEntity(assignment scenarioassignment.Entity) model.AutomaticScenarioAssignment { +func (_m *EntityConverter) FromEntity(assignment scenarioassignment.Entity) *model.AutomaticScenarioAssignment { ret := _m.Called(assignment) - var r0 model.AutomaticScenarioAssignment - if rf, ok := ret.Get(0).(func(scenarioassignment.Entity) model.AutomaticScenarioAssignment); ok { + if len(ret) == 0 { + panic("no return value specified for FromEntity") + } + + var r0 *model.AutomaticScenarioAssignment + if rf, ok := ret.Get(0).(func(scenarioassignment.Entity) *model.AutomaticScenarioAssignment); ok { r0 = rf(assignment) } else { - r0 = ret.Get(0).(model.AutomaticScenarioAssignment) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AutomaticScenarioAssignment) + } } return r0 } // ToEntity provides a mock function with given fields: assignment -func (_m *EntityConverter) ToEntity(assignment model.AutomaticScenarioAssignment) scenarioassignment.Entity { +func (_m *EntityConverter) ToEntity(assignment *model.AutomaticScenarioAssignment) scenarioassignment.Entity { ret := _m.Called(assignment) + if len(ret) == 0 { + panic("no return value specified for ToEntity") + } + var r0 scenarioassignment.Entity - if rf, ok := ret.Get(0).(func(model.AutomaticScenarioAssignment) scenarioassignment.Entity); ok { + if rf, ok := ret.Get(0).(func(*model.AutomaticScenarioAssignment) scenarioassignment.Entity); ok { r0 = rf(assignment) } else { r0 = ret.Get(0).(scenarioassignment.Entity) diff --git a/components/director/internal/domain/scenarioassignment/automock/gql_converter.go b/components/director/internal/domain/scenarioassignment/automock/gql_converter.go index dcc89365d2..41f0c41461 100644 --- a/components/director/internal/domain/scenarioassignment/automock/gql_converter.go +++ b/components/director/internal/domain/scenarioassignment/automock/gql_converter.go @@ -15,11 +15,15 @@ type GqlConverter struct { } // ToGraphQL provides a mock function with given fields: in, targetTenantExternalID -func (_m *GqlConverter) ToGraphQL(in model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment { +func (_m *GqlConverter) ToGraphQL(in *model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment { ret := _m.Called(in, targetTenantExternalID) + if len(ret) == 0 { + panic("no return value specified for ToGraphQL") + } + var r0 graphql.AutomaticScenarioAssignment - if rf, ok := ret.Get(0).(func(model.AutomaticScenarioAssignment, string) graphql.AutomaticScenarioAssignment); ok { + if rf, ok := ret.Get(0).(func(*model.AutomaticScenarioAssignment, string) graphql.AutomaticScenarioAssignment); ok { r0 = rf(in, targetTenantExternalID) } else { r0 = ret.Get(0).(graphql.AutomaticScenarioAssignment) diff --git a/components/director/internal/domain/scenarioassignment/automock/repository.go b/components/director/internal/domain/scenarioassignment/automock/repository.go index b8e2195aaf..e8d9153b2a 100644 --- a/components/director/internal/domain/scenarioassignment/automock/repository.go +++ b/components/director/internal/domain/scenarioassignment/automock/repository.go @@ -15,18 +15,24 @@ type Repository struct { } // GetForScenarioName provides a mock function with given fields: ctx, tenantID, scenarioName -func (_m *Repository) GetForScenarioName(ctx context.Context, tenantID string, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (_m *Repository) GetForScenarioName(ctx context.Context, tenantID string, scenarioName string) (*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, tenantID, scenarioName) - var r0 model.AutomaticScenarioAssignment + if len(ret) == 0 { + panic("no return value specified for GetForScenarioName") + } + + var r0 *model.AutomaticScenarioAssignment var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (model.AutomaticScenarioAssignment, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string) (*model.AutomaticScenarioAssignment, error)); ok { return rf(ctx, tenantID, scenarioName) } - if rf, ok := ret.Get(0).(func(context.Context, string, string) model.AutomaticScenarioAssignment); ok { + if rf, ok := ret.Get(0).(func(context.Context, string, string) *model.AutomaticScenarioAssignment); ok { r0 = rf(ctx, tenantID, scenarioName) } else { - r0 = ret.Get(0).(model.AutomaticScenarioAssignment) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.AutomaticScenarioAssignment) + } } if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { @@ -42,6 +48,10 @@ func (_m *Repository) GetForScenarioName(ctx context.Context, tenantID string, s func (_m *Repository) List(ctx context.Context, tenant string, pageSize int, cursor string) (*model.AutomaticScenarioAssignmentPage, error) { ret := _m.Called(ctx, tenant, pageSize, cursor) + if len(ret) == 0 { + panic("no return value specified for List") + } + var r0 *model.AutomaticScenarioAssignmentPage var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, int, string) (*model.AutomaticScenarioAssignmentPage, error)); ok { @@ -68,6 +78,10 @@ func (_m *Repository) List(ctx context.Context, tenant string, pageSize int, cur func (_m *Repository) ListForTargetTenant(ctx context.Context, tenantID string, targetTenantID string) ([]*model.AutomaticScenarioAssignment, error) { ret := _m.Called(ctx, tenantID, targetTenantID) + if len(ret) == 0 { + panic("no return value specified for ListForTargetTenant") + } + var r0 []*model.AutomaticScenarioAssignment var r1 error if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]*model.AutomaticScenarioAssignment, error)); ok { diff --git a/components/director/internal/domain/scenarioassignment/automock/scenarios_def_service.go b/components/director/internal/domain/scenarioassignment/automock/scenarios_def_service.go index e87b49a26c..36fdb4929a 100644 --- a/components/director/internal/domain/scenarioassignment/automock/scenarios_def_service.go +++ b/components/director/internal/domain/scenarioassignment/automock/scenarios_def_service.go @@ -17,6 +17,10 @@ type ScenariosDefService struct { func (_m *ScenariosDefService) GetAvailableScenarios(ctx context.Context, tenantID string) ([]string, error) { ret := _m.Called(ctx, tenantID) + if len(ret) == 0 { + panic("no return value specified for GetAvailableScenarios") + } + var r0 []string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]string, error)); ok { diff --git a/components/director/internal/domain/scenarioassignment/automock/tenant_service.go b/components/director/internal/domain/scenarioassignment/automock/tenant_service.go index 6bd86f810f..f0f1e11be0 100644 --- a/components/director/internal/domain/scenarioassignment/automock/tenant_service.go +++ b/components/director/internal/domain/scenarioassignment/automock/tenant_service.go @@ -17,6 +17,10 @@ type TenantService struct { func (_m *TenantService) GetExternalTenant(ctx context.Context, id string) (string, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for GetExternalTenant") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { @@ -41,6 +45,10 @@ func (_m *TenantService) GetExternalTenant(ctx context.Context, id string) (stri func (_m *TenantService) GetInternalTenant(ctx context.Context, externalTenant string) (string, error) { ret := _m.Called(ctx, externalTenant) + if len(ret) == 0 { + panic("no return value specified for GetInternalTenant") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok { diff --git a/components/director/internal/domain/scenarioassignment/converter.go b/components/director/internal/domain/scenarioassignment/converter.go index f8cd2e2cb2..4e2838b3c5 100644 --- a/components/director/internal/domain/scenarioassignment/converter.go +++ b/components/director/internal/domain/scenarioassignment/converter.go @@ -16,7 +16,7 @@ func NewConverter() *converter { type converter struct{} // ToGraphQL converts from internal model to GraphQL output -func (c *converter) ToGraphQL(in model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment { +func (c *converter) ToGraphQL(in *model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment { return graphql.AutomaticScenarioAssignment{ ScenarioName: in.ScenarioName, Selector: &graphql.Label{ @@ -27,7 +27,7 @@ func (c *converter) ToGraphQL(in model.AutomaticScenarioAssignment, targetTenant } // ToEntity converts from internal model to entity -func (c *converter) ToEntity(in model.AutomaticScenarioAssignment) Entity { +func (c *converter) ToEntity(in *model.AutomaticScenarioAssignment) Entity { return Entity{ TenantID: in.Tenant, Scenario: in.ScenarioName, @@ -36,8 +36,8 @@ func (c *converter) ToEntity(in model.AutomaticScenarioAssignment) Entity { } // FromEntity converts from entity to internal model -func (c *converter) FromEntity(in Entity) model.AutomaticScenarioAssignment { - return model.AutomaticScenarioAssignment{ +func (c *converter) FromEntity(in Entity) *model.AutomaticScenarioAssignment { + return &model.AutomaticScenarioAssignment{ ScenarioName: in.Scenario, Tenant: in.TenantID, TargetTenantID: in.TargetTenantID, diff --git a/components/director/internal/domain/scenarioassignment/converter_test.go b/components/director/internal/domain/scenarioassignment/converter_test.go index 14b89d807c..0df5b8cb13 100644 --- a/components/director/internal/domain/scenarioassignment/converter_test.go +++ b/components/director/internal/domain/scenarioassignment/converter_test.go @@ -13,7 +13,7 @@ func TestToGraphQL(t *testing.T) { // GIVEN sut := scenarioassignment.NewConverter() // WHEN - actual := sut.ToGraphQL(model.AutomaticScenarioAssignment{ + actual := sut.ToGraphQL(&model.AutomaticScenarioAssignment{ ScenarioName: scenarioName, Tenant: tenantID, TargetTenantID: targetTenantID, @@ -32,7 +32,7 @@ func TestToEntity(t *testing.T) { // GIVEN sut := scenarioassignment.NewConverter() // WHEN - actual := sut.ToEntity(model.AutomaticScenarioAssignment{ + actual := sut.ToEntity(&model.AutomaticScenarioAssignment{ ScenarioName: scenarioName, Tenant: tenantID, TargetTenantID: targetTenantID, @@ -57,7 +57,7 @@ func TestFromEntity(t *testing.T) { }) // THEN - assert.Equal(t, model.AutomaticScenarioAssignment{ + assert.Equal(t, &model.AutomaticScenarioAssignment{ ScenarioName: scenarioName, Tenant: tenantID, TargetTenantID: targetTenantID, diff --git a/components/director/internal/domain/scenarioassignment/fixtures_test.go b/components/director/internal/domain/scenarioassignment/fixtures_test.go index 412173a63e..a0452f4fa7 100644 --- a/components/director/internal/domain/scenarioassignment/fixtures_test.go +++ b/components/director/internal/domain/scenarioassignment/fixtures_test.go @@ -22,7 +22,7 @@ const ( errMsg = "some error" ) -func fixModel() model.AutomaticScenarioAssignment { +func fixModel() *model.AutomaticScenarioAssignment { return fixModelWithScenarioName(scenarioName) } @@ -32,8 +32,8 @@ func fixGQL() graphql.AutomaticScenarioAssignment { var testTableColumns = []string{"scenario", "tenant_id", "target_tenant_id"} -func fixModelWithScenarioName(scenario string) model.AutomaticScenarioAssignment { - return model.AutomaticScenarioAssignment{ +func fixModelWithScenarioName(scenario string) *model.AutomaticScenarioAssignment { + return &model.AutomaticScenarioAssignment{ ScenarioName: scenario, Tenant: tenantID, TargetTenantID: targetTenantID, diff --git a/components/director/internal/domain/scenarioassignment/repository.go b/components/director/internal/domain/scenarioassignment/repository.go index f5f8680585..faff00a5b6 100644 --- a/components/director/internal/domain/scenarioassignment/repository.go +++ b/components/director/internal/domain/scenarioassignment/repository.go @@ -46,12 +46,12 @@ type repository struct { // //go:generate mockery --name=EntityConverter --output=automock --outpkg=automock --case=underscore --disable-version-string type EntityConverter interface { - ToEntity(assignment model.AutomaticScenarioAssignment) Entity - FromEntity(assignment Entity) model.AutomaticScenarioAssignment + ToEntity(assignment *model.AutomaticScenarioAssignment) Entity + FromEntity(assignment Entity) *model.AutomaticScenarioAssignment } // Create missing godoc -func (r *repository) Create(ctx context.Context, model model.AutomaticScenarioAssignment) error { +func (r *repository) Create(ctx context.Context, model *model.AutomaticScenarioAssignment) error { entity := r.conv.ToEntity(model) return r.creator.Create(ctx, entity) } @@ -67,7 +67,7 @@ func (r *repository) ListAll(ctx context.Context, tenantID string) ([]*model.Aut for _, v := range out { item := r.conv.FromEntity(v) - items = append(items, &item) + items = append(items, item) } return items, nil @@ -88,14 +88,14 @@ func (r *repository) ListForTargetTenant(ctx context.Context, tenantID string, t for _, v := range out { item := r.conv.FromEntity(v) - items = append(items, &item) + items = append(items, item) } return items, nil } // GetForScenarioName missing godoc -func (r *repository) GetForScenarioName(ctx context.Context, tenantID, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (r *repository) GetForScenarioName(ctx context.Context, tenantID, scenarioName string) (*model.AutomaticScenarioAssignment, error) { var ent Entity conditions := repo.Conditions{ @@ -103,7 +103,7 @@ func (r *repository) GetForScenarioName(ctx context.Context, tenantID, scenarioN } if err := r.singleGetter.Get(ctx, resource.AutomaticScenarioAssigment, tenantID, conditions, repo.NoOrderBy, &ent); err != nil { - return model.AutomaticScenarioAssignment{}, err + return nil, err } assignmentModel := r.conv.FromEntity(ent) @@ -123,7 +123,7 @@ func (r *repository) List(ctx context.Context, tenantID string, pageSize int, cu for _, ent := range collection { m := r.conv.FromEntity(ent) - items = append(items, &m) + items = append(items, m) } return &model.AutomaticScenarioAssignmentPage{ diff --git a/components/director/internal/domain/scenarioassignment/repository_test.go b/components/director/internal/domain/scenarioassignment/repository_test.go index 6dbea0d224..6eacdaa71b 100644 --- a/components/director/internal/domain/scenarioassignment/repository_test.go +++ b/components/director/internal/domain/scenarioassignment/repository_test.go @@ -122,7 +122,7 @@ func TestRepository_ListAll(t *testing.T) { // GIVEN scenarioEntities := []scenarioassignment.Entity{fixEntityWithScenarioName(scenarioName), fixEntityWithScenarioName("scenario-B")} - scenarioModels := []model.AutomaticScenarioAssignment{fixModelWithScenarioName(scenarioName), + scenarioModels := []*model.AutomaticScenarioAssignment{fixModelWithScenarioName(scenarioName), fixModelWithScenarioName("scenario-B")} mockConverter := &automock.EntityConverter{} @@ -148,8 +148,8 @@ func TestRepository_ListAll(t *testing.T) { // THEN assert.NoError(t, err) - assert.Equal(t, scenarioModels[0], *result[0]) - assert.Equal(t, scenarioModels[1], *result[1]) + assert.Equal(t, scenarioModels[0], result[0]) + assert.Equal(t, scenarioModels[1], result[1]) }) t.Run("DB error", func(t *testing.T) { @@ -177,7 +177,7 @@ func TestRepository_ListForTargetTenant(t *testing.T) { // GIVEN scenarioEntities := []scenarioassignment.Entity{fixEntityWithScenarioName(scenarioName), fixEntityWithScenarioName("scenario-B")} - scenarioModels := []model.AutomaticScenarioAssignment{fixModelWithScenarioName(scenarioName), + scenarioModels := []*model.AutomaticScenarioAssignment{fixModelWithScenarioName(scenarioName), fixModelWithScenarioName("scenario-B")} mockConverter := &automock.EntityConverter{} @@ -203,8 +203,8 @@ func TestRepository_ListForTargetTenant(t *testing.T) { // THEN assert.NoError(t, err) - assert.Equal(t, scenarioModels[0], *result[0]) - assert.Equal(t, scenarioModels[1], *result[1]) + assert.Equal(t, scenarioModels[0], result[0]) + assert.Equal(t, scenarioModels[1], result[1]) }) t.Run("DB error", func(t *testing.T) { diff --git a/components/director/internal/domain/scenarioassignment/resolver.go b/components/director/internal/domain/scenarioassignment/resolver.go index 43ddf4667d..aed98be646 100644 --- a/components/director/internal/domain/scenarioassignment/resolver.go +++ b/components/director/internal/domain/scenarioassignment/resolver.go @@ -14,14 +14,14 @@ import ( //go:generate mockery --exported --name=gqlConverter --output=automock --outpkg=automock --case=underscore --disable-version-string type gqlConverter interface { - ToGraphQL(in model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment + ToGraphQL(in *model.AutomaticScenarioAssignment, targetTenantExternalID string) graphql.AutomaticScenarioAssignment } //go:generate mockery --exported --name=asaService --output=automock --outpkg=automock --case=underscore --disable-version-string type asaService interface { List(ctx context.Context, pageSize int, cursor string) (*model.AutomaticScenarioAssignmentPage, error) ListForTargetTenant(ctx context.Context, targetTenantInternalID string) ([]*model.AutomaticScenarioAssignment, error) - GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) + GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) } //go:generate mockery --exported --name=tenantService --output=automock --outpkg=automock --case=underscore --disable-version-string @@ -105,7 +105,7 @@ func (r *Resolver) AutomaticScenarioAssignmentsForSelector(ctx context.Context, gqlAssignments := make([]*graphql.AutomaticScenarioAssignment, 0, len(assignments)) for _, v := range assignments { - assignment := r.converter.ToGraphQL(*v, in.Value) + assignment := r.converter.ToGraphQL(v, in.Value) gqlAssignments = append(gqlAssignments, &assignment) } @@ -143,7 +143,7 @@ func (r *Resolver) AutomaticScenarioAssignments(ctx context.Context, first *int, return nil, errors.Wrap(err, "while converting tenant") } - assignment := r.converter.ToGraphQL(*v, targetTenant) + assignment := r.converter.ToGraphQL(v, targetTenant) gqlAssignments = append(gqlAssignments, &assignment) } diff --git a/components/director/internal/domain/scenarioassignment/resolver_test.go b/components/director/internal/domain/scenarioassignment/resolver_test.go index 68c18915d8..0140820c27 100644 --- a/components/director/internal/domain/scenarioassignment/resolver_test.go +++ b/components/director/internal/domain/scenarioassignment/resolver_test.go @@ -80,7 +80,7 @@ func TestResolver_GetAutomaticScenarioAssignmentByScenario(t *testing.T) { t.Run("error on receiving assignment by service", func(t *testing.T) { tx, transact := txGen.ThatDoesntExpectCommit() mockSvc := &automock.AsaService{} - mockSvc.On("GetForScenarioName", txtest.CtxWithDBMatcher(), scenarioName).Return(model.AutomaticScenarioAssignment{}, fixError()).Once() + mockSvc.On("GetForScenarioName", txtest.CtxWithDBMatcher(), scenarioName).Return(nil, fixError()).Once() defer mock.AssertExpectationsForObjects(t, tx, transact, mockSvc) sut := scenarioassignment.NewResolver(transact, mockSvc, nil, nil) @@ -151,8 +151,8 @@ func TestResolver_AutomaticScenarioAssignmentsForSelector(t *testing.T) { tx, transact := txGen.ThatSucceeds() mockConverter := &automock.GqlConverter{} - mockConverter.On("ToGraphQL", *expectedModels[0], externalTargetTenantID).Return(*expectedOutput[0]).Once() - mockConverter.On("ToGraphQL", *expectedModels[1], externalTargetTenantID).Return(*expectedOutput[1]).Once() + mockConverter.On("ToGraphQL", expectedModels[0], externalTargetTenantID).Return(*expectedOutput[0]).Once() + mockConverter.On("ToGraphQL", expectedModels[1], externalTargetTenantID).Return(*expectedOutput[1]).Once() mockSvc := &automock.AsaService{} mockSvc.On("ListForTargetTenant", mock.Anything, targetTenantID).Return(expectedModels, nil).Once() @@ -248,7 +248,7 @@ func TestResolver_AutomaticScenarioAssignments(t *testing.T) { mod1 := fixModelWithScenarioName("foo") mod2 := fixModelWithScenarioName("bar") modItems := []*model.AutomaticScenarioAssignment{ - &mod1, &mod2, + mod1, mod2, } modelPage := fixModelPageWithItems(modItems) diff --git a/components/director/internal/domain/scenarioassignment/service.go b/components/director/internal/domain/scenarioassignment/service.go index 061497a5cc..41450b135d 100644 --- a/components/director/internal/domain/scenarioassignment/service.go +++ b/components/director/internal/domain/scenarioassignment/service.go @@ -15,7 +15,7 @@ import ( //go:generate mockery --name=Repository --output=automock --outpkg=automock --case=underscore --disable-version-string type Repository interface { ListForTargetTenant(ctx context.Context, tenantID string, targetTenantID string) ([]*model.AutomaticScenarioAssignment, error) - GetForScenarioName(ctx context.Context, tenantID, scenarioName string) (model.AutomaticScenarioAssignment, error) + GetForScenarioName(ctx context.Context, tenantID, scenarioName string) (*model.AutomaticScenarioAssignment, error) List(ctx context.Context, tenant string, pageSize int, cursor string) (*model.AutomaticScenarioAssignmentPage, error) } @@ -54,15 +54,15 @@ func (s *service) ListForTargetTenant(ctx context.Context, targetTenantInternalI } // GetForScenarioName missing godoc -func (s *service) GetForScenarioName(ctx context.Context, scenarioName string) (model.AutomaticScenarioAssignment, error) { +func (s *service) GetForScenarioName(ctx context.Context, scenarioName string) (*model.AutomaticScenarioAssignment, error) { tenantID, err := tenant.LoadFromContext(ctx) if err != nil { - return model.AutomaticScenarioAssignment{}, err + return nil, err } sa, err := s.repo.GetForScenarioName(ctx, tenantID, scenarioName) if err != nil { - return model.AutomaticScenarioAssignment{}, errors.Wrap(err, "while getting Assignment") + return nil, errors.Wrap(err, "while getting Assignment") } return sa, nil } diff --git a/components/director/internal/domain/scenarioassignment/service_test.go b/components/director/internal/domain/scenarioassignment/service_test.go index a31496450b..986ae13abe 100644 --- a/components/director/internal/domain/scenarioassignment/service_test.go +++ b/components/director/internal/domain/scenarioassignment/service_test.go @@ -50,7 +50,7 @@ func TestService_GetByScenarioName(t *testing.T) { // GIVEN mockRepo := &automock.Repository{} defer mockRepo.AssertExpectations(t) - mockRepo.On("GetForScenarioName", fixCtxWithTenant(), mock.Anything, scenarioName).Return(model.AutomaticScenarioAssignment{}, fixError()).Once() + mockRepo.On("GetForScenarioName", fixCtxWithTenant(), mock.Anything, scenarioName).Return(nil, fixError()).Once() sut := scenarioassignment.NewService(mockRepo, nil) // WHEN @@ -65,7 +65,7 @@ func TestService_ListForTargetTenant(t *testing.T) { t.Run("happy path", func(t *testing.T) { // GIVEN assignment := fixModel() - result := []*model.AutomaticScenarioAssignment{&assignment} + result := []*model.AutomaticScenarioAssignment{assignment} mockRepo := &automock.Repository{} defer mockRepo.AssertExpectations(t) mockRepo.On("ListForTargetTenant", mock.Anything, tenantID, targetTenantID).Return(result, nil).Once() @@ -109,7 +109,7 @@ func TestService_List(t *testing.T) { mod1 := fixModelWithScenarioName("foo") mod2 := fixModelWithScenarioName("bar") modItems := []*model.AutomaticScenarioAssignment{ - &mod1, &mod2, + mod1, mod2, } modelPage := fixModelPageWithItems(modItems) From a799c2d37a1ef903097bc95d08de8cead2031830 Mon Sep 17 00:00:00 2001 From: StanislavStefanov <39959090+StanislavStefanov@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:56:40 +0300 Subject: [PATCH 19/19] Do not delete constraint if it is attached (#3801) * do not delete constraint if it is attached * adjust PR version * adapt test defers * remove cascade deletion * adjust PR version * address PR comments --- chart/compass/values.yaml | 6 +- .../formation_constraint_repository.go | 54 ++++++++-------- ...emplate_constraint_reference_repository.go | 38 +++++++++++ .../automock/uid_service.go | 4 ++ .../formationconstraint/fixtures_test.go | 4 ++ .../domain/formationconstraint/service.go | 15 +++++ .../formationconstraint/service_test.go | 63 +++++++++++++++---- ...emplate_constraint_reference_repository.go | 8 +++ .../repository.go | 10 +++ .../repository_test.go | 32 ++++++++++ ...remove_constrain_cascade_deletion.down.sql | 6 ++ ...6_remove_constrain_cascade_deletion.up.sql | 7 +++ .../formation_constraint_api_test.go | 3 + .../application_only_notifications_test.go | 14 ++++- ...ication_subscription_notifications_test.go | 9 ++- .../tests/notifications/fixtures_test.go | 33 +++++----- .../runtime_notification_subscription_test.go | 7 ++- 17 files changed, 251 insertions(+), 62 deletions(-) create mode 100644 components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.down.sql create mode 100644 components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.up.sql diff --git a/chart/compass/values.yaml b/chart/compass/values.yaml index e09e6e8893..68528ad14d 100644 --- a/chart/compass/values.yaml +++ b/chart/compass/values.yaml @@ -175,7 +175,7 @@ global: name: compass-pairing-adapter director: dir: dev/incubator/ - version: "PR-3796" + version: "PR-3801" name: compass-director hydrator: dir: dev/incubator/ @@ -211,7 +211,7 @@ global: name: compass-ord-service schema_migrator: dir: dev/incubator/ - version: "PR-3763" + version: "PR-3801" name: compass-schema-migrator system_broker: dir: dev/incubator/ @@ -232,7 +232,7 @@ global: name: compass-console e2e_tests: dir: dev/incubator/ - version: "PR-3803" + version: "PR-3801" name: compass-e2e-tests isLocalEnv: false isForTesting: false diff --git a/components/director/internal/domain/formationconstraint/automock/formation_constraint_repository.go b/components/director/internal/domain/formationconstraint/automock/formation_constraint_repository.go index 305e191144..b0803fe956 100644 --- a/components/director/internal/domain/formationconstraint/automock/formation_constraint_repository.go +++ b/components/director/internal/domain/formationconstraint/automock/formation_constraint_repository.go @@ -21,6 +21,10 @@ type FormationConstraintRepository struct { func (_m *FormationConstraintRepository) Create(ctx context.Context, item *model.FormationConstraint) error { ret := _m.Called(ctx, item) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.FormationConstraint) error); ok { r0 = rf(ctx, item) @@ -35,6 +39,10 @@ func (_m *FormationConstraintRepository) Create(ctx context.Context, item *model func (_m *FormationConstraintRepository) Delete(ctx context.Context, id string) error { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { r0 = rf(ctx, id) @@ -49,6 +57,10 @@ func (_m *FormationConstraintRepository) Delete(ctx context.Context, id string) func (_m *FormationConstraintRepository) Get(ctx context.Context, id string) (*model.FormationConstraint, error) { ret := _m.Called(ctx, id) + if len(ret) == 0 { + panic("no return value specified for Get") + } + var r0 *model.FormationConstraint var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) (*model.FormationConstraint, error)); ok { @@ -75,6 +87,10 @@ func (_m *FormationConstraintRepository) Get(ctx context.Context, id string) (*m func (_m *FormationConstraintRepository) ListAll(ctx context.Context) ([]*model.FormationConstraint, error) { ret := _m.Called(ctx) + if len(ret) == 0 { + panic("no return value specified for ListAll") + } + var r0 []*model.FormationConstraint var r1 error if rf, ok := ret.Get(0).(func(context.Context) ([]*model.FormationConstraint, error)); ok { @@ -97,36 +113,14 @@ func (_m *FormationConstraintRepository) ListAll(ctx context.Context) ([]*model. return r0, r1 } -// ListByIDs provides a mock function with given fields: ctx, formationConstraintIDs -func (_m *FormationConstraintRepository) ListByIDs(ctx context.Context, formationConstraintIDs []string) ([]*model.FormationConstraint, error) { - ret := _m.Called(ctx, formationConstraintIDs) - - var r0 []*model.FormationConstraint - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.FormationConstraint, error)); ok { - return rf(ctx, formationConstraintIDs) - } - if rf, ok := ret.Get(0).(func(context.Context, []string) []*model.FormationConstraint); ok { - r0 = rf(ctx, formationConstraintIDs) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*model.FormationConstraint) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok { - r1 = rf(ctx, formationConstraintIDs) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // ListByIDsAndGlobal provides a mock function with given fields: ctx, formationConstraintIDs func (_m *FormationConstraintRepository) ListByIDsAndGlobal(ctx context.Context, formationConstraintIDs []string) ([]*model.FormationConstraint, error) { ret := _m.Called(ctx, formationConstraintIDs) + if len(ret) == 0 { + panic("no return value specified for ListByIDsAndGlobal") + } + var r0 []*model.FormationConstraint var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.FormationConstraint, error)); ok { @@ -153,6 +147,10 @@ func (_m *FormationConstraintRepository) ListByIDsAndGlobal(ctx context.Context, func (_m *FormationConstraintRepository) ListMatchingFormationConstraints(ctx context.Context, formationConstraintIDs []string, location pkgformationconstraint.JoinPointLocation, details pkgformationconstraint.MatchingDetails) ([]*model.FormationConstraint, error) { ret := _m.Called(ctx, formationConstraintIDs, location, details) + if len(ret) == 0 { + panic("no return value specified for ListMatchingFormationConstraints") + } + var r0 []*model.FormationConstraint var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string, pkgformationconstraint.JoinPointLocation, pkgformationconstraint.MatchingDetails) ([]*model.FormationConstraint, error)); ok { @@ -179,6 +177,10 @@ func (_m *FormationConstraintRepository) ListMatchingFormationConstraints(ctx co func (_m *FormationConstraintRepository) Update(ctx context.Context, _a1 *model.FormationConstraint) error { ret := _m.Called(ctx, _a1) + if len(ret) == 0 { + panic("no return value specified for Update") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.FormationConstraint) error); ok { r0 = rf(ctx, _a1) diff --git a/components/director/internal/domain/formationconstraint/automock/formation_template_constraint_reference_repository.go b/components/director/internal/domain/formationconstraint/automock/formation_template_constraint_reference_repository.go index 922494ed87..d3e846f308 100644 --- a/components/director/internal/domain/formationconstraint/automock/formation_template_constraint_reference_repository.go +++ b/components/director/internal/domain/formationconstraint/automock/formation_template_constraint_reference_repository.go @@ -15,10 +15,44 @@ type FormationTemplateConstraintReferenceRepository struct { mock.Mock } +// ListByConstraintID provides a mock function with given fields: ctx, constraintID +func (_m *FormationTemplateConstraintReferenceRepository) ListByConstraintID(ctx context.Context, constraintID string) ([]*model.FormationTemplateConstraintReference, error) { + ret := _m.Called(ctx, constraintID) + + if len(ret) == 0 { + panic("no return value specified for ListByConstraintID") + } + + var r0 []*model.FormationTemplateConstraintReference + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.FormationTemplateConstraintReference, error)); ok { + return rf(ctx, constraintID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []*model.FormationTemplateConstraintReference); ok { + r0 = rf(ctx, constraintID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.FormationTemplateConstraintReference) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, constraintID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // ListByFormationTemplateID provides a mock function with given fields: ctx, formationTemplateID func (_m *FormationTemplateConstraintReferenceRepository) ListByFormationTemplateID(ctx context.Context, formationTemplateID string) ([]*model.FormationTemplateConstraintReference, error) { ret := _m.Called(ctx, formationTemplateID) + if len(ret) == 0 { + panic("no return value specified for ListByFormationTemplateID") + } + var r0 []*model.FormationTemplateConstraintReference var r1 error if rf, ok := ret.Get(0).(func(context.Context, string) ([]*model.FormationTemplateConstraintReference, error)); ok { @@ -45,6 +79,10 @@ func (_m *FormationTemplateConstraintReferenceRepository) ListByFormationTemplat func (_m *FormationTemplateConstraintReferenceRepository) ListByFormationTemplateIDs(ctx context.Context, formationTemplateIDs []string) ([]*model.FormationTemplateConstraintReference, error) { ret := _m.Called(ctx, formationTemplateIDs) + if len(ret) == 0 { + panic("no return value specified for ListByFormationTemplateIDs") + } + var r0 []*model.FormationTemplateConstraintReference var r1 error if rf, ok := ret.Get(0).(func(context.Context, []string) ([]*model.FormationTemplateConstraintReference, error)); ok { diff --git a/components/director/internal/domain/formationconstraint/automock/uid_service.go b/components/director/internal/domain/formationconstraint/automock/uid_service.go index bf16e8d7f0..a985d364a8 100644 --- a/components/director/internal/domain/formationconstraint/automock/uid_service.go +++ b/components/director/internal/domain/formationconstraint/automock/uid_service.go @@ -13,6 +13,10 @@ type UidService struct { func (_m *UidService) Generate() string { ret := _m.Called() + if len(ret) == 0 { + panic("no return value specified for Generate") + } + var r0 string if rf, ok := ret.Get(0).(func() string); ok { r0 = rf() diff --git a/components/director/internal/domain/formationconstraint/fixtures_test.go b/components/director/internal/domain/formationconstraint/fixtures_test.go index 1a97ab81b1..91927d7bff 100644 --- a/components/director/internal/domain/formationconstraint/fixtures_test.go +++ b/components/director/internal/domain/formationconstraint/fixtures_test.go @@ -370,6 +370,10 @@ func UnusedFormationConstraintConverter() *automock.FormationConstraintConverter return &automock.FormationConstraintConverter{} } +func UnusedFormationTemplateConstraintReferenceRepository() *automock.FormationTemplateConstraintReferenceRepository { + return &automock.FormationTemplateConstraintReferenceRepository{} +} + func fixColumns() []string { return []string{"id", "name", "description", "constraint_type", "target_operation", "operator", "resource_type", "resource_subtype", "input_template", "constraint_scope", "priority", "created_at"} } diff --git a/components/director/internal/domain/formationconstraint/service.go b/components/director/internal/domain/formationconstraint/service.go index 33c7871896..06897a6276 100644 --- a/components/director/internal/domain/formationconstraint/service.go +++ b/components/director/internal/domain/formationconstraint/service.go @@ -24,6 +24,7 @@ type formationConstraintRepository interface { type formationTemplateConstraintReferenceRepository interface { ListByFormationTemplateID(ctx context.Context, formationTemplateID string) ([]*model.FormationTemplateConstraintReference, error) ListByFormationTemplateIDs(ctx context.Context, formationTemplateIDs []string) ([]*model.FormationTemplateConstraintReference, error) + ListByConstraintID(ctx context.Context, constraintID string) ([]*model.FormationTemplateConstraintReference, error) } //go:generate mockery --exported --name=uidService --output=automock --outpkg=automock --case=underscore --disable-version-string @@ -150,6 +151,20 @@ func (s *service) ListByFormationTemplateIDs(ctx context.Context, formationTempl // Delete deletes formation constraint by id func (s *service) Delete(ctx context.Context, id string) error { + formationTemplateConstraintReferences, err := s.formationTemplateConstraintReferenceRepo.ListByConstraintID(ctx, id) + if err != nil { + return errors.Wrapf(err, "while listing Formation Template Constraint References for Constraint with ID %s", id) + } + + formationTemplateIDs := make([]string, 0, len(formationTemplateConstraintReferences)) + for _, reference := range formationTemplateConstraintReferences { + formationTemplateIDs = append(formationTemplateIDs, reference.FormationTemplateID) + } + + if len(formationTemplateConstraintReferences) > 0 { + return errors.Errorf("cannot delete Formation Constraint with ID %s because it is used by Formation Templates with IDs %v", id, formationTemplateIDs) + } + if err := s.repo.Delete(ctx, id); err != nil { return errors.Wrapf(err, "while deleting Formation Constraint with ID %s", id) } diff --git a/components/director/internal/domain/formationconstraint/service_test.go b/components/director/internal/domain/formationconstraint/service_test.go index 7eb5295542..56f10271bf 100644 --- a/components/director/internal/domain/formationconstraint/service_test.go +++ b/components/director/internal/domain/formationconstraint/service_test.go @@ -3,6 +3,7 @@ package formationconstraint_test import ( "context" "errors" + "fmt" "reflect" "testing" @@ -306,11 +307,12 @@ func TestService_Delete(t *testing.T) { testErr := errors.New("test error") testCases := []struct { - Name string - Context context.Context - Input string - FormationConstraintRepository func() *automock.FormationConstraintRepository - ExpectedError error + Name string + Context context.Context + Input string + FormationConstraintRepository func() *automock.FormationConstraintRepository + FormationTemplateConstraintReferencesRepository func() *automock.FormationTemplateConstraintReferenceRepository + ExpectedErrorMessage string }{ { Name: "Success", @@ -321,7 +323,12 @@ func TestService_Delete(t *testing.T) { repo.On("Delete", ctx, testID).Return(nil).Once() return repo }, - ExpectedError: nil, + FormationTemplateConstraintReferencesRepository: func() *automock.FormationTemplateConstraintReferenceRepository { + repo := &automock.FormationTemplateConstraintReferenceRepository{} + repo.On("ListByConstraintID", ctx, testID).Return(nil, nil).Once() + return repo + }, + ExpectedErrorMessage: "", }, { Name: "Error when deleting formation constraint", @@ -332,23 +339,57 @@ func TestService_Delete(t *testing.T) { repo.On("Delete", ctx, testID).Return(testErr).Once() return repo }, - ExpectedError: testErr, + FormationTemplateConstraintReferencesRepository: func() *automock.FormationTemplateConstraintReferenceRepository { + repo := &automock.FormationTemplateConstraintReferenceRepository{} + repo.On("ListByConstraintID", ctx, testID).Return(nil, nil).Once() + return repo + }, + ExpectedErrorMessage: testErr.Error(), + }, + { + Name: "Error when the constraint is attached to formation templates", + Context: ctx, + Input: testID, + FormationTemplateConstraintReferencesRepository: func() *automock.FormationTemplateConstraintReferenceRepository { + repo := &automock.FormationTemplateConstraintReferenceRepository{} + repo.On("ListByConstraintID", ctx, testID).Return([]*model.FormationTemplateConstraintReference{formationConstraintReference}, nil).Once() + return repo + }, + ExpectedErrorMessage: fmt.Sprintf("cannot delete Formation Constraint with ID %s because it is used by Formation Templates with IDs %v", testID, []string{formationConstraintReference.FormationTemplateID}), + }, + { + Name: "Error when listing formation template constraint references", + Context: ctx, + Input: testID, + FormationTemplateConstraintReferencesRepository: func() *automock.FormationTemplateConstraintReferenceRepository { + repo := &automock.FormationTemplateConstraintReferenceRepository{} + repo.On("ListByConstraintID", ctx, testID).Return(nil, testErr).Once() + return repo + }, + ExpectedErrorMessage: testErr.Error(), }, } for _, testCase := range testCases { t.Run(testCase.Name, func(t *testing.T) { - formationConstraintRepo := testCase.FormationConstraintRepository() + formationConstraintRepo := UnusedFormationConstraintRepository() + if testCase.FormationConstraintRepository != nil { + formationConstraintRepo = testCase.FormationConstraintRepository() + } + formationConstraintReferenceRepo := UnusedFormationTemplateConstraintReferenceRepository() + if testCase.FormationTemplateConstraintReferencesRepository != nil { + formationConstraintReferenceRepo = testCase.FormationTemplateConstraintReferencesRepository() + } - svc := formationconstraint.NewService(formationConstraintRepo, nil, nil, nil) + svc := formationconstraint.NewService(formationConstraintRepo, formationConstraintReferenceRepo, nil, nil) // WHEN err := svc.Delete(testCase.Context, testCase.Input) // THEN - if testCase.ExpectedError != nil { + if testCase.ExpectedErrorMessage != "" { require.Error(t, err) - assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) + assert.Contains(t, err.Error(), testCase.ExpectedErrorMessage) } else { assert.NoError(t, err) } diff --git a/components/director/internal/domain/formationtemplateconstraintreferences/automock/formation_template_constraint_reference_repository.go b/components/director/internal/domain/formationtemplateconstraintreferences/automock/formation_template_constraint_reference_repository.go index 1c62cc1291..ea3a4d2836 100644 --- a/components/director/internal/domain/formationtemplateconstraintreferences/automock/formation_template_constraint_reference_repository.go +++ b/components/director/internal/domain/formationtemplateconstraintreferences/automock/formation_template_constraint_reference_repository.go @@ -19,6 +19,10 @@ type FormationTemplateConstraintReferenceRepository struct { func (_m *FormationTemplateConstraintReferenceRepository) Create(ctx context.Context, item *model.FormationTemplateConstraintReference) error { ret := _m.Called(ctx, item) + if len(ret) == 0 { + panic("no return value specified for Create") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *model.FormationTemplateConstraintReference) error); ok { r0 = rf(ctx, item) @@ -33,6 +37,10 @@ func (_m *FormationTemplateConstraintReferenceRepository) Create(ctx context.Con func (_m *FormationTemplateConstraintReferenceRepository) Delete(ctx context.Context, formationTemplateID string, constraintID string) error { ret := _m.Called(ctx, formationTemplateID, constraintID) + if len(ret) == 0 { + panic("no return value specified for Delete") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { r0 = rf(ctx, formationTemplateID, constraintID) diff --git a/components/director/internal/domain/formationtemplateconstraintreferences/repository.go b/components/director/internal/domain/formationtemplateconstraintreferences/repository.go index ba95261bd8..9592d83caf 100644 --- a/components/director/internal/domain/formationtemplateconstraintreferences/repository.go +++ b/components/director/internal/domain/formationtemplateconstraintreferences/repository.go @@ -59,6 +59,16 @@ func (r *repository) ListByFormationTemplateIDs(ctx context.Context, formationTe return r.multipleFromEntities(entityCollection) } +// ListByConstraintID lists formationTemplateConstraintReferences for the provided constraint ID +func (r *repository) ListByConstraintID(ctx context.Context, constraintID string) ([]*model.FormationTemplateConstraintReference, error) { + var entityCollection EntityCollection + + if err := r.lister.ListGlobal(ctx, &entityCollection, repo.NewEqualCondition(formationConstraintColumn, constraintID)); err != nil { + return nil, errors.Wrapf(err, "while listing formationTemplate-constraint references by constraint ID for constraint ID: %q", constraintID) + } + return r.multipleFromEntities(entityCollection) +} + // Create stores new formationTemplateConstraintReference in the database func (r *repository) Create(ctx context.Context, item *model.FormationTemplateConstraintReference) error { if item == nil { diff --git a/components/director/internal/domain/formationtemplateconstraintreferences/repository_test.go b/components/director/internal/domain/formationtemplateconstraintreferences/repository_test.go index 448e97ccd4..adcabcb419 100644 --- a/components/director/internal/domain/formationtemplateconstraintreferences/repository_test.go +++ b/components/director/internal/domain/formationtemplateconstraintreferences/repository_test.go @@ -119,3 +119,35 @@ func TestRepository_ListByFormationTemplateIDs(t *testing.T) { suite.Run(t) } + +func TestRepository_ListByConstraintID(t *testing.T) { + suite := testdb.RepoListTestSuite{ + Name: "List Formation Constraints by Constraint ID", + MethodName: "ListByConstraintID", + SQLQueryDetails: []testdb.SQLQueryDetails{ + { + Query: regexp.QuoteMeta(`SELECT formation_constraint_id, formation_template_id FROM public.formation_template_constraint_references WHERE formation_constraint_id = $1`), + IsSelect: true, + Args: []driver.Value{ + constraintID, + }, + ValidRowsProvider: func() []*sqlmock.Rows { + return []*sqlmock.Rows{sqlmock.NewRows(fixColumns()).AddRow(entity.FormationTemplateID, entity.ConstraintID)} + }, + InvalidRowsProvider: func() []*sqlmock.Rows { + return []*sqlmock.Rows{sqlmock.NewRows(fixColumns())} + }, + }}, + ConverterMockProvider: func() testdb.Mock { + conv := &automock.ConstraintReferenceConverter{} + return conv + }, + RepoConstructorFunc: formationtemplateconstraintreferences.NewRepository, + MethodArgs: []interface{}{constraintID}, + ExpectedDBEntities: []interface{}{entity}, + ExpectedModelEntities: []interface{}{constraintReference}, + DisableConverterErrorTest: true, + } + + suite.Run(t) +} diff --git a/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.down.sql b/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.down.sql new file mode 100644 index 0000000000..88e8a64c90 --- /dev/null +++ b/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE formation_template_constraint_references DROP CONSTRAINT IF EXISTS formation_template_constraint_refe_formation_constraint_id_fkey; +ALTER TABLE formation_template_constraint_references ADD CONSTRAINT formation_template_constraint_refe_formation_constraint_id_fkey FOREIGN KEY (formation_constraint_id) REFERENCES formation_constraints(id) ON DELETE CASCADE; + +COMMIT; diff --git a/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.up.sql b/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.up.sql new file mode 100644 index 0000000000..4bcc4ec68a --- /dev/null +++ b/components/schema-migrator/migrations/director/20240410172316_remove_constrain_cascade_deletion.up.sql @@ -0,0 +1,7 @@ +BEGIN; + +-- the only way to remove a cascade is to delete the constraint and recreate it +ALTER TABLE formation_template_constraint_references DROP CONSTRAINT IF EXISTS formation_template_constraint_refe_formation_constraint_id_fkey; +ALTER TABLE formation_template_constraint_references ADD CONSTRAINT formation_template_constraint_refe_formation_constraint_id_fkey FOREIGN KEY (formation_constraint_id) REFERENCES formation_constraints(id); + +COMMIT; diff --git a/tests/director/tests/formation/formation_constraint_api_test.go b/tests/director/tests/formation/formation_constraint_api_test.go index 1a274dd03b..3de00eba85 100644 --- a/tests/director/tests/formation/formation_constraint_api_test.go +++ b/tests/director/tests/formation/formation_constraint_api_test.go @@ -314,6 +314,7 @@ func TestListFormationConstraintsForFormationTemplate(t *testing.T) { secondFormationTemplateOutput := fixtures.QueryFormationTemplateWithConstraints(t, ctx, certSecuredGraphQLClient, secondFormationTemplate.ID) require.ElementsMatch(t, secondFormationTemplateOutput.FormationConstraints, globalFormationConstraints) // only global constraints and no attached ones + defer fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, constraint.ID, formationTemplate.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, constraint.ID, constraint.Name, formationTemplate.ID, formationTemplate.Name) // Assert the constraint is attached only to the first formation template @@ -340,6 +341,7 @@ func TestListFormationConstraintsForFormationTemplate(t *testing.T) { defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, constraintSecond.ID) require.NotEmpty(t, constraintSecond.ID) + defer fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, constraintSecond.ID, formationTemplate.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, constraintSecond.ID, constraintSecond.Name, formationTemplate.ID, formationTemplate.Name) // Assert the two constraints are attached only to the first formation template @@ -366,6 +368,7 @@ func TestListFormationConstraintsForFormationTemplate(t *testing.T) { defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, constraintForOtherTemplate.ID) require.NotEmpty(t, constraintForOtherTemplate.ID) + defer fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, constraintForOtherTemplate.ID, secondFormationTemplate.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, constraintForOtherTemplate.ID, constraintForOtherTemplate.Name, secondFormationTemplate.ID, formationTemplate.Name) // Assert the two constraints are attached to the first formation template and one to the second formation template diff --git a/tests/director/tests/notifications/application_only_notifications_test.go b/tests/director/tests/notifications/application_only_notifications_test.go index ab4e7cd77b..2742a1ccea 100644 --- a/tests/director/tests/notifications/application_only_notifications_test.go +++ b/tests/director/tests/notifications/application_only_notifications_test.go @@ -343,7 +343,12 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsOldFormat(t *testi // Create formation constraints for destination creator operator and attach them to a given formation template. // So we can verify the destination creator will not fail if in the configuration there is no destination information - attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + constraintCleanupFunctions := attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + defer func() { + for _, cleanup := range constraintCleanupFunctions { + cleanup() + } + }() formationName := "app-to-app-formation-name" t.Logf("Creating formation with name: %q from template with name: %q", formationName, formationTmplName) @@ -634,7 +639,12 @@ func TestFormationNotificationsWithApplicationOnlyParticipantsOldFormat(t *testi // Create formation constraints for destination creator operator and attach them to a given formation template. // So we can verify the destination creator will not fail if in the configuration there is no destination information - attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + constraintCleanupFunctions := attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + defer func() { + for _, cleanup := range constraintCleanupFunctions { + cleanup() + } + }() formationName := "app-to-app-formation-name" t.Logf("Creating formation with name: %q from template with name: %q", formationName, formationTmplName) diff --git a/tests/director/tests/notifications/application_subscription_notifications_test.go b/tests/director/tests/notifications/application_subscription_notifications_test.go index 2bb9261d0f..7603a54584 100644 --- a/tests/director/tests/notifications/application_subscription_notifications_test.go +++ b/tests/director/tests/notifications/application_subscription_notifications_test.go @@ -217,7 +217,12 @@ func TestFormationNotificationsWithApplicationSubscription(stdT *testing.T) { // Create formation constraints for destination creator operator and attach them to a given formation template. // So we can verify the destination creator will not fail if in the configuration there is no destination information - attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + constraintCleanupFunctions := attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeApplication, graphql.ResourceTypeApplication) + defer func() { + for _, cleanup := range constraintCleanupFunctions { + cleanup() + } + }() t.Logf("Creating formation with name: %q from template with name: %q", formationName, providerFormationTmplName) defer fixtures.DeleteFormationWithinTenant(t, ctx, certSecuredGraphQLClient, subscriptionConsumerAccountID, formationName) @@ -356,6 +361,7 @@ func TestFormationNotificationsWithApplicationSubscription(stdT *testing.T) { defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, firstConstraint.ID) require.NotEmpty(t, firstConstraint.ID) + defer fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, firstConstraint.ID, ft.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, firstConstraint.ID, firstConstraint.Name, ft.ID, ft.Name) // second constraint @@ -374,6 +380,7 @@ func TestFormationNotificationsWithApplicationSubscription(stdT *testing.T) { defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, secondConstraint.ID) require.NotEmpty(t, secondConstraint.ID) + defer fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, secondConstraint.ID, ft.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, secondConstraint.ID, secondConstraint.Name, ft.ID, ft.Name) // create formation diff --git a/tests/director/tests/notifications/fixtures_test.go b/tests/director/tests/notifications/fixtures_test.go index f44d87fce9..21f640a903 100644 --- a/tests/director/tests/notifications/fixtures_test.go +++ b/tests/director/tests/notifications/fixtures_test.go @@ -19,7 +19,6 @@ import ( "github.com/kyma-incubator/compass/components/director/pkg/graphql" "github.com/kyma-incubator/compass/components/director/pkg/str" esmdestinationcreator "github.com/kyma-incubator/compass/components/external-services-mock/pkg/destinationcreator" - "github.com/kyma-incubator/compass/tests/pkg/certs" "github.com/kyma-incubator/compass/tests/pkg/clients" "github.com/kyma-incubator/compass/tests/pkg/fixtures" jsonutils "github.com/kyma-incubator/compass/tests/pkg/json" @@ -169,7 +168,8 @@ func assertFormationStatus(t *testing.T, ctx context.Context, tenant, formationI } } -func attachDestinationCreatorConstraints(t *testing.T, ctx context.Context, formationTemplate graphql.FormationTemplate, statusReturnedConstraintResourceType, sendNotificationConstraintResourceType graphql.ResourceType) { +func attachDestinationCreatorConstraints(t *testing.T, ctx context.Context, formationTemplate graphql.FormationTemplate, statusReturnedConstraintResourceType, sendNotificationConstraintResourceType graphql.ResourceType) []func() { + deferredFunctions := make([]func(), 0, 4) firstConstraintInput := graphql.FormationConstraintInput{ Name: "e2e-destination-creator-notification-status-returned", ConstraintType: graphql.ConstraintTypePre, @@ -182,10 +182,15 @@ func attachDestinationCreatorConstraints(t *testing.T, ctx context.Context, form } firstConstraint := fixtures.CreateFormationConstraint(t, ctx, certSecuredGraphQLClient, firstConstraintInput) - defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, firstConstraint.ID) + deferredFunctions = append([]func(){func() { + fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, firstConstraint.ID) + }}, deferredFunctions...) require.NotEmpty(t, firstConstraint.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, firstConstraint.ID, firstConstraint.Name, formationTemplate.ID, formationTemplate.Name) + deferredFunctions = append([]func(){func() { + fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, firstConstraint.ID, formationTemplate.ID) + }}, deferredFunctions...) // second constraint secondConstraintInput := graphql.FormationConstraintInput{ @@ -200,24 +205,16 @@ func attachDestinationCreatorConstraints(t *testing.T, ctx context.Context, form } secondConstraint := fixtures.CreateFormationConstraint(t, ctx, certSecuredGraphQLClient, secondConstraintInput) - defer fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, secondConstraint.ID) + deferredFunctions = append([]func(){func() { + fixtures.CleanupFormationConstraint(t, ctx, certSecuredGraphQLClient, secondConstraint.ID) + }}, deferredFunctions...) require.NotEmpty(t, secondConstraint.ID) fixtures.AttachConstraintToFormationTemplate(t, ctx, certSecuredGraphQLClient, secondConstraint.ID, secondConstraint.Name, formationTemplate.ID, formationTemplate.Name) -} - -func assertTrustDetailsForTargetAndNoTrustDetailsForSource(t *testing.T, assignNotificationAboutApp2 gjson.Result, expectedSubjectOne, expectedSubjectSecond string) { - t.Logf("Assert trust details are send to the target") - notificationItems := assignNotificationAboutApp2.Get("RequestBody.items") - app1FromNotification := notificationItems.Array()[0] - targetTrustDetails := app1FromNotification.Get("target-trust-details") - certificateDetails := targetTrustDetails.Array()[0].String() - certificateDetailsSecond := targetTrustDetails.Array()[1].String() - require.ElementsMatch(t, []string{certs.SortSubject(expectedSubjectOne), certs.SortSubject(expectedSubjectSecond)}, []string{certificateDetails, certificateDetailsSecond}) - - t.Logf("Assert that there are no trust details for the source") - sourceTrustDetails := app1FromNotification.Get("source-trust-details") - require.Equal(t, 0, len(sourceTrustDetails.Array())) + deferredFunctions = append([]func(){func() { + fixtures.DetachConstraintFromFormationTemplate(t, ctx, certSecuredGraphQLClient, secondConstraint.ID, formationTemplate.ID) + }}, deferredFunctions...) + return deferredFunctions } func cleanupNotificationsFromExternalSvcMock(t *testing.T, client *http.Client) { diff --git a/tests/director/tests/notifications/runtime_notification_subscription_test.go b/tests/director/tests/notifications/runtime_notification_subscription_test.go index 82124b99de..e97ecb3f40 100644 --- a/tests/director/tests/notifications/runtime_notification_subscription_test.go +++ b/tests/director/tests/notifications/runtime_notification_subscription_test.go @@ -533,7 +533,12 @@ func TestFormationNotificationsWithRuntimeAndApplicationParticipants(stdT *testi // Create formation constraints for destination creator operator and attach them to a given formation template. // So we can verify the destination creator will not fail if in the configuration there is no destination information - attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeRuntimeContext, graphql.ResourceTypeApplication) + constraintCleanupFunctions := attachDestinationCreatorConstraints(t, ctx, ft, graphql.ResourceTypeRuntimeContext, graphql.ResourceTypeApplication) + defer func() { + for _, cleanup := range constraintCleanupFunctions { + cleanup() + } + }() t.Logf("Creating formation with name: %q from template with name: %q", formationName, formationTemplateName) defer fixtures.DeleteFormationWithinTenant(t, ctx, certSecuredGraphQLClient, subscriptionConsumerAccountID, formationName)