github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/repo/create_test.go (about) 1 package repo_test 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "testing" 8 "time" 9 10 "github.com/DATA-DOG/go-sqlmock" 11 "github.com/kyma-incubator/compass/components/director/internal/repo" 12 "github.com/kyma-incubator/compass/components/director/internal/repo/testdb" 13 "github.com/kyma-incubator/compass/components/director/pkg/apperrors" 14 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 15 "github.com/kyma-incubator/compass/components/director/pkg/resource" 16 "github.com/lib/pq" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestCreate(t *testing.T) { 22 t.Run("Top Level Entity", func(t *testing.T) { 23 creator := repo.NewCreator(appTableName, appColumns) 24 resourceType := resource.Application 25 m2mTable, ok := resourceType.TenantAccessTable() 26 require.True(t, ok) 27 28 t.Run("success", func(t *testing.T) { 29 db, mock := testdb.MockDatabase(t) 30 ctx := persistence.SaveToContext(context.TODO(), db) 31 defer mock.AssertExpectations(t) 32 33 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, name, description ) VALUES ( ?, ?, ? )", appTableName))). 34 WithArgs(appID, appName, appDescription).WillReturnResult(sqlmock.NewResult(1, 1)) 35 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("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 %s ( %s, %s, %s ) (SELECT parents.id AS tenant_id, ? as id, ? AS owner FROM parents)", m2mTable, repo.M2MTenantIDColumn, repo.M2MResourceIDColumn, repo.M2MOwnerColumn))). 36 WithArgs(tenantID, appID, true).WillReturnResult(sqlmock.NewResult(1, 1)) 37 38 err := creator.Create(ctx, resourceType, tenantID, fixApp) 39 require.NoError(t, err) 40 }) 41 42 t.Run("returns error when operation on db failed", func(t *testing.T) { 43 // GIVEN 44 db, mock := testdb.MockDatabase(t) 45 ctx := persistence.SaveToContext(context.TODO(), db) 46 defer mock.AssertExpectations(t) 47 48 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, name, description ) VALUES ( ?, ?, ? )", appTableName))). 49 WillReturnError(someError()) 50 // WHEN 51 err := creator.Create(ctx, resourceType, tenantID, fixApp) 52 // THEN 53 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 54 }) 55 56 t.Run("context properly canceled", func(t *testing.T) { 57 db, mock := testdb.MockDatabase(t) 58 defer mock.AssertExpectations(t) 59 60 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 61 defer cancel() 62 63 ctx = persistence.SaveToContext(ctx, db) 64 err := creator.Create(ctx, resourceType, tenantID, fixApp) 65 require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached") 66 }) 67 68 t.Run("returns non unique error", func(t *testing.T) { 69 // GIVEN 70 db, mock := testdb.MockDatabase(t) 71 ctx := persistence.SaveToContext(context.TODO(), db) 72 defer mock.AssertExpectations(t) 73 74 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, name, description ) VALUES ( ?, ?, ? )", appTableName))). 75 WillReturnError(&pq.Error{Code: persistence.UniqueViolation}) 76 // WHEN 77 err := creator.Create(ctx, resourceType, tenantID, fixApp) 78 // THEN 79 require.True(t, apperrors.IsNotUniqueError(err)) 80 }) 81 82 t.Run("returns non unique error if there are matcher columns and the entity already exists", func(t *testing.T) { 83 creator := repo.NewCreatorWithMatchingColumns(appTableName, appColumns, []string{"id"}) 84 // GIVEN 85 db, mock := testdb.MockDatabase(t) 86 ctx := persistence.SaveToContext(context.TODO(), db) 87 defer mock.AssertExpectations(t) 88 89 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, name, description ) VALUES ( ?, ?, ? )", appTableName))). 90 WithArgs(appID, appName, appDescription).WillReturnResult(sqlmock.NewResult(1, 0)) 91 // WHEN 92 err := creator.Create(ctx, resourceType, tenantID, fixApp) 93 // THEN 94 require.True(t, apperrors.IsNotUniqueError(err)) 95 }) 96 97 t.Run("returns error if missing persistence context", func(t *testing.T) { 98 // WHEN 99 err := creator.Create(context.TODO(), resourceType, tenantID, fixApp) 100 // THEN 101 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 102 }) 103 104 t.Run("returns error if destination is nil", func(t *testing.T) { 105 // WHEN 106 err := creator.Create(context.TODO(), resourceType, tenantID, nil) 107 // THEN 108 require.EqualError(t, err, "Internal Server Error: item cannot be nil") 109 }) 110 111 t.Run("returns error if id cannot be found", func(t *testing.T) { 112 // WHEN 113 err := creator.Create(context.TODO(), resourceType, tenantID, struct{}{}) 114 // THEN 115 require.EqualError(t, err, "Internal Server Error: id cannot be empty, check if the entity implements Identifiable") 116 }) 117 }) 118 119 t.Run("Child Entity", func(t *testing.T) { 120 creator := repo.NewCreator(bundlesTableName, bundleColumns) 121 resourceType := resource.Bundle 122 m2mTable, ok := resource.Application.TenantAccessTable() 123 require.True(t, ok) 124 125 t.Run("success", func(t *testing.T) { 126 db, mock := testdb.MockDatabase(t) 127 ctx := persistence.SaveToContext(context.TODO(), db) 128 defer mock.AssertExpectations(t) 129 130 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT 1 FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", m2mTable, repo.M2MTenantIDColumn, repo.M2MResourceIDColumn, repo.M2MOwnerColumn))). 131 WithArgs(tenantID, appID, true).WillReturnRows(testdb.RowWhenObjectExist()) 132 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, name, description, app_id ) VALUES ( ?, ?, ?, ? )", bundlesTableName))). 133 WithArgs(bundleID, bundleName, bundleDescription, appID).WillReturnResult(sqlmock.NewResult(1, 1)) 134 135 err := creator.Create(ctx, resourceType, tenantID, fixBundle) 136 require.NoError(t, err) 137 }) 138 139 t.Run("returns error if tenant does not have access to the parent entity", func(t *testing.T) { 140 db, mock := testdb.MockDatabase(t) 141 ctx := persistence.SaveToContext(context.TODO(), db) 142 defer mock.AssertExpectations(t) 143 144 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT 1 FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", m2mTable, repo.M2MTenantIDColumn, repo.M2MResourceIDColumn, repo.M2MOwnerColumn))). 145 WithArgs(tenantID, appID, true).WillReturnRows(testdb.RowWhenObjectDoesNotExist()) 146 147 err := creator.Create(ctx, resourceType, tenantID, fixBundle) 148 require.Error(t, err) 149 require.Contains(t, err.Error(), fmt.Sprintf("tenant %s does not have access to the parent resource %s with ID %s", tenantID, resource.Application, appID)) 150 }) 151 152 t.Run("returns error if checking for parent access fails", func(t *testing.T) { 153 db, mock := testdb.MockDatabase(t) 154 ctx := persistence.SaveToContext(context.TODO(), db) 155 defer mock.AssertExpectations(t) 156 157 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT 1 FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", m2mTable, repo.M2MTenantIDColumn, repo.M2MResourceIDColumn, repo.M2MOwnerColumn))). 158 WithArgs(tenantID, appID, true).WillReturnError(someError()) 159 160 err := creator.Create(ctx, resourceType, tenantID, fixBundle) 161 require.Error(t, err) 162 require.Contains(t, err.Error(), "while checking for tenant access") 163 }) 164 165 t.Run("context properly canceled", func(t *testing.T) { 166 db, mock := testdb.MockDatabase(t) 167 defer mock.AssertExpectations(t) 168 169 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 170 defer cancel() 171 172 ctx = persistence.SaveToContext(ctx, db) 173 err := creator.Create(ctx, resourceType, tenantID, fixBundle) 174 require.Error(t, err) 175 require.Contains(t, err.Error(), "Maximum processing timeout reached") 176 }) 177 178 t.Run("returns error if missing parent", func(t *testing.T) { 179 // WHEN 180 err := creator.Create(context.TODO(), UserType, tenantID, fixUser) 181 require.Error(t, err) 182 // THEN 183 require.Contains(t, err.Error(), fmt.Sprintf("unknown parent for entity type %s", UserType)) 184 }) 185 186 t.Run("returns error if missing parent ID", func(t *testing.T) { 187 // WHEN 188 err := creator.Create(context.TODO(), resource.API, tenantID, fixUser) 189 require.Error(t, err) 190 // THEN 191 require.Contains(t, err.Error(), fmt.Sprintf("unknown parent for entity type %s", resource.API)) 192 }) 193 194 t.Run("returns error if missing persistence context", func(t *testing.T) { 195 // WHEN 196 err := creator.Create(context.TODO(), resourceType, tenantID, fixBundle) 197 require.Error(t, err) 198 // THEN 199 require.Contains(t, err.Error(), "unable to fetch database from context") 200 }) 201 202 t.Run("returns error if destination is nil", func(t *testing.T) { 203 // WHEN 204 err := creator.Create(context.TODO(), resourceType, tenantID, nil) 205 // THEN 206 require.EqualError(t, err, "Internal Server Error: item cannot be nil") 207 }) 208 209 t.Run("returns error if id cannot be found", func(t *testing.T) { 210 // WHEN 211 err := creator.Create(context.TODO(), resourceType, tenantID, struct{}{}) 212 // THEN 213 require.EqualError(t, err, "Internal Server Error: id cannot be empty, check if the entity implements Identifiable") 214 }) 215 }) 216 217 t.Run("Child Entity whose parent is with embedded tenant", func(t *testing.T) { 218 creator := repo.NewCreator(webhooksTableName, webhookColumns) 219 resourceType := resource.FormationTemplateWebhook 220 m2mTable, ok := resource.FormationTemplate.EmbeddedTenantTable() 221 require.True(t, ok) 222 223 t.Run("success", func(t *testing.T) { 224 db, mock := testdb.MockDatabase(t) 225 ctx := persistence.SaveToContext(context.TODO(), db) 226 defer mock.AssertExpectations(t) 227 228 mock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf("SELECT 1 FROM %s WHERE %s = $1 AND %s = $2", m2mTable, repo.M2MTenantIDColumn, repo.M2MResourceIDColumn))). 229 WithArgs(tenantID, ftID).WillReturnRows(testdb.RowWhenObjectExist()) 230 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, formation_template_id ) VALUES ( ?, ? )", webhooksTableName))). 231 WithArgs(whID, ftID).WillReturnResult(sqlmock.NewResult(1, 1)) 232 233 err := creator.Create(ctx, resourceType, tenantID, fixWebhook) 234 require.NoError(t, err) 235 }) 236 }) 237 } 238 239 func TestCreateGlobal(t *testing.T) { 240 sut := repo.NewCreatorGlobal(UserType, userTableName, []string{"id", "tenant_id", "first_name", "last_name", "age"}) 241 t.Run("success", func(t *testing.T) { 242 // GIVEN 243 db, mock := testdb.MockDatabase(t) 244 ctx := persistence.SaveToContext(context.TODO(), db) 245 defer mock.AssertExpectations(t) 246 givenUser := User{ 247 ID: "given_id", 248 Tenant: "given_tenant", 249 FirstName: "given_first_name", 250 LastName: "given_last_name", 251 Age: 55, 252 } 253 254 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, tenant_id, first_name, last_name, age ) VALUES ( ?, ?, ?, ?, ? )", userTableName))). 255 WithArgs("given_id", "given_tenant", "given_first_name", "given_last_name", 55).WillReturnResult(sqlmock.NewResult(1, 1)) 256 // WHEN 257 err := sut.Create(ctx, givenUser) 258 // THEN 259 require.NoError(t, err) 260 }) 261 262 t.Run("returns error when operation on db failed", func(t *testing.T) { 263 // GIVEN 264 db, mock := testdb.MockDatabase(t) 265 ctx := persistence.SaveToContext(context.TODO(), db) 266 defer mock.AssertExpectations(t) 267 givenUser := User{} 268 269 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, tenant_id, first_name, last_name, age ) VALUES ( ?, ?, ?, ?, ? )", userTableName))). 270 WillReturnError(someError()) 271 // WHEN 272 err := sut.Create(ctx, givenUser) 273 // THEN 274 require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query") 275 }) 276 277 t.Run("context properly canceled", func(t *testing.T) { 278 db, mock := testdb.MockDatabase(t) 279 defer mock.AssertExpectations(t) 280 givenUser := User{} 281 282 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) 283 defer cancel() 284 285 ctx = persistence.SaveToContext(ctx, db) 286 err := sut.Create(ctx, givenUser) 287 require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached") 288 }) 289 290 t.Run("returns non unique error", func(t *testing.T) { 291 // GIVEN 292 db, mock := testdb.MockDatabase(t) 293 ctx := persistence.SaveToContext(context.TODO(), db) 294 defer mock.AssertExpectations(t) 295 givenUser := User{} 296 297 mock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf("INSERT INTO %s ( id, tenant_id, first_name, last_name, age ) VALUES ( ?, ?, ?, ?, ? )", userTableName))). 298 WillReturnError(&pq.Error{Code: persistence.UniqueViolation}) 299 // WHEN 300 err := sut.Create(ctx, givenUser) 301 // THEN 302 require.True(t, apperrors.IsNotUniqueError(err)) 303 }) 304 305 t.Run("returns error if missing persistence context", func(t *testing.T) { 306 // WHEN 307 err := sut.Create(context.TODO(), User{}) 308 // THEN 309 require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error()) 310 }) 311 312 t.Run("returns error if destination is nil", func(t *testing.T) { 313 // WHEN 314 err := sut.Create(context.TODO(), nil) 315 // THEN 316 require.EqualError(t, err, "Internal Server Error: item cannot be nil") 317 }) 318 } 319 320 func TestCreateWhenWrongConfiguration(t *testing.T) { 321 sut := repo.NewCreatorGlobal(UserType, userTableName, []string{"id", "column_does_not_exist"}) 322 // GIVEN 323 db, mock := testdb.MockDatabase(t) 324 ctx := persistence.SaveToContext(context.TODO(), db) 325 defer mock.AssertExpectations(t) 326 // WHEN 327 err := sut.Create(ctx, User{}) 328 // THEN 329 require.Error(t, err) 330 assert.Contains(t, err.Error(), "Unexpected error while executing SQL query") 331 }