github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/domain/runtime/repository_test.go (about) 1 package runtime_test 2 3 import ( 4 "context" 5 "database/sql/driver" 6 "fmt" 7 "regexp" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 12 "github.com/google/uuid" 13 "github.com/kyma-incubator/compass/components/director/pkg/pagination" 14 "github.com/kyma-incubator/compass/components/director/pkg/str" 15 16 "github.com/DATA-DOG/go-sqlmock" 17 "github.com/kyma-incubator/compass/components/director/internal/domain/runtime" 18 "github.com/kyma-incubator/compass/components/director/internal/domain/runtime/automock" 19 "github.com/kyma-incubator/compass/components/director/internal/labelfilter" 20 "github.com/kyma-incubator/compass/components/director/internal/model" 21 "github.com/kyma-incubator/compass/components/director/internal/repo/testdb" 22 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func TestPgRepository_GetByID(t *testing.T) { 27 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 28 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 29 30 suite := testdb.RepoGetTestSuite{ 31 Name: "Get Runtime By ID", 32 SQLQueryDetails: []testdb.SQLQueryDetails{ 33 { 34 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes WHERE id = $1 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $2))`), 35 Args: []driver.Value{runtimeID, tenantID}, 36 IsSelect: true, 37 ValidRowsProvider: func() []*sqlmock.Rows { 38 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns).AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace)} 39 }, 40 InvalidRowsProvider: func() []*sqlmock.Rows { 41 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 42 }, 43 }, 44 }, 45 ConverterMockProvider: func() testdb.Mock { 46 return &automock.EntityConverter{} 47 }, 48 RepoConstructorFunc: runtime.NewRepository, 49 ExpectedModelEntity: rtModel, 50 ExpectedDBEntity: rtEntity, 51 MethodArgs: []interface{}{tenantID, runtimeID}, 52 DisableConverterErrorTest: true, 53 } 54 55 suite.Run(t) 56 } 57 58 func TestPgRepository_GetByFiltersAndID(t *testing.T) { 59 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 60 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 61 62 suite := testdb.RepoGetTestSuite{ 63 Name: "Get Runtime By Filters and ID", 64 SQLQueryDetails: []testdb.SQLQueryDetails{ 65 { 66 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes WHERE id = $1 67 AND id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $2)) AND "key" = $3 AND "value" ?| array[$4]) 68 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $5))`), 69 Args: []driver.Value{runtimeID, tenantID, model.ScenariosKey, "scenario", tenantID}, 70 IsSelect: true, 71 ValidRowsProvider: func() []*sqlmock.Rows { 72 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns).AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace)} 73 }, 74 InvalidRowsProvider: func() []*sqlmock.Rows { 75 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 76 }, 77 }, 78 }, 79 ConverterMockProvider: func() testdb.Mock { 80 return &automock.EntityConverter{} 81 }, 82 RepoConstructorFunc: runtime.NewRepository, 83 ExpectedModelEntity: rtModel, 84 ExpectedDBEntity: rtEntity, 85 MethodName: "GetByFiltersAndID", 86 MethodArgs: []interface{}{tenantID, runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}}, 87 DisableConverterErrorTest: true, 88 } 89 90 suite.Run(t) 91 } 92 93 func TestPgRepository_GetByFiltersAndIDUsingUnion(t *testing.T) { 94 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 95 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 96 97 suite := testdb.RepoGetTestSuite{ 98 Name: "Get Runtime By Filters and ID", 99 SQLQueryDetails: []testdb.SQLQueryDetails{ 100 { 101 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace 102 FROM public.runtimes 103 WHERE id = $1 AND 104 id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $2)) AND "key" = $3 AND "value" ?| array[$4] 105 UNION 106 SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $5)) AND "key" = $6 AND "value" ?| array[$7]) 107 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $8))`), 108 Args: []driver.Value{runtimeID, tenantID, "runtimeType", "runtimeType1", tenantID, "runtimeType", "runtimeType2", tenantID}, 109 IsSelect: true, 110 ValidRowsProvider: func() []*sqlmock.Rows { 111 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns).AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace)} 112 }, 113 InvalidRowsProvider: func() []*sqlmock.Rows { 114 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 115 }, 116 }, 117 }, 118 ConverterMockProvider: func() testdb.Mock { 119 return &automock.EntityConverter{} 120 }, 121 RepoConstructorFunc: runtime.NewRepository, 122 ExpectedModelEntity: rtModel, 123 ExpectedDBEntity: rtEntity, 124 MethodName: "GetByFiltersAndIDUsingUnion", 125 MethodArgs: []interface{}{tenantID, runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery("runtimeType", `$[*] ? (@ == "runtimeType1")`), labelfilter.NewForKeyWithQuery("runtimeType", `$[*] ? (@ == "runtimeType2")`)}}, 126 DisableConverterErrorTest: true, 127 } 128 129 suite.Run(t) 130 } 131 132 func TestPgRepository_GetByFilters(t *testing.T) { 133 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 134 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 135 136 suite := testdb.RepoGetTestSuite{ 137 Name: "Get Runtime By Filters", 138 SQLQueryDetails: []testdb.SQLQueryDetails{ 139 { 140 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes WHERE 141 id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 142 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4))`), 143 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 144 IsSelect: true, 145 ValidRowsProvider: func() []*sqlmock.Rows { 146 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns).AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace)} 147 }, 148 InvalidRowsProvider: func() []*sqlmock.Rows { 149 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 150 }, 151 }, 152 }, 153 ConverterMockProvider: func() testdb.Mock { 154 return &automock.EntityConverter{} 155 }, 156 RepoConstructorFunc: runtime.NewRepository, 157 ExpectedModelEntity: rtModel, 158 ExpectedDBEntity: rtEntity, 159 MethodName: "GetByFilters", 160 MethodArgs: []interface{}{tenantID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}}, 161 DisableConverterErrorTest: true, 162 } 163 164 suite.Run(t) 165 } 166 167 func TestPgRepository_GetByFiltersGlobal_ShouldReturnRuntimeModelForRuntimeEntity(t *testing.T) { 168 // GIVEN 169 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 170 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 171 172 mockConverter := &automock.EntityConverter{} 173 mockConverter.On("FromEntity", rtEntity).Return(rtModel, nil).Once() 174 175 sqlxDB, sqlMock := testdb.MockDatabase(t) 176 defer sqlMock.AssertExpectations(t) 177 178 rows := sqlmock.NewRows([]string{"id", "name", "description", "status_condition", "status_timestamp", "creation_timestamp", "application_namespace"}). 179 AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace) 180 181 sqlMock.ExpectQuery(`^SELECT (.+) FROM public.runtimes WHERE id IN \(SELECT "runtime_id" FROM public\.labels WHERE "runtime_id" IS NOT NULL AND "key" = \$1\)$`). 182 WithArgs("someKey"). 183 WillReturnRows(rows) 184 185 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 186 187 pgRepository := runtime.NewRepository(mockConverter) 188 189 // WHEN 190 filters := []*labelfilter.LabelFilter{labelfilter.NewForKey("someKey")} 191 modelRuntime, err := pgRepository.GetByFiltersGlobal(ctx, filters) 192 193 // THEN 194 require.NoError(t, err) 195 require.Equal(t, rtModel, modelRuntime) 196 mockConverter.AssertExpectations(t) 197 } 198 199 func TestPgRepository_GetOldestForFilters(t *testing.T) { 200 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 201 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 202 203 suite := testdb.RepoGetTestSuite{ 204 Name: "Get Oldest Runtime By Filters", 205 SQLQueryDetails: []testdb.SQLQueryDetails{ 206 { 207 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes WHERE 208 id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 209 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4)) ORDER BY creation_timestamp ASC`), 210 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 211 IsSelect: true, 212 ValidRowsProvider: func() []*sqlmock.Rows { 213 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns).AddRow(rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace)} 214 }, 215 InvalidRowsProvider: func() []*sqlmock.Rows { 216 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 217 }, 218 }, 219 }, 220 ConverterMockProvider: func() testdb.Mock { 221 return &automock.EntityConverter{} 222 }, 223 RepoConstructorFunc: runtime.NewRepository, 224 ExpectedModelEntity: rtModel, 225 ExpectedDBEntity: rtEntity, 226 MethodName: "GetOldestForFilters", 227 MethodArgs: []interface{}{tenantID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}}, 228 DisableConverterErrorTest: true, 229 } 230 231 suite.Run(t) 232 } 233 234 func TestPgRepository_ListByFiltersGlobal(t *testing.T) { 235 // GIVEN 236 runtime1ID := uuid.New().String() 237 runtime2ID := uuid.New().String() 238 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 239 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 240 241 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 242 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 243 244 mockConverter := &automock.EntityConverter{} 245 mockConverter.On("FromEntity", runtimeEntity1).Return(runtimeModel1) 246 mockConverter.On("FromEntity", runtimeEntity2).Return(runtimeModel2) 247 248 sqlxDB, sqlMock := testdb.MockDatabase(t) 249 defer sqlMock.AssertExpectations(t) 250 251 rows := sqlmock.NewRows([]string{"id", "name", "description", "status_condition", "status_timestamp", "creation_timestamp", "application_namespace"}). 252 AddRow(runtime1ID, runtimeModel1.Name, runtimeModel1.Description, runtimeModel1.Status.Condition, runtimeModel1.CreationTimestamp, runtimeModel1.CreationTimestamp, runtimeModel1.ApplicationNamespace). 253 AddRow(runtime2ID, runtimeModel2.Name, runtimeModel2.Description, runtimeModel2.Status.Condition, runtimeModel2.CreationTimestamp, runtimeModel2.CreationTimestamp, runtimeModel2.ApplicationNamespace) 254 255 sqlMock.ExpectQuery(`^SELECT (.+) FROM public.runtimes WHERE id IN \(SELECT "runtime_id" FROM public\.labels WHERE "runtime_id" IS NOT NULL AND "key" = \$1 AND "value" \@\> \$2\ INTERSECT SELECT "runtime_id" FROM public\.labels WHERE "runtime_id" IS NOT NULL AND "key" = \$3 AND "value" \@\> \$4\)$`). 256 WithArgs("someKey", "someValue", "someKey2", "someValue2"). 257 WillReturnRows(rows) 258 259 ctx := persistence.SaveToContext(context.TODO(), sqlxDB) 260 261 pgRepository := runtime.NewRepository(mockConverter) 262 263 filters := []*labelfilter.LabelFilter{ 264 { 265 Key: "someKey", 266 Query: str.Ptr(`someValue`), 267 }, 268 { 269 Key: "someKey2", 270 Query: str.Ptr(`someValue2`), 271 }, 272 } 273 // WHEN 274 modelRuntimes, err := pgRepository.ListByFiltersGlobal(ctx, filters) 275 276 // THEN 277 require.NoError(t, err) 278 require.NotNil(t, modelRuntimes) 279 require.NoError(t, sqlMock.ExpectationsWereMet()) 280 281 require.Len(t, modelRuntimes, 2) 282 require.Equal(t, runtimeModel1, modelRuntimes[0]) 283 require.Equal(t, runtimeModel2, modelRuntimes[1]) 284 } 285 286 func TestPgRepository_List(t *testing.T) { 287 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 288 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 289 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 290 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 291 292 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 293 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 294 295 suite := testdb.RepoListPageableTestSuite{ 296 Name: "List Runtimes", 297 SQLQueryDetails: []testdb.SQLQueryDetails{ 298 { 299 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes 300 WHERE (id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 301 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4))) ORDER BY name LIMIT 2 OFFSET 0`), 302 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 303 IsSelect: true, 304 ValidRowsProvider: func() []*sqlmock.Rows { 305 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 306 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 307 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 308 } 309 }, 310 }, 311 { 312 Query: regexp.QuoteMeta(`SELECT COUNT(*) FROM public.runtimes 313 WHERE (id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 314 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4)))`), 315 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 316 IsSelect: true, 317 ValidRowsProvider: func() []*sqlmock.Rows { 318 return []*sqlmock.Rows{sqlmock.NewRows([]string{"count"}).AddRow(2)} 319 }, 320 }, 321 }, 322 Pages: []testdb.PageDetails{ 323 { 324 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 325 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 326 ExpectedPage: &model.RuntimePage{ 327 Data: []*model.Runtime{runtimeModel1, runtimeModel2}, 328 PageInfo: &pagination.Page{ 329 StartCursor: "", 330 EndCursor: "", 331 HasNextPage: false, 332 }, 333 TotalCount: 2, 334 }, 335 }, 336 }, 337 ConverterMockProvider: func() testdb.Mock { 338 return &automock.EntityConverter{} 339 }, 340 RepoConstructorFunc: runtime.NewRepository, 341 MethodArgs: []interface{}{tenantID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}, 2, ""}, 342 MethodName: "List", 343 DisableConverterErrorTest: true, 344 } 345 346 suite.Run(t) 347 } 348 349 func TestPgRepository_ListAll(t *testing.T) { 350 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 351 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 352 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 353 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 354 355 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 356 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 357 358 suite := testdb.RepoListTestSuite{ 359 Name: "List Runtimes Without Paging", 360 SQLQueryDetails: []testdb.SQLQueryDetails{ 361 { 362 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes 363 WHERE id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 364 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4))`), 365 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 366 IsSelect: true, 367 ValidRowsProvider: func() []*sqlmock.Rows { 368 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 369 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 370 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 371 } 372 }, 373 InvalidRowsProvider: func() []*sqlmock.Rows { 374 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 375 }, 376 }, 377 }, 378 ConverterMockProvider: func() testdb.Mock { 379 return &automock.EntityConverter{} 380 }, 381 RepoConstructorFunc: runtime.NewRepository, 382 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 383 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 384 MethodArgs: []interface{}{tenantID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}}, 385 MethodName: "ListAll", 386 DisableConverterErrorTest: true, 387 } 388 389 suite.Run(t) 390 } 391 392 func TestPgRepository_ListOwnedRuntimes(t *testing.T) { 393 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 394 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 395 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 396 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 397 398 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 399 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 400 401 suite := testdb.RepoListTestSuite{ 402 Name: "List Runtimes Without Paging", 403 SQLQueryDetails: []testdb.SQLQueryDetails{ 404 { 405 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes 406 WHERE id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) AND "key" = $2 AND "value" ?| array[$3]) 407 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $4 AND owner = true))`), 408 Args: []driver.Value{tenantID, model.ScenariosKey, "scenario", tenantID}, 409 IsSelect: true, 410 ValidRowsProvider: func() []*sqlmock.Rows { 411 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 412 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 413 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 414 } 415 }, 416 InvalidRowsProvider: func() []*sqlmock.Rows { 417 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 418 }, 419 }, 420 }, 421 ConverterMockProvider: func() testdb.Mock { 422 return &automock.EntityConverter{} 423 }, 424 RepoConstructorFunc: runtime.NewRepository, 425 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 426 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 427 MethodArgs: []interface{}{tenantID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery(model.ScenariosKey, `$[*] ? ( @ == "scenario" )`)}}, 428 MethodName: "ListOwnedRuntimes", 429 DisableConverterErrorTest: true, 430 } 431 432 suite.Run(t) 433 } 434 435 func TestPgRepository_Create(t *testing.T) { 436 var nilRtModel *model.Runtime 437 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 438 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 439 440 suite := testdb.RepoCreateTestSuite{ 441 Name: "Generic Create Runtime", 442 SQLQueryDetails: []testdb.SQLQueryDetails{ 443 { 444 Query: regexp.QuoteMeta(`INSERT INTO public.runtimes ( id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace ) VALUES ( ?, ?, ?, ?, ?, ?, ? )`), 445 Args: []driver.Value{rtModel.ID, rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.CreationTimestamp, rtModel.ApplicationNamespace}, 446 ValidResult: sqlmock.NewResult(-1, 1), 447 }, 448 { 449 Query: regexp.QuoteMeta(`WITH RECURSIVE parents AS (SELECT t1.id, t1.parent FROM business_tenant_mappings t1 WHERE id = ? UNION ALL SELECT t2.id, t2.parent FROM business_tenant_mappings t2 INNER JOIN parents t on t2.id = t.parent) INSERT INTO tenant_runtimes ( tenant_id, id, owner ) (SELECT parents.id AS tenant_id, ? as id, ? AS owner FROM parents)`), 450 Args: []driver.Value{tenantID, rtModel.ID, true}, 451 ValidResult: sqlmock.NewResult(-1, 1), 452 }, 453 }, 454 ConverterMockProvider: func() testdb.Mock { 455 return &automock.EntityConverter{} 456 }, 457 RepoConstructorFunc: runtime.NewRepository, 458 ModelEntity: rtModel, 459 DBEntity: rtEntity, 460 NilModelEntity: nilRtModel, 461 TenantID: tenantID, 462 IsTopLevelEntity: true, 463 } 464 465 suite.Run(t) 466 } 467 468 func TestPgRepository_Update(t *testing.T) { 469 var nilRtModel *model.Runtime 470 rtModel := fixDetailedModelRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 471 rtEntity := fixDetailedEntityRuntime(t, "foo", "Foo", "Lorem ipsum", "test.ns") 472 473 suite := testdb.RepoUpdateTestSuite{ 474 Name: "Update Runtime", 475 SQLQueryDetails: []testdb.SQLQueryDetails{ 476 { 477 Query: regexp.QuoteMeta(`UPDATE public.runtimes SET name = ?, description = ?, status_condition = ?, status_timestamp = ?, application_namespace = ? WHERE id = ? AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = ? AND owner = true))`), 478 Args: []driver.Value{rtModel.Name, rtModel.Description, rtModel.Status.Condition, rtModel.Status.Timestamp, rtModel.ApplicationNamespace, rtModel.ID, tenantID}, 479 ValidResult: sqlmock.NewResult(-1, 1), 480 InvalidResult: sqlmock.NewResult(-1, 0), 481 }, 482 }, 483 ConverterMockProvider: func() testdb.Mock { 484 return &automock.EntityConverter{} 485 }, 486 RepoConstructorFunc: runtime.NewRepository, 487 ModelEntity: rtModel, 488 DBEntity: rtEntity, 489 NilModelEntity: nilRtModel, 490 TenantID: tenantID, 491 } 492 493 suite.Run(t) 494 } 495 496 func TestPgRepository_Delete(t *testing.T) { 497 suite := testdb.RepoDeleteTestSuite{ 498 Name: "Runtime Delete", 499 SQLQueryDetails: []testdb.SQLQueryDetails{ 500 { 501 Query: regexp.QuoteMeta(`DELETE FROM public.runtimes WHERE id = $1 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $2 AND owner = true))`), 502 Args: []driver.Value{runtimeID, tenantID}, 503 ValidResult: sqlmock.NewResult(-1, 1), 504 InvalidResult: sqlmock.NewResult(-1, 2), 505 }, 506 }, 507 ConverterMockProvider: func() testdb.Mock { 508 return &automock.EntityConverter{} 509 }, 510 RepoConstructorFunc: runtime.NewRepository, 511 MethodArgs: []interface{}{tenantID, runtimeID}, 512 } 513 514 suite.Run(t) 515 } 516 517 func TestPgRepository_Exist(t *testing.T) { 518 suite := testdb.RepoExistTestSuite{ 519 Name: "Runtime Exists", 520 SQLQueryDetails: []testdb.SQLQueryDetails{ 521 { 522 Query: regexp.QuoteMeta(`SELECT 1 FROM public.runtimes WHERE id = $1 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $2))`), 523 Args: []driver.Value{runtimeID, tenantID}, 524 IsSelect: true, 525 ValidRowsProvider: func() []*sqlmock.Rows { 526 return []*sqlmock.Rows{testdb.RowWhenObjectExist()} 527 }, 528 InvalidRowsProvider: func() []*sqlmock.Rows { 529 return []*sqlmock.Rows{testdb.RowWhenObjectDoesNotExist()} 530 }, 531 }, 532 }, 533 ConverterMockProvider: func() testdb.Mock { 534 return &automock.EntityConverter{} 535 }, 536 RepoConstructorFunc: runtime.NewRepository, 537 TargetID: runtimeID, 538 TenantID: tenantID, 539 MethodName: "Exists", 540 MethodArgs: []interface{}{tenantID, runtimeID}, 541 } 542 543 suite.Run(t) 544 } 545 546 func TestPgRepository_OwnerExists(t *testing.T) { 547 suite := testdb.RepoExistTestSuite{ 548 Name: "Owned Runtime Exists", 549 SQLQueryDetails: []testdb.SQLQueryDetails{ 550 { 551 Query: regexp.QuoteMeta(`SELECT 1 FROM public.runtimes WHERE id = $1 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $2 AND owner = true))`), 552 Args: []driver.Value{runtimeID, tenantID}, 553 IsSelect: true, 554 ValidRowsProvider: func() []*sqlmock.Rows { 555 return []*sqlmock.Rows{testdb.RowWhenObjectExist()} 556 }, 557 InvalidRowsProvider: func() []*sqlmock.Rows { 558 return []*sqlmock.Rows{testdb.RowWhenObjectDoesNotExist()} 559 }, 560 }, 561 }, 562 ConverterMockProvider: func() testdb.Mock { 563 return &automock.EntityConverter{} 564 }, 565 RepoConstructorFunc: runtime.NewRepository, 566 TargetID: runtimeID, 567 TenantID: tenantID, 568 MethodName: "OwnerExists", 569 MethodArgs: []interface{}{tenantID, runtimeID}, 570 } 571 572 suite.Run(t) 573 } 574 575 func TestPgRepository_OwnerExistsByFiltersAndID(t *testing.T) { 576 suite := testdb.RepoExistTestSuite{ 577 Name: "Owned Runtime With Runtime Type Exists", 578 SQLQueryDetails: []testdb.SQLQueryDetails{ 579 { 580 Query: regexp.QuoteMeta(` SELECT 1 581 FROM public.runtimes 582 WHERE id = $1 AND 583 id IN (SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $2)) AND "key" = $3 AND "value" ?| array[$4] 584 UNION 585 SELECT "runtime_id" FROM public.labels WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $5)) AND "key" = $6 AND "value" ?| array[$7]) 586 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $8 AND owner = true))`), 587 588 Args: []driver.Value{runtimeID, tenantID, runtimeType, runtimeType, tenantID, runtimeType, "runtimeType2", tenantID}, 589 IsSelect: true, 590 ValidRowsProvider: func() []*sqlmock.Rows { 591 return []*sqlmock.Rows{testdb.RowWhenObjectExist()} 592 }, 593 InvalidRowsProvider: func() []*sqlmock.Rows { 594 return []*sqlmock.Rows{testdb.RowWhenObjectDoesNotExist()} 595 }, 596 }, 597 }, 598 ConverterMockProvider: func() testdb.Mock { 599 return &automock.EntityConverter{} 600 }, 601 RepoConstructorFunc: runtime.NewRepository, 602 TargetID: runtimeID, 603 TenantID: tenantID, 604 MethodName: "OwnerExistsByFiltersAndID", 605 MethodArgs: []interface{}{tenantID, runtimeID, []*labelfilter.LabelFilter{labelfilter.NewForKeyWithQuery("runtimeType", fmt.Sprintf("$[*] ? ( @ == \"%s\" )", runtimeType)), labelfilter.NewForKeyWithQuery("runtimeType", fmt.Sprintf("$[*] ? ( @ == \"%s\" )", "runtimeType2"))}}, 606 } 607 608 suite.Run(t) 609 } 610 611 func TestPgRepository_ListByIDs(t *testing.T) { 612 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 613 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 614 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 615 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 616 617 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 618 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 619 620 suite := testdb.RepoListTestSuite{ 621 Name: "List Runtimes By IDs", 622 SQLQueryDetails: []testdb.SQLQueryDetails{ 623 { 624 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes WHERE id IN ($1, $2) AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $3))`), 625 Args: []driver.Value{runtime1ID, runtime2ID, tenantID}, 626 IsSelect: true, 627 ValidRowsProvider: func() []*sqlmock.Rows { 628 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 629 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 630 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 631 } 632 }, 633 InvalidRowsProvider: func() []*sqlmock.Rows { 634 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 635 }, 636 }, 637 }, 638 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 639 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 640 ConverterMockProvider: func() testdb.Mock { 641 return &automock.EntityConverter{} 642 }, 643 RepoConstructorFunc: runtime.NewRepository, 644 MethodArgs: []interface{}{tenantID, []string{runtime1ID, runtime2ID}}, 645 MethodName: "ListByIDs", 646 DisableConverterErrorTest: true, 647 } 648 649 suite.Run(t) 650 651 // Additional test - empty slice because test suite returns empty result given valid query 652 t.Run("returns empty slice given no scenarios", func(t *testing.T) { 653 // GIVEN 654 ctx := context.TODO() 655 repository := runtime.NewRepository(nil) 656 657 // WHEN 658 actual, err := repository.ListByIDs(ctx, tenantID, []string{}) 659 660 // THEN 661 assert.NoError(t, err) 662 assert.Nil(t, actual) 663 }) 664 } 665 666 func TestPgRepository_ListByScenariosAndIDs(t *testing.T) { 667 scenario1 := "scenario-1" 668 scenario2 := "scenario-2" 669 670 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 671 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 672 673 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 674 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 675 676 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 677 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 678 679 suite := testdb.RepoListTestSuite{ 680 Name: "List Runtimes By IDs and scenarios", 681 SQLQueryDetails: []testdb.SQLQueryDetails{ 682 { 683 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes 684 WHERE id IN (SELECT "runtime_id" FROM public.labels 685 WHERE "runtime_id" IS NOT NULL 686 AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) 687 AND "key" = $2 AND "value" ?| array[$3] 688 UNION SELECT "runtime_id" FROM public.labels 689 WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $4)) 690 AND "key" = $5 AND "value" ?| array[$6]) 691 AND id IN ($7, $8) 692 AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $9))`), 693 Args: []driver.Value{tenantID, model.ScenariosKey, scenario1, tenantID, model.ScenariosKey, scenario2, runtime1ID, runtime2ID, tenantID}, 694 IsSelect: true, 695 ValidRowsProvider: func() []*sqlmock.Rows { 696 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 697 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 698 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 699 } 700 }, 701 InvalidRowsProvider: func() []*sqlmock.Rows { 702 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 703 }, 704 }, 705 }, 706 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 707 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 708 ConverterMockProvider: func() testdb.Mock { 709 return &automock.EntityConverter{} 710 }, 711 RepoConstructorFunc: runtime.NewRepository, 712 MethodArgs: []interface{}{tenantID, []string{scenario1, scenario2}, []string{runtime1ID, runtime2ID}}, 713 MethodName: "ListByScenariosAndIDs", 714 DisableConverterErrorTest: true, 715 } 716 717 suite.Run(t) 718 719 // Additional test - empty slice because test suite returns empty result given valid query 720 t.Run("returns empty slice given no scenarios", func(t *testing.T) { 721 // GIVEN 722 ctx := context.TODO() 723 repository := runtime.NewRepository(nil) 724 725 // WHEN 726 actual, err := repository.ListByScenariosAndIDs(ctx, tenantID, []string{}, []string{}) 727 728 // THEN 729 assert.NoError(t, err) 730 assert.Nil(t, actual) 731 }) 732 } 733 734 func TestPgRepository_ListByScenarios(t *testing.T) { 735 scenario1 := "scenario-1" 736 scenario2 := "scenario-2" 737 738 runtime1ID := "aec0e9c5-06da-4625-9f8a-bda17ab8c3b9" 739 runtime2ID := "ccdbef8f-b97a-490c-86e2-2bab2862a6e4" 740 741 runtimeEntity1 := fixDetailedEntityRuntime(t, runtime1ID, "Runtime 1", "Runtime desc 1", "test.ns1") 742 runtimeEntity2 := fixDetailedEntityRuntime(t, runtime2ID, "Runtime 2", "Runtime desc 2", "test.ns2") 743 744 runtimeModel1 := fixModelRuntime(t, runtime1ID, tenantID, "Runtime 1", "Runtime desc 1", "test.ns1") 745 runtimeModel2 := fixModelRuntime(t, runtime2ID, tenantID, "Runtime 2", "Runtime desc 2", "test.ns2") 746 747 suite := testdb.RepoListTestSuite{ 748 Name: "List Runtimes By scenarios", 749 SQLQueryDetails: []testdb.SQLQueryDetails{ 750 { 751 Query: regexp.QuoteMeta(`SELECT id, name, description, status_condition, status_timestamp, creation_timestamp, application_namespace FROM public.runtimes 752 WHERE id IN (SELECT "runtime_id" FROM public.labels 753 WHERE "runtime_id" IS NOT NULL 754 AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $1)) 755 AND "key" = $2 AND "value" ?| array[$3] UNION SELECT "runtime_id" FROM public.labels 756 WHERE "runtime_id" IS NOT NULL AND (id IN (SELECT id FROM runtime_labels_tenants WHERE tenant_id = $4)) 757 AND "key" = $5 AND "value" ?| array[$6]) AND (id IN (SELECT id FROM tenant_runtimes WHERE tenant_id = $7))`), 758 Args: []driver.Value{tenantID, model.ScenariosKey, scenario1, tenantID, model.ScenariosKey, scenario2, tenantID}, 759 IsSelect: true, 760 ValidRowsProvider: func() []*sqlmock.Rows { 761 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns). 762 AddRow(runtimeEntity1.ID, runtimeEntity1.Name, runtimeEntity1.Description, runtimeEntity1.StatusCondition, runtimeEntity1.StatusTimestamp, runtimeEntity1.CreationTimestamp, runtimeEntity1.ApplicationNamespace). 763 AddRow(runtimeEntity2.ID, runtimeEntity2.Name, runtimeEntity2.Description, runtimeEntity2.StatusCondition, runtimeEntity2.StatusTimestamp, runtimeEntity2.CreationTimestamp, runtimeEntity2.ApplicationNamespace), 764 } 765 }, 766 InvalidRowsProvider: func() []*sqlmock.Rows { 767 return []*sqlmock.Rows{sqlmock.NewRows(fixColumns)} 768 }, 769 }, 770 }, 771 ExpectedModelEntities: []interface{}{runtimeModel1, runtimeModel2}, 772 ExpectedDBEntities: []interface{}{runtimeEntity1, runtimeEntity2}, 773 ConverterMockProvider: func() testdb.Mock { 774 return &automock.EntityConverter{} 775 }, 776 RepoConstructorFunc: runtime.NewRepository, 777 MethodArgs: []interface{}{tenantID, []string{scenario1, scenario2}}, 778 MethodName: "ListByScenarios", 779 DisableConverterErrorTest: true, 780 } 781 782 suite.Run(t) 783 784 // Additional test - empty slice because test suite returns empty result given valid query 785 t.Run("returns empty slice given no scenarios", func(t *testing.T) { 786 // GIVEN 787 ctx := context.TODO() 788 repository := runtime.NewRepository(nil) 789 790 // WHEN 791 actual, err := repository.ListByScenarios(ctx, tenantID, []string{}) 792 793 // THEN 794 assert.NoError(t, err) 795 assert.Nil(t, actual) 796 }) 797 }