github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/tenantfetchersvc/handler_test.go (about)

     1  package tenantfetchersvc_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  
    12  	"github.com/gorilla/mux"
    13  	"github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc"
    14  	"github.com/kyma-incubator/compass/components/director/internal/tenantfetchersvc/automock"
    15  	"github.com/kyma-incubator/compass/components/director/pkg/persistence/txtest"
    16  	"github.com/pkg/errors"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/mock"
    19  )
    20  
    21  type regionalTenantCreationRequest struct {
    22  	SubaccountID                string `json:"subaccountTenantId"`
    23  	TenantID                    string `json:"tenantId"`
    24  	Subdomain                   string `json:"subdomain"`
    25  	SubscriptionProviderID      string `json:"subscriptionProviderId"`
    26  	ProviderSubaccountID        string `json:"providerSubaccountId"`
    27  	ConsumerTenantID            string `json:"consumerTenantID"`
    28  	SubscriptionProviderAppName string `json:"subscriptionProviderAppName"`
    29  }
    30  
    31  type errReader int
    32  
    33  func (errReader) Read(p []byte) (n int, err error) {
    34  	return 0, errors.New("test error")
    35  }
    36  
    37  func TestService_SubscriptionFlows(t *testing.T) {
    38  	// GIVEN
    39  	region := "eu-1"
    40  
    41  	target := "http://example.com/foo/:region"
    42  	txtest.CtxWithDBMatcher()
    43  
    44  	validRequestBody, err := json.Marshal(regionalTenantCreationRequest{
    45  		SubaccountID:                subaccountTenantExtID,
    46  		TenantID:                    tenantExtID,
    47  		Subdomain:                   regionalTenantSubdomain,
    48  		SubscriptionProviderID:      subscriptionProviderID,
    49  		ProviderSubaccountID:        providerSubaccountID,
    50  		ConsumerTenantID:            consumerTenantID,
    51  		SubscriptionProviderAppName: subscriptionProviderAppName,
    52  	})
    53  	assert.NoError(t, err)
    54  
    55  	bodyWithMissingParent, err := json.Marshal(regionalTenantCreationRequest{
    56  		SubaccountID:                subaccountTenantExtID,
    57  		Subdomain:                   regionalTenantSubdomain,
    58  		SubscriptionProviderID:      subscriptionProviderID,
    59  		ProviderSubaccountID:        providerSubaccountID,
    60  		ConsumerTenantID:            consumerTenantID,
    61  		SubscriptionProviderAppName: subscriptionProviderAppName,
    62  	})
    63  	assert.NoError(t, err)
    64  
    65  	bodyWithMissingTenantSubdomain, err := json.Marshal(regionalTenantCreationRequest{
    66  		SubaccountID:                subaccountTenantExtID,
    67  		TenantID:                    tenantExtID,
    68  		SubscriptionProviderID:      subscriptionProviderID,
    69  		ProviderSubaccountID:        providerSubaccountID,
    70  		ConsumerTenantID:            consumerTenantID,
    71  		SubscriptionProviderAppName: subscriptionProviderAppName,
    72  	})
    73  	assert.NoError(t, err)
    74  
    75  	bodyWithMissingSubscriptionConsumerID, err := json.Marshal(regionalTenantCreationRequest{
    76  		SubaccountID:                subaccountTenantExtID,
    77  		TenantID:                    tenantExtID,
    78  		Subdomain:                   regionalTenantSubdomain,
    79  		ProviderSubaccountID:        providerSubaccountID,
    80  		ConsumerTenantID:            consumerTenantID,
    81  		SubscriptionProviderAppName: subscriptionProviderAppName,
    82  	})
    83  	assert.NoError(t, err)
    84  
    85  	bodyWithMatchingAccountAndSubaccountIDs, err := json.Marshal(regionalTenantCreationRequest{
    86  		TenantID:                    tenantExtID,
    87  		SubaccountID:                tenantExtID,
    88  		Subdomain:                   regionalTenantSubdomain,
    89  		SubscriptionProviderID:      subscriptionProviderID,
    90  		ProviderSubaccountID:        providerSubaccountID,
    91  		ConsumerTenantID:            consumerTenantID,
    92  		SubscriptionProviderAppName: subscriptionProviderAppName,
    93  	})
    94  	assert.NoError(t, err)
    95  
    96  	bodyWithMissingProviderSubaccountID, err := json.Marshal(regionalTenantCreationRequest{
    97  		SubaccountID:                subaccountTenantExtID,
    98  		TenantID:                    tenantExtID,
    99  		Subdomain:                   regionalTenantSubdomain,
   100  		SubscriptionProviderID:      subscriptionProviderID,
   101  		ConsumerTenantID:            consumerTenantID,
   102  		SubscriptionProviderAppName: subscriptionProviderAppName,
   103  	})
   104  	assert.NoError(t, err)
   105  
   106  	bodyWithMissingConsumerTenantID, err := json.Marshal(regionalTenantCreationRequest{
   107  		SubaccountID:                subaccountTenantExtID,
   108  		TenantID:                    tenantExtID,
   109  		Subdomain:                   regionalTenantSubdomain,
   110  		SubscriptionProviderID:      subscriptionProviderID,
   111  		ProviderSubaccountID:        providerSubaccountID,
   112  		SubscriptionProviderAppName: subscriptionProviderAppName,
   113  	})
   114  	assert.NoError(t, err)
   115  
   116  	bodyWithMissingSubscriptionProviderAppName, err := json.Marshal(regionalTenantCreationRequest{
   117  		SubaccountID:           subaccountTenantExtID,
   118  		TenantID:               tenantExtID,
   119  		Subdomain:              regionalTenantSubdomain,
   120  		SubscriptionProviderID: subscriptionProviderID,
   121  		ProviderSubaccountID:   providerSubaccountID,
   122  		ConsumerTenantID:       consumerTenantID,
   123  	})
   124  	assert.NoError(t, err)
   125  
   126  	validHandlerConfig := tenantfetchersvc.HandlerConfig{
   127  		RegionPathParam: "region",
   128  		TenantProviderConfig: tenantfetchersvc.TenantProviderConfig{
   129  			TenantProvider:                      testProviderName,
   130  			TenantIDProperty:                    tenantProviderTenantIDProperty,
   131  			SubaccountTenantIDProperty:          tenantProviderSubaccountTenantIDProperty,
   132  			CustomerIDProperty:                  tenantProviderCustomerIDProperty,
   133  			SubdomainProperty:                   tenantProviderSubdomainProperty,
   134  			SubscriptionProviderIDProperty:      subscriptionProviderIDProperty,
   135  			ProviderSubaccountIDProperty:        providerSubaccountIDProperty,
   136  			ConsumerTenantIDProperty:            consumerTenantIDProperty,
   137  			SubscriptionProviderAppNameProperty: subscriptionProviderAppNameProperty,
   138  		},
   139  	}
   140  	regionalTenant := tenantfetchersvc.TenantSubscriptionRequest{
   141  		SubaccountTenantID:          subaccountTenantExtID,
   142  		AccountTenantID:             tenantExtID,
   143  		Subdomain:                   regionalTenantSubdomain,
   144  		Region:                      region,
   145  		SubscriptionProviderID:      subscriptionProviderID,
   146  		ProviderSubaccountID:        providerSubaccountID,
   147  		ConsumerTenantID:            consumerTenantID,
   148  		SubscriptionProviderAppName: subscriptionProviderAppName,
   149  		SubscriptionPayload:         string(validRequestBody),
   150  	}
   151  
   152  	regionalTenantWithMatchingParentID := tenantfetchersvc.TenantSubscriptionRequest{
   153  		SubaccountTenantID:          "",
   154  		AccountTenantID:             tenantExtID,
   155  		Subdomain:                   regionalTenantSubdomain,
   156  		Region:                      region,
   157  		SubscriptionProviderID:      subscriptionProviderID,
   158  		ProviderSubaccountID:        providerSubaccountID,
   159  		ConsumerTenantID:            consumerTenantID,
   160  		SubscriptionProviderAppName: subscriptionProviderAppName,
   161  		SubscriptionPayload:         string(bodyWithMatchingAccountAndSubaccountIDs),
   162  	}
   163  
   164  	// Subscribe flow
   165  	testCases := []struct {
   166  		Name                  string
   167  		TenantSubscriberFn    func() *automock.TenantSubscriber
   168  		Request               *http.Request
   169  		Region                string
   170  		ExpectedErrorOutput   string
   171  		ExpectedSuccessOutput string
   172  		ExpectedStatusCode    int
   173  	}{
   174  		{
   175  			Name: "Succeeds",
   176  			TenantSubscriberFn: func() *automock.TenantSubscriber {
   177  				subscriber := &automock.TenantSubscriber{}
   178  				subscriber.On("Subscribe", mock.Anything, &regionalTenant).Return(nil).Once()
   179  				return subscriber
   180  			},
   181  			Request:               httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(validRequestBody)),
   182  			Region:                region,
   183  			ExpectedSuccessOutput: compassURL,
   184  			ExpectedStatusCode:    http.StatusOK,
   185  		},
   186  		{
   187  			Name: "Succeeds to create account tenant when account ID and subaccount IDs are matching",
   188  			TenantSubscriberFn: func() *automock.TenantSubscriber {
   189  				subscriber := &automock.TenantSubscriber{}
   190  				subscriber.On("Subscribe", mock.Anything, &regionalTenantWithMatchingParentID).Return(nil).Once()
   191  				return subscriber
   192  			},
   193  			Request:               httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMatchingAccountAndSubaccountIDs)),
   194  			Region:                region,
   195  			ExpectedSuccessOutput: compassURL,
   196  			ExpectedStatusCode:    http.StatusOK,
   197  		},
   198  		{
   199  			Name:                "Returns error when region path parameter is missing",
   200  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   201  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(validRequestBody)),
   202  			ExpectedStatusCode:  http.StatusBadRequest,
   203  			ExpectedErrorOutput: "Region path parameter is missing from request",
   204  		},
   205  		{
   206  			Name:                "Returns error when parent tenant is not found in body",
   207  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   208  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingParent)),
   209  			Region:              region,
   210  			ExpectedStatusCode:  http.StatusBadRequest,
   211  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", tenantProviderTenantIDProperty),
   212  		},
   213  		{
   214  			Name:                "Returns error when SubscriptionProviderID is not found in body",
   215  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   216  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingSubscriptionConsumerID)),
   217  			Region:              region,
   218  			ExpectedStatusCode:  http.StatusBadRequest,
   219  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", subscriptionProviderIDProperty),
   220  		},
   221  		{
   222  			Name:                "Returns error when providerSubaccountIDProperty is not found in body",
   223  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   224  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingProviderSubaccountID)),
   225  			Region:              region,
   226  			ExpectedStatusCode:  http.StatusBadRequest,
   227  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", providerSubaccountIDProperty),
   228  		},
   229  		{
   230  			Name:                "Returns error when consumerTenantIDProperty is not found in body",
   231  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   232  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingConsumerTenantID)),
   233  			Region:              region,
   234  			ExpectedStatusCode:  http.StatusBadRequest,
   235  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", consumerTenantIDProperty),
   236  		},
   237  		{
   238  			Name:                "Returns error when subscriptionProviderAppNameProperty is not found in body",
   239  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   240  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingSubscriptionProviderAppName)),
   241  			Region:              region,
   242  			ExpectedStatusCode:  http.StatusBadRequest,
   243  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", subscriptionProviderAppNameProperty),
   244  		},
   245  		{
   246  			Name:                "Returns error when request body doesn't contain tenant subdomain",
   247  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   248  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(bodyWithMissingTenantSubdomain)),
   249  			Region:              region,
   250  			ExpectedErrorOutput: fmt.Sprintf("mandatory property %q is missing from request body", tenantProviderSubdomainProperty),
   251  			ExpectedStatusCode:  http.StatusBadRequest,
   252  		},
   253  		{
   254  			Name:                "Returns error when reading request body fails",
   255  			TenantSubscriberFn:  func() *automock.TenantSubscriber { return &automock.TenantSubscriber{} },
   256  			Request:             httptest.NewRequest(http.MethodPut, target, errReader(0)),
   257  			Region:              region,
   258  			ExpectedErrorOutput: tenantfetchersvc.InternalServerError,
   259  			ExpectedStatusCode:  http.StatusInternalServerError,
   260  		},
   261  		{
   262  			Name: "Returns error when tenant subscription fails",
   263  			TenantSubscriberFn: func() *automock.TenantSubscriber {
   264  				subscriber := &automock.TenantSubscriber{}
   265  				subscriber.On("Subscribe", mock.Anything, &regionalTenant).Return(testError).Once()
   266  				return subscriber
   267  			},
   268  			Request:             httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(validRequestBody)),
   269  			Region:              region,
   270  			ExpectedStatusCode:  http.StatusInternalServerError,
   271  			ExpectedErrorOutput: tenantfetchersvc.InternalServerError,
   272  		},
   273  	}
   274  
   275  	for _, testCase := range testCases {
   276  		t.Run(testCase.Name, func(t *testing.T) {
   277  			subscriber := testCase.TenantSubscriberFn()
   278  			defer mock.AssertExpectationsForObjects(t, subscriber)
   279  
   280  			handler := tenantfetchersvc.NewTenantsHTTPHandler(subscriber, validHandlerConfig)
   281  			req := testCase.Request
   282  
   283  			if len(testCase.Region) > 0 {
   284  				vars := map[string]string{
   285  					"region": testCase.Region,
   286  				}
   287  				req = mux.SetURLVars(req, vars)
   288  			}
   289  
   290  			w := httptest.NewRecorder()
   291  
   292  			// WHEN
   293  			handler.SubscribeTenant(w, req)
   294  
   295  			// THEN
   296  			resp := w.Result()
   297  			body, err := io.ReadAll(resp.Body)
   298  			assert.NoError(t, err)
   299  
   300  			if len(testCase.ExpectedErrorOutput) > 0 {
   301  				assert.Contains(t, string(body), testCase.ExpectedErrorOutput)
   302  			} else {
   303  				assert.NoError(t, err)
   304  			}
   305  
   306  			if testCase.ExpectedSuccessOutput != "" {
   307  				assert.Equal(t, testCase.ExpectedSuccessOutput, string(body))
   308  			}
   309  
   310  			assert.Equal(t, testCase.ExpectedStatusCode, resp.StatusCode)
   311  		})
   312  	}
   313  
   314  	// Unsubscribe flow
   315  	t.Run("Unsubscribe", func(t *testing.T) {
   316  		subscriber := &automock.TenantSubscriber{}
   317  		subscriber.On("Unsubscribe", mock.Anything, &regionalTenant).Return(testError).Once()
   318  		defer mock.AssertExpectationsForObjects(t, subscriber)
   319  
   320  		handler := tenantfetchersvc.NewTenantsHTTPHandler(subscriber, validHandlerConfig)
   321  		req := httptest.NewRequest(http.MethodPut, target, bytes.NewBuffer(validRequestBody))
   322  
   323  		vars := map[string]string{
   324  			validHandlerConfig.RegionPathParam: region,
   325  		}
   326  		req = mux.SetURLVars(req, vars)
   327  
   328  		w := httptest.NewRecorder()
   329  
   330  		// WHEN
   331  		handler.UnSubscribeTenant(w, req)
   332  
   333  		// THEN
   334  		resp := w.Result()
   335  		body, err := io.ReadAll(resp.Body)
   336  		assert.NoError(t, err)
   337  
   338  		assert.Contains(t, string(body), tenantfetchersvc.InternalServerError)
   339  		assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
   340  	})
   341  }
   342  
   343  func TestService_Dependencies(t *testing.T) {
   344  	const (
   345  		regionPathVar  = "region"
   346  		missingRegion  = "eu-2"
   347  		existingRegion = "eu-1"
   348  		xsappname      = "xsappname"
   349  	)
   350  	target := fmt.Sprintf("/v1/regional/:%s/dependencies", regionPathVar)
   351  
   352  	subscriberSvc := &automock.TenantSubscriber{}
   353  
   354  	validHandlerConfig := tenantfetchersvc.HandlerConfig{
   355  		RegionPathParam: "region",
   356  		RegionToDependenciesConfig: map[string][]tenantfetchersvc.Dependency{
   357  			existingRegion: []tenantfetchersvc.Dependency{
   358  				tenantfetchersvc.Dependency{Xsappname: xsappname},
   359  			},
   360  		},
   361  		OmitDependenciesCallbackParam:      "omit",
   362  		OmitDependenciesCallbackParamValue: "true",
   363  	}
   364  
   365  	validResponse := fmt.Sprintf("[{\"xsappname\":\"%s\"}]", xsappname)
   366  	validOmitParam := fmt.Sprintf("?%s=%s", validHandlerConfig.OmitDependenciesCallbackParam, validHandlerConfig.OmitDependenciesCallbackParamValue)
   367  	invalidOmitParam := fmt.Sprintf("?%s=%s-invalid", validHandlerConfig.OmitDependenciesCallbackParam, validHandlerConfig.OmitDependenciesCallbackParamValue)
   368  
   369  	testCases := []struct {
   370  		Name                  string
   371  		Request               *http.Request
   372  		PathParams            map[string]string
   373  		ExpectedErrorOutput   string
   374  		ExpectedStatusCode    int
   375  		ExpectedSuccessOutput string
   376  	}{
   377  		{
   378  			Name:                "Failure when region path param is missing",
   379  			Request:             httptest.NewRequest(http.MethodGet, target, nil),
   380  			PathParams:          map[string]string{},
   381  			ExpectedStatusCode:  http.StatusBadRequest,
   382  			ExpectedErrorOutput: "Region path parameter is missing from request",
   383  		},
   384  		{
   385  			Name:                "Failure when region path param is missing and omit is enabled",
   386  			Request:             httptest.NewRequest(http.MethodGet, target+validOmitParam, nil),
   387  			PathParams:          map[string]string{},
   388  			ExpectedStatusCode:  http.StatusBadRequest,
   389  			ExpectedErrorOutput: "Region path parameter is missing from request",
   390  		},
   391  		{
   392  			Name:    "Failure when region is invalid",
   393  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   394  			PathParams: map[string]string{
   395  				regionPathVar: missingRegion,
   396  			},
   397  			ExpectedStatusCode:  http.StatusBadRequest,
   398  			ExpectedErrorOutput: fmt.Sprintf("Invalid region provided: %s", missingRegion),
   399  		},
   400  		{
   401  			Name:    "Success when existing region is provided",
   402  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   403  			PathParams: map[string]string{
   404  				regionPathVar: existingRegion,
   405  			},
   406  			ExpectedStatusCode:    http.StatusOK,
   407  			ExpectedSuccessOutput: validResponse,
   408  		},
   409  		{
   410  			Name:    "Empty dependencies list when existing region is provided and omit param matches",
   411  			Request: httptest.NewRequest(http.MethodGet, target+validOmitParam, nil),
   412  			PathParams: map[string]string{
   413  				regionPathVar: existingRegion,
   414  			},
   415  			ExpectedStatusCode:    http.StatusOK,
   416  			ExpectedSuccessOutput: "[]",
   417  		},
   418  		{
   419  			Name:    "Dependencies list when existing region is provided and omit param does not match",
   420  			Request: httptest.NewRequest(http.MethodGet, target+invalidOmitParam, nil),
   421  			PathParams: map[string]string{
   422  				regionPathVar: existingRegion,
   423  			},
   424  			ExpectedStatusCode:    http.StatusOK,
   425  			ExpectedSuccessOutput: validResponse,
   426  		},
   427  	}
   428  
   429  	for _, testCase := range testCases {
   430  		t.Run(testCase.Name, func(t *testing.T) {
   431  			defer mock.AssertExpectationsForObjects(t, subscriberSvc)
   432  
   433  			handler := tenantfetchersvc.NewTenantsHTTPHandler(subscriberSvc, validHandlerConfig)
   434  			req := testCase.Request
   435  			req = mux.SetURLVars(req, testCase.PathParams)
   436  
   437  			w := httptest.NewRecorder()
   438  
   439  			// WHEN
   440  			handler.Dependencies(w, req)
   441  
   442  			// THEN
   443  			resp := w.Result()
   444  			body, err := io.ReadAll(resp.Body)
   445  			assert.NoError(t, err)
   446  
   447  			if len(testCase.ExpectedErrorOutput) > 0 {
   448  				assert.Contains(t, string(body), testCase.ExpectedErrorOutput)
   449  			} else {
   450  				assert.NoError(t, err)
   451  			}
   452  
   453  			if testCase.ExpectedSuccessOutput != "" {
   454  				assert.Equal(t, testCase.ExpectedSuccessOutput, string(body))
   455  			}
   456  
   457  			assert.Equal(t, testCase.ExpectedStatusCode, resp.StatusCode)
   458  		})
   459  	}
   460  }
   461  func TestService_FetchTenantOnDemand(t *testing.T) {
   462  	const (
   463  		parentIDPathVar = "tenantId"
   464  		tenantIDPathVar = "parentTenantId"
   465  		parentID        = "fd116270-b71d-4c49-a4d7-4a03785a5e6a"
   466  		tenantID        = "f09ba084-0e82-49ab-ab2e-b7ecc988312d"
   467  	)
   468  
   469  	target := fmt.Sprintf("/v1/fetch/:%s/:%s", parentIDPathVar, tenantIDPathVar)
   470  
   471  	validHandlerConfig := tenantfetchersvc.HandlerConfig{
   472  		TenantPathParam:       "tenantId",
   473  		ParentTenantPathParam: "parentTenantId",
   474  	}
   475  
   476  	testCases := []struct {
   477  		Name                string
   478  		Request             *http.Request
   479  		PathParams          map[string]string
   480  		TenantFetcherSvc    func() *automock.TenantFetcher
   481  		ExpectedErrorOutput string
   482  		ExpectedStatusCode  int
   483  	}{
   484  		{
   485  			Name:    "Successful fetch on-demand",
   486  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   487  			PathParams: map[string]string{
   488  				validHandlerConfig.ParentTenantPathParam: parentID,
   489  				validHandlerConfig.TenantPathParam:       tenantID,
   490  			},
   491  			TenantFetcherSvc: func() *automock.TenantFetcher {
   492  				svc := &automock.TenantFetcher{}
   493  				svc.On("SynchronizeTenant", mock.Anything, parentID, tenantID).Return(nil)
   494  				return svc
   495  			},
   496  			ExpectedStatusCode: http.StatusOK,
   497  		},
   498  		{
   499  			Name:    "Failure when parent ID is missing",
   500  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   501  			PathParams: map[string]string{
   502  				validHandlerConfig.ParentTenantPathParam: "",
   503  				validHandlerConfig.TenantPathParam:       tenantID,
   504  			},
   505  			TenantFetcherSvc:    func() *automock.TenantFetcher { return &automock.TenantFetcher{} },
   506  			ExpectedStatusCode:  http.StatusBadRequest,
   507  			ExpectedErrorOutput: "Parent tenant ID path parameter is missing from request",
   508  		},
   509  		{
   510  			Name:    "Failure when tenant ID is missing",
   511  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   512  			PathParams: map[string]string{
   513  				validHandlerConfig.ParentTenantPathParam: parentIDPathVar,
   514  				validHandlerConfig.TenantPathParam:       "",
   515  			},
   516  			TenantFetcherSvc:    func() *automock.TenantFetcher { return &automock.TenantFetcher{} },
   517  			ExpectedStatusCode:  http.StatusBadRequest,
   518  			ExpectedErrorOutput: "Tenant path parameter is missing from request",
   519  		},
   520  		{
   521  			Name:    "Failure when fetch on-demand returns an error",
   522  			Request: httptest.NewRequest(http.MethodGet, target, nil),
   523  			PathParams: map[string]string{
   524  				validHandlerConfig.ParentTenantPathParam: parentID,
   525  				validHandlerConfig.TenantPathParam:       tenantID,
   526  			},
   527  			TenantFetcherSvc: func() *automock.TenantFetcher {
   528  				svc := &automock.TenantFetcher{}
   529  				svc.On("SynchronizeTenant", mock.Anything, parentID, tenantID).Return(errors.New("error"))
   530  				return svc
   531  			},
   532  			ExpectedStatusCode: http.StatusInternalServerError,
   533  		},
   534  	}
   535  	for _, testCase := range testCases {
   536  		t.Run(testCase.Name, func(t *testing.T) {
   537  			tf := testCase.TenantFetcherSvc()
   538  			defer mock.AssertExpectationsForObjects(t, tf)
   539  
   540  			handler := tenantfetchersvc.NewTenantFetcherHTTPHandler(tf, validHandlerConfig)
   541  			req := testCase.Request
   542  			req = mux.SetURLVars(req, testCase.PathParams)
   543  
   544  			w := httptest.NewRecorder()
   545  
   546  			// WHEN
   547  			handler.FetchTenantOnDemand(w, req)
   548  
   549  			// THEN
   550  			resp := w.Result()
   551  			body, err := io.ReadAll(resp.Body)
   552  			assert.NoError(t, err)
   553  
   554  			if len(testCase.ExpectedErrorOutput) > 0 {
   555  				assert.Contains(t, string(body), testCase.ExpectedErrorOutput)
   556  			} else {
   557  				assert.NoError(t, err)
   558  			}
   559  
   560  			assert.Equal(t, testCase.ExpectedStatusCode, resp.StatusCode)
   561  		})
   562  	}
   563  }