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  }