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 }