github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/systemfetcher/client_test.go (about) 1 package systemfetcher_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "net/http/httptest" 8 "sync" 9 "testing" 10 "time" 11 12 "github.com/kyma-incubator/compass/components/director/internal/model" 13 14 "github.com/kyma-incubator/compass/components/director/pkg/oauth" 15 16 "github.com/kyma-incubator/compass/components/director/internal/systemfetcher" 17 "github.com/stretchr/testify/require" 18 ) 19 20 var fourSystemsResp = `[{ 21 "displayName": "name1", 22 "productDescription": "description", 23 "productId": "FSM", 24 "ppmsProductVersionId": "123456", 25 "baseUrl": "url", 26 "infrastructureProvider": "provider1", 27 "templateProp": "type1" 28 }, 29 { 30 "displayName": "name2", 31 "productDescription": "description", 32 "productId": "FSM", 33 "ppmsProductVersionId": "123456", 34 "baseUrl": "url", 35 "infrastructureProvider": "provider1", 36 "templateProp": "type1" 37 }, 38 { 39 "displayName": "name3", 40 "productDescription": "description", 41 "productId": "FSM", 42 "ppmsProductVersionId": "123456", 43 "baseUrl": "url", 44 "infrastructureProvider": "provider1", 45 "templateProp": "type1" 46 }, { 47 "displayName": "name4", 48 "productDescription": "description", 49 "productId": "FSM", 50 "ppmsProductVersionId": "123456", 51 "baseUrl": "url", 52 "infrastructureProvider": "provider1", 53 "templateProp": "type1" 54 }]` 55 56 func TestFetchSystemsForTenant(t *testing.T) { 57 systemsJSON, err := json.Marshal(fixSystems()) 58 require.NoError(t, err) 59 60 mock, url := fixHTTPClient(t) 61 mock.bodiesToReturn = [][]byte{systemsJSON} 62 mock.expectedFilterCriteria = "" 63 64 sourceKey := "key" 65 labelFilter := "templateProp" 66 67 tenantID := "tenantId1" 68 syncTimestampID := "timestampId1" 69 70 systemfetcher.SystemSourceKey = sourceKey 71 systemfetcher.ApplicationTemplateLabelFilter = labelFilter 72 73 var mutex sync.Mutex 74 client := systemfetcher.NewClient(systemfetcher.APIConfig{ 75 Endpoint: url + "/fetch", 76 FilterCriteria: "%s", 77 PageSize: 4, 78 PagingSkipParam: "$skip", 79 PagingSizeParam: "$top", 80 SystemSourceKey: sourceKey, 81 SystemRPSLimit: 15, 82 }, mock.httpClient) 83 84 t.Run("Success", func(t *testing.T) { 85 mock.callNumber = 0 86 mock.pageCount = 1 87 systems, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 88 require.NoError(t, err) 89 require.Len(t, systems, 1) 90 require.Equal(t, systems[0].TemplateID, "") 91 }) 92 93 t.Run("Success with template mappings", func(t *testing.T) { 94 mock.expectedFilterCriteria = "(key eq 'type1')" 95 96 systemfetcher.ApplicationTemplates = []systemfetcher.TemplateMapping{ 97 { 98 AppTemplate: &model.ApplicationTemplate{ 99 ID: "type1", 100 }, 101 Labels: map[string]*model.Label{ 102 labelFilter: { 103 Key: labelFilter, 104 Value: "type1", 105 }, 106 }, 107 }, 108 } 109 mock.bodiesToReturn = [][]byte{[]byte(`[{ 110 "displayName": "name1", 111 "productDescription": "description", 112 "baseUrl": "url", 113 "infrastructureProvider": "provider1", 114 "key": "type1" 115 }, { 116 "displayName": "name2", 117 "productDescription": "description", 118 "baseUrl": "url", 119 "infrastructureProvider": "provider1", 120 "key": "type2" 121 }]`)} 122 mock.callNumber = 0 123 mock.pageCount = 1 124 systems, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 125 require.NoError(t, err) 126 require.Len(t, systems, 2) 127 require.Equal(t, systems[0].TemplateID, "type1") 128 require.Equal(t, systems[1].TemplateID, "") 129 }) 130 131 t.Run("Success with template mappings and SystemSynchronizationTimestamps exist", func(t *testing.T) { 132 mock.expectedFilterCriteria = "(key eq 'type1' and lastChangeDateTime gt '2023-05-02 20:30:00 +0000 UTC')" 133 134 systemfetcher.ApplicationTemplates = []systemfetcher.TemplateMapping{ 135 { 136 AppTemplate: &model.ApplicationTemplate{ 137 ID: "type1", 138 }, 139 Labels: map[string]*model.Label{ 140 labelFilter: { 141 Key: labelFilter, 142 Value: "type1", 143 }, 144 }, 145 }, 146 } 147 148 systemfetcher.SystemSynchronizationTimestamps = map[string]map[string]systemfetcher.SystemSynchronizationTimestamp{ 149 tenantID: { 150 "type1": { 151 ID: syncTimestampID, 152 LastSyncTimestamp: time.Date(2023, 5, 2, 20, 30, 0, 0, time.UTC).UTC(), 153 }, 154 }, 155 } 156 157 mock.bodiesToReturn = [][]byte{[]byte(`[{ 158 "displayName": "name1", 159 "productDescription": "description", 160 "baseUrl": "url", 161 "infrastructureProvider": "provider1", 162 "key": "type1" 163 }, { 164 "displayName": "name2", 165 "productDescription": "description", 166 "baseUrl": "url", 167 "infrastructureProvider": "provider1", 168 "key": "type2" 169 }]`)} 170 mock.callNumber = 0 171 mock.pageCount = 1 172 systems, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 173 require.NoError(t, err) 174 require.Len(t, systems, 2) 175 require.Equal(t, systems[0].TemplateID, "type1") 176 require.Equal(t, systems[1].TemplateID, "") 177 178 systemfetcher.SystemSynchronizationTimestamps = nil 179 }) 180 181 t.Run("Success for more than one page", func(t *testing.T) { 182 mock.expectedFilterCriteria = "(key eq 'type1')" 183 184 systemfetcher.ApplicationTemplates = []systemfetcher.TemplateMapping{ 185 { 186 AppTemplate: &model.ApplicationTemplate{ 187 ID: "type1", 188 }, 189 Labels: map[string]*model.Label{ 190 labelFilter: { 191 Key: labelFilter, 192 Value: "type1", 193 }, 194 }, 195 }, 196 } 197 198 mock.bodiesToReturn = [][]byte{ 199 []byte(fourSystemsResp), 200 []byte(`[{ 201 "displayName": "name5", 202 "productDescription": "description", 203 "baseUrl": "url", 204 "infrastructureProvider": "provider1", 205 "key": "type1" 206 }]`)} 207 mock.callNumber = 0 208 mock.pageCount = 2 209 systems, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 210 require.NoError(t, err) 211 require.Len(t, systems, 5) 212 }) 213 214 t.Run("Does not map to the last template mapping if haven't matched before", func(t *testing.T) { 215 mock.expectedFilterCriteria = "(key eq 'type1') or (key eq 'type2') or (key eq 'type3')" 216 systemfetcher.ApplicationTemplates = []systemfetcher.TemplateMapping{ 217 { 218 AppTemplate: &model.ApplicationTemplate{ 219 ID: "type1", 220 }, 221 Labels: map[string]*model.Label{ 222 labelFilter: { 223 Key: labelFilter, 224 Value: "type1", 225 }, 226 }, 227 }, 228 { 229 AppTemplate: &model.ApplicationTemplate{ 230 ID: "type2", 231 }, 232 Labels: map[string]*model.Label{ 233 labelFilter: { 234 Key: labelFilter, 235 Value: "type2", 236 }, 237 }, 238 }, 239 { 240 AppTemplate: &model.ApplicationTemplate{ 241 ID: "type3", 242 }, 243 Labels: map[string]*model.Label{ 244 labelFilter: { 245 Key: labelFilter, 246 Value: "type3", 247 }, 248 }, 249 }, 250 } 251 252 mock.bodiesToReturn = [][]byte{[]byte(`[{ 253 "displayName": "name1", 254 "productDescription": "description", 255 "baseUrl": "url", 256 "infrastructureProvider": "provider1", 257 "key": "type1" 258 }, { 259 "displayName": "name2", 260 "productDescription": "description", 261 "baseUrl": "url", 262 "infrastructureProvider": "provider1", 263 "key": "type2" 264 }, { 265 "displayName": "name3", 266 "productDescription": "description", 267 "baseUrl": "url", 268 "infrastructureProvider": "provider1", 269 "key": "type4" 270 }]`)} 271 mock.callNumber = 0 272 mock.pageCount = 1 273 systems, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 274 require.NoError(t, err) 275 require.Len(t, systems, 3) 276 require.Equal(t, systems[0].TemplateID, "type1") 277 require.Equal(t, systems[1].TemplateID, "type2") 278 require.Equal(t, systems[2].TemplateID, "") 279 }) 280 281 t.Run("Fail with unexpected status code", func(t *testing.T) { 282 mock.callNumber = 0 283 mock.pageCount = 1 284 mock.statusCodeToReturn = http.StatusBadRequest 285 _, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 286 require.Contains(t, err.Error(), "unexpected status code") 287 }) 288 289 t.Run("Fail because response body is not JSON", func(t *testing.T) { 290 mock.callNumber = 0 291 mock.pageCount = 1 292 mock.bodiesToReturn = [][]byte{[]byte("not a JSON")} 293 mock.statusCodeToReturn = http.StatusOK 294 _, err := client.FetchSystemsForTenant(context.Background(), "tenant1", &mutex) 295 require.Contains(t, err.Error(), "failed to unmarshal systems response") 296 }) 297 } 298 299 type mockData struct { 300 expectedFilterCriteria string 301 statusCodeToReturn int 302 bodiesToReturn [][]byte 303 httpClient systemfetcher.APIClient 304 callNumber int 305 pageCount int 306 } 307 308 func fixHTTPClient(t *testing.T) (*mockData, string) { 309 mux := http.NewServeMux() 310 requests := []string{} 311 312 mock := mockData{ 313 callNumber: 1, 314 } 315 mux.HandleFunc("/fetch", func(w http.ResponseWriter, r *http.Request) { 316 filter := r.URL.Query().Get("$filter") 317 require.Equal(t, mock.expectedFilterCriteria, filter) 318 319 requests = append(requests, filter) 320 w.Header().Set("Content-Type", "application/json") 321 if mock.statusCodeToReturn == 0 { 322 mock.statusCodeToReturn = http.StatusOK 323 } 324 w.WriteHeader(mock.statusCodeToReturn) 325 326 if mock.statusCodeToReturn == http.StatusOK { 327 index := mock.callNumber % mock.pageCount //this way each of the body to return mocks will be returned once for both filter criteria 328 _, err := w.Write(mock.bodiesToReturn[index]) 329 require.NoError(t, err) 330 } else { 331 _, err := w.Write([]byte{}) 332 require.NoError(t, err) 333 } 334 mock.callNumber++ 335 }) 336 337 ts := httptest.NewServer(mux) 338 mock.httpClient = systemfetcher.NewOauthClient(oauth.Config{}, ts.Client()) 339 340 return &mock, ts.URL 341 } 342 343 func fixSystems() []systemfetcher.System { 344 return []systemfetcher.System{ 345 { 346 SystemPayload: map[string]interface{}{ 347 "displayName": "System1", 348 "productDescription": "System1 description", 349 "baseUrl": "http://example1.com", 350 "infrastructureProvider": "test", 351 "additionalUrls": map[string]string{"mainUrl": "http://mainurl.com"}, 352 }, 353 StatusCondition: model.ApplicationStatusConditionInitial, 354 }, 355 } 356 } 357 358 func fixSystemsWithTbt() []systemfetcher.System { 359 return []systemfetcher.System{ 360 { 361 SystemPayload: map[string]interface{}{ 362 "displayName": "System2", 363 "productDescription": "System2 description", 364 "baseUrl": "http://example2.com", 365 "infrastructureProvider": "test", 366 "additionalUrls": map[string]string{"mainUrl": "http://mainurl.com"}, 367 "businessTypeId": "Test business type id", 368 "businessTypeDescription": "Test business description", 369 }, 370 StatusCondition: model.ApplicationStatusConditionInitial, 371 }, 372 } 373 }