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

     1  package repo_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"regexp"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/kyma-incubator/compass/components/director/pkg/resource"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  
    14  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    15  
    16  	"github.com/lib/pq"
    17  
    18  	"github.com/DATA-DOG/go-sqlmock"
    19  
    20  	"github.com/kyma-incubator/compass/components/director/internal/repo"
    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 TestUpsertGlobal(t *testing.T) {
    27  	expectedQuery := regexp.QuoteMeta(`INSERT INTO users ( id, tenant_id, first_name, last_name, age )
    28  		VALUES ( ?, ?, ?, ?, ? ) ON CONFLICT ( tenant_id, first_name, last_name ) DO UPDATE SET age=EXCLUDED.age`)
    29  	expectedQueryWithTenantCheck := regexp.QuoteMeta(`INSERT INTO users ( id, tenant_id, first_name, last_name, age )
    30  		VALUES ( ?, ?, ?, ?, ? ) ON CONFLICT ( tenant_id, first_name, last_name ) DO UPDATE SET age=EXCLUDED.age WHERE users.tenant_id = ?`)
    31  
    32  	sut := repo.NewUpserterGlobal(UserType, "users", []string{"id", "tenant_id", "first_name", "last_name", "age"}, []string{"tenant_id", "first_name", "last_name"}, []string{"age"})
    33  	sutWithEmbededTenant := repo.NewUpserterWithEmbeddedTenant(UserType, "users", []string{"id", "tenant_id", "first_name", "last_name", "age"}, []string{"tenant_id", "first_name", "last_name"}, []string{"age"}, "tenant_id")
    34  
    35  	t.Run("success", func(t *testing.T) {
    36  		// GIVEN
    37  		db, mock := testdb.MockDatabase(t)
    38  		ctx := persistence.SaveToContext(context.TODO(), db)
    39  		defer mock.AssertExpectations(t)
    40  		givenUser := User{
    41  			ID:        "given_id",
    42  			Tenant:    "given_tenant",
    43  			FirstName: "given_first_name",
    44  			LastName:  "given_last_name",
    45  			Age:       55,
    46  		}
    47  
    48  		mock.ExpectExec(expectedQuery).
    49  			WithArgs("given_id", "given_tenant", "given_first_name", "given_last_name", 55).WillReturnResult(sqlmock.NewResult(1, 1))
    50  		// WHEN
    51  		err := sut.UpsertGlobal(ctx, givenUser)
    52  		// THEN
    53  		require.NoError(t, err)
    54  	})
    55  
    56  	t.Run("returns error when operation on db failed", func(t *testing.T) {
    57  		// GIVEN
    58  		db, mock := testdb.MockDatabase(t)
    59  		ctx := persistence.SaveToContext(context.TODO(), db)
    60  		defer mock.AssertExpectations(t)
    61  		givenUser := User{}
    62  
    63  		mock.ExpectExec(expectedQuery).
    64  			WillReturnError(someError())
    65  		// WHEN
    66  		err := sut.UpsertGlobal(ctx, givenUser)
    67  		// THEN
    68  		require.EqualError(t, err, "Internal Server Error: Unexpected error while executing SQL query")
    69  	})
    70  
    71  	t.Run("context properly canceled", func(t *testing.T) {
    72  		db, mock := testdb.MockDatabase(t)
    73  		defer mock.AssertExpectations(t)
    74  		givenUser := User{}
    75  
    76  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
    77  		defer cancel()
    78  
    79  		ctx = persistence.SaveToContext(ctx, db)
    80  
    81  		err := sut.UpsertGlobal(ctx, givenUser)
    82  
    83  		require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
    84  	})
    85  
    86  	t.Run("returns non unique error", func(t *testing.T) {
    87  		// GIVEN
    88  		db, mock := testdb.MockDatabase(t)
    89  		ctx := persistence.SaveToContext(context.TODO(), db)
    90  		defer mock.AssertExpectations(t)
    91  		givenUser := User{}
    92  
    93  		mock.ExpectExec(expectedQuery).
    94  			WillReturnError(&pq.Error{Code: persistence.UniqueViolation})
    95  		// WHEN
    96  		err := sut.UpsertGlobal(ctx, givenUser)
    97  		// THEN
    98  		require.True(t, apperrors.IsNotUniqueError(err))
    99  	})
   100  
   101  	t.Run("returns error if missing persistence context", func(t *testing.T) {
   102  		// WHEN
   103  		err := sut.UpsertGlobal(context.TODO(), User{})
   104  		// THEN
   105  		require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   106  	})
   107  
   108  	t.Run("returns error if destination is nil", func(t *testing.T) {
   109  		// WHEN
   110  		err := sut.UpsertGlobal(context.TODO(), nil)
   111  		// THEN
   112  		require.EqualError(t, err, apperrors.NewInternalError("item cannot be nil").Error())
   113  	})
   114  
   115  	t.Run("success with embedded tenant", func(t *testing.T) {
   116  		// GIVEN
   117  		db, mock := testdb.MockDatabase(t)
   118  		ctx := persistence.SaveToContext(context.TODO(), db)
   119  		defer mock.AssertExpectations(t)
   120  		givenUser := User{
   121  			ID:        "given_id",
   122  			Tenant:    "given_tenant",
   123  			FirstName: "given_first_name",
   124  			LastName:  "given_last_name",
   125  			Age:       55,
   126  		}
   127  
   128  		mock.ExpectExec(expectedQueryWithTenantCheck).
   129  			WithArgs("given_id", "given_tenant", "given_first_name", "given_last_name", 55, "given_tenant").WillReturnResult(sqlmock.NewResult(1, 1))
   130  		// WHEN
   131  		err := sutWithEmbededTenant.UpsertGlobal(ctx, givenUser)
   132  		// THEN
   133  		require.NoError(t, err)
   134  	})
   135  }
   136  
   137  func TestUpsert(t *testing.T) {
   138  	expectedQuery := regexp.QuoteMeta(`INSERT INTO apps ( id, name, description ) VALUES ( $1, $2, $3 ) ON CONFLICT ( id ) DO UPDATE SET name=EXCLUDED.name, description=EXCLUDED.description WHERE (apps.id IN (SELECT id FROM tenant_applications WHERE tenant_id = $4 AND owner = true)) RETURNING id;`)
   139  
   140  	expectedTenantAccessQuery := 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)", "tenant_applications", repo.M2MTenantIDColumn, repo.M2MResourceIDColumn, repo.M2MOwnerColumn))
   141  
   142  	sut := repo.NewUpserter(appTableName, []string{"id", "name", "description"}, []string{"id"}, []string{"name", "description"})
   143  
   144  	resourceType := resource.Application
   145  	tenant := "tenant"
   146  
   147  	t.Run("success", func(t *testing.T) {
   148  		// GIVEN
   149  		db, mock := testdb.MockDatabase(t)
   150  		ctx := persistence.SaveToContext(context.TODO(), db)
   151  		defer mock.AssertExpectations(t)
   152  
   153  		rows := sqlmock.NewRows([]string{"id"}).AddRow(appID)
   154  		mock.ExpectQuery(expectedQuery).
   155  			WithArgs(appID, appName, appDescription, tenant).WillReturnRows(rows)
   156  		mock.ExpectExec(expectedTenantAccessQuery).
   157  			WithArgs(tenant, appID, true).WillReturnResult(sqlmock.NewResult(1, 1))
   158  		// WHEN
   159  		_, err := sut.Upsert(ctx, resourceType, tenant, fixApp)
   160  		// THEN
   161  		require.NoError(t, err)
   162  	})
   163  
   164  	t.Run("returns error when upsert operation failed", func(t *testing.T) {
   165  		// GIVEN
   166  		db, mock := testdb.MockDatabase(t)
   167  		ctx := persistence.SaveToContext(context.TODO(), db)
   168  		defer mock.AssertExpectations(t)
   169  
   170  		mock.ExpectQuery(expectedQuery).
   171  			WithArgs(appID, appName, appDescription, tenant).WillReturnError(someError())
   172  		// WHEN
   173  		_, err := sut.Upsert(ctx, resourceType, tenant, fixApp)
   174  		// THEN
   175  		require.Contains(t, err.Error(), "Internal Server Error: Unexpected error while executing SQL query")
   176  	})
   177  
   178  	t.Run("returns error when adding tenant access record failed", func(t *testing.T) {
   179  		// GIVEN
   180  		db, mock := testdb.MockDatabase(t)
   181  		ctx := persistence.SaveToContext(context.TODO(), db)
   182  		defer mock.AssertExpectations(t)
   183  
   184  		rows := sqlmock.NewRows([]string{"id"}).AddRow(appID)
   185  		mock.ExpectQuery(expectedQuery).
   186  			WithArgs(appID, appName, appDescription, tenant).WillReturnRows(rows)
   187  		mock.ExpectExec(expectedTenantAccessQuery).
   188  			WithArgs(tenant, appID, true).WillReturnError(someError())
   189  		// WHEN
   190  		_, err := sut.Upsert(ctx, resourceType, tenant, fixApp)
   191  		// THEN
   192  		require.Contains(t, err.Error(), "Internal Server Error: Unexpected error while executing SQL query")
   193  	})
   194  
   195  	t.Run("context properly canceled", func(t *testing.T) {
   196  		db, mock := testdb.MockDatabase(t)
   197  		defer mock.AssertExpectations(t)
   198  
   199  		ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
   200  		defer cancel()
   201  
   202  		ctx = persistence.SaveToContext(ctx, db)
   203  
   204  		_, err := sut.Upsert(ctx, resourceType, tenant, fixApp)
   205  
   206  		require.EqualError(t, err, "Internal Server Error: Maximum processing timeout reached")
   207  	})
   208  
   209  	t.Run("returns error if missing persistence context", func(t *testing.T) {
   210  		// WHEN
   211  		_, err := sut.Upsert(context.TODO(), resourceType, tenant, fixApp)
   212  		// THEN
   213  		require.EqualError(t, err, apperrors.NewInternalError("unable to fetch database from context").Error())
   214  	})
   215  
   216  	t.Run("returns error if destination is nil", func(t *testing.T) {
   217  		// WHEN
   218  		_, err := sut.Upsert(context.TODO(), resourceType, tenant, nil)
   219  		// THEN
   220  		require.EqualError(t, err, apperrors.NewInternalError("item cannot be nil").Error())
   221  	})
   222  
   223  	t.Run("returns error if the entity does not have accessTable", func(t *testing.T) {
   224  		// GIVEN
   225  		db, mock := testdb.MockDatabase(t)
   226  		ctx := persistence.SaveToContext(context.TODO(), db)
   227  		defer mock.AssertExpectations(t)
   228  
   229  		// WHEN
   230  		_, err := sut.Upsert(ctx, resource.Tenant, tenant, fixApp)
   231  		// THEN
   232  		require.Contains(t, err.Error(), "entity tenant does not have access table")
   233  	})
   234  }
   235  
   236  func TestUpsertGlobalWhenWrongConfiguration(t *testing.T) {
   237  	sut := repo.NewUpserterGlobal("users", "UserType", []string{"id", "tenant_id", "column_does_not_exist"}, []string{"id", "tenant_id"}, []string{"column_does_not_exist"})
   238  	// GIVEN
   239  	db, mock := testdb.MockDatabase(t)
   240  	ctx := persistence.SaveToContext(context.TODO(), db)
   241  	defer mock.AssertExpectations(t)
   242  	// WHEN
   243  	err := sut.UpsertGlobal(ctx, User{})
   244  	// THEN
   245  	require.Error(t, err)
   246  	assert.Contains(t, err.Error(), "Unexpected error while executing SQL query")
   247  }