github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/broker/instance_update_test.go (about) 1 package broker 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "net/http" 8 "testing" 9 "time" 10 11 "github.com/kyma-project/kyma-environment-broker/internal" 12 "github.com/kyma-project/kyma-environment-broker/internal/broker/automock" 13 14 "github.com/stretchr/testify/mock" 15 16 "github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema" 17 "github.com/kyma-project/kyma-environment-broker/internal/dashboard" 18 19 "github.com/kyma-project/kyma-environment-broker/internal/fixture" 20 "github.com/kyma-project/kyma-environment-broker/internal/ptr" 21 "github.com/kyma-project/kyma-environment-broker/internal/storage" 22 "github.com/pivotal-cf/brokerapi/v8/domain" 23 "github.com/pivotal-cf/brokerapi/v8/domain/apiresponses" 24 "github.com/sirupsen/logrus" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 ) 28 29 var dashboardConfig = dashboard.Config{LandscapeURL: "https://dashboard.example.com"} 30 31 type handler struct { 32 Instance internal.Instance 33 ersContext internal.ERSContext 34 } 35 36 func (h *handler) Handle(inst *internal.Instance, ers internal.ERSContext) (bool, error) { 37 h.Instance = *inst 38 h.ersContext = ers 39 return false, nil 40 } 41 42 func TestUpdateEndpoint_UpdateSuspension(t *testing.T) { 43 // given 44 instance := internal.Instance{ 45 InstanceID: instanceID, 46 ServicePlanID: TrialPlanID, 47 Parameters: internal.ProvisioningParameters{ 48 PlanID: TrialPlanID, 49 ErsContext: internal.ERSContext{ 50 TenantID: "", 51 SubAccountID: "", 52 GlobalAccountID: "", 53 Active: nil, 54 }, 55 }, 56 } 57 st := storage.NewMemoryStorage() 58 st.Instances().Insert(instance) 59 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 60 st.Operations().InsertDeprovisioningOperation(fixSuspensionOperation()) 61 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("02")) 62 63 handler := &handler{} 64 q := &automock.Queue{} 65 q.On("Add", mock.AnythingOfType("string")) 66 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 67 return &gqlschema.ClusterConfigInput{}, nil 68 } 69 svc := NewUpdate( 70 Config{}, 71 st.Instances(), 72 st.RuntimeStates(), 73 st.Operations(), 74 handler, 75 true, 76 false, 77 q, 78 PlansConfig{}, 79 planDefaults, 80 logrus.New(), 81 dashboardConfig) 82 83 // when 84 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 85 ServiceID: "", 86 PlanID: TrialPlanID, 87 RawParameters: nil, 88 PreviousValues: domain.PreviousValues{}, 89 RawContext: json.RawMessage("{\"active\":false}"), 90 MaintenanceInfo: nil, 91 }, true) 92 require.NoError(t, err) 93 94 // then 95 96 assert.Equal(t, internal.ERSContext{ 97 Active: ptr.Bool(false), 98 }, handler.ersContext) 99 100 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 101 assert.True(t, *handler.Instance.Parameters.ErsContext.Active) 102 assert.Len(t, response.Metadata.Labels, 1) 103 104 inst, err := st.Instances().GetByID(instanceID) 105 assert.False(t, *inst.Parameters.ErsContext.Active) 106 } 107 108 func TestUpdateEndpoint_UpdateExpirationOfTrial(t *testing.T) { 109 // given 110 instance := internal.Instance{ 111 InstanceID: instanceID, 112 ServicePlanID: TrialPlanID, 113 Parameters: internal.ProvisioningParameters{ 114 PlanID: TrialPlanID, 115 ErsContext: internal.ERSContext{ 116 TenantID: "", 117 SubAccountID: "", 118 GlobalAccountID: "", 119 Active: nil, 120 }, 121 }, 122 } 123 st := storage.NewMemoryStorage() 124 st.Instances().Insert(instance) 125 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 126 127 handler := &handler{} 128 q := &automock.Queue{} 129 q.On("Add", mock.AnythingOfType("string")) 130 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 131 return &gqlschema.ClusterConfigInput{}, nil 132 } 133 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 134 planDefaults, logrus.New(), dashboardConfig) 135 136 // when 137 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 138 ServiceID: "", 139 PlanID: TrialPlanID, 140 RawParameters: json.RawMessage(`{"expired": true}`), 141 PreviousValues: domain.PreviousValues{}, 142 RawContext: json.RawMessage("{\"active\":false}"), 143 MaintenanceInfo: nil, 144 }, true) 145 require.NoError(t, err) 146 147 // then 148 149 assert.Equal(t, internal.ERSContext{ 150 Active: ptr.Bool(false), 151 }, handler.ersContext) 152 153 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 154 assert.True(t, *handler.Instance.Parameters.ErsContext.Active) 155 assert.Len(t, response.Metadata.Labels, 1) 156 inst, err := st.Instances().GetByID(instanceID) 157 require.NoError(t, err) 158 assert.True(t, inst.IsExpired()) 159 assert.False(t, *inst.Parameters.ErsContext.Active) 160 } 161 162 func TestUpdateEndpoint_UpdateExpirationOfExpiredTrial(t *testing.T) { 163 // given 164 instance := internal.Instance{ 165 InstanceID: instanceID, 166 ServicePlanID: TrialPlanID, 167 Parameters: internal.ProvisioningParameters{ 168 PlanID: TrialPlanID, 169 ErsContext: internal.ERSContext{ 170 TenantID: "", 171 SubAccountID: "", 172 GlobalAccountID: "", 173 Active: ptr.Bool(false), 174 }, 175 }, 176 ExpiredAt: ptr.Time(time.Now()), 177 } 178 st := storage.NewMemoryStorage() 179 st.Instances().Insert(instance) 180 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 181 182 handler := &handler{} 183 q := &automock.Queue{} 184 q.On("Add", mock.AnythingOfType("string")) 185 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 186 return &gqlschema.ClusterConfigInput{}, nil 187 } 188 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 189 planDefaults, logrus.New(), dashboardConfig) 190 191 // when 192 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 193 ServiceID: "", 194 PlanID: TrialPlanID, 195 RawParameters: json.RawMessage(`{"expired": true}`), 196 PreviousValues: domain.PreviousValues{}, 197 RawContext: json.RawMessage("{\"active\":false}"), 198 MaintenanceInfo: nil, 199 }, true) 200 require.NoError(t, err) 201 202 // then 203 204 // we expect the handler is called. The handler is responsible to skip processing 205 assert.Equal(t, internal.ERSContext{ 206 Active: ptr.Bool(false), 207 }, handler.ersContext) 208 209 assert.Len(t, response.Metadata.Labels, 1) 210 inst, err := st.Instances().GetByID(instanceID) 211 require.NoError(t, err) 212 assert.True(t, inst.IsExpired()) 213 } 214 215 func TestUpdateEndpoint_UpdateOfExpiredTrial(t *testing.T) { 216 // given 217 instance := internal.Instance{ 218 InstanceID: instanceID, 219 ServicePlanID: TrialPlanID, 220 Parameters: internal.ProvisioningParameters{ 221 PlanID: TrialPlanID, 222 ErsContext: internal.ERSContext{ 223 TenantID: "", 224 SubAccountID: "", 225 GlobalAccountID: "", 226 Active: ptr.Bool(false), 227 }, 228 }, 229 ExpiredAt: ptr.Time(time.Now()), 230 } 231 st := storage.NewMemoryStorage() 232 st.Instances().Insert(instance) 233 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 234 235 handler := &handler{} 236 q := &automock.Queue{} 237 q.On("Add", mock.AnythingOfType("string")) 238 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 239 return &gqlschema.ClusterConfigInput{}, nil 240 } 241 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 242 planDefaults, logrus.New(), dashboardConfig) 243 244 // when 245 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 246 ServiceID: "", 247 PlanID: TrialPlanID, 248 RawParameters: json.RawMessage(`{"autoScalerMin": 3}`), 249 PreviousValues: domain.PreviousValues{}, 250 RawContext: json.RawMessage("{\"active\":false}"), 251 MaintenanceInfo: nil, 252 }, true) 253 254 // then 255 assert.NoError(t, err) 256 assert.False(t, response.IsAsync) 257 } 258 259 func TestUpdateEndpoint_UpdateAutoscalerParams(t *testing.T) { 260 // given 261 instance := internal.Instance{ 262 InstanceID: instanceID, 263 ServicePlanID: AWSPlanID, 264 Parameters: internal.ProvisioningParameters{ 265 PlanID: AWSPlanID, 266 ErsContext: internal.ERSContext{ 267 TenantID: "", 268 SubAccountID: "", 269 GlobalAccountID: "", 270 Active: ptr.Bool(false), 271 }, 272 }, 273 } 274 st := storage.NewMemoryStorage() 275 st.Instances().Insert(instance) 276 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 277 278 handler := &handler{} 279 q := &automock.Queue{} 280 q.On("Add", mock.AnythingOfType("string")) 281 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 282 return &gqlschema.ClusterConfigInput{}, nil 283 } 284 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 285 planDefaults, logrus.New(), dashboardConfig) 286 287 t.Run("Should fail on invalid (too low) autoScalerMin and autoScalerMax", func(t *testing.T) { 288 289 // when 290 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 291 ServiceID: "", 292 PlanID: AWSPlanID, 293 RawParameters: json.RawMessage(`{"autoScalerMin": 1, "autoScalerMax": 1}`), 294 PreviousValues: domain.PreviousValues{}, 295 RawContext: json.RawMessage("{\"active\":false}"), 296 MaintenanceInfo: nil, 297 }, true) 298 299 // then 300 assert.ErrorContains(t, err, "while validating update parameters:") 301 assert.False(t, response.IsAsync) 302 }) 303 304 t.Run("Should fail on invalid autoScalerMin and autoScalerMax", func(t *testing.T) { 305 306 // when 307 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 308 ServiceID: "", 309 PlanID: AWSPlanID, 310 RawParameters: json.RawMessage(`{"autoScalerMin": 4, "autoScalerMax": 3}`), 311 PreviousValues: domain.PreviousValues{}, 312 RawContext: json.RawMessage("{\"active\":false}"), 313 MaintenanceInfo: nil, 314 }, true) 315 316 // then 317 assert.ErrorContains(t, err, "AutoScalerMax 3 should be larger than AutoScalerMin 4") 318 assert.False(t, response.IsAsync) 319 }) 320 321 t.Run("Should fail on invalid autoScalerMin and autoScalerMax and JSON validation should precede", func(t *testing.T) { 322 323 // when 324 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 325 ServiceID: "", 326 PlanID: AWSPlanID, 327 RawParameters: json.RawMessage(`{"autoScalerMin": 2, "autoScalerMax": 1}`), 328 PreviousValues: domain.PreviousValues{}, 329 RawContext: json.RawMessage("{\"active\":false}"), 330 MaintenanceInfo: nil, 331 }, true) 332 333 // then 334 assert.ErrorContains(t, err, "while validating update parameters:") 335 assert.False(t, response.IsAsync) 336 }) 337 } 338 339 func TestUpdateEndpoint_UpdateUnsuspension(t *testing.T) { 340 // given 341 instance := internal.Instance{ 342 InstanceID: instanceID, 343 ServicePlanID: TrialPlanID, 344 Parameters: internal.ProvisioningParameters{ 345 PlanID: TrialPlanID, 346 ErsContext: internal.ERSContext{ 347 TenantID: "", 348 SubAccountID: "", 349 GlobalAccountID: "", 350 Active: nil, 351 }, 352 }, 353 } 354 st := storage.NewMemoryStorage() 355 st.Instances().Insert(instance) 356 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 357 st.Operations().InsertDeprovisioningOperation(fixSuspensionOperation()) 358 359 handler := &handler{} 360 q := &automock.Queue{} 361 q.On("Add", mock.AnythingOfType("string")) 362 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 363 return &gqlschema.ClusterConfigInput{}, nil 364 } 365 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 366 planDefaults, logrus.New(), dashboardConfig) 367 368 // when 369 svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 370 ServiceID: "", 371 PlanID: TrialPlanID, 372 RawParameters: nil, 373 PreviousValues: domain.PreviousValues{}, 374 RawContext: json.RawMessage("{\"active\":true}"), 375 MaintenanceInfo: nil, 376 }, true) 377 378 // then 379 380 assert.Equal(t, internal.ERSContext{ 381 Active: ptr.Bool(true), 382 }, handler.ersContext) 383 384 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 385 assert.False(t, *handler.Instance.Parameters.ErsContext.Active) 386 } 387 388 func TestUpdateEndpoint_UpdateInstanceWithWrongActiveValue(t *testing.T) { 389 // given 390 instance := internal.Instance{ 391 InstanceID: instanceID, 392 ServicePlanID: TrialPlanID, 393 Parameters: internal.ProvisioningParameters{ 394 PlanID: TrialPlanID, 395 ErsContext: internal.ERSContext{ 396 TenantID: "", 397 SubAccountID: "", 398 GlobalAccountID: "", 399 Active: ptr.Bool(false), 400 }, 401 }, 402 } 403 st := storage.NewMemoryStorage() 404 st.Instances().Insert(instance) 405 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 406 handler := &handler{} 407 q := &automock.Queue{} 408 q.On("Add", mock.AnythingOfType("string")) 409 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 410 return &gqlschema.ClusterConfigInput{}, nil 411 } 412 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 413 planDefaults, logrus.New(), dashboardConfig) 414 415 // when 416 svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 417 ServiceID: "", 418 PlanID: TrialPlanID, 419 RawParameters: nil, 420 PreviousValues: domain.PreviousValues{}, 421 RawContext: json.RawMessage("{\"active\":false}"), 422 MaintenanceInfo: nil, 423 }, true) 424 425 // then 426 assert.Equal(t, internal.ERSContext{ 427 Active: ptr.Bool(false), 428 }, handler.ersContext) 429 430 assert.True(t, *handler.Instance.Parameters.ErsContext.Active) 431 } 432 433 func TestUpdateEndpoint_UpdateNonExistingInstance(t *testing.T) { 434 // given 435 st := storage.NewMemoryStorage() 436 handler := &handler{} 437 q := &automock.Queue{} 438 q.On("Add", mock.AnythingOfType("string")) 439 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 440 return &gqlschema.ClusterConfigInput{}, nil 441 } 442 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 443 planDefaults, logrus.New(), dashboardConfig) 444 445 // when 446 _, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 447 ServiceID: "", 448 PlanID: TrialPlanID, 449 RawParameters: nil, 450 PreviousValues: domain.PreviousValues{}, 451 RawContext: json.RawMessage("{\"active\":false}"), 452 MaintenanceInfo: nil, 453 }, true) 454 455 // then 456 assert.IsType(t, err, &apiresponses.FailureResponse{}, "Updating returned error of unexpected type") 457 apierr := err.(*apiresponses.FailureResponse) 458 assert.Equal(t, apierr.ValidatedStatusCode(nil), http.StatusNotFound, "Updating status code not matching") 459 } 460 461 func fixProvisioningOperation(id string) internal.ProvisioningOperation { 462 provisioningOperation := fixture.FixProvisioningOperation(id, instanceID) 463 464 return internal.ProvisioningOperation{Operation: provisioningOperation} 465 } 466 467 func fixSuspensionOperation() internal.DeprovisioningOperation { 468 deprovisioningOperation := fixture.FixDeprovisioningOperation("id", instanceID) 469 deprovisioningOperation.Temporary = true 470 471 return deprovisioningOperation 472 } 473 474 func TestUpdateEndpoint_UpdateGlobalAccountID(t *testing.T) { 475 // given 476 instance := internal.Instance{ 477 InstanceID: instanceID, 478 ServicePlanID: TrialPlanID, 479 GlobalAccountID: "origin-account-id", 480 Parameters: internal.ProvisioningParameters{ 481 PlanID: TrialPlanID, 482 ErsContext: internal.ERSContext{ 483 TenantID: "", 484 SubAccountID: "", 485 GlobalAccountID: "", 486 Active: nil, 487 }, 488 }, 489 } 490 newGlobalAccountID := "updated-account-id" 491 st := storage.NewMemoryStorage() 492 st.Instances().Insert(instance) 493 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 494 st.Operations().InsertDeprovisioningOperation(fixSuspensionOperation()) 495 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("02")) 496 497 handler := &handler{} 498 q := &automock.Queue{} 499 q.On("Add", mock.AnythingOfType("string")) 500 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 501 return &gqlschema.ClusterConfigInput{}, nil 502 } 503 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, true, q, PlansConfig{}, 504 planDefaults, logrus.New(), dashboardConfig) 505 506 // when 507 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 508 ServiceID: "", 509 PlanID: TrialPlanID, 510 RawParameters: nil, 511 PreviousValues: domain.PreviousValues{}, 512 RawContext: json.RawMessage("{\"globalaccount_id\":\"" + newGlobalAccountID + "\", \"active\":true}"), 513 MaintenanceInfo: nil, 514 }, true) 515 require.NoError(t, err) 516 517 // then 518 inst, err := st.Instances().GetByID(instanceID) 519 require.NoError(t, err) 520 // Check if SubscriptionGlobalAccountID is not empty 521 assert.NotEmpty(t, inst.SubscriptionGlobalAccountID) 522 523 // Check if SubscriptionGlobalAccountID is now the same as GlobalAccountID 524 assert.Equal(t, inst.GlobalAccountID, newGlobalAccountID) 525 526 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 527 assert.True(t, *handler.Instance.Parameters.ErsContext.Active) 528 assert.Len(t, response.Metadata.Labels, 1) 529 } 530 531 func TestUpdateEndpoint_UpdateParameters(t *testing.T) { 532 // given 533 instance := fixture.FixInstance(instanceID) 534 st := storage.NewMemoryStorage() 535 st.Instances().Insert(instance) 536 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("provisioning01")) 537 538 handler := &handler{} 539 q := &automock.Queue{} 540 q.On("Add", mock.AnythingOfType("string")) 541 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 542 return &gqlschema.ClusterConfigInput{}, nil 543 } 544 545 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, true, q, PlansConfig{}, 546 planDefaults, logrus.New(), dashboardConfig) 547 548 t.Run("Should fail on invalid OIDC params", func(t *testing.T) { 549 // given 550 oidcParams := `"clientID":"{clientID}","groupsClaim":"groups","issuerURL":"{issuerURL}","signingAlgs":["RS256"],"usernameClaim":"email","usernamePrefix":"-"` 551 errMsg := fmt.Errorf("issuerURL must be a valid URL, issuerURL must have https scheme") 552 expectedErr := apiresponses.NewFailureResponse(errMsg, http.StatusUnprocessableEntity, errMsg.Error()) 553 554 // when 555 _, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 556 ServiceID: "", 557 PlanID: AzurePlanID, 558 RawParameters: json.RawMessage("{\"oidc\":{" + oidcParams + "}}"), 559 PreviousValues: domain.PreviousValues{}, 560 RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"), 561 MaintenanceInfo: nil, 562 }, true) 563 564 // then 565 require.Error(t, err) 566 assert.IsType(t, &apiresponses.FailureResponse{}, err) 567 apierr := err.(*apiresponses.FailureResponse) 568 assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil)) 569 assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction()) 570 }) 571 572 t.Run("Should fail on insufficient OIDC params (missing issuerURL)", func(t *testing.T) { 573 // given 574 oidcParams := `"clientID":"client-id"` 575 errMsg := fmt.Errorf("issuerURL must not be empty") 576 expectedErr := apiresponses.NewFailureResponse(errMsg, http.StatusUnprocessableEntity, errMsg.Error()) 577 578 // when 579 _, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 580 ServiceID: "", 581 PlanID: AzurePlanID, 582 RawParameters: json.RawMessage("{\"oidc\":{" + oidcParams + "}}"), 583 PreviousValues: domain.PreviousValues{}, 584 RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"), 585 MaintenanceInfo: nil, 586 }, true) 587 588 // then 589 require.Error(t, err) 590 assert.IsType(t, &apiresponses.FailureResponse{}, err) 591 apierr := err.(*apiresponses.FailureResponse) 592 assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil)) 593 assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction()) 594 }) 595 596 t.Run("Should fail on insufficient OIDC params (missing clientID)", func(t *testing.T) { 597 // given 598 oidcParams := `"issuerURL":"https://test.local"` 599 errMsg := fmt.Errorf("clientID must not be empty") 600 expectedErr := apiresponses.NewFailureResponse(errMsg, http.StatusUnprocessableEntity, errMsg.Error()) 601 602 // when 603 _, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 604 ServiceID: "", 605 PlanID: AzurePlanID, 606 RawParameters: json.RawMessage("{\"oidc\":{" + oidcParams + "}}"), 607 PreviousValues: domain.PreviousValues{}, 608 RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"), 609 MaintenanceInfo: nil, 610 }, true) 611 612 // then 613 require.Error(t, err) 614 assert.IsType(t, &apiresponses.FailureResponse{}, err) 615 apierr := err.(*apiresponses.FailureResponse) 616 assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil)) 617 assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction()) 618 }) 619 620 t.Run("Should fail on invalid OIDC signingAlgs param", func(t *testing.T) { 621 // given 622 oidcParams := `"clientID":"client-id","issuerURL":"https://test.local","signingAlgs":["RS256","notValid"]` 623 errMsg := fmt.Errorf("signingAlgs must contain valid signing algorithm(s)") 624 expectedErr := apiresponses.NewFailureResponse(errMsg, http.StatusUnprocessableEntity, errMsg.Error()) 625 626 // when 627 _, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 628 ServiceID: "", 629 PlanID: AzurePlanID, 630 RawParameters: json.RawMessage("{\"oidc\":{" + oidcParams + "}}"), 631 PreviousValues: domain.PreviousValues{}, 632 RawContext: json.RawMessage("{\"globalaccount_id\":\"globalaccount_id_1\", \"active\":true}"), 633 MaintenanceInfo: nil, 634 }, true) 635 636 // then 637 require.Error(t, err) 638 assert.IsType(t, &apiresponses.FailureResponse{}, err) 639 apierr := err.(*apiresponses.FailureResponse) 640 assert.Equal(t, expectedErr.ValidatedStatusCode(nil), apierr.ValidatedStatusCode(nil)) 641 assert.Equal(t, expectedErr.LoggerAction(), apierr.LoggerAction()) 642 }) 643 } 644 645 func TestUpdateEndpoint_UpdateWithEnabledDashboard(t *testing.T) { 646 // given 647 instance := internal.Instance{ 648 InstanceID: instanceID, 649 ServicePlanID: TrialPlanID, 650 Parameters: internal.ProvisioningParameters{ 651 PlanID: TrialPlanID, 652 ErsContext: internal.ERSContext{ 653 TenantID: "", 654 SubAccountID: "", 655 GlobalAccountID: "", 656 Active: nil, 657 }, 658 }, 659 DashboardURL: "https://console.cd6e47b.example.com", 660 } 661 st := storage.NewMemoryStorage() 662 st.Instances().Insert(instance) 663 st.Operations().InsertProvisioningOperation(fixProvisioningOperation("01")) 664 // st.Operations().InsertDeprovisioningOperation(fixSuspensionOperation()) 665 // st.Operations().InsertProvisioningOperation(fixProvisioningOperation("02")) 666 667 handler := &handler{} 668 q := &automock.Queue{} 669 q.On("Add", mock.AnythingOfType("string")) 670 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 671 return &gqlschema.ClusterConfigInput{}, nil 672 } 673 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 674 planDefaults, logrus.New(), dashboardConfig) 675 676 // when 677 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 678 ServiceID: "", 679 PlanID: TrialPlanID, 680 RawParameters: nil, 681 PreviousValues: domain.PreviousValues{}, 682 RawContext: json.RawMessage("{\"active\":false}"), 683 MaintenanceInfo: nil, 684 }, true) 685 require.NoError(t, err) 686 687 // then 688 inst, err := st.Instances().GetByID(instanceID) 689 require.NoError(t, err) 690 691 // check if the instance is updated successfully 692 assert.Regexp(t, `^https:\/\/dashboard\.example\.com\/\?kubeconfigID=`, inst.DashboardURL) 693 // check if the API response is correct 694 assert.Regexp(t, `^https:\/\/dashboard\.example\.com\/\?kubeconfigID=`, response.DashboardURL) 695 } 696 697 func TestUpdateEndpoint_UpdateExpirationOfNonTrial(t *testing.T) { 698 // given 699 instance := internal.Instance{ 700 InstanceID: instanceID, 701 ServicePlanID: AWSPlanID, 702 Parameters: internal.ProvisioningParameters{ 703 PlanID: AWSPlanID, 704 ErsContext: internal.ERSContext{ 705 TenantID: "", 706 SubAccountID: "", 707 GlobalAccountID: "", 708 Active: nil, 709 }, 710 }, 711 } 712 713 st := storage.NewMemoryStorage() 714 err := st.Instances().Insert(instance) 715 require.NoError(t, err) 716 717 provisioningOp := fixProvisioningOperation("provisioning-aws-01") 718 err = st.Operations().InsertProvisioningOperation(provisioningOp) 719 require.NoError(t, err) 720 721 handler := &handler{} 722 q := &automock.Queue{} 723 q.On("Add", mock.AnythingOfType("string")) 724 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 725 return &gqlschema.ClusterConfigInput{}, nil 726 } 727 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 728 planDefaults, logrus.New(), dashboardConfig) 729 730 // when 731 _, err = svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 732 ServiceID: "", 733 PlanID: AWSPlanID, 734 RawParameters: json.RawMessage(`{"expired": true}`), 735 PreviousValues: domain.PreviousValues{}, 736 RawContext: json.RawMessage(`{"active":false}`), 737 MaintenanceInfo: nil, 738 }, true) 739 require.Error(t, err) 740 741 // then 742 assert.IsType(t, err, &apiresponses.FailureResponse{}, "Updating returned error of unexpected type") 743 apierr := err.(*apiresponses.FailureResponse) 744 assert.Equal(t, apierr.ValidatedStatusCode(nil), http.StatusBadRequest, "Updating status code not matching") 745 746 require.Nil(t, handler.ersContext.Active) 747 require.Nil(t, handler.Instance.Parameters.ErsContext.Active) 748 inst, err := st.Instances().GetByID(instanceID) 749 require.NoError(t, err) 750 assert.False(t, inst.IsExpired()) 751 } 752 753 func TestUpdateEndpoint_ExpiryOnFailedOperations(t *testing.T) { 754 t.Run("should expire trial on a failed provisioning operation", func(t *testing.T) { 755 // given 756 instance := internal.Instance{ 757 InstanceID: instanceID, 758 ServicePlanID: TrialPlanID, 759 Parameters: internal.ProvisioningParameters{ 760 PlanID: TrialPlanID, 761 ErsContext: internal.ERSContext{ 762 TenantID: "", 763 SubAccountID: "", 764 GlobalAccountID: "", 765 Active: nil, 766 }, 767 }, 768 } 769 770 st := storage.NewMemoryStorage() 771 err := st.Instances().Insert(instance) 772 require.NoError(t, err) 773 774 failedProvisioningOp := fixProvisioningOperation("failed-provisioning-trial-01") 775 failedProvisioningOp.State = domain.Failed 776 err = st.Operations().InsertProvisioningOperation(failedProvisioningOp) 777 require.NoError(t, err) 778 779 handler := &handler{} 780 q := &automock.Queue{} 781 q.On("Add", mock.AnythingOfType("string")) 782 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 783 return &gqlschema.ClusterConfigInput{}, nil 784 } 785 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 786 planDefaults, logrus.New(), dashboardConfig) 787 788 // when 789 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 790 ServiceID: "", 791 PlanID: TrialPlanID, 792 RawParameters: json.RawMessage(`{"expired": true}`), 793 PreviousValues: domain.PreviousValues{}, 794 RawContext: json.RawMessage(`{"active":false}`), 795 MaintenanceInfo: nil, 796 }, true) 797 require.NoError(t, err) 798 799 // then 800 assert.Equal(t, internal.ERSContext{ 801 Active: ptr.Bool(false), 802 }, handler.ersContext) 803 804 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 805 assert.True(t, *handler.Instance.Parameters.ErsContext.Active) 806 assert.Len(t, response.Metadata.Labels, 1) 807 inst, err := st.Instances().GetByID(instanceID) 808 require.NoError(t, err) 809 assert.True(t, inst.IsExpired()) 810 assert.False(t, *inst.Parameters.ErsContext.Active) 811 }) 812 813 t.Run("should return a failure response on a failed provisioning operation when the plan is not trial", func(t *testing.T) { 814 // given 815 instance := internal.Instance{ 816 InstanceID: instanceID, 817 ServicePlanID: AWSPlanID, 818 Parameters: internal.ProvisioningParameters{ 819 PlanID: AWSPlanID, 820 ErsContext: internal.ERSContext{ 821 TenantID: "", 822 SubAccountID: "", 823 GlobalAccountID: "", 824 Active: nil, 825 }, 826 }, 827 } 828 829 st := storage.NewMemoryStorage() 830 err := st.Instances().Insert(instance) 831 require.NoError(t, err) 832 833 failedProvisioningOp := fixProvisioningOperation("failed-provisioning-aws-01") 834 failedProvisioningOp.State = domain.Failed 835 err = st.Operations().InsertProvisioningOperation(failedProvisioningOp) 836 require.NoError(t, err) 837 838 handler := &handler{} 839 q := &automock.Queue{} 840 q.On("Add", mock.AnythingOfType("string")) 841 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 842 return &gqlschema.ClusterConfigInput{}, nil 843 } 844 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 845 planDefaults, logrus.New(), dashboardConfig) 846 847 // when 848 _, err = svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 849 ServiceID: "", 850 PlanID: AWSPlanID, 851 RawParameters: json.RawMessage(``), 852 PreviousValues: domain.PreviousValues{}, 853 RawContext: json.RawMessage(`{"globalaccount_id": "GAID-01"}`), 854 MaintenanceInfo: nil, 855 }, true) 856 require.Error(t, err) 857 858 // then 859 assert.IsType(t, err, &apiresponses.FailureResponse{}, "Updating returned error of unexpected type") 860 apierr := err.(*apiresponses.FailureResponse) 861 assert.Equal(t, apierr.ValidatedStatusCode(nil), http.StatusUnprocessableEntity, "Updating status code not matching") 862 863 require.Nil(t, handler.ersContext.Active) 864 require.Nil(t, handler.Instance.Parameters.ErsContext.Active) 865 inst, err := st.Instances().GetByID(instanceID) 866 require.NoError(t, err) 867 assert.False(t, inst.IsExpired()) 868 }) 869 870 t.Run("should expire trial on a failed deprovisioning operation", func(t *testing.T) { 871 // given 872 instance := internal.Instance{ 873 InstanceID: instanceID, 874 ServicePlanID: TrialPlanID, 875 Parameters: internal.ProvisioningParameters{ 876 PlanID: TrialPlanID, 877 ErsContext: internal.ERSContext{ 878 TenantID: "", 879 SubAccountID: "", 880 GlobalAccountID: "", 881 Active: nil, 882 }, 883 }, 884 } 885 886 st := storage.NewMemoryStorage() 887 err := st.Instances().Insert(instance) 888 require.NoError(t, err) 889 890 provisioningOp := fixProvisioningOperation("provisioning-trial-01") 891 err = st.Operations().InsertProvisioningOperation(provisioningOp) 892 require.NoError(t, err) 893 894 failedDeprovisioningOp := fixture.FixDeprovisioningOperation("failed-deprovisioning-trial-01", instanceID) 895 failedDeprovisioningOp.CreatedAt = time.Now().Add(5 * time.Minute) 896 failedDeprovisioningOp.State = domain.Failed 897 err = st.Operations().InsertDeprovisioningOperation(failedDeprovisioningOp) 898 require.NoError(t, err) 899 900 handler := &handler{} 901 q := &automock.Queue{} 902 q.On("Add", mock.AnythingOfType("string")) 903 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 904 return &gqlschema.ClusterConfigInput{}, nil 905 } 906 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 907 planDefaults, logrus.New(), dashboardConfig) 908 909 // when 910 response, err := svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 911 ServiceID: "", 912 PlanID: TrialPlanID, 913 RawParameters: json.RawMessage(`{"expired": true}`), 914 PreviousValues: domain.PreviousValues{}, 915 RawContext: json.RawMessage(`{"active": false}`), 916 MaintenanceInfo: nil, 917 }, true) 918 require.NoError(t, err) 919 920 // then 921 assert.Equal(t, internal.ERSContext{ 922 Active: ptr.Bool(false), 923 }, handler.ersContext) 924 925 require.NotNil(t, handler.Instance.Parameters.ErsContext.Active) 926 assert.False(t, *handler.Instance.Parameters.ErsContext.Active) 927 assert.Len(t, response.Metadata.Labels, 1) 928 inst, err := st.Instances().GetByID(instanceID) 929 require.NoError(t, err) 930 assert.True(t, inst.IsExpired()) 931 assert.False(t, *inst.Parameters.ErsContext.Active) 932 }) 933 934 t.Run("should return a failure response on a failed deprovisioning operation when the plan is not trial", func(t *testing.T) { 935 // given 936 instance := internal.Instance{ 937 InstanceID: instanceID, 938 ServicePlanID: AWSPlanID, 939 Parameters: internal.ProvisioningParameters{ 940 PlanID: AWSPlanID, 941 ErsContext: internal.ERSContext{ 942 TenantID: "", 943 SubAccountID: "", 944 GlobalAccountID: "", 945 Active: nil, 946 }, 947 }, 948 } 949 950 st := storage.NewMemoryStorage() 951 err := st.Instances().Insert(instance) 952 require.NoError(t, err) 953 954 provisioningOp := fixProvisioningOperation("provisioning-aws-01") 955 err = st.Operations().InsertProvisioningOperation(provisioningOp) 956 require.NoError(t, err) 957 958 failedDeprovisioningOp := fixture.FixDeprovisioningOperation("failed-deprovisioning-aws-01", instanceID) 959 failedDeprovisioningOp.CreatedAt = time.Now().Add(5 * time.Minute) 960 failedDeprovisioningOp.State = domain.Failed 961 err = st.Operations().InsertDeprovisioningOperation(failedDeprovisioningOp) 962 require.NoError(t, err) 963 964 handler := &handler{} 965 q := &automock.Queue{} 966 q.On("Add", mock.AnythingOfType("string")) 967 planDefaults := func(planID string, platformProvider internal.CloudProvider, provider *internal.CloudProvider) (*gqlschema.ClusterConfigInput, error) { 968 return &gqlschema.ClusterConfigInput{}, nil 969 } 970 svc := NewUpdate(Config{}, st.Instances(), st.RuntimeStates(), st.Operations(), handler, true, false, q, PlansConfig{}, 971 planDefaults, logrus.New(), dashboardConfig) 972 973 // when 974 _, err = svc.Update(context.Background(), instanceID, domain.UpdateDetails{ 975 ServiceID: "", 976 PlanID: AWSPlanID, 977 RawParameters: json.RawMessage(``), 978 PreviousValues: domain.PreviousValues{}, 979 RawContext: json.RawMessage(`{"globalaccount_id": "GAID-01"}`), 980 MaintenanceInfo: nil, 981 }, true) 982 require.Error(t, err) 983 984 // then 985 assert.IsType(t, err, &apiresponses.FailureResponse{}, "Updating returned error of unexpected type") 986 apierr := err.(*apiresponses.FailureResponse) 987 assert.Equal(t, apierr.ValidatedStatusCode(nil), http.StatusUnprocessableEntity, "Updating status code not matching") 988 989 require.Nil(t, handler.ersContext.Active) 990 require.Nil(t, handler.Instance.Parameters.ErsContext.Active) 991 inst, err := st.Instances().GetByID(instanceID) 992 require.NoError(t, err) 993 assert.False(t, inst.IsExpired()) 994 }) 995 }