github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/runtime/service_test.go (about) 1 package runtime_test 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 8 "github.com/kyma-incubator/compass/components/director/pkg/consumer" 9 "github.com/kyma-incubator/compass/components/hydrator/pkg/oathkeeper" 10 11 "github.com/kyma-incubator/compass/components/director/pkg/graphql" 12 13 "github.com/kyma-incubator/compass/components/director/internal/domain/runtime/rtmtest" 14 15 "github.com/kyma-incubator/compass/components/director/internal/domain/runtime" 16 "github.com/kyma-incubator/compass/components/director/internal/domain/runtime/automock" 17 "github.com/kyma-incubator/compass/components/director/internal/domain/scenarioassignment" 18 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 19 "github.com/kyma-incubator/compass/components/director/internal/labelfilter" 20 "github.com/kyma-incubator/compass/components/director/internal/model" 21 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 22 "github.com/kyma-incubator/compass/components/director/pkg/pagination" 23 "github.com/kyma-incubator/compass/components/director/pkg/str" 24 "github.com/pkg/errors" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/mock" 27 "github.com/stretchr/testify/require" 28 ) 29 30 var ( 31 labelsWithNormalization = map[string]interface{}{runtime.IsNormalizedLabel: "true"} 32 protectedLabelPattern = ".*_defaultEventing$|^consumer_subaccount_ids$" 33 immutableLabelPattern = "^xsappnameCMPClone$|^runtimeType$|^CMPSaaSAppName$" 34 runtimeTypeLabelKey = "runtimeType" 35 regionLabelKey = "region" 36 regionLabelValue = "test-region" 37 kymaRuntimeTypeLabelValue = "kyma" 38 kymaApplicationNamespaceValue = "kyma.ns" 39 testUUID = "b3ea1977-582e-4d61-ae12-b3a837a3858e" 40 testScenario = "test-scenario" 41 ) 42 43 func TestService_CreateWithMandatoryLabels(t *testing.T) { 44 // GIVEN 45 testErr := errors.New("Test error") 46 47 extSubaccountID := "extSubaccountID" 48 subaccountID := "subaccountID" 49 xsappNameCMPClone := "xsappnameCMPClone" 50 xsappNameCMPCloneValue := "xsappnameCMPCloneValue" 51 52 desc := "Lorem ipsum" 53 labels := map[string]interface{}{ 54 model.ScenariosKey: []interface{}{testScenario}, 55 "protected_defaultEventing": "true", 56 } 57 58 webhookInput := model.WebhookInput{ 59 Type: "type", 60 } 61 62 modelInput := func() model.RuntimeRegisterInput { 63 return model.RuntimeRegisterInput{ 64 Name: "foo.bar-not", 65 Description: &desc, 66 Labels: labels, 67 Webhooks: []*model.WebhookInput{{ 68 Type: "type", 69 }}, 70 } 71 } 72 73 modelInputWithoutWebhooks := func() model.RuntimeRegisterInput { 74 return model.RuntimeRegisterInput{ 75 Name: "foo.bar-not", 76 Description: &desc, 77 Labels: labels, 78 } 79 } 80 81 modelInputWithSubaccountLabel := func() model.RuntimeRegisterInput { 82 return model.RuntimeRegisterInput{ 83 Name: "foo.bar-not", 84 Description: &desc, 85 Labels: map[string]interface{}{ 86 scenarioassignment.SubaccountIDKey: extSubaccountID, 87 }, 88 Webhooks: []*model.WebhookInput{{ 89 Type: "type", 90 }}, 91 } 92 } 93 94 modelInputWithInvalidSubaccountLabel := func() model.RuntimeRegisterInput { 95 return model.RuntimeRegisterInput{ 96 Name: "foo.bar-not", 97 Description: &desc, 98 Labels: map[string]interface{}{ 99 model.ScenariosKey: []interface{}{testScenario}, 100 scenarioassignment.SubaccountIDKey: 213, 101 }, 102 Webhooks: []*model.WebhookInput{{ 103 Type: "type", 104 }}, 105 } 106 } 107 108 labelsForDBMockWithSubaccount := map[string]interface{}{ 109 runtime.IsNormalizedLabel: "true", 110 scenarioassignment.SubaccountIDKey: extSubaccountID, 111 runtimeTypeLabelKey: kymaRuntimeTypeLabelValue, 112 regionLabelKey: regionLabelValue, 113 } 114 115 labelsForDBMockWithMandatoryLabels := map[string]interface{}{ 116 runtime.IsNormalizedLabel: "true", 117 xsappNameCMPClone: xsappNameCMPCloneValue, 118 runtimeTypeLabelKey: kymaRuntimeTypeLabelValue, 119 regionLabelKey: "", 120 } 121 122 labelsForDBMockWithRuntimeType := map[string]interface{}{ 123 runtime.IsNormalizedLabel: "true", 124 runtimeTypeLabelKey: kymaRuntimeTypeLabelValue, 125 regionLabelKey: "", 126 } 127 128 modelRegionLabel := &model.Label{ 129 ID: "id", 130 Tenant: &subaccountID, 131 Key: regionLabelKey, 132 Value: regionLabelValue, 133 ObjectID: subaccountID, 134 ObjectType: model.TenantLabelableObject, 135 Version: 0, 136 } 137 138 modelInputWithoutLabels := func() model.RuntimeRegisterInput { 139 return model.RuntimeRegisterInput{ 140 Name: "foo.bar-not", 141 Description: &desc, 142 Webhooks: []*model.WebhookInput{{ 143 Type: "type", 144 }}, 145 } 146 } 147 148 var nilLabels map[string]interface{} 149 150 runtimeModel := mock.MatchedBy(func(rtm *model.Runtime) bool { 151 return rtm.Name == modelInput().Name && rtm.Description == modelInput().Description && 152 rtm.Status.Condition == model.RuntimeStatusConditionInitial 153 }) 154 155 tnt := "tenant" 156 externalTnt := "external-tnt" 157 IntSysConsumer := consumer.Consumer{ 158 ConsumerID: "consumerID", 159 ConsumerType: consumer.IntegrationSystem, 160 Flow: oathkeeper.OAuth2Flow, 161 } 162 163 ctx := context.TODO() 164 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 165 ctxWithSubaccount := tenant.SaveToContext(ctx, subaccountID, extSubaccountID) 166 ctxWithSubaccountAndIntSys := consumer.SaveToContext(ctxWithSubaccount, IntSysConsumer) 167 ctxWithIntSysConsumer := consumer.SaveToContext(ctx, IntSysConsumer) 168 169 ctxWithSubaccountMatcher := mock.MatchedBy(func(ctx context.Context) bool { 170 tenantCtx, err := tenant.LoadTenantPairFromContext(ctx) 171 require.NoError(t, err) 172 return subaccountID == tenantCtx.InternalID && extSubaccountID == tenantCtx.ExternalID 173 }) 174 ctxWithGlobalaccountMatcher := mock.MatchedBy(func(ctx context.Context) bool { 175 tenantCtx, err := tenant.LoadTenantPairFromContext(ctx) 176 require.NoError(t, err) 177 return tnt == tenantCtx.InternalID 178 }) 179 180 ga := &model.BusinessTenantMapping{ 181 ID: tnt, 182 Name: "ga", 183 ExternalTenant: externalTnt, 184 Type: "account", 185 Provider: "test", 186 Status: "Active", 187 } 188 189 subaccount := &model.BusinessTenantMapping{ 190 ID: subaccountID, 191 Name: "sa", 192 ExternalTenant: extSubaccountID, 193 Parent: tnt, 194 Type: "subaccount", 195 Provider: "test", 196 Status: "Active", 197 } 198 199 subaccountInput := func() model.BusinessTenantMappingInput { 200 return model.BusinessTenantMappingInput{ 201 ExternalTenant: extSubaccountID, 202 Parent: tnt, 203 Type: "subaccount", 204 Provider: "lazilyWhileRuntimeCreation", 205 } 206 } 207 208 testCases := []struct { 209 Name string 210 RuntimeRepositoryFn func() *automock.RuntimeRepository 211 TenantSvcFn func() *automock.TenantService 212 LabelServiceFn func() *automock.LabelService 213 UIDServiceFn func() *automock.UidService 214 WebhookServiceFn func() *automock.WebhookService 215 FormationServiceFn func() *automock.FormationService 216 Input model.RuntimeRegisterInput 217 MandatoryLabels func() map[string]interface{} 218 Context context.Context 219 ExpectedErr error 220 }{ 221 { 222 Name: "Success", 223 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 224 repo := &automock.RuntimeRepository{} 225 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 226 return repo 227 }, 228 LabelServiceFn: func() *automock.LabelService { 229 svc := &automock.LabelService{} 230 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithMandatoryLabels).Return(nil).Once() 231 return svc 232 }, 233 TenantSvcFn: func() *automock.TenantService { 234 tenantSvc := &automock.TenantService{} 235 tenantSvc.On("GetTenantByID", ctxWithIntSysConsumer, tnt).Return(ga, nil).Once() 236 return tenantSvc 237 }, 238 UIDServiceFn: rtmtest.UnusedUUIDService, 239 WebhookServiceFn: func() *automock.WebhookService { 240 webhookSvc := &automock.WebhookService{} 241 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 242 return webhookSvc 243 }, 244 FormationServiceFn: func() *automock.FormationService { 245 svc := &automock.FormationService{} 246 svc.On("AssignFormation", ctxWithIntSysConsumer, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: testScenario}).Return(&model.Formation{Name: testScenario}, nil) 247 return svc 248 }, 249 Input: modelInput(), 250 MandatoryLabels: func() map[string]interface{} { 251 mandatoryLabels := make(map[string]interface{}) 252 mandatoryLabels[xsappNameCMPClone] = xsappNameCMPCloneValue 253 mandatoryLabels[runtimeTypeLabelKey] = kymaRuntimeTypeLabelValue 254 mandatoryLabels[regionLabelKey] = "" 255 return mandatoryLabels 256 }, 257 Context: ctxWithIntSysConsumer, 258 ExpectedErr: nil, 259 }, 260 { 261 Name: "Success without webhooks", 262 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 263 repo := &automock.RuntimeRepository{} 264 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 265 return repo 266 }, 267 LabelServiceFn: func() *automock.LabelService { 268 svc := &automock.LabelService{} 269 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithMandatoryLabels).Return(nil).Once() 270 return svc 271 }, 272 TenantSvcFn: func() *automock.TenantService { 273 tenantSvc := &automock.TenantService{} 274 tenantSvc.On("GetTenantByID", ctxWithIntSysConsumer, tnt).Return(ga, nil).Once() 275 return tenantSvc 276 }, 277 UIDServiceFn: rtmtest.UnusedUUIDService, 278 WebhookServiceFn: rtmtest.UnusedWebhookService, 279 FormationServiceFn: func() *automock.FormationService { 280 svc := &automock.FormationService{} 281 svc.On("AssignFormation", ctxWithIntSysConsumer, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: testScenario}).Return(&model.Formation{Name: testScenario}, nil) 282 return svc 283 }, 284 Input: modelInputWithoutWebhooks(), 285 MandatoryLabels: func() map[string]interface{} { 286 mandatoryLabels := make(map[string]interface{}) 287 mandatoryLabels[xsappNameCMPClone] = xsappNameCMPCloneValue 288 mandatoryLabels[runtimeTypeLabelKey] = kymaRuntimeTypeLabelValue 289 mandatoryLabels[regionLabelKey] = "" 290 return mandatoryLabels 291 }, 292 Context: ctxWithIntSysConsumer, 293 ExpectedErr: nil, 294 }, 295 { 296 Name: "Success with Subaccount label", 297 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 298 repo := &automock.RuntimeRepository{} 299 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 300 return repo 301 }, 302 LabelServiceFn: func() *automock.LabelService { 303 svc := &automock.LabelService{} 304 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 305 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 306 return svc 307 }, 308 TenantSvcFn: func() *automock.TenantService { 309 tenantSvc := &automock.TenantService{} 310 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountAndIntSys, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 311 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(subaccount, nil).Once() 312 return tenantSvc 313 }, 314 UIDServiceFn: rtmtest.UnusedUUIDService, 315 WebhookServiceFn: func() *automock.WebhookService { 316 webhookSvc := &automock.WebhookService{} 317 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 318 return webhookSvc 319 }, 320 FormationServiceFn: func() *automock.FormationService { 321 svc := &automock.FormationService{} 322 svc.On("AssignFormation", mock.Anything, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: "test"}).Return(&model.Formation{Name: "test"}, nil) 323 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctxWithGlobalaccountMatcher, map[string]interface{}{}, runtimeID).Return([]interface{}{"test"}, nil) 324 return svc 325 }, 326 Input: modelInputWithSubaccountLabel(), 327 MandatoryLabels: func() map[string]interface{} { 328 return nilLabels 329 }, 330 Context: ctxWithSubaccountAndIntSys, 331 ExpectedErr: nil, 332 }, 333 { 334 Name: "Success with Subaccount label when caller and label are the same", 335 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 336 repo := &automock.RuntimeRepository{} 337 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 338 return repo 339 }, 340 LabelServiceFn: func() *automock.LabelService { 341 svc := &automock.LabelService{} 342 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 343 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 344 return svc 345 }, 346 TenantSvcFn: func() *automock.TenantService { 347 tenantSvc := &automock.TenantService{} 348 subaccountInput := subaccountInput() 349 subaccountInput.Parent = subaccountID 350 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountAndIntSys, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 351 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(subaccount, nil).Once() 352 return tenantSvc 353 }, 354 UIDServiceFn: rtmtest.UnusedUUIDService, 355 WebhookServiceFn: func() *automock.WebhookService { 356 webhookSvc := &automock.WebhookService{} 357 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 358 return webhookSvc 359 }, 360 FormationServiceFn: func() *automock.FormationService { 361 svc := &automock.FormationService{} 362 svc.On("AssignFormation", mock.Anything, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: "test"}).Return(&model.Formation{Name: "test"}, nil) 363 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctxWithGlobalaccountMatcher, map[string]interface{}{}, runtimeID).Return([]interface{}{"test"}, nil) 364 return svc 365 }, 366 Input: modelInputWithSubaccountLabel(), 367 MandatoryLabels: func() map[string]interface{} { 368 return nilLabels 369 }, 370 Context: ctxWithSubaccountAndIntSys, 371 ExpectedErr: nil, 372 }, 373 { 374 Name: "Success with Subaccount label and no scenarios from ASAs in parent", 375 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 376 repo := &automock.RuntimeRepository{} 377 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 378 return repo 379 }, 380 LabelServiceFn: func() *automock.LabelService { 381 svc := &automock.LabelService{} 382 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 383 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 384 return svc 385 }, 386 TenantSvcFn: func() *automock.TenantService { 387 tenantSvc := &automock.TenantService{} 388 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountAndIntSys, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 389 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(subaccount, nil).Once() 390 return tenantSvc 391 }, 392 UIDServiceFn: rtmtest.UnusedUUIDService, 393 WebhookServiceFn: func() *automock.WebhookService { 394 webhookSvc := &automock.WebhookService{} 395 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 396 return webhookSvc 397 }, 398 FormationServiceFn: func() *automock.FormationService { 399 svc := &automock.FormationService{} 400 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctxWithGlobalaccountMatcher, map[string]interface{}{}, runtimeID).Return([]interface{}{}, nil) 401 return svc 402 }, 403 Input: modelInputWithSubaccountLabel(), 404 MandatoryLabels: func() map[string]interface{} { 405 return nilLabels 406 }, 407 Context: ctxWithSubaccountAndIntSys, 408 ExpectedErr: nil, 409 }, 410 { 411 Name: "Success when labels are empty", 412 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 413 repo := &automock.RuntimeRepository{} 414 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 415 return repo 416 }, 417 LabelServiceFn: func() *automock.LabelService { 418 svc := &automock.LabelService{} 419 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithRuntimeType).Return(nil).Once() 420 return svc 421 }, 422 TenantSvcFn: func() *automock.TenantService { 423 tenantSvc := &automock.TenantService{} 424 tenantSvc.On("GetTenantByID", ctxWithIntSysConsumer, tnt).Return(ga, nil).Once() 425 return tenantSvc 426 }, 427 UIDServiceFn: rtmtest.UnusedUUIDService, 428 WebhookServiceFn: func() *automock.WebhookService { 429 webhookSvc := &automock.WebhookService{} 430 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 431 return webhookSvc 432 }, 433 FormationServiceFn: unusedFormationService, 434 Input: modelInputWithoutLabels(), 435 MandatoryLabels: func() map[string]interface{} { 436 return nilLabels 437 }, 438 Context: ctxWithIntSysConsumer, 439 ExpectedErr: nil, 440 }, 441 { 442 Name: "Returns error when subaccount label conversion fail", 443 RuntimeRepositoryFn: unusedRuntimeRepository, 444 LabelServiceFn: unusedLabelService, 445 TenantSvcFn: unusedTenantService, 446 UIDServiceFn: rtmtest.UnusedUUIDService, 447 WebhookServiceFn: rtmtest.UnusedWebhookService, 448 FormationServiceFn: unusedFormationService, 449 Input: modelInputWithInvalidSubaccountLabel(), 450 MandatoryLabels: func() map[string]interface{} { 451 return nilLabels 452 }, 453 Context: ctx, 454 ExpectedErr: errors.New("while converting global_subaccount_id label"), 455 }, 456 { 457 Name: "Returns error when subaccount get from DB fail", 458 RuntimeRepositoryFn: unusedRuntimeRepository, 459 LabelServiceFn: unusedLabelService, 460 TenantSvcFn: func() *automock.TenantService { 461 tenantSvc := &automock.TenantService{} 462 tenantSvc.On("GetTenantByExternalID", ctx, extSubaccountID).Return(nil, testErr).Once() 463 return tenantSvc 464 }, 465 UIDServiceFn: rtmtest.UnusedUUIDService, 466 WebhookServiceFn: rtmtest.UnusedWebhookService, 467 FormationServiceFn: unusedFormationService, 468 Input: modelInputWithSubaccountLabel(), 469 MandatoryLabels: func() map[string]interface{} { 470 return nilLabels 471 }, 472 Context: ctx, 473 ExpectedErr: testErr, 474 }, 475 { 476 Name: "Returns error when runtime creation failed", 477 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 478 repo := &automock.RuntimeRepository{} 479 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(testErr).Once() 480 return repo 481 }, 482 LabelServiceFn: unusedLabelService, 483 TenantSvcFn: unusedTenantService, 484 UIDServiceFn: rtmtest.UnusedUUIDService, 485 WebhookServiceFn: rtmtest.UnusedWebhookService, 486 FormationServiceFn: unusedFormationService, 487 Input: modelInput(), 488 MandatoryLabels: func() map[string]interface{} { 489 return nilLabels 490 }, 491 Context: ctxWithSubaccountAndIntSys, 492 ExpectedErr: testErr, 493 }, 494 { 495 Name: "Returns error when subaccount in the label is not child of the caller", 496 RuntimeRepositoryFn: unusedRuntimeRepository, 497 LabelServiceFn: unusedLabelService, 498 TenantSvcFn: func() *automock.TenantService { 499 tenantSvc := &automock.TenantService{} 500 tenantSvc.On("GetTenantByExternalID", ctx, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: "anotherParent"}, nil).Once() 501 return tenantSvc 502 }, 503 UIDServiceFn: rtmtest.UnusedUUIDService, 504 WebhookServiceFn: rtmtest.UnusedWebhookService, 505 FormationServiceFn: unusedFormationService, 506 Input: modelInputWithSubaccountLabel(), 507 MandatoryLabels: func() map[string]interface{} { 508 return nilLabels 509 }, 510 Context: ctx, 511 ExpectedErr: apperrors.NewInvalidOperationError(fmt.Sprintf("Tenant provided in %s label should be child of the caller tenant", scenarioassignment.SubaccountIDKey)), 512 }, 513 { 514 Name: "Return error when get calling tenant from DB fail", 515 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 516 repo := &automock.RuntimeRepository{} 517 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 518 return repo 519 }, 520 LabelServiceFn: func() *automock.LabelService { 521 svc := &automock.LabelService{} 522 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 523 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 524 return svc 525 }, 526 TenantSvcFn: func() *automock.TenantService { 527 tenantSvc := &automock.TenantService{} 528 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountMatcher, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 529 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(nil, testErr).Once() 530 return tenantSvc 531 }, 532 UIDServiceFn: rtmtest.UnusedUUIDService, 533 WebhookServiceFn: func() *automock.WebhookService { 534 webhookSvc := &automock.WebhookService{} 535 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 536 return webhookSvc 537 }, 538 FormationServiceFn: unusedFormationService, 539 Input: modelInputWithSubaccountLabel(), 540 MandatoryLabels: func() map[string]interface{} { 541 return nilLabels 542 }, 543 Context: ctxWithSubaccountAndIntSys, 544 ExpectedErr: testErr, 545 }, 546 { 547 Name: "Returns error when webhook creation failed", 548 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 549 repo := &automock.RuntimeRepository{} 550 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 551 return repo 552 }, 553 LabelServiceFn: func() *automock.LabelService { 554 svc := &automock.LabelService{} 555 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithMandatoryLabels).Return(nil).Once() 556 return svc 557 }, 558 TenantSvcFn: unusedTenantService, 559 UIDServiceFn: rtmtest.UnusedUUIDService, 560 WebhookServiceFn: func() *automock.WebhookService { 561 webhookSvc := &automock.WebhookService{} 562 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("", testErr) 563 return webhookSvc 564 }, 565 FormationServiceFn: func() *automock.FormationService { 566 svc := &automock.FormationService{} 567 svc.On("AssignFormation", mock.Anything, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: testScenario}).Return(&model.Formation{Name: testScenario}, nil) 568 return svc 569 }, 570 Input: modelInput(), 571 MandatoryLabels: func() map[string]interface{} { 572 mandatoryLabels := make(map[string]interface{}) 573 mandatoryLabels[xsappNameCMPClone] = xsappNameCMPCloneValue 574 mandatoryLabels[runtimeTypeLabelKey] = kymaRuntimeTypeLabelValue 575 return mandatoryLabels 576 }, 577 Context: ctxWithIntSysConsumer, 578 ExpectedErr: testErr, 579 }, 580 { 581 Name: "Return error when merge of scenarios and assignments failed", 582 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 583 repo := &automock.RuntimeRepository{} 584 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 585 return repo 586 }, 587 LabelServiceFn: func() *automock.LabelService { 588 svc := &automock.LabelService{} 589 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 590 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 591 return svc 592 }, 593 TenantSvcFn: func() *automock.TenantService { 594 tenantSvc := &automock.TenantService{} 595 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountMatcher, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 596 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(subaccount, nil).Once() 597 return tenantSvc 598 }, 599 UIDServiceFn: rtmtest.UnusedUUIDService, 600 WebhookServiceFn: func() *automock.WebhookService { 601 webhookSvc := &automock.WebhookService{} 602 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 603 return webhookSvc 604 }, 605 FormationServiceFn: func() *automock.FormationService { 606 svc := &automock.FormationService{} 607 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctxWithGlobalaccountMatcher, map[string]interface{}{}, runtimeID).Return(nil, testErr) 608 return svc 609 }, 610 Input: modelInputWithSubaccountLabel(), 611 MandatoryLabels: func() map[string]interface{} { 612 return nilLabels 613 }, 614 Context: ctxWithSubaccountAndIntSys, 615 ExpectedErr: testErr, 616 }, 617 { 618 Name: "Returns error when getting region label failed", 619 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 620 repo := &automock.RuntimeRepository{} 621 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 622 return repo 623 }, 624 LabelServiceFn: func() *automock.LabelService { 625 svc := &automock.LabelService{} 626 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(nil, testErr) 627 return svc 628 }, 629 TenantSvcFn: func() *automock.TenantService { 630 tenantSvc := &automock.TenantService{} 631 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountAndIntSys, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 632 return tenantSvc 633 }, 634 UIDServiceFn: rtmtest.UnusedUUIDService, 635 WebhookServiceFn: rtmtest.UnusedWebhookService, 636 FormationServiceFn: unusedFormationService, 637 Input: modelInputWithSubaccountLabel(), 638 MandatoryLabels: func() map[string]interface{} { 639 return nilLabels 640 }, 641 Context: ctxWithSubaccountAndIntSys, 642 ExpectedErr: testErr, 643 }, 644 { 645 Name: "Returns error when label upserting failed", 646 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 647 repo := &automock.RuntimeRepository{} 648 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 649 return repo 650 }, 651 LabelServiceFn: func() *automock.LabelService { 652 svc := &automock.LabelService{} 653 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithRuntimeType).Return(testErr).Once() 654 return svc 655 }, 656 TenantSvcFn: unusedTenantService, 657 UIDServiceFn: rtmtest.UnusedUUIDService, 658 WebhookServiceFn: rtmtest.UnusedWebhookService, 659 FormationServiceFn: unusedFormationService, 660 Input: modelInput(), 661 MandatoryLabels: func() map[string]interface{} { 662 return nilLabels 663 }, 664 Context: ctxWithIntSysConsumer, 665 ExpectedErr: testErr, 666 }, 667 { 668 Name: "Returns error when assigning scenarios to subaccount fails", 669 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 670 repo := &automock.RuntimeRepository{} 671 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 672 return repo 673 }, 674 LabelServiceFn: func() *automock.LabelService { 675 svc := &automock.LabelService{} 676 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, tnt, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithMandatoryLabels).Return(nil).Once() 677 return svc 678 }, 679 TenantSvcFn: unusedTenantService, 680 UIDServiceFn: rtmtest.UnusedUUIDService, 681 WebhookServiceFn: unusedWebhookService, 682 FormationServiceFn: func() *automock.FormationService { 683 svc := &automock.FormationService{} 684 svc.On("AssignFormation", mock.Anything, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: testScenario}).Return(nil, testErr) 685 return svc 686 }, 687 Input: modelInput(), 688 MandatoryLabels: func() map[string]interface{} { 689 mandatoryLabels := make(map[string]interface{}) 690 mandatoryLabels[xsappNameCMPClone] = xsappNameCMPCloneValue 691 mandatoryLabels[runtimeTypeLabelKey] = kymaRuntimeTypeLabelValue 692 return mandatoryLabels 693 }, 694 Context: ctxWithIntSysConsumer, 695 ExpectedErr: testErr, 696 }, 697 { 698 Name: "Returns error when can't assign scenarios to parent", 699 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 700 repo := &automock.RuntimeRepository{} 701 repo.On("Create", ctxWithSubaccountMatcher, subaccountID, runtimeModel).Return(nil).Once() 702 return repo 703 }, 704 LabelServiceFn: func() *automock.LabelService { 705 svc := &automock.LabelService{} 706 svc.On("UpsertMultipleLabels", ctxWithSubaccountMatcher, subaccountID, model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithSubaccount).Return(nil).Once() 707 svc.On("GetByKey", ctxWithSubaccountMatcher, subaccountID, model.TenantLabelableObject, subaccountID, regionLabelKey).Return(modelRegionLabel, nil) 708 return svc 709 }, 710 TenantSvcFn: func() *automock.TenantService { 711 tenantSvc := &automock.TenantService{} 712 tenantSvc.On("GetTenantByExternalID", ctxWithSubaccountMatcher, extSubaccountID).Return(&model.BusinessTenantMapping{ID: subaccountID, ExternalTenant: extSubaccountID, Parent: tnt}, nil).Once() 713 tenantSvc.On("GetTenantByID", ctxWithSubaccountMatcher, subaccountID).Return(subaccount, nil).Once() 714 return tenantSvc 715 }, 716 UIDServiceFn: rtmtest.UnusedUUIDService, 717 WebhookServiceFn: func() *automock.WebhookService { 718 webhookSvc := &automock.WebhookService{} 719 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 720 return webhookSvc 721 }, 722 FormationServiceFn: func() *automock.FormationService { 723 svc := &automock.FormationService{} 724 svc.On("AssignFormation", mock.Anything, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: "test"}).Return(nil, testErr) 725 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctxWithGlobalaccountMatcher, map[string]interface{}{}, runtimeID).Return([]interface{}{"test"}, nil) 726 return svc 727 }, 728 Input: modelInputWithSubaccountLabel(), 729 MandatoryLabels: func() map[string]interface{} { 730 return nilLabels 731 }, 732 Context: ctxWithSubaccountAndIntSys, 733 ExpectedErr: testErr, 734 }, 735 { 736 Name: "Successfully added runtime type label when the consumer type is integration system", 737 RuntimeRepositoryFn: func() *automock.RuntimeRepository { 738 repo := &automock.RuntimeRepository{} 739 repo.On("Create", ctxWithIntSysConsumer, tnt, runtimeModel).Return(nil).Once() 740 return repo 741 }, 742 LabelServiceFn: func() *automock.LabelService { 743 svc := &automock.LabelService{} 744 svc.On("UpsertMultipleLabels", ctxWithIntSysConsumer, "tenant", model.RuntimeLabelableObject, runtimeID, labelsForDBMockWithRuntimeType).Return(nil).Once() 745 return svc 746 }, 747 TenantSvcFn: func() *automock.TenantService { 748 tenantSvc := &automock.TenantService{} 749 tenantSvc.On("GetTenantByID", ctxWithIntSysConsumer, tnt).Return(ga, nil).Once() 750 return tenantSvc 751 }, 752 UIDServiceFn: rtmtest.UnusedUUIDService, 753 WebhookServiceFn: func() *automock.WebhookService { 754 webhookSvc := &automock.WebhookService{} 755 webhookSvc.Mock.On("Create", mock.Anything, runtimeID, webhookInput, model.RuntimeWebhookReference).Return("webhookID", nil) 756 return webhookSvc 757 }, 758 FormationServiceFn: func() *automock.FormationService { 759 svc := &automock.FormationService{} 760 svc.On("AssignFormation", ctxWithIntSysConsumer, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: testScenario}).Return(&model.Formation{Name: testScenario}, nil) 761 return svc 762 }, 763 Input: modelInput(), 764 MandatoryLabels: func() map[string]interface{} { 765 return nilLabels 766 }, 767 Context: ctxWithIntSysConsumer, 768 ExpectedErr: nil, 769 }, 770 { 771 Name: "Returns error when there is no consumer in the context", 772 RuntimeRepositoryFn: unusedRuntimeRepository, 773 LabelServiceFn: unusedLabelService, 774 TenantSvcFn: unusedTenantService, 775 UIDServiceFn: rtmtest.UnusedUUIDService, 776 WebhookServiceFn: unusedWebhookService, 777 FormationServiceFn: unusedFormationService, 778 Input: modelInput(), 779 MandatoryLabels: func() map[string]interface{} { 780 return nilLabels 781 }, 782 Context: ctx, 783 ExpectedErr: errors.New("while loading consumer: Internal Server Error: cannot read consumer from context"), 784 }, 785 } 786 787 for _, testCase := range testCases { 788 t.Run(testCase.Name, func(t *testing.T) { 789 repo := testCase.RuntimeRepositoryFn() 790 idSvc := testCase.UIDServiceFn() 791 labelSvc := testCase.LabelServiceFn() 792 engineSvc := testCase.FormationServiceFn() 793 tenantSvc := testCase.TenantSvcFn() 794 mandatoryLabels := testCase.MandatoryLabels() 795 webhookSvc := testCase.WebhookServiceFn() 796 svc := runtime.NewService(repo, nil, labelSvc, idSvc, engineSvc, tenantSvc, webhookSvc, nil, protectedLabelPattern, immutableLabelPattern, runtimeTypeLabelKey, kymaRuntimeTypeLabelValue, kymaApplicationNamespaceValue) 797 798 // WHEN 799 err := svc.CreateWithMandatoryLabels(testCase.Context, testCase.Input, runtimeID, mandatoryLabels) 800 801 // then 802 if err == nil { 803 require.Nil(t, testCase.ExpectedErr) 804 } else { 805 require.NotNil(t, testCase.ExpectedErr) 806 assert.Contains(t, err.Error(), testCase.ExpectedErr.Error()) 807 } 808 809 mock.AssertExpectationsForObjects(t, repo, idSvc, labelSvc, engineSvc, tenantSvc, webhookSvc) 810 }) 811 } 812 813 t.Run("Returns error on loading tenant", func(t *testing.T) { 814 // GIVEN 815 uuidSvc := &automock.UidService{} 816 uuidSvc.On("Generate").Return(testUUID).Once() 817 818 svc := runtime.NewService(nil, nil, nil, uuidSvc, nil, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 819 // WHEN 820 _, err := svc.Create(context.TODO(), model.RuntimeRegisterInput{}) 821 // then 822 require.Error(t, err) 823 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 824 uuidSvc.AssertExpectations(t) 825 }) 826 } 827 828 func TestService_Update(t *testing.T) { 829 // GIVEN 830 testErr := errors.New("Test error") 831 desc := "Lorem ipsum" 832 833 scenario := "SCENARIO" 834 scenariosLabelValueFirst := []interface{}{scenario} 835 runtimeModelWithFirstScenario := model.RuntimeUpdateInput{ 836 Name: "bar", 837 Labels: map[string]interface{}{ 838 model.ScenariosKey: []interface{}{scenario}, 839 }, 840 } 841 842 scenarioSecond := "SCENARIO2" 843 scenariosLabelValueTwo := []interface{}{scenario, scenarioSecond} 844 labelMapWithTwoScenarios := map[string]*model.Label{ 845 model.ScenariosKey: { 846 ID: "id", 847 Tenant: str.Ptr("tenant"), 848 Key: model.ScenariosKey, 849 Value: scenariosLabelValueTwo, 850 ObjectID: "obj-id", 851 ObjectType: model.RuntimeLabelableObject, 852 }, 853 } 854 855 scenariosLabelValueSecond := []interface{}{scenarioSecond} 856 labelMapWithSecondScenario := map[string]*model.Label{ 857 model.ScenariosKey: { 858 ID: "id", 859 Tenant: str.Ptr("tenant"), 860 Key: model.ScenariosKey, 861 Value: scenariosLabelValueSecond, 862 ObjectID: "obj-id", 863 ObjectType: model.RuntimeLabelableObject, 864 }, 865 } 866 867 labelsDBMock := map[string]interface{}{ 868 "label1": "val1", 869 runtime.IsNormalizedLabel: "true", 870 } 871 labels := map[string]interface{}{ 872 "label1": "val1", 873 } 874 normalizedLabels := map[string]interface{}{ 875 runtime.IsNormalizedLabel: "true", 876 } 877 protectedLabels := map[string]interface{}{ 878 "protected_defaultEventing": "true", 879 "label1": "val1", 880 } 881 modelInput := model.RuntimeUpdateInput{ 882 Name: "bar", 883 Labels: labels, 884 } 885 886 modelInputWithProtectedLabels := model.RuntimeUpdateInput{ 887 Name: "bar", 888 Labels: protectedLabels, 889 } 890 891 inputRuntimeModel := mock.MatchedBy(func(rtm *model.Runtime) bool { 892 return rtm.Name == modelInput.Name 893 }) 894 895 inputProtectedRuntimeModel := mock.MatchedBy(func(rtm *model.Runtime) bool { 896 return rtm.Name == modelInput.Name 897 }) 898 899 runtimeModel := &model.Runtime{ 900 ID: runtimeID, 901 Name: "Foo", 902 Description: &desc, 903 } 904 905 tnt := "tenant" 906 externalTnt := "external-tnt" 907 ctx := context.TODO() 908 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 909 910 testCases := []struct { 911 Name string 912 RepositoryFn func() *automock.RuntimeRepository 913 LabelRepositoryFn func() *automock.LabelRepository 914 labelServiceFn func() *automock.LabelService 915 FormationServiceFn func() *automock.FormationService 916 Input model.RuntimeUpdateInput 917 InputID string 918 ExpectedErrMessage string 919 }{ 920 { 921 Name: "Success", 922 RepositoryFn: func() *automock.RuntimeRepository { 923 repo := &automock.RuntimeRepository{} 924 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 925 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 926 return repo 927 }, 928 LabelRepositoryFn: func() *automock.LabelRepository { 929 repo := &automock.LabelRepository{} 930 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 931 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 932 return repo 933 }, 934 labelServiceFn: func() *automock.LabelService { 935 repo := &automock.LabelService{} 936 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, modelInput.Labels).Return(nil).Once() 937 return repo 938 }, 939 FormationServiceFn: func() *automock.FormationService { 940 svc := &automock.FormationService{} 941 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return([]interface{}{}, nil) 942 return svc 943 }, 944 InputID: runtimeID, 945 Input: modelInput, 946 ExpectedErrMessage: "", 947 }, 948 { 949 Name: "Success when updating with protected labels", 950 RepositoryFn: func() *automock.RuntimeRepository { 951 repo := &automock.RuntimeRepository{} 952 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 953 repo.On("Update", ctx, tnt, inputProtectedRuntimeModel).Return(nil).Once() 954 return repo 955 }, 956 LabelRepositoryFn: func() *automock.LabelRepository { 957 repo := &automock.LabelRepository{} 958 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 959 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 960 return repo 961 }, 962 labelServiceFn: func() *automock.LabelService { 963 repo := &automock.LabelService{} 964 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, labelsDBMock).Return(nil).Once() 965 return repo 966 }, 967 FormationServiceFn: func() *automock.FormationService { 968 svc := &automock.FormationService{} 969 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, protectedLabels, runtimeID).Return([]interface{}{}, nil) 970 return svc 971 }, 972 InputID: runtimeID, 973 Input: modelInputWithProtectedLabels, 974 ExpectedErrMessage: "", 975 }, 976 { 977 Name: "Success when there are scenarios to set from assignments", 978 RepositoryFn: func() *automock.RuntimeRepository { 979 repo := &automock.RuntimeRepository{} 980 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 981 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 982 return repo 983 }, 984 LabelRepositoryFn: func() *automock.LabelRepository { 985 repo := &automock.LabelRepository{} 986 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 987 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 988 return repo 989 }, 990 labelServiceFn: func() *automock.LabelService { 991 repo := &automock.LabelService{} 992 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, labelsDBMock).Return(nil).Once() 993 return repo 994 }, 995 FormationServiceFn: func() *automock.FormationService { 996 svc := &automock.FormationService{} 997 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return([]interface{}{scenario}, nil) 998 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(&model.Formation{Name: scenario}, nil).Once() 999 return svc 1000 }, 1001 InputID: runtimeID, 1002 Input: modelInput, 1003 ExpectedErrMessage: "", 1004 }, 1005 { 1006 Name: "Success when there are scenarios to unassign", 1007 RepositoryFn: func() *automock.RuntimeRepository { 1008 repo := &automock.RuntimeRepository{} 1009 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1010 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1011 return repo 1012 }, 1013 LabelRepositoryFn: func() *automock.LabelRepository { 1014 repo := &automock.LabelRepository{} 1015 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithTwoScenarios, nil) 1016 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 1017 return repo 1018 }, 1019 labelServiceFn: func() *automock.LabelService { 1020 repo := &automock.LabelService{} 1021 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, normalizedLabels).Return(nil).Once() 1022 return repo 1023 }, 1024 FormationServiceFn: func() *automock.FormationService { 1025 svc := &automock.FormationService{} 1026 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, runtimeModelWithFirstScenario.Labels, runtimeID).Return(scenariosLabelValueFirst, nil) 1027 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(&model.Formation{Name: scenarioSecond}, nil).Once() 1028 return svc 1029 }, 1030 InputID: runtimeID, 1031 Input: runtimeModelWithFirstScenario, 1032 ExpectedErrMessage: "", 1033 }, 1034 { 1035 Name: "Success when there are scenarios to assign and unassign", 1036 RepositoryFn: func() *automock.RuntimeRepository { 1037 repo := &automock.RuntimeRepository{} 1038 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1039 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1040 return repo 1041 }, 1042 LabelRepositoryFn: func() *automock.LabelRepository { 1043 repo := &automock.LabelRepository{} 1044 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithSecondScenario, nil) 1045 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 1046 return repo 1047 }, 1048 labelServiceFn: func() *automock.LabelService { 1049 repo := &automock.LabelService{} 1050 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, normalizedLabels).Return(nil).Once() 1051 return repo 1052 }, 1053 FormationServiceFn: func() *automock.FormationService { 1054 svc := &automock.FormationService{} 1055 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, runtimeModelWithFirstScenario.Labels, runtimeID).Return(scenariosLabelValueFirst, nil) 1056 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(&model.Formation{Name: scenarioSecond}, nil).Once() 1057 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(&model.Formation{Name: scenario}, nil).Once() 1058 return svc 1059 }, 1060 InputID: runtimeID, 1061 Input: runtimeModelWithFirstScenario, 1062 ExpectedErrMessage: "", 1063 }, 1064 { 1065 Name: "Success when labels are nil", 1066 RepositoryFn: func() *automock.RuntimeRepository { 1067 repo := &automock.RuntimeRepository{} 1068 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1069 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1070 return repo 1071 }, 1072 LabelRepositoryFn: func() *automock.LabelRepository { 1073 repo := &automock.LabelRepository{} 1074 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 1075 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 1076 return repo 1077 }, 1078 labelServiceFn: func() *automock.LabelService { 1079 repo := &automock.LabelService{} 1080 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, labelsWithNormalization).Return(nil).Once() 1081 return repo 1082 }, 1083 FormationServiceFn: func() *automock.FormationService { 1084 svc := &automock.FormationService{} 1085 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labelsWithNormalization, runtimeID).Return([]interface{}{}, nil) 1086 return svc 1087 }, 1088 InputID: runtimeID, 1089 Input: model.RuntimeUpdateInput{ 1090 Name: "bar", 1091 }, 1092 ExpectedErrMessage: "", 1093 }, 1094 { 1095 Name: "Returns error when runtime update failed", 1096 RepositoryFn: func() *automock.RuntimeRepository { 1097 repo := &automock.RuntimeRepository{} 1098 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1099 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(testErr).Once() 1100 return repo 1101 }, 1102 LabelRepositoryFn: unusedLabelRepository, 1103 labelServiceFn: unusedLabelService, 1104 FormationServiceFn: unusedFormationService, 1105 InputID: runtimeID, 1106 Input: modelInput, 1107 ExpectedErrMessage: testErr.Error(), 1108 }, 1109 { 1110 Name: "Returns error when assign formation fails", 1111 RepositoryFn: func() *automock.RuntimeRepository { 1112 repo := &automock.RuntimeRepository{} 1113 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1114 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1115 return repo 1116 }, 1117 LabelRepositoryFn: func() *automock.LabelRepository { 1118 repo := &automock.LabelRepository{} 1119 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 1120 return repo 1121 }, 1122 labelServiceFn: unusedLabelService, 1123 FormationServiceFn: func() *automock.FormationService { 1124 svc := &automock.FormationService{} 1125 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return([]interface{}{scenario}, nil) 1126 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(nil, testErr).Once() 1127 return svc 1128 }, 1129 InputID: runtimeID, 1130 Input: modelInput, 1131 ExpectedErrMessage: testErr.Error(), 1132 }, 1133 { 1134 Name: "Returns error when unassign formation fails", 1135 RepositoryFn: func() *automock.RuntimeRepository { 1136 repo := &automock.RuntimeRepository{} 1137 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1138 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1139 return repo 1140 }, 1141 LabelRepositoryFn: func() *automock.LabelRepository { 1142 repo := &automock.LabelRepository{} 1143 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithTwoScenarios, nil) 1144 return repo 1145 }, 1146 labelServiceFn: unusedLabelService, 1147 FormationServiceFn: func() *automock.FormationService { 1148 svc := &automock.FormationService{} 1149 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, runtimeModelWithFirstScenario.Labels, runtimeID).Return(scenariosLabelValueFirst, nil) 1150 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(nil, testErr).Once() 1151 return svc 1152 }, 1153 InputID: runtimeID, 1154 Input: runtimeModelWithFirstScenario, 1155 ExpectedErrMessage: testErr.Error(), 1156 }, 1157 { 1158 Name: "Returns error when runtime retrieval failed", 1159 RepositoryFn: func() *automock.RuntimeRepository { 1160 repo := &automock.RuntimeRepository{} 1161 repo.On("GetByID", ctx, tnt, runtimeID).Return(nil, testErr).Once() 1162 return repo 1163 }, 1164 LabelRepositoryFn: unusedLabelRepository, 1165 labelServiceFn: unusedLabelService, 1166 FormationServiceFn: unusedFormationService, 1167 InputID: runtimeID, 1168 Input: modelInput, 1169 ExpectedErrMessage: testErr.Error(), 1170 }, 1171 { 1172 Name: "Returns error when label deletion failed", 1173 RepositoryFn: func() *automock.RuntimeRepository { 1174 repo := &automock.RuntimeRepository{} 1175 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1176 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1177 return repo 1178 }, 1179 LabelRepositoryFn: func() *automock.LabelRepository { 1180 repo := &automock.LabelRepository{} 1181 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 1182 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(testErr).Once() 1183 return repo 1184 }, 1185 labelServiceFn: unusedLabelService, 1186 FormationServiceFn: func() *automock.FormationService { 1187 svc := &automock.FormationService{} 1188 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labelsDBMock, runtimeID).Return([]interface{}{}, nil) 1189 return svc 1190 }, 1191 InputID: runtimeID, 1192 Input: modelInput, 1193 ExpectedErrMessage: testErr.Error(), 1194 }, 1195 { 1196 Name: "Returns error if merge of scenarios and assignments failed", 1197 RepositoryFn: func() *automock.RuntimeRepository { 1198 repo := &automock.RuntimeRepository{} 1199 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1200 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1201 return repo 1202 }, 1203 LabelRepositoryFn: unusedLabelRepository, 1204 labelServiceFn: unusedLabelService, 1205 FormationServiceFn: func() *automock.FormationService { 1206 svc := &automock.FormationService{} 1207 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return(nil, testErr) 1208 return svc 1209 }, 1210 InputID: runtimeID, 1211 Input: modelInput, 1212 ExpectedErrMessage: testErr.Error(), 1213 }, 1214 { 1215 Name: "Returns error if listing current runtime scenarios failed", 1216 RepositoryFn: func() *automock.RuntimeRepository { 1217 repo := &automock.RuntimeRepository{} 1218 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1219 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1220 return repo 1221 }, 1222 LabelRepositoryFn: func() *automock.LabelRepository { 1223 repo := &automock.LabelRepository{} 1224 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(nil, testErr) 1225 return repo 1226 }, 1227 labelServiceFn: unusedLabelService, 1228 FormationServiceFn: func() *automock.FormationService { 1229 svc := &automock.FormationService{} 1230 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return([]interface{}{}, nil) 1231 return svc 1232 }, 1233 InputID: runtimeID, 1234 Input: modelInput, 1235 ExpectedErrMessage: testErr.Error(), 1236 }, 1237 { 1238 Name: "Returns error when upserting labels failed", 1239 RepositoryFn: func() *automock.RuntimeRepository { 1240 repo := &automock.RuntimeRepository{} 1241 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1242 repo.On("Update", ctx, tnt, inputRuntimeModel).Return(nil).Once() 1243 return repo 1244 }, 1245 LabelRepositoryFn: func() *automock.LabelRepository { 1246 repo := &automock.LabelRepository{} 1247 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(map[string]*model.Label{}, nil) 1248 repo.On("DeleteByKeyNegationPattern", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, mock.AnythingOfType("string")).Return(nil).Once() 1249 return repo 1250 }, 1251 labelServiceFn: func() *automock.LabelService { 1252 repo := &automock.LabelService{} 1253 repo.On("UpsertMultipleLabels", ctx, tnt, model.RuntimeLabelableObject, runtimeModel.ID, modelInput.Labels).Return(testErr).Once() 1254 return repo 1255 }, 1256 FormationServiceFn: func() *automock.FormationService { 1257 svc := &automock.FormationService{} 1258 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, labels, runtimeID).Return([]interface{}{}, nil) 1259 return svc 1260 }, 1261 InputID: runtimeID, 1262 Input: modelInput, 1263 ExpectedErrMessage: testErr.Error(), 1264 }, 1265 } 1266 1267 for _, testCase := range testCases { 1268 t.Run(testCase.Name, func(t *testing.T) { 1269 repo := testCase.RepositoryFn() 1270 labelRepo := testCase.LabelRepositoryFn() 1271 labelSvc := testCase.labelServiceFn() 1272 engineSvc := testCase.FormationServiceFn() 1273 svc := runtime.NewService(repo, labelRepo, labelSvc, nil, engineSvc, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 1274 1275 // WHEN 1276 err := svc.Update(ctx, testCase.InputID, testCase.Input) 1277 1278 // then 1279 if testCase.ExpectedErrMessage == "" { 1280 require.NoError(t, err) 1281 } else { 1282 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 1283 } 1284 1285 mock.AssertExpectationsForObjects(t, repo, labelRepo, labelSvc, engineSvc) 1286 }) 1287 } 1288 1289 t.Run("Returns error on loading tenant", func(t *testing.T) { 1290 // GIVEN 1291 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1292 // WHEN 1293 err := svc.Update(context.TODO(), "id", model.RuntimeUpdateInput{}) 1294 // then 1295 require.Error(t, err) 1296 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 1297 }) 1298 } 1299 1300 func TestService_Delete(t *testing.T) { 1301 // GIVEN 1302 testErr := errors.New("Test error") 1303 1304 id := "foo" 1305 rtmCtxID := "rtmCtx" 1306 1307 desc := "Lorem ipsum" 1308 1309 runtimeModel := &model.Runtime{ 1310 ID: id, 1311 Name: "Foo", 1312 Description: &desc, 1313 } 1314 1315 tnt := "tenant" 1316 externalTnt := "external-tnt" 1317 ctx := context.TODO() 1318 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 1319 1320 runtimeContext := &model.RuntimeContext{ 1321 ID: rtmCtxID, 1322 RuntimeID: id, 1323 Key: "test", 1324 Value: "test", 1325 } 1326 runtimeContexts := []*model.RuntimeContext{runtimeContext} 1327 1328 labels := map[string]*model.Label{ 1329 "testKey": { 1330 Key: "testKey", 1331 Value: "testVal", 1332 }, 1333 model.ScenariosKey: { 1334 Key: model.ScenariosKey, 1335 Value: []interface{}{"scenario1", "scenario2"}, 1336 }, 1337 } 1338 1339 labelsWithoutScenarios := map[string]*model.Label{ 1340 "testKey": { 1341 Key: "testKey", 1342 Value: "testVal", 1343 }, 1344 } 1345 1346 testCases := []struct { 1347 Name string 1348 RepositoryFn func() *automock.RuntimeRepository 1349 RuntimeContextSvcFn func() *automock.RuntimeContextService 1350 LabelRepoFn func() *automock.LabelRepository 1351 FormationServiceFn func() *automock.FormationService 1352 InputID string 1353 ExpectedErrMessage string 1354 }{ 1355 { 1356 Name: "Success for runtime with formations", 1357 RepositoryFn: func() *automock.RuntimeRepository { 1358 repo := &automock.RuntimeRepository{} 1359 repo.On("Delete", ctx, tnt, id).Return(nil).Once() 1360 return repo 1361 }, 1362 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1363 runtimeContextSvc := &automock.RuntimeContextService{} 1364 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1365 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1366 return runtimeContextSvc 1367 }, 1368 LabelRepoFn: func() *automock.LabelRepository { 1369 repo := &automock.LabelRepository{} 1370 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(labels, nil) 1371 return repo 1372 }, 1373 FormationServiceFn: func() *automock.FormationService { 1374 engine := &automock.FormationService{} 1375 engine.On("UnassignFormation", ctx, tnt, id, graphql.FormationObjectTypeRuntime, model.Formation{Name: "scenario1"}).Return(&model.Formation{Name: "scenario1"}, nil) 1376 engine.On("UnassignFormation", ctx, tnt, id, graphql.FormationObjectTypeRuntime, model.Formation{Name: "scenario2"}).Return(&model.Formation{Name: "scenario2"}, nil) 1377 return engine 1378 }, 1379 InputID: id, 1380 ExpectedErrMessage: "", 1381 }, 1382 { 1383 Name: "Success for runtime without formations", 1384 RepositoryFn: func() *automock.RuntimeRepository { 1385 repo := &automock.RuntimeRepository{} 1386 repo.On("Delete", ctx, tnt, id).Return(nil).Once() 1387 return repo 1388 }, 1389 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1390 runtimeContextSvc := &automock.RuntimeContextService{} 1391 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1392 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1393 return runtimeContextSvc 1394 }, 1395 LabelRepoFn: func() *automock.LabelRepository { 1396 repo := &automock.LabelRepository{} 1397 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(labelsWithoutScenarios, nil) 1398 return repo 1399 }, 1400 FormationServiceFn: unusedFormationService, 1401 InputID: id, 1402 ExpectedErrMessage: "", 1403 }, 1404 { 1405 Name: "Returns error while listing runtime contexts", 1406 RepositoryFn: func() *automock.RuntimeRepository { 1407 return &automock.RuntimeRepository{} 1408 }, 1409 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1410 runtimeContextSvc := &automock.RuntimeContextService{} 1411 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(nil, testErr).Once() 1412 return runtimeContextSvc 1413 }, 1414 LabelRepoFn: unusedLabelRepository, 1415 FormationServiceFn: unusedFormationService, 1416 InputID: id, 1417 ExpectedErrMessage: "while listing runtimeContexts for runtime", 1418 }, 1419 { 1420 Name: "Returns error while deleting runtime context", 1421 RepositoryFn: func() *automock.RuntimeRepository { 1422 return &automock.RuntimeRepository{} 1423 }, 1424 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1425 runtimeContextSvc := &automock.RuntimeContextService{} 1426 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1427 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(testErr).Once() 1428 return runtimeContextSvc 1429 }, 1430 LabelRepoFn: unusedLabelRepository, 1431 FormationServiceFn: unusedFormationService, 1432 InputID: id, 1433 ExpectedErrMessage: "while deleting runtimeContext", 1434 }, 1435 { 1436 Name: "Returns error when runtime deletion failed", 1437 RepositoryFn: func() *automock.RuntimeRepository { 1438 repo := &automock.RuntimeRepository{} 1439 repo.On("Delete", ctx, tnt, id).Return(nil).Once() 1440 return repo 1441 }, 1442 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1443 runtimeContextSvc := &automock.RuntimeContextService{} 1444 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1445 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1446 return runtimeContextSvc 1447 }, 1448 LabelRepoFn: func() *automock.LabelRepository { 1449 repo := &automock.LabelRepository{} 1450 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(labelsWithoutScenarios, nil) 1451 return repo 1452 }, 1453 FormationServiceFn: unusedFormationService, 1454 InputID: id, 1455 ExpectedErrMessage: "", 1456 }, 1457 { 1458 Name: "Returns error when unassign formation fails", 1459 RepositoryFn: unusedRuntimeRepository, 1460 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1461 runtimeContextSvc := &automock.RuntimeContextService{} 1462 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1463 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1464 return runtimeContextSvc 1465 }, 1466 LabelRepoFn: func() *automock.LabelRepository { 1467 repo := &automock.LabelRepository{} 1468 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(labels, nil) 1469 return repo 1470 }, 1471 FormationServiceFn: func() *automock.FormationService { 1472 engine := &automock.FormationService{} 1473 engine.On("UnassignFormation", ctx, tnt, id, graphql.FormationObjectTypeRuntime, model.Formation{Name: "scenario1"}).Return(nil, testErr) 1474 return engine 1475 }, 1476 InputID: id, 1477 ExpectedErrMessage: testErr.Error(), 1478 }, 1479 { 1480 Name: "Returns error when listing current runtime formation fails", 1481 RepositoryFn: unusedRuntimeRepository, 1482 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1483 runtimeContextSvc := &automock.RuntimeContextService{} 1484 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1485 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1486 return runtimeContextSvc 1487 }, 1488 LabelRepoFn: func() *automock.LabelRepository { 1489 repo := &automock.LabelRepository{} 1490 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(nil, testErr) 1491 return repo 1492 }, 1493 FormationServiceFn: unusedFormationService, 1494 InputID: id, 1495 ExpectedErrMessage: testErr.Error(), 1496 }, 1497 { 1498 Name: "Returns error when runtime deletion failed", 1499 RepositoryFn: func() *automock.RuntimeRepository { 1500 repo := &automock.RuntimeRepository{} 1501 repo.On("Delete", ctx, tnt, runtimeModel.ID).Return(testErr).Once() 1502 return repo 1503 }, 1504 RuntimeContextSvcFn: func() *automock.RuntimeContextService { 1505 runtimeContextSvc := &automock.RuntimeContextService{} 1506 runtimeContextSvc.On("ListAllForRuntime", ctx, id).Return(runtimeContexts, nil).Once() 1507 runtimeContextSvc.On("Delete", ctx, rtmCtxID).Return(nil).Once() 1508 return runtimeContextSvc 1509 }, 1510 LabelRepoFn: func() *automock.LabelRepository { 1511 repo := &automock.LabelRepository{} 1512 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, id).Return(labelsWithoutScenarios, nil) 1513 return repo 1514 }, 1515 FormationServiceFn: unusedFormationService, 1516 InputID: id, 1517 ExpectedErrMessage: testErr.Error(), 1518 }, 1519 } 1520 1521 for _, testCase := range testCases { 1522 t.Run(testCase.Name, func(t *testing.T) { 1523 repo := testCase.RepositoryFn() 1524 labelRepo := testCase.LabelRepoFn() 1525 engine := testCase.FormationServiceFn() 1526 rtmCtxSvc := testCase.RuntimeContextSvcFn() 1527 svc := runtime.NewService(repo, labelRepo, nil, nil, engine, nil, nil, rtmCtxSvc, "", "", "", "", "") 1528 1529 // WHEN 1530 err := svc.Delete(ctx, testCase.InputID) 1531 1532 // then 1533 if testCase.ExpectedErrMessage == "" { 1534 require.NoError(t, err) 1535 } else { 1536 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 1537 } 1538 1539 mock.AssertExpectationsForObjects(t, repo, labelRepo, engine, rtmCtxSvc) 1540 }) 1541 } 1542 1543 t.Run("Returns error on loading tenant", func(t *testing.T) { 1544 // GIVEN 1545 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1546 // WHEN 1547 err := svc.Delete(context.TODO(), "id") 1548 // then 1549 require.Error(t, err) 1550 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 1551 }) 1552 } 1553 1554 func TestService_Get(t *testing.T) { 1555 // GIVEN 1556 testErr := errors.New("Test error") 1557 1558 id := runtimeID 1559 desc := "Lorem ipsum" 1560 tnt := "tenant" 1561 externalTnt := "external-tnt" 1562 1563 runtimeModel := &model.Runtime{ 1564 ID: runtimeID, 1565 Name: "Foo", 1566 Description: &desc, 1567 } 1568 1569 ctx := context.TODO() 1570 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 1571 1572 testCases := []struct { 1573 Name string 1574 RepositoryFn func() *automock.RuntimeRepository 1575 InputID string 1576 ExpectedRuntime *model.Runtime 1577 ExpectedErrMessage string 1578 }{ 1579 { 1580 Name: "Success", 1581 RepositoryFn: func() *automock.RuntimeRepository { 1582 repo := &automock.RuntimeRepository{} 1583 repo.On("GetByID", ctx, tnt, runtimeID).Return(runtimeModel, nil).Once() 1584 return repo 1585 }, 1586 InputID: id, 1587 ExpectedRuntime: runtimeModel, 1588 ExpectedErrMessage: "", 1589 }, 1590 { 1591 Name: "Returns error when runtime retrieval failed", 1592 RepositoryFn: func() *automock.RuntimeRepository { 1593 repo := &automock.RuntimeRepository{} 1594 repo.On("GetByID", ctx, tnt, runtimeID).Return(nil, testErr).Once() 1595 return repo 1596 }, 1597 InputID: id, 1598 ExpectedRuntime: runtimeModel, 1599 ExpectedErrMessage: testErr.Error(), 1600 }, 1601 } 1602 1603 for _, testCase := range testCases { 1604 t.Run(testCase.Name, func(t *testing.T) { 1605 repo := testCase.RepositoryFn() 1606 1607 svc := runtime.NewService(repo, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1608 1609 // WHEN 1610 rtm, err := svc.Get(ctx, testCase.InputID) 1611 1612 // then 1613 if testCase.ExpectedErrMessage == "" { 1614 require.NoError(t, err) 1615 assert.Equal(t, testCase.ExpectedRuntime, rtm) 1616 } else { 1617 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 1618 } 1619 1620 repo.AssertExpectations(t) 1621 }) 1622 } 1623 1624 t.Run("Returns error on loading tenant", func(t *testing.T) { 1625 // GIVEN 1626 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1627 // WHEN 1628 _, err := svc.Get(context.TODO(), "id") 1629 // then 1630 require.Error(t, err) 1631 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 1632 }) 1633 } 1634 1635 func TestService_GetByTokenIssuer(t *testing.T) { 1636 // GIVEN 1637 testErr := errors.New("Test error") 1638 1639 id := "foo" 1640 desc := "Lorem ipsum" 1641 tokenIssuer := "https://dex.domain.local" 1642 filter := []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery("runtime_consoleUrl", `"https://console.domain.local"`)} 1643 1644 runtimeModel := &model.Runtime{ 1645 ID: "foo", 1646 Name: "Foo", 1647 Description: &desc, 1648 } 1649 1650 ctx := context.TODO() 1651 1652 testCases := []struct { 1653 Name string 1654 RepositoryFn func() *automock.RuntimeRepository 1655 Input model.RuntimeRegisterInput 1656 InputID string 1657 ExpectedRuntime *model.Runtime 1658 ExpectedErrMessage string 1659 }{ 1660 { 1661 Name: "Success", 1662 RepositoryFn: func() *automock.RuntimeRepository { 1663 repo := &automock.RuntimeRepository{} 1664 repo.On("GetByFiltersGlobal", ctx, filter).Return(runtimeModel, nil).Once() 1665 return repo 1666 }, 1667 InputID: id, 1668 ExpectedRuntime: runtimeModel, 1669 ExpectedErrMessage: "", 1670 }, 1671 { 1672 Name: "Returns error when runtime retrieval failed", 1673 RepositoryFn: func() *automock.RuntimeRepository { 1674 repo := &automock.RuntimeRepository{} 1675 repo.On("GetByFiltersGlobal", ctx, filter).Return(nil, testErr).Once() 1676 return repo 1677 }, 1678 InputID: id, 1679 ExpectedRuntime: runtimeModel, 1680 ExpectedErrMessage: testErr.Error(), 1681 }, 1682 } 1683 1684 for _, testCase := range testCases { 1685 t.Run(testCase.Name, func(t *testing.T) { 1686 repo := testCase.RepositoryFn() 1687 1688 svc := runtime.NewService(repo, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1689 1690 // WHEN 1691 rtm, err := svc.GetByTokenIssuer(ctx, tokenIssuer) 1692 1693 // then 1694 if testCase.ExpectedErrMessage == "" { 1695 require.NoError(t, err) 1696 assert.Equal(t, testCase.ExpectedRuntime, rtm) 1697 } else { 1698 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 1699 } 1700 1701 repo.AssertExpectations(t) 1702 }) 1703 } 1704 } 1705 1706 func TestService_Exist(t *testing.T) { 1707 tnt := "tenant" 1708 externalTnt := "external-tnt" 1709 ctx := context.TODO() 1710 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 1711 testError := errors.New("Test error") 1712 1713 rtmID := "id" 1714 1715 testCases := []struct { 1716 Name string 1717 RepositoryFn func() *automock.RuntimeRepository 1718 InputRuntimeID string 1719 ExpectedValue bool 1720 ExpectedError error 1721 }{ 1722 { 1723 Name: "Runtime exits", 1724 RepositoryFn: func() *automock.RuntimeRepository { 1725 repo := &automock.RuntimeRepository{} 1726 repo.On("Exists", ctx, tnt, rtmID).Return(true, nil) 1727 return repo 1728 }, 1729 InputRuntimeID: rtmID, 1730 ExpectedValue: true, 1731 ExpectedError: nil, 1732 }, 1733 { 1734 Name: "Runtime not exits", 1735 RepositoryFn: func() *automock.RuntimeRepository { 1736 repo := &automock.RuntimeRepository{} 1737 repo.On("Exists", ctx, tnt, rtmID).Return(false, nil) 1738 return repo 1739 }, 1740 InputRuntimeID: rtmID, 1741 ExpectedValue: false, 1742 ExpectedError: nil, 1743 }, 1744 { 1745 Name: "Returns error", 1746 RepositoryFn: func() *automock.RuntimeRepository { 1747 repo := &automock.RuntimeRepository{} 1748 repo.On("Exists", ctx, tnt, rtmID).Return(false, testError) 1749 return repo 1750 }, 1751 InputRuntimeID: rtmID, 1752 ExpectedValue: false, 1753 ExpectedError: testError, 1754 }, 1755 } 1756 1757 for _, testCase := range testCases { 1758 t.Run(testCase.Name, func(t *testing.T) { 1759 // GIVEN 1760 rtmRepo := testCase.RepositoryFn() 1761 svc := runtime.NewService(rtmRepo, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1762 1763 // WHEN 1764 value, err := svc.Exist(ctx, testCase.InputRuntimeID) 1765 1766 // THEN 1767 if testCase.ExpectedError != nil { 1768 require.Error(t, err) 1769 assert.Contains(t, err.Error(), testCase.ExpectedError.Error()) 1770 } else { 1771 require.Nil(t, err) 1772 } 1773 1774 assert.Equal(t, testCase.ExpectedValue, value) 1775 rtmRepo.AssertExpectations(t) 1776 }) 1777 } 1778 t.Run("Returns error on loading tenant", func(t *testing.T) { 1779 // GIVEN 1780 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1781 // WHEN 1782 _, err := svc.Exist(context.TODO(), "id") 1783 // then 1784 require.Error(t, err) 1785 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 1786 }) 1787 } 1788 1789 func TestService_List(t *testing.T) { 1790 // GIVEN 1791 testErr := errors.New("Test error") 1792 1793 modelRuntimes := []*model.Runtime{ 1794 fixModelRuntime(t, "foo", "tenant-foo", "Foo", "Lorem Ipsum", "test.ns.foo"), 1795 fixModelRuntime(t, "bar", "tenant-bar", "Bar", "Lorem Ipsum", "test.ns.bar"), 1796 } 1797 runtimePage := &model.RuntimePage{ 1798 Data: modelRuntimes, 1799 TotalCount: len(modelRuntimes), 1800 PageInfo: &pagination.Page{ 1801 HasNextPage: false, 1802 EndCursor: "end", 1803 StartCursor: "start", 1804 }, 1805 } 1806 1807 first := 2 1808 after := "test" 1809 filter := []*labelfilter.LabelFilter{{Key: ""}} 1810 1811 tnt := "tenant" 1812 externalTnt := "external-tnt" 1813 1814 ctx := context.TODO() 1815 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 1816 1817 testCases := []struct { 1818 Name string 1819 RepositoryFn func() *automock.RuntimeRepository 1820 InputLabelFilters []*labelfilter.LabelFilter 1821 InputPageSize int 1822 InputCursor string 1823 ExpectedResult *model.RuntimePage 1824 ExpectedErrMessage string 1825 }{ 1826 { 1827 Name: "Success", 1828 RepositoryFn: func() *automock.RuntimeRepository { 1829 repo := &automock.RuntimeRepository{} 1830 repo.On("List", ctx, tnt, filter, first, after).Return(runtimePage, nil).Once() 1831 return repo 1832 }, 1833 InputLabelFilters: filter, 1834 InputPageSize: first, 1835 InputCursor: after, 1836 ExpectedResult: runtimePage, 1837 ExpectedErrMessage: "", 1838 }, 1839 { 1840 Name: "Returns error when runtime listing failed", 1841 RepositoryFn: func() *automock.RuntimeRepository { 1842 repo := &automock.RuntimeRepository{} 1843 repo.On("List", ctx, tnt, filter, first, after).Return(nil, testErr).Once() 1844 return repo 1845 }, 1846 InputLabelFilters: filter, 1847 InputPageSize: first, 1848 InputCursor: after, 1849 ExpectedResult: nil, 1850 ExpectedErrMessage: testErr.Error(), 1851 }, 1852 { 1853 Name: "Returns error when pageSize is less than 1", 1854 RepositoryFn: unusedRuntimeRepository, 1855 InputLabelFilters: filter, 1856 InputPageSize: 0, 1857 InputCursor: after, 1858 ExpectedResult: nil, 1859 ExpectedErrMessage: "page size must be between 1 and 200", 1860 }, 1861 { 1862 Name: "Returns error when pageSize is bigger than 200", 1863 RepositoryFn: unusedRuntimeRepository, 1864 InputLabelFilters: filter, 1865 InputPageSize: 201, 1866 InputCursor: after, 1867 ExpectedResult: nil, 1868 ExpectedErrMessage: "page size must be between 1 and 200", 1869 }, 1870 } 1871 1872 for _, testCase := range testCases { 1873 t.Run(testCase.Name, func(t *testing.T) { 1874 repo := testCase.RepositoryFn() 1875 1876 svc := runtime.NewService(repo, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1877 1878 // WHEN 1879 rtm, err := svc.List(ctx, testCase.InputLabelFilters, testCase.InputPageSize, testCase.InputCursor) 1880 1881 // then 1882 if testCase.ExpectedErrMessage == "" { 1883 require.NoError(t, err) 1884 assert.Equal(t, testCase.ExpectedResult, rtm) 1885 } else { 1886 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 1887 } 1888 1889 repo.AssertExpectations(t) 1890 }) 1891 } 1892 1893 t.Run("Returns error on loading tenant", func(t *testing.T) { 1894 // GIVEN 1895 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 1896 // WHEN 1897 _, err := svc.List(context.TODO(), nil, 1, "") 1898 // then 1899 require.Error(t, err) 1900 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 1901 }) 1902 } 1903 1904 func TestService_GetLabel(t *testing.T) { 1905 // GIVEN 1906 tnt := "tenant" 1907 externalTnt := "external-tnt" 1908 1909 ctx := context.TODO() 1910 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 1911 1912 testErr := errors.New("Test error") 1913 1914 runtimeID := "foo" 1915 labelKey := "key" 1916 labelValue := []string{"value1"} 1917 1918 label := &model.LabelInput{ 1919 Key: labelKey, 1920 Value: labelValue, 1921 ObjectID: runtimeID, 1922 ObjectType: model.RuntimeLabelableObject, 1923 } 1924 1925 modelLabel := &model.Label{ 1926 ID: "5d23d9d9-3d04-4fa9-95e6-d22e1ae62c11", 1927 Key: labelKey, 1928 Value: labelValue, 1929 ObjectID: runtimeID, 1930 ObjectType: model.RuntimeLabelableObject, 1931 } 1932 1933 testCases := []struct { 1934 Name string 1935 RepositoryFn func() *automock.RuntimeRepository 1936 LabelRepositoryFn func() *automock.LabelRepository 1937 InputRuntimeID string 1938 InputLabel *model.LabelInput 1939 ExpectedLabel *model.Label 1940 ExpectedErrMessage string 1941 }{ 1942 { 1943 Name: "Success", 1944 RepositoryFn: func() *automock.RuntimeRepository { 1945 repo := &automock.RuntimeRepository{} 1946 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 1947 return repo 1948 }, 1949 LabelRepositoryFn: func() *automock.LabelRepository { 1950 repo := &automock.LabelRepository{} 1951 repo.On("GetByKey", ctx, tnt, model.RuntimeLabelableObject, runtimeID, labelKey).Return(modelLabel, nil).Once() 1952 return repo 1953 }, 1954 InputRuntimeID: runtimeID, 1955 InputLabel: label, 1956 ExpectedLabel: modelLabel, 1957 ExpectedErrMessage: "", 1958 }, 1959 { 1960 Name: "Returns error when label receiving failed", 1961 RepositoryFn: func() *automock.RuntimeRepository { 1962 repo := &automock.RuntimeRepository{} 1963 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 1964 1965 return repo 1966 }, 1967 LabelRepositoryFn: func() *automock.LabelRepository { 1968 repo := &automock.LabelRepository{} 1969 repo.On("GetByKey", ctx, tnt, model.RuntimeLabelableObject, runtimeID, labelKey).Return(nil, testErr).Once() 1970 return repo 1971 }, 1972 InputRuntimeID: runtimeID, 1973 InputLabel: label, 1974 ExpectedLabel: nil, 1975 ExpectedErrMessage: testErr.Error(), 1976 }, 1977 { 1978 Name: "Returns error when exists function for runtime failed", 1979 RepositoryFn: func() *automock.RuntimeRepository { 1980 repo := &automock.RuntimeRepository{} 1981 repo.On("Exists", ctx, tnt, runtimeID).Return(false, testErr).Once() 1982 1983 return repo 1984 }, 1985 LabelRepositoryFn: unusedLabelRepository, 1986 InputRuntimeID: runtimeID, 1987 InputLabel: label, 1988 ExpectedErrMessage: testErr.Error(), 1989 }, 1990 { 1991 Name: "Returns error when runtime doesn't exist", 1992 RepositoryFn: func() *automock.RuntimeRepository { 1993 repo := &automock.RuntimeRepository{} 1994 repo.On("Exists", ctx, tnt, runtimeID).Return(false, nil).Once() 1995 1996 return repo 1997 }, 1998 LabelRepositoryFn: unusedLabelRepository, 1999 InputRuntimeID: runtimeID, 2000 InputLabel: label, 2001 ExpectedErrMessage: fmt.Sprintf("Runtime with ID %s doesn't exist", runtimeID), 2002 }, 2003 } 2004 2005 for _, testCase := range testCases { 2006 t.Run(testCase.Name, func(t *testing.T) { 2007 repo := testCase.RepositoryFn() 2008 labelRepo := testCase.LabelRepositoryFn() 2009 svc := runtime.NewService(repo, labelRepo, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 2010 2011 // WHEN 2012 l, err := svc.GetLabel(ctx, testCase.InputRuntimeID, testCase.InputLabel.Key) 2013 2014 // then 2015 if testCase.ExpectedErrMessage == "" { 2016 require.NoError(t, err) 2017 assert.Equal(t, l, testCase.ExpectedLabel) 2018 } else { 2019 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2020 } 2021 2022 repo.AssertExpectations(t) 2023 labelRepo.AssertExpectations(t) 2024 }) 2025 } 2026 2027 t.Run("Returns error on loading tenant", func(t *testing.T) { 2028 // GIVEN 2029 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 2030 // WHEN 2031 _, err := svc.GetLabel(context.TODO(), "id", "key") 2032 // then 2033 require.Error(t, err) 2034 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 2035 }) 2036 } 2037 2038 func TestService_ListLabels(t *testing.T) { 2039 // GIVEN 2040 tnt := "tenant" 2041 externalTnt := "external-tnt" 2042 2043 ctx := context.TODO() 2044 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 2045 2046 testErr := errors.New("Test error") 2047 2048 runtimeID := "foo" 2049 labelKey := "key" 2050 labelValue := []string{"value1"} 2051 2052 label := &model.LabelInput{ 2053 Key: labelKey, 2054 Value: labelValue, 2055 ObjectID: runtimeID, 2056 ObjectType: model.RuntimeLabelableObject, 2057 } 2058 2059 modelLabel := &model.Label{ 2060 ID: "5d23d9d9-3d04-4fa9-95e6-d22e1ae62c11", 2061 Key: labelKey, 2062 Value: labelValue, 2063 ObjectID: runtimeID, 2064 ObjectType: model.RuntimeLabelableObject, 2065 } 2066 2067 protectedModelLabel := &model.Label{ 2068 ID: "5d23d9d9-3d04-4fa9-95e6-d22e1ae62c12", 2069 Key: "protected_defaultEventing", 2070 Value: labelValue, 2071 ObjectID: runtimeID, 2072 ObjectType: model.RuntimeLabelableObject, 2073 } 2074 2075 labels := map[string]*model.Label{"protected_defaultEventing": protectedModelLabel, "first": modelLabel, "second": modelLabel} 2076 expectedLabelWithoutProtected := map[string]*model.Label{"first": modelLabel, "second": modelLabel} 2077 testCases := []struct { 2078 Name string 2079 RepositoryFn func() *automock.RuntimeRepository 2080 LabelRepositoryFn func() *automock.LabelRepository 2081 InputRuntimeID string 2082 InputLabel *model.LabelInput 2083 ExpectedOutput map[string]*model.Label 2084 ExpectedErrMessage string 2085 }{ 2086 { 2087 Name: "Success", 2088 RepositoryFn: func() *automock.RuntimeRepository { 2089 repo := &automock.RuntimeRepository{} 2090 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2091 return repo 2092 }, 2093 LabelRepositoryFn: func() *automock.LabelRepository { 2094 repo := &automock.LabelRepository{} 2095 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labels, nil).Once() 2096 return repo 2097 }, 2098 InputRuntimeID: runtimeID, 2099 InputLabel: label, 2100 ExpectedOutput: expectedLabelWithoutProtected, 2101 ExpectedErrMessage: "", 2102 }, 2103 { 2104 Name: "Returns error when labels receiving failed", 2105 RepositoryFn: func() *automock.RuntimeRepository { 2106 repo := &automock.RuntimeRepository{} 2107 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2108 2109 return repo 2110 }, 2111 LabelRepositoryFn: func() *automock.LabelRepository { 2112 repo := &automock.LabelRepository{} 2113 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(nil, testErr).Once() 2114 return repo 2115 }, 2116 InputRuntimeID: runtimeID, 2117 InputLabel: label, 2118 ExpectedOutput: nil, 2119 ExpectedErrMessage: testErr.Error(), 2120 }, 2121 { 2122 Name: "Returns error when runtime exists function failed", 2123 RepositoryFn: func() *automock.RuntimeRepository { 2124 repo := &automock.RuntimeRepository{} 2125 repo.On("Exists", ctx, tnt, runtimeID).Return(false, testErr).Once() 2126 2127 return repo 2128 }, 2129 LabelRepositoryFn: unusedLabelRepository, 2130 InputRuntimeID: runtimeID, 2131 InputLabel: label, 2132 ExpectedErrMessage: testErr.Error(), 2133 }, 2134 { 2135 Name: "Returns error when runtime does not exists", 2136 RepositoryFn: func() *automock.RuntimeRepository { 2137 repo := &automock.RuntimeRepository{} 2138 repo.On("Exists", ctx, tnt, runtimeID).Return(false, nil).Once() 2139 2140 return repo 2141 }, 2142 LabelRepositoryFn: unusedLabelRepository, 2143 InputRuntimeID: runtimeID, 2144 InputLabel: label, 2145 ExpectedErrMessage: fmt.Sprintf("Runtime with ID %s doesn't exist", runtimeID), 2146 }, 2147 } 2148 2149 for _, testCase := range testCases { 2150 t.Run(testCase.Name, func(t *testing.T) { 2151 repo := testCase.RepositoryFn() 2152 labelRepo := testCase.LabelRepositoryFn() 2153 svc := runtime.NewService(repo, labelRepo, nil, nil, nil, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2154 2155 // WHEN 2156 l, err := svc.ListLabels(ctx, testCase.InputRuntimeID) 2157 2158 // then 2159 if testCase.ExpectedErrMessage == "" { 2160 require.NoError(t, err) 2161 assert.Equal(t, testCase.ExpectedOutput, l) 2162 } else { 2163 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2164 } 2165 2166 repo.AssertExpectations(t) 2167 labelRepo.AssertExpectations(t) 2168 }) 2169 } 2170 2171 t.Run("Returns error on loading tenant", func(t *testing.T) { 2172 // GIVEN 2173 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, "", "", "", "", "") 2174 // WHEN 2175 _, err := svc.ListLabels(context.TODO(), "id") 2176 // then 2177 require.Error(t, err) 2178 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 2179 }) 2180 } 2181 2182 func TestService_SetLabel(t *testing.T) { 2183 // GIVEN 2184 tnt := "tenant" 2185 externalTnt := "external-tnt" 2186 2187 ctx := context.TODO() 2188 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 2189 2190 testErr := errors.New("Test error") 2191 2192 runtimeID := "foo" 2193 2194 labelKey := "key" 2195 protectedLabelKey := "protected_defaultEventing" 2196 2197 modelLabelInput := model.LabelInput{ 2198 Key: labelKey, 2199 Value: []string{"value1"}, 2200 ObjectID: runtimeID, 2201 ObjectType: model.RuntimeLabelableObject, 2202 } 2203 2204 modelProtectedLabelInput := model.LabelInput{ 2205 Key: protectedLabelKey, 2206 Value: []string{"value1"}, 2207 ObjectID: runtimeID, 2208 ObjectType: model.RuntimeLabelableObject, 2209 } 2210 2211 labelMapWithoutScenario := map[string]*model.Label{ 2212 "test": { 2213 ID: "id", 2214 Tenant: str.Ptr("tenant"), 2215 Key: "test", 2216 Value: "test", 2217 ObjectID: "obj-id", 2218 ObjectType: model.RuntimeLabelableObject, 2219 }, 2220 } 2221 2222 scenario := "SCENARIO" 2223 scenariosLabelValueFirst := []interface{}{scenario} 2224 modelScenariosLabelInputFirst := model.LabelInput{ 2225 Key: model.ScenariosKey, 2226 Value: scenariosLabelValueFirst, 2227 ObjectID: runtimeID, 2228 ObjectType: model.RuntimeLabelableObject, 2229 } 2230 2231 labelMapWithFirstScenario := map[string]*model.Label{ 2232 model.ScenariosKey: { 2233 ID: "id", 2234 Tenant: str.Ptr("tenant"), 2235 Key: model.ScenariosKey, 2236 Value: scenariosLabelValueFirst, 2237 ObjectID: "obj-id", 2238 ObjectType: model.RuntimeLabelableObject, 2239 }, 2240 } 2241 2242 scenarioSecond := "SCENARIO2" 2243 scenariosLabelValueTwo := []interface{}{scenario, scenarioSecond} 2244 labelMapWithTwoScenarios := map[string]*model.Label{ 2245 model.ScenariosKey: { 2246 ID: "id", 2247 Tenant: str.Ptr("tenant"), 2248 Key: model.ScenariosKey, 2249 Value: scenariosLabelValueTwo, 2250 ObjectID: "obj-id", 2251 ObjectType: model.RuntimeLabelableObject, 2252 }, 2253 } 2254 2255 scenariosLabelValueSecond := []interface{}{scenarioSecond} 2256 labelMapWithSecondScenario := map[string]*model.Label{ 2257 model.ScenariosKey: { 2258 ID: "id", 2259 Tenant: str.Ptr("tenant"), 2260 Key: model.ScenariosKey, 2261 Value: scenariosLabelValueSecond, 2262 ObjectID: "obj-id", 2263 ObjectType: model.RuntimeLabelableObject, 2264 }, 2265 } 2266 2267 testCases := []struct { 2268 Name string 2269 RepositoryFn func() *automock.RuntimeRepository 2270 labelServiceFn func() *automock.LabelService 2271 LabelRepositoryFn func() *automock.LabelRepository 2272 FormationServiceFn func() *automock.FormationService 2273 InputRuntimeID string 2274 InputLabel *model.LabelInput 2275 ExpectedErrMessage string 2276 }{ 2277 { 2278 Name: "Success", 2279 RepositoryFn: func() *automock.RuntimeRepository { 2280 repo := &automock.RuntimeRepository{} 2281 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2282 return repo 2283 }, 2284 labelServiceFn: func() *automock.LabelService { 2285 svc := &automock.LabelService{} 2286 svc.On("UpsertLabel", ctx, tnt, &modelLabelInput).Return(nil).Once() 2287 return svc 2288 }, 2289 LabelRepositoryFn: unusedLabelRepository, 2290 FormationServiceFn: unusedFormationService, 2291 InputRuntimeID: runtimeID, 2292 InputLabel: &modelLabelInput, 2293 ExpectedErrMessage: "", 2294 }, 2295 { 2296 Name: "Success when label key is scenarios and values are already set", 2297 RepositoryFn: func() *automock.RuntimeRepository { 2298 repo := &automock.RuntimeRepository{} 2299 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2300 return repo 2301 }, 2302 labelServiceFn: unusedLabelService, 2303 LabelRepositoryFn: func() *automock.LabelRepository { 2304 repo := &automock.LabelRepository{} 2305 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithFirstScenario, nil).Once() 2306 return repo 2307 }, 2308 FormationServiceFn: func() *automock.FormationService { 2309 svc := &automock.FormationService{} 2310 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2311 return svc 2312 }, 2313 InputRuntimeID: runtimeID, 2314 InputLabel: &modelScenariosLabelInputFirst, 2315 ExpectedErrMessage: "", 2316 }, 2317 { 2318 Name: "Success when label key is scenarios and there is formation for unassign", 2319 RepositoryFn: func() *automock.RuntimeRepository { 2320 repo := &automock.RuntimeRepository{} 2321 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2322 return repo 2323 }, 2324 labelServiceFn: unusedLabelService, 2325 LabelRepositoryFn: func() *automock.LabelRepository { 2326 repo := &automock.LabelRepository{} 2327 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithTwoScenarios, nil).Once() 2328 return repo 2329 }, 2330 FormationServiceFn: func() *automock.FormationService { 2331 svc := &automock.FormationService{} 2332 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2333 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(&model.Formation{Name: scenarioSecond}, nil).Once() 2334 return svc 2335 }, 2336 InputRuntimeID: runtimeID, 2337 InputLabel: &modelScenariosLabelInputFirst, 2338 ExpectedErrMessage: "", 2339 }, 2340 { 2341 Name: "Success when label key is scenarios and there is formation for assign", 2342 RepositoryFn: func() *automock.RuntimeRepository { 2343 repo := &automock.RuntimeRepository{} 2344 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2345 return repo 2346 }, 2347 labelServiceFn: unusedLabelService, 2348 LabelRepositoryFn: func() *automock.LabelRepository { 2349 repo := &automock.LabelRepository{} 2350 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithFirstScenario, nil).Once() 2351 return repo 2352 }, 2353 FormationServiceFn: func() *automock.FormationService { 2354 svc := &automock.FormationService{} 2355 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueTwo, nil).Once() 2356 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(&model.Formation{Name: scenarioSecond}, nil).Once() 2357 return svc 2358 }, 2359 InputRuntimeID: runtimeID, 2360 InputLabel: &modelScenariosLabelInputFirst, 2361 ExpectedErrMessage: "", 2362 }, 2363 { 2364 Name: "Success when label key is scenarios, runtime don't have scenarios and there is formation for assign", 2365 RepositoryFn: func() *automock.RuntimeRepository { 2366 repo := &automock.RuntimeRepository{} 2367 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2368 return repo 2369 }, 2370 labelServiceFn: unusedLabelService, 2371 LabelRepositoryFn: func() *automock.LabelRepository { 2372 repo := &automock.LabelRepository{} 2373 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithoutScenario, nil).Once() 2374 return repo 2375 }, 2376 FormationServiceFn: func() *automock.FormationService { 2377 svc := &automock.FormationService{} 2378 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2379 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(&model.Formation{Name: scenario}, nil).Once() 2380 return svc 2381 }, 2382 InputRuntimeID: runtimeID, 2383 InputLabel: &modelScenariosLabelInputFirst, 2384 ExpectedErrMessage: "", 2385 }, 2386 { 2387 Name: "Success when label key is scenarios and there is both formation for assign and unassign", 2388 RepositoryFn: func() *automock.RuntimeRepository { 2389 repo := &automock.RuntimeRepository{} 2390 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2391 return repo 2392 }, 2393 labelServiceFn: unusedLabelService, 2394 LabelRepositoryFn: func() *automock.LabelRepository { 2395 repo := &automock.LabelRepository{} 2396 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithSecondScenario, nil).Once() 2397 return repo 2398 }, 2399 FormationServiceFn: func() *automock.FormationService { 2400 svc := &automock.FormationService{} 2401 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2402 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(&model.Formation{Name: scenario}, nil).Once() 2403 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(&model.Formation{Name: scenarioSecond}, nil).Once() 2404 return svc 2405 }, 2406 InputRuntimeID: runtimeID, 2407 InputLabel: &modelScenariosLabelInputFirst, 2408 ExpectedErrMessage: "", 2409 }, 2410 { 2411 Name: "Returns error when checking if runtime exists failed", 2412 RepositoryFn: func() *automock.RuntimeRepository { 2413 repo := &automock.RuntimeRepository{} 2414 repo.On("Exists", ctx, tnt, runtimeID).Return(false, testErr).Once() 2415 return repo 2416 }, 2417 labelServiceFn: unusedLabelService, 2418 LabelRepositoryFn: unusedLabelRepository, 2419 FormationServiceFn: unusedFormationService, 2420 InputRuntimeID: runtimeID, 2421 InputLabel: &modelLabelInput, 2422 ExpectedErrMessage: testErr.Error(), 2423 }, 2424 { 2425 Name: "Returns error when checking if runtime doesn't exists", 2426 RepositoryFn: func() *automock.RuntimeRepository { 2427 repo := &automock.RuntimeRepository{} 2428 repo.On("Exists", ctx, tnt, runtimeID).Return(false, nil).Once() 2429 return repo 2430 }, 2431 labelServiceFn: unusedLabelService, 2432 LabelRepositoryFn: unusedLabelRepository, 2433 FormationServiceFn: unusedFormationService, 2434 InputRuntimeID: runtimeID, 2435 InputLabel: &modelLabelInput, 2436 ExpectedErrMessage: fmt.Sprintf("Runtime with ID %s doesn't exist", runtimeID), 2437 }, 2438 { 2439 Name: "Returns error when getting current labels for runtime failed", 2440 RepositoryFn: func() *automock.RuntimeRepository { 2441 repo := &automock.RuntimeRepository{} 2442 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2443 return repo 2444 }, 2445 labelServiceFn: unusedLabelService, 2446 LabelRepositoryFn: func() *automock.LabelRepository { 2447 repo := &automock.LabelRepository{} 2448 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(nil, testErr).Once() 2449 return repo 2450 }, 2451 FormationServiceFn: func() *automock.FormationService { 2452 svc := &automock.FormationService{} 2453 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2454 return svc 2455 }, 2456 InputRuntimeID: runtimeID, 2457 InputLabel: &modelScenariosLabelInputFirst, 2458 ExpectedErrMessage: testErr.Error(), 2459 }, 2460 { 2461 Name: "Returns error when label key is scenarios and merge scenarios and assignments failed", 2462 RepositoryFn: func() *automock.RuntimeRepository { 2463 repo := &automock.RuntimeRepository{} 2464 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2465 return repo 2466 }, 2467 labelServiceFn: unusedLabelService, 2468 LabelRepositoryFn: unusedLabelRepository, 2469 FormationServiceFn: func() *automock.FormationService { 2470 svc := &automock.FormationService{} 2471 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(nil, testErr).Once() 2472 return svc 2473 }, 2474 InputRuntimeID: runtimeID, 2475 InputLabel: &modelScenariosLabelInputFirst, 2476 ExpectedErrMessage: testErr.Error(), 2477 }, 2478 { 2479 Name: "Returns error when upsert label fails", 2480 RepositoryFn: func() *automock.RuntimeRepository { 2481 repo := &automock.RuntimeRepository{} 2482 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2483 return repo 2484 }, 2485 labelServiceFn: func() *automock.LabelService { 2486 svc := &automock.LabelService{} 2487 svc.On("UpsertLabel", ctx, tnt, &modelLabelInput).Return(testErr).Once() 2488 return svc 2489 }, 2490 LabelRepositoryFn: unusedLabelRepository, 2491 FormationServiceFn: unusedFormationService, 2492 InputRuntimeID: runtimeID, 2493 InputLabel: &modelLabelInput, 2494 ExpectedErrMessage: testErr.Error(), 2495 }, 2496 { 2497 Name: "Returns error when label key is scenarios and assign formation fails", 2498 RepositoryFn: func() *automock.RuntimeRepository { 2499 repo := &automock.RuntimeRepository{} 2500 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2501 return repo 2502 }, 2503 labelServiceFn: unusedLabelService, 2504 LabelRepositoryFn: func() *automock.LabelRepository { 2505 repo := &automock.LabelRepository{} 2506 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithFirstScenario, nil).Once() 2507 return repo 2508 }, 2509 FormationServiceFn: func() *automock.FormationService { 2510 svc := &automock.FormationService{} 2511 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueTwo, nil).Once() 2512 svc.On("AssignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(nil, testErr).Once() 2513 return svc 2514 }, 2515 InputRuntimeID: runtimeID, 2516 InputLabel: &modelScenariosLabelInputFirst, 2517 ExpectedErrMessage: testErr.Error(), 2518 }, 2519 { 2520 Name: "Success when label key is scenarios and there is formation for unassign", 2521 RepositoryFn: func() *automock.RuntimeRepository { 2522 repo := &automock.RuntimeRepository{} 2523 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2524 return repo 2525 }, 2526 labelServiceFn: unusedLabelService, 2527 LabelRepositoryFn: func() *automock.LabelRepository { 2528 repo := &automock.LabelRepository{} 2529 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithTwoScenarios, nil).Once() 2530 return repo 2531 }, 2532 FormationServiceFn: func() *automock.FormationService { 2533 svc := &automock.FormationService{} 2534 svc.On("MergeScenariosFromInputLabelsAndAssignments", ctx, map[string]interface{}{model.ScenariosKey: scenariosLabelValueFirst}, runtimeID).Return(scenariosLabelValueFirst, nil).Once() 2535 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenarioSecond}).Return(nil, testErr).Once() 2536 return svc 2537 }, 2538 InputRuntimeID: runtimeID, 2539 InputLabel: &modelScenariosLabelInputFirst, 2540 ExpectedErrMessage: testErr.Error(), 2541 }, 2542 { 2543 Name: "Returns an error when trying to set protected label", 2544 RepositoryFn: func() *automock.RuntimeRepository { 2545 repo := &automock.RuntimeRepository{} 2546 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2547 return repo 2548 }, 2549 labelServiceFn: unusedLabelService, 2550 LabelRepositoryFn: unusedLabelRepository, 2551 FormationServiceFn: unusedFormationService, 2552 InputRuntimeID: runtimeID, 2553 InputLabel: &modelProtectedLabelInput, 2554 ExpectedErrMessage: "could not set unmodifiable label with key protected_defaultEventing", 2555 }, 2556 } 2557 2558 for _, testCase := range testCases { 2559 t.Run(testCase.Name, func(t *testing.T) { 2560 repo := testCase.RepositoryFn() 2561 labelSvc := testCase.labelServiceFn() 2562 labelRepo := testCase.LabelRepositoryFn() 2563 engineSvc := testCase.FormationServiceFn() 2564 svc := runtime.NewService(repo, labelRepo, labelSvc, nil, engineSvc, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2565 2566 // WHEN 2567 err := svc.SetLabel(ctx, testCase.InputLabel) 2568 2569 // then 2570 if testCase.ExpectedErrMessage == "" { 2571 require.NoError(t, err) 2572 } else { 2573 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2574 } 2575 2576 mock.AssertExpectationsForObjects(t, repo, labelSvc, labelRepo, engineSvc) 2577 }) 2578 } 2579 2580 t.Run("Returns error on loading tenant", func(t *testing.T) { 2581 // GIVEN 2582 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2583 // WHEN 2584 err := svc.SetLabel(context.TODO(), &model.LabelInput{}) 2585 // then 2586 require.Error(t, err) 2587 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 2588 }) 2589 } 2590 2591 func TestService_DeleteLabel(t *testing.T) { 2592 // GIVEN 2593 tnt := "tenant" 2594 externalTnt := "external-tnt" 2595 2596 ctx := context.TODO() 2597 ctx = tenant.SaveToContext(ctx, tnt, externalTnt) 2598 2599 testErr := errors.New("Test error") 2600 2601 runtimeID := "foo" 2602 2603 labelKey := "key" 2604 protectedLabelKey := "protected_defaultEventing" 2605 labelValue := "val" 2606 scenario := "SCENARIO" 2607 secondScenario := "SECOND_SCENARIO" 2608 scenariosLabelValueWithMultipleValues := []interface{}{scenario, secondScenario} 2609 2610 labelMapWithScenariosLabelWithMultipleValues := map[string]*model.Label{ 2611 model.ScenariosKey: { 2612 ID: "id", 2613 Tenant: str.Ptr("tenant"), 2614 Key: model.ScenariosKey, 2615 Value: scenariosLabelValueWithMultipleValues, 2616 ObjectID: "obj-id", 2617 ObjectType: model.RuntimeLabelableObject, 2618 }, 2619 labelKey: { 2620 ID: "id", 2621 Key: labelKey, 2622 Value: labelValue, 2623 ObjectID: "obj-id", 2624 ObjectType: model.RuntimeLabelableObject, 2625 }, 2626 } 2627 2628 testCases := []struct { 2629 Name string 2630 RepositoryFn func() *automock.RuntimeRepository 2631 LabelRepositoryFn func() *automock.LabelRepository 2632 FormationServiceFn func() *automock.FormationService 2633 InputRuntimeID string 2634 InputKey string 2635 ExpectedErrMessage string 2636 }{ 2637 { 2638 Name: "Success", 2639 RepositoryFn: func() *automock.RuntimeRepository { 2640 repo := &automock.RuntimeRepository{} 2641 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2642 return repo 2643 }, 2644 LabelRepositoryFn: func() *automock.LabelRepository { 2645 repo := &automock.LabelRepository{} 2646 repo.On("Delete", ctx, tnt, model.RuntimeLabelableObject, runtimeID, labelKey).Return(nil).Once() 2647 return repo 2648 }, 2649 FormationServiceFn: unusedFormationService, 2650 InputRuntimeID: runtimeID, 2651 InputKey: labelKey, 2652 ExpectedErrMessage: "", 2653 }, 2654 { 2655 Name: "Success when label key is scenarios", 2656 RepositoryFn: func() *automock.RuntimeRepository { 2657 repo := &automock.RuntimeRepository{} 2658 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2659 return repo 2660 }, 2661 LabelRepositoryFn: func() *automock.LabelRepository { 2662 repo := &automock.LabelRepository{} 2663 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithScenariosLabelWithMultipleValues, nil).Once() 2664 return repo 2665 }, 2666 FormationServiceFn: func() *automock.FormationService { 2667 svc := &automock.FormationService{} 2668 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(&model.Formation{Name: scenario}, nil).Once() 2669 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: secondScenario}).Return(&model.Formation{Name: secondScenario}, nil).Once() 2670 return svc 2671 }, 2672 InputRuntimeID: runtimeID, 2673 InputKey: model.ScenariosKey, 2674 ExpectedErrMessage: "", 2675 }, 2676 { 2677 Name: "Success when label key is selector", 2678 RepositoryFn: func() *automock.RuntimeRepository { 2679 repo := &automock.RuntimeRepository{} 2680 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2681 return repo 2682 }, 2683 LabelRepositoryFn: func() *automock.LabelRepository { 2684 repo := &automock.LabelRepository{} 2685 repo.On("Delete", ctx, tnt, model.RuntimeLabelableObject, runtimeID, labelKey).Return(nil).Once() 2686 return repo 2687 }, 2688 FormationServiceFn: unusedFormationService, 2689 InputRuntimeID: runtimeID, 2690 InputKey: labelKey, 2691 ExpectedErrMessage: "", 2692 }, 2693 { 2694 Name: "Returns error when checking if runtime exists failed", 2695 RepositoryFn: func() *automock.RuntimeRepository { 2696 repo := &automock.RuntimeRepository{} 2697 repo.On("Exists", ctx, tnt, runtimeID).Return(false, testErr).Once() 2698 return repo 2699 }, 2700 LabelRepositoryFn: unusedLabelRepository, 2701 FormationServiceFn: unusedFormationService, 2702 InputRuntimeID: runtimeID, 2703 InputKey: labelKey, 2704 ExpectedErrMessage: testErr.Error(), 2705 }, 2706 { 2707 Name: "Returns error when checking if runtime does not exists", 2708 RepositoryFn: func() *automock.RuntimeRepository { 2709 repo := &automock.RuntimeRepository{} 2710 repo.On("Exists", ctx, tnt, runtimeID).Return(false, nil).Once() 2711 return repo 2712 }, 2713 LabelRepositoryFn: unusedLabelRepository, 2714 FormationServiceFn: unusedFormationService, 2715 InputRuntimeID: runtimeID, 2716 InputKey: labelKey, 2717 ExpectedErrMessage: fmt.Sprintf("Runtime with ID %s doesn't exist", runtimeID), 2718 }, 2719 { 2720 Name: "Returns error if listing current labels for runtime failed", 2721 RepositoryFn: func() *automock.RuntimeRepository { 2722 repo := &automock.RuntimeRepository{} 2723 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2724 return repo 2725 }, 2726 LabelRepositoryFn: func() *automock.LabelRepository { 2727 repo := &automock.LabelRepository{} 2728 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(nil, testErr).Once() 2729 return repo 2730 }, 2731 FormationServiceFn: unusedFormationService, 2732 InputRuntimeID: runtimeID, 2733 InputKey: model.ScenariosKey, 2734 ExpectedErrMessage: testErr.Error(), 2735 }, 2736 { 2737 Name: "Returns error when runtime unassign formation failed", 2738 RepositoryFn: func() *automock.RuntimeRepository { 2739 repo := &automock.RuntimeRepository{} 2740 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2741 return repo 2742 }, 2743 LabelRepositoryFn: func() *automock.LabelRepository { 2744 repo := &automock.LabelRepository{} 2745 repo.On("ListForObject", ctx, tnt, model.RuntimeLabelableObject, runtimeID).Return(labelMapWithScenariosLabelWithMultipleValues, nil).Once() 2746 return repo 2747 }, 2748 FormationServiceFn: func() *automock.FormationService { 2749 svc := &automock.FormationService{} 2750 svc.On("UnassignFormation", ctx, tnt, runtimeID, graphql.FormationObjectTypeRuntime, model.Formation{Name: scenario}).Return(nil, testErr) 2751 return svc 2752 }, 2753 InputRuntimeID: runtimeID, 2754 InputKey: model.ScenariosKey, 2755 ExpectedErrMessage: testErr.Error(), 2756 }, 2757 { 2758 Name: "Returns error when runtime label delete failed", 2759 RepositoryFn: func() *automock.RuntimeRepository { 2760 repo := &automock.RuntimeRepository{} 2761 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2762 return repo 2763 }, 2764 LabelRepositoryFn: func() *automock.LabelRepository { 2765 repo := &automock.LabelRepository{} 2766 repo.On("Delete", ctx, tnt, model.RuntimeLabelableObject, runtimeID, labelKey).Return(testErr).Once() 2767 return repo 2768 }, 2769 FormationServiceFn: unusedFormationService, 2770 InputRuntimeID: runtimeID, 2771 InputKey: labelKey, 2772 ExpectedErrMessage: testErr.Error(), 2773 }, 2774 { 2775 Name: "Returns an error when trying to delete protected label", 2776 RepositoryFn: func() *automock.RuntimeRepository { 2777 repo := &automock.RuntimeRepository{} 2778 repo.On("Exists", ctx, tnt, runtimeID).Return(true, nil).Once() 2779 return repo 2780 }, 2781 LabelRepositoryFn: unusedLabelRepository, 2782 FormationServiceFn: unusedFormationService, 2783 InputRuntimeID: runtimeID, 2784 InputKey: protectedLabelKey, 2785 ExpectedErrMessage: "could not delete unmodifiable label with key protected_defaultEventing", 2786 }, 2787 } 2788 2789 for _, testCase := range testCases { 2790 t.Run(testCase.Name, func(t *testing.T) { 2791 repo := testCase.RepositoryFn() 2792 labelRepo := testCase.LabelRepositoryFn() 2793 engineSvc := testCase.FormationServiceFn() 2794 svc := runtime.NewService(repo, labelRepo, nil, nil, engineSvc, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2795 2796 // WHEN 2797 err := svc.DeleteLabel(ctx, testCase.InputRuntimeID, testCase.InputKey) 2798 2799 // then 2800 if testCase.ExpectedErrMessage == "" { 2801 require.NoError(t, err) 2802 } else { 2803 require.Error(t, err) 2804 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2805 } 2806 2807 mock.AssertExpectationsForObjects(t, repo, labelRepo, engineSvc) 2808 }) 2809 } 2810 2811 t.Run("Returns error on loading tenant", func(t *testing.T) { 2812 // GIVEN 2813 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2814 // WHEN 2815 err := svc.DeleteLabel(context.TODO(), "id", "key") 2816 // then 2817 require.Error(t, err) 2818 assert.EqualError(t, err, "while loading tenant from context: cannot read tenant from context") 2819 }) 2820 } 2821 2822 func TestService_GetByFiltersGlobal(t *testing.T) { 2823 // GIVEN 2824 testErr := errors.New("Test error") 2825 filters := []*labelfilter.LabelFilter{ 2826 {Key: "test-key", Query: str.Ptr("test-filter")}, 2827 } 2828 testRuntime := &model.Runtime{ 2829 ID: "test-id", 2830 Name: "test-runtime", 2831 } 2832 ctx := context.TODO() 2833 2834 testCases := []struct { 2835 Name string 2836 RepositoryFn func() *automock.RuntimeRepository 2837 ExpectedErrMessage string 2838 }{ 2839 { 2840 Name: "Success", 2841 RepositoryFn: func() *automock.RuntimeRepository { 2842 repo := &automock.RuntimeRepository{} 2843 repo.On("GetByFiltersGlobal", ctx, filters).Return(testRuntime, nil).Once() 2844 return repo 2845 }, 2846 2847 ExpectedErrMessage: "", 2848 }, 2849 { 2850 Name: "Fails on repository error", 2851 RepositoryFn: func() *automock.RuntimeRepository { 2852 repo := &automock.RuntimeRepository{} 2853 repo.On("GetByFiltersGlobal", ctx, filters).Return(nil, testErr).Once() 2854 return repo 2855 }, 2856 2857 ExpectedErrMessage: testErr.Error(), 2858 }, 2859 } 2860 2861 for _, testCase := range testCases { 2862 t.Run(testCase.Name, func(t *testing.T) { 2863 repo := testCase.RepositoryFn() 2864 labelRepository := &automock.LabelRepository{} 2865 labelService := &automock.LabelService{} 2866 formationService := &automock.FormationService{} 2867 uidSvc := &automock.UidService{} 2868 svc := runtime.NewService(repo, labelRepository, labelService, uidSvc, formationService, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 2869 2870 // WHEN 2871 actualRuntime, err := svc.GetByFiltersGlobal(ctx, filters) 2872 // then 2873 if testCase.ExpectedErrMessage == "" { 2874 require.Equal(t, testRuntime, actualRuntime) 2875 require.NoError(t, err) 2876 } else { 2877 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2878 } 2879 2880 mock.AssertExpectationsForObjects(t, repo, labelService, labelRepository, formationService, uidSvc) 2881 }) 2882 } 2883 } 2884 2885 func TestService_GetByFilters(t *testing.T) { 2886 // GIVEN 2887 tnt := "tenant" 2888 testErr := errors.New("Test error") 2889 filters := []*labelfilter.LabelFilter{ 2890 {Key: "test-key", Query: str.Ptr("test-filter")}, 2891 } 2892 modelRuntime := fixModelRuntime(t, "foo", tnt, "Foo", "Lorem Ipsum", "test.ns") 2893 ctx := tenant.SaveToContext(context.TODO(), tnt, tnt) 2894 2895 testCases := []struct { 2896 Name string 2897 RepositoryFn func() *automock.RuntimeRepository 2898 Context context.Context 2899 ExpectedErrMessage string 2900 }{ 2901 { 2902 Name: "Success", 2903 RepositoryFn: func() *automock.RuntimeRepository { 2904 repo := &automock.RuntimeRepository{} 2905 repo.On("GetByFilters", contextThatHasTenant(tnt), tnt, filters).Return(modelRuntime, nil).Once() 2906 return repo 2907 }, 2908 Context: ctx, 2909 ExpectedErrMessage: "", 2910 }, 2911 { 2912 Name: "Fails on repository error", 2913 RepositoryFn: func() *automock.RuntimeRepository { 2914 repo := &automock.RuntimeRepository{} 2915 repo.On("GetByFilters", contextThatHasTenant(tnt), tnt, filters).Return(nil, testErr).Once() 2916 return repo 2917 }, 2918 Context: ctx, 2919 ExpectedErrMessage: testErr.Error(), 2920 }, 2921 { 2922 Name: "Fails when no tenant in the context", 2923 RepositoryFn: unusedRuntimeRepository, 2924 Context: context.TODO(), 2925 ExpectedErrMessage: "while loading tenant from context", 2926 }, 2927 } 2928 2929 for _, testCase := range testCases { 2930 t.Run(testCase.Name, func(t *testing.T) { 2931 repo := testCase.RepositoryFn() 2932 labelRepository := &automock.LabelRepository{} 2933 labelService := &automock.LabelService{} 2934 formationService := &automock.FormationService{} 2935 uidSvc := &automock.UidService{} 2936 svc := runtime.NewService(repo, labelRepository, labelService, uidSvc, formationService, nil, nil, nil, ".*_defaultEventing$", immutableLabelPattern, "", "", "") 2937 2938 // WHEN 2939 actualRuntime, err := svc.GetByFilters(testCase.Context, filters) 2940 // then 2941 if testCase.ExpectedErrMessage == "" { 2942 require.NoError(t, err) 2943 require.Equal(t, modelRuntime, actualRuntime) 2944 } else { 2945 require.Error(t, err) 2946 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 2947 } 2948 2949 mock.AssertExpectationsForObjects(t, repo, labelService, labelRepository, formationService, uidSvc) 2950 }) 2951 } 2952 } 2953 2954 func TestService_ListByFiltersGlobal(t *testing.T) { 2955 // GIVEN 2956 testErr := errors.New("Test error") 2957 filters := []*labelfilter.LabelFilter{ 2958 {Key: "test-key", Query: str.Ptr("test-filter")}, 2959 } 2960 modelRuntimes := []*model.Runtime{ 2961 fixModelRuntime(t, "foo", "tenant-foo", "Foo", "Lorem Ipsum", "test.ns.foo"), 2962 fixModelRuntime(t, "bar", "tenant-bar", "Bar", "Lorem Ipsum", "test.ns.bar"), 2963 } 2964 ctx := context.TODO() 2965 2966 testCases := []struct { 2967 Name string 2968 RepositoryFn func() *automock.RuntimeRepository 2969 ExpectedErrMessage string 2970 }{ 2971 { 2972 Name: "Success", 2973 RepositoryFn: func() *automock.RuntimeRepository { 2974 repo := &automock.RuntimeRepository{} 2975 repo.On("ListByFiltersGlobal", ctx, filters).Return(modelRuntimes, nil).Once() 2976 return repo 2977 }, 2978 2979 ExpectedErrMessage: "", 2980 }, 2981 { 2982 Name: "Fails on repository error", 2983 RepositoryFn: func() *automock.RuntimeRepository { 2984 repo := &automock.RuntimeRepository{} 2985 repo.On("ListByFiltersGlobal", ctx, filters).Return(nil, testErr).Once() 2986 return repo 2987 }, 2988 2989 ExpectedErrMessage: testErr.Error(), 2990 }, 2991 } 2992 2993 for _, testCase := range testCases { 2994 t.Run(testCase.Name, func(t *testing.T) { 2995 repo := testCase.RepositoryFn() 2996 labelRepository := &automock.LabelRepository{} 2997 labelService := &automock.LabelService{} 2998 formationService := &automock.FormationService{} 2999 uidSvc := &automock.UidService{} 3000 svc := runtime.NewService(repo, labelRepository, labelService, uidSvc, formationService, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 3001 3002 // WHEN 3003 actualRuntimes, err := svc.ListByFiltersGlobal(ctx, filters) 3004 // then 3005 if testCase.ExpectedErrMessage == "" { 3006 require.NoError(t, err) 3007 require.Equal(t, modelRuntimes, actualRuntimes) 3008 } else { 3009 require.Error(t, err) 3010 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 3011 } 3012 3013 mock.AssertExpectationsForObjects(t, repo, labelService, labelRepository, formationService, uidSvc) 3014 }) 3015 } 3016 } 3017 3018 func TestService_ListByFilters(t *testing.T) { 3019 // GIVEN 3020 tnt := "tenant" 3021 testErr := errors.New("Test error") 3022 filters := []*labelfilter.LabelFilter{ 3023 {Key: "test-key", Query: str.Ptr("test-filter")}, 3024 } 3025 modelRuntimes := []*model.Runtime{ 3026 fixModelRuntime(t, "foo", tnt, "Foo", "Lorem Ipsum", "test.ns.foo"), 3027 fixModelRuntime(t, "bar", tnt, "Bar", "Lorem Ipsum", "test.ns.bar"), 3028 } 3029 ctx := tenant.SaveToContext(context.TODO(), tnt, tnt) 3030 3031 testCases := []struct { 3032 Name string 3033 RepositoryFn func() *automock.RuntimeRepository 3034 Context context.Context 3035 ExpectedErrMessage string 3036 }{ 3037 { 3038 Name: "Success", 3039 RepositoryFn: func() *automock.RuntimeRepository { 3040 repo := &automock.RuntimeRepository{} 3041 repo.On("ListAll", contextThatHasTenant(tnt), tnt, filters).Return(modelRuntimes, nil).Once() 3042 return repo 3043 }, 3044 Context: ctx, 3045 ExpectedErrMessage: "", 3046 }, 3047 { 3048 Name: "Fails on repository error", 3049 RepositoryFn: func() *automock.RuntimeRepository { 3050 repo := &automock.RuntimeRepository{} 3051 repo.On("ListAll", contextThatHasTenant(tnt), tnt, filters).Return(nil, testErr).Once() 3052 return repo 3053 }, 3054 Context: ctx, 3055 ExpectedErrMessage: testErr.Error(), 3056 }, 3057 { 3058 Name: "Fails when no tenant in the context", 3059 RepositoryFn: unusedRuntimeRepository, 3060 Context: context.TODO(), 3061 ExpectedErrMessage: "while loading tenant from context", 3062 }, 3063 } 3064 3065 for _, testCase := range testCases { 3066 t.Run(testCase.Name, func(t *testing.T) { 3067 repo := testCase.RepositoryFn() 3068 labelRepository := &automock.LabelRepository{} 3069 labelService := &automock.LabelService{} 3070 formationService := &automock.FormationService{} 3071 uidSvc := &automock.UidService{} 3072 svc := runtime.NewService(repo, labelRepository, labelService, uidSvc, formationService, nil, nil, nil, ".*_defaultEventing$", immutableLabelPattern, "", "", "") 3073 3074 // WHEN 3075 actualRuntimes, err := svc.ListByFilters(testCase.Context, filters) 3076 // then 3077 if testCase.ExpectedErrMessage == "" { 3078 require.NoError(t, err) 3079 require.Equal(t, modelRuntimes, actualRuntimes) 3080 } else { 3081 require.Error(t, err) 3082 assert.Contains(t, err.Error(), testCase.ExpectedErrMessage) 3083 } 3084 3085 mock.AssertExpectationsForObjects(t, repo, labelService, labelRepository, formationService, uidSvc) 3086 }) 3087 } 3088 } 3089 3090 func TestService_UnsafeExtractModifiableLabels(t *testing.T) { 3091 testCases := []struct { 3092 Name string 3093 InputLabels map[string]interface{} 3094 ExpectedLabels map[string]interface{} 3095 ExpectedErr error 3096 }{ 3097 { 3098 Name: "Success without protected and immutable labels", 3099 InputLabels: map[string]interface{}{"test1": "test1", "test2": "test2"}, 3100 ExpectedLabels: map[string]interface{}{"test1": "test1", "test2": "test2"}, 3101 ExpectedErr: nil, 3102 }, 3103 { 3104 Name: "Success with protected labels", 3105 InputLabels: map[string]interface{}{"test_defaultEventing": "protected", "test2": "test2"}, 3106 ExpectedLabels: map[string]interface{}{"test2": "test2"}, 3107 ExpectedErr: nil, 3108 }, 3109 { 3110 Name: "Success with immutable labels", 3111 InputLabels: map[string]interface{}{runtimeTypeLabelKey: "immutable", "test2": "test2"}, 3112 ExpectedLabels: map[string]interface{}{"test2": "test2"}, 3113 ExpectedErr: nil, 3114 }, 3115 { 3116 Name: "Success with protected and immutable labels", 3117 InputLabels: map[string]interface{}{runtimeTypeLabelKey: "test1", "test_defaultEventing": "test2", "test3": "test3"}, 3118 ExpectedLabels: map[string]interface{}{"test3": "test3"}, 3119 ExpectedErr: nil, 3120 }, 3121 } 3122 3123 for _, testCase := range testCases { 3124 t.Run(testCase.Name, func(t *testing.T) { 3125 // GIVEN 3126 svc := runtime.NewService(nil, nil, nil, nil, nil, nil, nil, nil, protectedLabelPattern, immutableLabelPattern, "", "", "") 3127 3128 // WHEN 3129 extractedLabels, err := svc.UnsafeExtractModifiableLabels(testCase.InputLabels) 3130 // THEN 3131 if testCase.ExpectedErr != nil { 3132 require.Error(t, err) 3133 require.Equal(t, nil, extractedLabels) 3134 } else { 3135 require.NoError(t, err) 3136 require.Equal(t, extractedLabels, testCase.ExpectedLabels) 3137 } 3138 }) 3139 } 3140 } 3141 3142 func contextThatHasTenant(expectedTenant string) interface{} { 3143 return mock.MatchedBy(func(actual context.Context) bool { 3144 actualTenant, err := tenant.LoadFromContext(actual) 3145 if err != nil { 3146 return false 3147 } 3148 return actualTenant == expectedTenant 3149 }) 3150 } 3151 3152 func unusedFormationService() *automock.FormationService { 3153 return &automock.FormationService{} 3154 } 3155 3156 func unusedRuntimeRepository() *automock.RuntimeRepository { 3157 return &automock.RuntimeRepository{} 3158 } 3159 3160 func unusedLabelService() *automock.LabelService { 3161 return &automock.LabelService{} 3162 } 3163 3164 func unusedTenantService() *automock.TenantService { 3165 return &automock.TenantService{} 3166 } 3167 3168 func unusedWebhookService() *automock.WebhookService { 3169 return &automock.WebhookService{} 3170 } 3171 3172 func unusedLabelRepository() *automock.LabelRepository { 3173 return &automock.LabelRepository{} 3174 }