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

     1  package statusupdate_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  
    11  	"github.com/kyma-incubator/compass/components/hydrator/pkg/oathkeeper"
    12  
    13  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    14  	logrustest "github.com/sirupsen/logrus/hooks/test"
    15  
    16  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    17  
    18  	"github.com/sirupsen/logrus"
    19  
    20  	"github.com/stretchr/testify/mock"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  
    24  	"github.com/kyma-incubator/compass/components/director/internal/statusupdate"
    25  
    26  	"github.com/stretchr/testify/require"
    27  
    28  	"github.com/kyma-incubator/compass/components/director/internal/statusupdate/automock"
    29  	persistenceautomock "github.com/kyma-incubator/compass/components/director/pkg/persistence/automock"
    30  
    31  	"github.com/kyma-incubator/compass/components/director/pkg/persistence/txtest"
    32  
    33  	"github.com/kyma-incubator/compass/components/director/pkg/consumer"
    34  )
    35  
    36  func TestUpdate_Handler(t *testing.T) {
    37  	// GIVEN
    38  	testErr := errors.New("test")
    39  	txGen := txtest.NewTransactionContextGenerator(testErr)
    40  
    41  	testCases := []struct {
    42  		Name                 string
    43  		TxFn                 func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
    44  		RepoFn               func() *automock.StatusUpdateRepository
    45  		Request              *http.Request
    46  		ExpectedStatus       int
    47  		ExpectedResponse     string
    48  		ExpectedErrorMessage *string
    49  		ExpectedError        *string
    50  		MockNextHandler      http.Handler
    51  	}{
    52  		{
    53  			Name: "In case of Integration System do nothing and execute next handler",
    54  			TxFn: txGen.ThatDoesntStartTransaction,
    55  			RepoFn: func() *automock.StatusUpdateRepository {
    56  				return nil
    57  			},
    58  			Request:          createRequestWithClaims(testID, consumer.IntegrationSystem, oathkeeper.OAuth2Flow),
    59  			ExpectedStatus:   http.StatusOK,
    60  			ExpectedResponse: "OK",
    61  			MockNextHandler:  fixNextHandler(t),
    62  		},
    63  		{
    64  			Name: "In case of Static User do nothing and execute next handler",
    65  			TxFn: txGen.ThatDoesntStartTransaction,
    66  			RepoFn: func() *automock.StatusUpdateRepository {
    67  				return nil
    68  			},
    69  			Request:          createRequestWithClaims(testID, consumer.User, oathkeeper.JWTAuthFlow),
    70  			ExpectedStatus:   http.StatusOK,
    71  			ExpectedResponse: "OK",
    72  			MockNextHandler:  fixNextHandler(t),
    73  		},
    74  		{
    75  			Name: "In case of Application and Certificate flow update status and execute next handler",
    76  			TxFn: txGen.ThatSucceeds,
    77  			RepoFn: func() *automock.StatusUpdateRepository {
    78  				repo := automock.StatusUpdateRepository{}
    79  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(false, nil)
    80  				repo.On("UpdateStatus", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(nil)
    81  				return &repo
    82  			},
    83  			Request:          createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
    84  			ExpectedStatus:   http.StatusOK,
    85  			ExpectedResponse: "OK",
    86  			MockNextHandler:  fixNextHandler(t),
    87  		},
    88  		{
    89  			Name: "In case of Application and OneTimeToken flow do nothing and execute next handler",
    90  			TxFn: txGen.ThatDoesntStartTransaction,
    91  			RepoFn: func() *automock.StatusUpdateRepository {
    92  				return nil
    93  			},
    94  			Request:          createRequestWithClaims(testID, consumer.Application, oathkeeper.OneTimeTokenFlow),
    95  			ExpectedStatus:   http.StatusOK,
    96  			ExpectedResponse: "OK",
    97  			MockNextHandler:  fixNextHandler(t),
    98  		},
    99  		{
   100  			Name: "In case of Runtime and Certificate flow update status and execute next handler",
   101  			TxFn: txGen.ThatSucceeds,
   102  			RepoFn: func() *automock.StatusUpdateRepository {
   103  				repo := automock.StatusUpdateRepository{}
   104  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Runtimes).Return(false, nil)
   105  				repo.On("UpdateStatus", txtest.CtxWithDBMatcher(), testID, statusupdate.Runtimes).Return(nil)
   106  				return &repo
   107  			},
   108  			Request:          createRequestWithClaims(testID, consumer.Runtime, oathkeeper.CertificateFlow),
   109  			ExpectedStatus:   http.StatusOK,
   110  			ExpectedResponse: "OK",
   111  			MockNextHandler:  fixNextHandler(t),
   112  		},
   113  		{
   114  			Name: "In case of Runtime and OneTimeToken flow do nothing and execute next handler",
   115  			TxFn: txGen.ThatDoesntStartTransaction,
   116  			RepoFn: func() *automock.StatusUpdateRepository {
   117  				return nil
   118  			},
   119  			Request:          createRequestWithClaims(testID, consumer.Runtime, oathkeeper.OneTimeTokenFlow),
   120  			ExpectedStatus:   http.StatusOK,
   121  			ExpectedResponse: "OK",
   122  			MockNextHandler:  fixNextHandler(t),
   123  		},
   124  		{
   125  			Name: "In case of application already connected do nothing and execute next handler",
   126  			TxFn: txGen.ThatSucceeds,
   127  			RepoFn: func() *automock.StatusUpdateRepository {
   128  				repo := automock.StatusUpdateRepository{}
   129  				repo.On("IsConnected", mock.Anything, testID, statusupdate.Applications).Return(true, nil)
   130  				return &repo
   131  			},
   132  			Request:          createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
   133  			ExpectedStatus:   http.StatusOK,
   134  			ExpectedResponse: "OK",
   135  			MockNextHandler:  fixNextHandler(t),
   136  		},
   137  		{
   138  			Name: "In case of runtime already connected do nothing and execute next handler",
   139  			TxFn: txGen.ThatSucceeds,
   140  			RepoFn: func() *automock.StatusUpdateRepository {
   141  				repo := automock.StatusUpdateRepository{}
   142  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Runtimes).Return(true, nil)
   143  				return &repo
   144  			},
   145  			Request:          createRequestWithClaims(testID, consumer.Runtime, oathkeeper.CertificateFlow),
   146  			ExpectedStatus:   http.StatusOK,
   147  			ExpectedResponse: "OK",
   148  			MockNextHandler:  fixNextHandler(t),
   149  		},
   150  		{
   151  			Name: "Error when no consumer info in context",
   152  			TxFn: txGen.ThatDoesntStartTransaction,
   153  			RepoFn: func() *automock.StatusUpdateRepository {
   154  				repo := automock.StatusUpdateRepository{}
   155  				return &repo
   156  			},
   157  			Request:              &http.Request{},
   158  			ExpectedStatus:       http.StatusOK,
   159  			ExpectedErrorMessage: str.Ptr("An error has occurred while fetching consumer info from context:"),
   160  			ExpectedError:        str.Ptr("Internal Server Error: cannot read consumer from context"),
   161  			MockNextHandler:      fixNextHandler(t),
   162  		},
   163  		{
   164  			Name: "Error when starting transaction",
   165  			TxFn: txGen.ThatFailsOnBegin,
   166  			RepoFn: func() *automock.StatusUpdateRepository {
   167  				repo := automock.StatusUpdateRepository{}
   168  				return &repo
   169  			},
   170  			Request:              createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
   171  			ExpectedStatus:       http.StatusOK,
   172  			ExpectedErrorMessage: str.Ptr("An error has occurred while opening transaction:"),
   173  			ExpectedError:        str.Ptr("test"),
   174  			MockNextHandler:      fixNextHandler(t),
   175  		},
   176  		{
   177  			Name: "Error when checking if already connected",
   178  			TxFn: txGen.ThatDoesntExpectCommit,
   179  			RepoFn: func() *automock.StatusUpdateRepository {
   180  				repo := automock.StatusUpdateRepository{}
   181  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(false, testErr)
   182  				return &repo
   183  			},
   184  			Request:              createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
   185  			ExpectedStatus:       http.StatusOK,
   186  			ExpectedErrorMessage: str.Ptr("An error has occurred while checking repository status for applications with ID foo:"),
   187  			ExpectedError:        str.Ptr("test"),
   188  			MockNextHandler:      fixNextHandler(t),
   189  		},
   190  		{
   191  			Name: "Error when failing on commit",
   192  			TxFn: txGen.ThatFailsOnCommit,
   193  			RepoFn: func() *automock.StatusUpdateRepository {
   194  				repo := automock.StatusUpdateRepository{}
   195  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(true, nil)
   196  				return &repo
   197  			},
   198  			Request:              createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
   199  			ExpectedStatus:       http.StatusOK,
   200  			ExpectedErrorMessage: str.Ptr("An error has occurred while committing transaction:"),
   201  			ExpectedError:        str.Ptr("test"),
   202  			MockNextHandler:      fixNextHandler(t),
   203  		},
   204  		{
   205  			Name: "Error when updating status",
   206  			TxFn: txGen.ThatDoesntExpectCommit,
   207  			RepoFn: func() *automock.StatusUpdateRepository {
   208  				repo := automock.StatusUpdateRepository{}
   209  				repo.On("IsConnected", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(false, nil)
   210  				repo.On("UpdateStatus", txtest.CtxWithDBMatcher(), testID, statusupdate.Applications).Return(testErr)
   211  				return &repo
   212  			},
   213  			Request:              createRequestWithClaims(testID, consumer.Application, oathkeeper.CertificateFlow),
   214  			ExpectedStatus:       http.StatusOK,
   215  			ExpectedErrorMessage: str.Ptr("An error has occurred while updating repository status for applications with ID foo:"),
   216  			ExpectedError:        str.Ptr("test"),
   217  			MockNextHandler:      fixNextHandler(t),
   218  		},
   219  	}
   220  	for _, testCase := range testCases {
   221  		t.Run(testCase.Name, func(t *testing.T) {
   222  			repo := testCase.RepoFn()
   223  			persist, transact := testCase.TxFn()
   224  			var actualLog bytes.Buffer
   225  			logger, hook := logrustest.NewNullLogger()
   226  			logger.SetFormatter(&logrus.TextFormatter{
   227  				DisableTimestamp: true,
   228  			})
   229  			logger.SetOutput(&actualLog)
   230  			ctx := log.ContextWithLogger(testCase.Request.Context(), logrus.NewEntry(logger))
   231  			update := statusupdate.New(transact, repo)
   232  			req := testCase.Request.WithContext(ctx)
   233  			// WHEN
   234  			rr := httptest.NewRecorder()
   235  			updateHandler := update.Handler()
   236  			updateHandler(testCase.MockNextHandler).ServeHTTP(rr, req)
   237  
   238  			// THEN
   239  			response := rr.Body.String()
   240  			assert.Equal(t, testCase.ExpectedStatus, rr.Code)
   241  			if testCase.ExpectedResponse == "OK" {
   242  				assert.Equal(t, testCase.ExpectedResponse, response)
   243  			}
   244  			if testCase.ExpectedErrorMessage != nil {
   245  				assert.Equal(t, *testCase.ExpectedErrorMessage+" "+*testCase.ExpectedError, hook.LastEntry().Message)
   246  			}
   247  			if testCase.ExpectedError != nil {
   248  				assert.Equal(t, *testCase.ExpectedError, hook.LastEntry().Data["error"].(error).Error())
   249  			}
   250  			persist.AssertExpectations(t)
   251  			transact.AssertExpectations(t)
   252  		})
   253  	}
   254  }
   255  
   256  func createRequestWithClaims(id string, consumerType consumer.ConsumerType, flow oathkeeper.AuthFlow) *http.Request {
   257  	req := http.Request{}
   258  	apiConsumer := consumer.Consumer{ConsumerID: id, ConsumerType: consumerType, Flow: flow}
   259  	ctxWithConsumerInfo := consumer.SaveToContext(context.TODO(), apiConsumer)
   260  	return req.WithContext(ctxWithConsumerInfo)
   261  }
   262  
   263  func fixNextHandler(t *testing.T) http.HandlerFunc {
   264  	return func(w http.ResponseWriter, r *http.Request) {
   265  		_, err := w.Write([]byte("OK"))
   266  		require.NoError(t, err)
   267  	}
   268  }