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  }