github.com/companieshouse/lfp-pay-api@v0.0.0-20230203133422-0ca455cd79f9/handlers/patch_payable_resource_test.go (about)

     1  package handlers
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"io"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"testing"
    12  
    13  	"github.com/companieshouse/api-sdk-go/companieshouseapi"
    14  	"github.com/companieshouse/go-session-handler/httpsession"
    15  	"github.com/companieshouse/go-session-handler/session"
    16  	"github.com/companieshouse/lfp-pay-api-core/constants"
    17  	"github.com/companieshouse/lfp-pay-api-core/models"
    18  	"github.com/companieshouse/lfp-pay-api/config"
    19  	"github.com/companieshouse/lfp-pay-api/dao"
    20  	"github.com/companieshouse/lfp-pay-api/e5"
    21  	"github.com/companieshouse/lfp-pay-api/mocks"
    22  	"github.com/companieshouse/lfp-pay-api/service"
    23  	"github.com/golang/mock/gomock"
    24  	"github.com/jarcoal/httpmock"
    25  	. "github.com/smartystreets/goconvey/convey"
    26  )
    27  
    28  var e5ValidationError = `
    29  {
    30    "httpStatusCode" : 400,
    31    "status" : "BAD_REQUEST",
    32    "timestamp" : "2019-07-07T18:40:07Z",
    33    "messageCode" : null,
    34    "message" : "Constraint Validation error",
    35    "debugMessage" : null,
    36    "subErrors" : [ {
    37      "object" : "String",
    38      "field" : "companyCode",
    39      "rejectedValue" : "LPs",
    40      "message" : "size must be between 0 and 2"
    41    } ]
    42  }
    43  `
    44  
    45  // reduces the boilerplate code needed to create, dispatch and unmarshal response body
    46  func dispatchPayResourceHandler(
    47  	ctx context.Context,
    48  	t *testing.T,
    49  	reqBody *models.PatchResourceRequest,
    50  	daoSvc dao.Service) (*httptest.ResponseRecorder, *models.ResponseResource) {
    51  
    52  	svc := &service.PayableResourceService{}
    53  
    54  	if daoSvc != nil {
    55  		svc.DAO = daoSvc
    56  	}
    57  
    58  	var body io.Reader
    59  	if reqBody != nil {
    60  		b, err := json.Marshal(reqBody)
    61  		if err != nil {
    62  			t.Fatal("failed to marshal request body")
    63  		}
    64  		body = bytes.NewReader(b)
    65  	}
    66  
    67  	ctx = context.WithValue(ctx, httpsession.ContextKeySession, &session.Session{})
    68  
    69  	h := PayResourceHandler(svc, e5.NewClient("foo", "e5api"))
    70  	req := httptest.NewRequest(http.MethodPost, "/", body).WithContext(ctx)
    71  	res := httptest.NewRecorder()
    72  
    73  	h.ServeHTTP(res, req.WithContext(ctx))
    74  
    75  	if res.Body.Len() > 0 {
    76  		var responseBody models.ResponseResource
    77  		err := json.NewDecoder(res.Body).Decode(&responseBody)
    78  		if err != nil {
    79  			t.Errorf("failed to read response body")
    80  		}
    81  
    82  		return res, &responseBody
    83  	}
    84  
    85  	return res, nil
    86  }
    87  
    88  // Mock function for erroring when preparing and sending kafka message
    89  func mockSendEmailKafkaMessageError(payableResource models.PayableResource, req *http.Request) error {
    90  	return errors.New("error")
    91  }
    92  
    93  // Mock function for successful preparing and sending of kafka message
    94  func mockSendEmailKafkaMessage(payableResource models.PayableResource, req *http.Request) error {
    95  	return nil
    96  }
    97  
    98  func TestUnitPayResourceHandler(t *testing.T) {
    99  	Convey("PayResourceHandler tests", t, func() {
   100  		httpmock.Activate()
   101  		defer httpmock.DeactivateAndReset()
   102  
   103  		Convey("payable resource must be in context", func() {
   104  			res, body := dispatchPayResourceHandler(context.Background(), t, nil, nil)
   105  
   106  			So(res.Code, ShouldEqual, http.StatusBadRequest)
   107  			So(body.Message, ShouldEqual, "no payable request present in request context")
   108  		})
   109  
   110  		Convey("reference is required in request body", func() {
   111  			ctx := context.WithValue(context.Background(), config.PayableResource, &models.PayableResource{})
   112  			res, body := dispatchPayResourceHandler(ctx, t, &models.PatchResourceRequest{}, nil)
   113  
   114  			So(res.Code, ShouldEqual, http.StatusBadRequest)
   115  			So(body.Message, ShouldEqual, "the request contained insufficient data and/or failed validation")
   116  		})
   117  
   118  		Convey("bad responses from payment api", func() {
   119  			defer httpmock.Reset()
   120  
   121  			httpmock.RegisterResponder(
   122  				http.MethodGet,
   123  				"/payments/123",
   124  				httpmock.NewStringResponder(404, ""),
   125  			)
   126  
   127  			model := &models.PayableResource{Reference: "123"}
   128  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   129  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   130  
   131  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, nil)
   132  
   133  			So(res.Code, ShouldEqual, http.StatusBadRequest)
   134  			So(body.Message, ShouldEqual, "the payable resource does not exist")
   135  		})
   136  
   137  		Convey("payment (from payments api) is not paid", func() {
   138  			mockCtrl := gomock.NewController(t)
   139  			defer mockCtrl.Finish()
   140  
   141  			// stub the response from the payments api
   142  			p := &companieshouseapi.PaymentResource{Status: "failed", Amount: "150"}
   143  			responder, _ := httpmock.NewJsonResponder(http.StatusOK, p)
   144  			httpmock.RegisterResponder(
   145  				http.MethodGet,
   146  				companieshouseapi.PaymentsBasePath+"/payments/123",
   147  				responder,
   148  			)
   149  			httpmock.RegisterResponder(
   150  				http.MethodGet,
   151  				companieshouseapi.PaymentsBasePath+"/private/payments/123/payment-details",
   152  				httpmock.NewStringResponder(http.StatusOK, "{}"),
   153  			)
   154  
   155  			// the payable resource in the request context
   156  			model := &models.PayableResource{Reference: "123"}
   157  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   158  
   159  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   160  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, nil)
   161  
   162  			So(res.Code, ShouldEqual, http.StatusBadRequest)
   163  			So(body.Message, ShouldEqual, "there was a problem validating this payment")
   164  		})
   165  
   166  		Convey("problem with sending confirmation email", func() {
   167  			mockCtrl := gomock.NewController(t)
   168  			defer mockCtrl.Finish()
   169  
   170  			// stub the response from the payments api
   171  			p := &companieshouseapi.PaymentResource{Status: "paid", Amount: "0", Reference: "late_filing_penalty_123"}
   172  			responder, _ := httpmock.NewJsonResponder(http.StatusOK, p)
   173  			httpmock.RegisterResponder(
   174  				http.MethodGet,
   175  				companieshouseapi.PaymentsBasePath+"/payments/123",
   176  				responder,
   177  			)
   178  
   179  			httpmock.RegisterResponder(
   180  				http.MethodGet,
   181  				companieshouseapi.PaymentsBasePath+"/private/payments/123/payment-details",
   182  				httpmock.NewStringResponder(http.StatusOK, "{}"),
   183  			)
   184  
   185  			// stub the mongo lookup
   186  			dataModel := &models.PayableResourceDao{}
   187  			mockService := mocks.NewMockService(mockCtrl)
   188  			mockService.EXPECT().GetPayableResource(gomock.Any(), gomock.Any()).Return(dataModel, nil)
   189  			mockService.EXPECT().UpdatePaymentDetails(dataModel).Times(1)
   190  			mockService.EXPECT().SaveE5Error("", "123", e5.CreateAction).Return(errors.New(""))
   191  
   192  			// the payable resource in the request context
   193  			model := &models.PayableResource{Reference: "123"}
   194  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   195  
   196  			// stub kafka message
   197  			handleEmailKafkaMessage = mockSendEmailKafkaMessageError
   198  
   199  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   200  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, mockService)
   201  
   202  			So(dataModel.IsPaid(), ShouldBeTrue)
   203  			So(res.Code, ShouldEqual, http.StatusInternalServerError)
   204  			So(body, ShouldBeNil)
   205  		})
   206  
   207  		Convey("LFP has already been paid", func() {
   208  			mockCtrl := gomock.NewController(t)
   209  			defer mockCtrl.Finish()
   210  
   211  			// stub the response from the payments api
   212  			p := &companieshouseapi.PaymentResource{Status: "paid", Amount: "0", Reference: "late_filing_penalty_123"}
   213  			responder, _ := httpmock.NewJsonResponder(http.StatusOK, p)
   214  			httpmock.RegisterResponder(
   215  				http.MethodGet,
   216  				companieshouseapi.PaymentsBasePath+"/payments/123",
   217  				responder,
   218  			)
   219  
   220  			httpmock.RegisterResponder(
   221  				http.MethodGet,
   222  				companieshouseapi.PaymentsBasePath+"/private/payments/123/payment-details",
   223  				httpmock.NewStringResponder(http.StatusOK, "{}"),
   224  			)
   225  
   226  			// stub the mongo lookup
   227  			dataModel := &models.PayableResourceDao{
   228  				Data: models.PayableResourceDataDao{
   229  					Payment: models.PaymentDao{
   230  						Status: constants.Paid.String(),
   231  					},
   232  				},
   233  			}
   234  
   235  			mockService := mocks.NewMockService(mockCtrl)
   236  			mockService.EXPECT().GetPayableResource(gomock.Any(), gomock.Any()).Return(dataModel, nil)
   237  			mockService.EXPECT().SaveE5Error("", "123", e5.CreateAction).Return(errors.New(""))
   238  
   239  			// the payable resource in the request context
   240  			model := &models.PayableResource{Reference: "123"}
   241  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   242  
   243  			// stub kafka message
   244  			handleEmailKafkaMessage = mockSendEmailKafkaMessage
   245  
   246  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   247  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, mockService)
   248  
   249  			So(dataModel.IsPaid(), ShouldBeTrue)
   250  			So(res.Code, ShouldEqual, http.StatusInternalServerError)
   251  			So(body, ShouldBeNil)
   252  		})
   253  
   254  		Convey("problem with sending request to E5", func() {
   255  			mockCtrl := gomock.NewController(t)
   256  			defer mockCtrl.Finish()
   257  
   258  			// stub the response from the payments api
   259  			p := &companieshouseapi.PaymentResource{Status: "paid", Amount: "0", Reference: "late_filing_penalty_123"}
   260  			responder, _ := httpmock.NewJsonResponder(http.StatusOK, p)
   261  			httpmock.RegisterResponder(
   262  				http.MethodGet,
   263  				companieshouseapi.PaymentsBasePath+"/payments/123",
   264  				responder,
   265  			)
   266  
   267  			httpmock.RegisterResponder(
   268  				http.MethodGet,
   269  				companieshouseapi.PaymentsBasePath+"/private/payments/123/payment-details",
   270  				httpmock.NewStringResponder(http.StatusOK, "{}"),
   271  			)
   272  
   273  			// stub the response from the e5 api
   274  			e5Responder := httpmock.NewStringResponder(http.StatusBadRequest, e5ValidationError)
   275  			httpmock.RegisterResponder(http.MethodPost, "/arTransactions/payment", e5Responder)
   276  
   277  			// stub the mongo lookup
   278  			dataModel := &models.PayableResourceDao{}
   279  			mockService := mocks.NewMockService(mockCtrl)
   280  			mockService.EXPECT().GetPayableResource(gomock.Any(), gomock.Any()).Return(dataModel, nil)
   281  			mockService.EXPECT().UpdatePaymentDetails(dataModel).Times(1)
   282  			mockService.EXPECT().SaveE5Error("", "123", e5.CreateAction).Return(errors.New(""))
   283  
   284  			// the payable resource in the request context
   285  			model := &models.PayableResource{Reference: "123"}
   286  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   287  
   288  			// stub kafka message
   289  			handleEmailKafkaMessage = mockSendEmailKafkaMessage
   290  
   291  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   292  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, mockService)
   293  
   294  			So(dataModel.IsPaid(), ShouldBeTrue)
   295  			So(res.Code, ShouldEqual, http.StatusInternalServerError)
   296  			So(body, ShouldBeNil)
   297  		})
   298  
   299  		Convey("success when payment is valid", func() {
   300  			mockCtrl := gomock.NewController(t)
   301  			defer mockCtrl.Finish()
   302  
   303  			// stub the response from the payments api
   304  			p := &companieshouseapi.PaymentResource{
   305  				Status:    "paid",
   306  				Amount:    "150",
   307  				Reference: "late_filing_penalty_123",
   308  				CreatedBy: companieshouseapi.CreatedBy{
   309  					Email: "test@example.com",
   310  				},
   311  			}
   312  
   313  			responder, _ := httpmock.NewJsonResponder(http.StatusOK, p)
   314  			httpmock.RegisterResponder(
   315  				http.MethodGet,
   316  				companieshouseapi.PaymentsBasePath+"/payments/123",
   317  				responder,
   318  			)
   319  
   320  			httpmock.RegisterResponder(
   321  				http.MethodGet,
   322  				companieshouseapi.PaymentsBasePath+"/private/payments/123/payment-details",
   323  				httpmock.NewStringResponder(http.StatusOK, "{}"),
   324  			)
   325  
   326  			// stub the response from the e5 api
   327  			e5Responder := httpmock.NewBytesResponder(http.StatusOK, nil)
   328  			httpmock.RegisterResponder(http.MethodPost, "e5api/arTransactions/payment", e5Responder)
   329  			httpmock.RegisterResponder(http.MethodPost, "e5api/arTransactions/payment/authorise", e5Responder)
   330  			httpmock.RegisterResponder(http.MethodPost, "e5api/arTransactions/payment/confirm", e5Responder)
   331  
   332  			// stub the mongo lookup
   333  			dataModel := &models.PayableResourceDao{}
   334  			mockService := mocks.NewMockService(mockCtrl)
   335  			mockService.EXPECT().GetPayableResource(gomock.Any(), gomock.Any()).Return(dataModel, nil)
   336  			mockService.EXPECT().UpdatePaymentDetails(dataModel).Times(1)
   337  
   338  			// the payable resource in the request context
   339  			model := &models.PayableResource{
   340  				Reference:     "123",
   341  				CompanyNumber: "10000024",
   342  				Transactions: []models.TransactionItem{
   343  					{TransactionID: "123", Amount: 150},
   344  				},
   345  			}
   346  			ctx := context.WithValue(context.Background(), config.PayableResource, model)
   347  
   348  			// stub kafka message
   349  			handleEmailKafkaMessage = mockSendEmailKafkaMessage
   350  
   351  			reqBody := &models.PatchResourceRequest{Reference: "123"}
   352  			res, body := dispatchPayResourceHandler(ctx, t, reqBody, mockService)
   353  
   354  			So(res.Code, ShouldEqual, http.StatusNoContent)
   355  			So(body, ShouldBeNil)
   356  		})
   357  	})
   358  }