diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberController.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberController.java index 16b297fb..10da03a9 100644 --- a/backend/src/main/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberController.java +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberController.java @@ -21,15 +21,19 @@ import ca.bc.gov.hlth.hnweb.converter.rapid.RPBSPED0Converter; import ca.bc.gov.hlth.hnweb.converter.rapid.RPBSPEE0Converter; import ca.bc.gov.hlth.hnweb.converter.rapid.RPBSPWC0Converter; +import ca.bc.gov.hlth.hnweb.converter.rapid.RPBSPWP0Converter; import ca.bc.gov.hlth.hnweb.converter.rapid.RPBSPXP0Converter; import ca.bc.gov.hlth.hnweb.exception.HNWebException; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPED0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPEE0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPWC0; +import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPWP0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPXP0; import ca.bc.gov.hlth.hnweb.model.rest.StatusEnum; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.AddGroupMemberRequest; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.AddGroupMemberResponse; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentRequest; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentResponse; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelGroupMemberRequest; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelGroupMemberResponse; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.UpdateNumberAndDeptRequest; @@ -173,6 +177,42 @@ public ResponseEntity cancelGroupMember(@Valid @Reque } } + + /** + * Cancels a group member's dependent coverage. + * Maps to the legacy R36. + * + * @param cancelDependentRequest + * @return The result of the operation. + */ + @PostMapping("/cancel-dependent") + public ResponseEntity cancelDependent(@Valid @RequestBody CancelDependentRequest cancelDependentRequest) { + + try { + RPBSPWP0Converter converter = new RPBSPWP0Converter(); + RPBSPWP0 rpbspwp0 = converter.convertRequest(cancelDependentRequest); + RPBSPWP0 rpbspwc0Response = groupMemberService.cancelDependent(rpbspwp0); + CancelDependentResponse cancelDependentResponse = converter.convertResponse(rpbspwc0Response); + + ResponseEntity response = ResponseEntity.ok(cancelDependentResponse); + + logger.info("CancelDependentResponse response: {} ", cancelDependentResponse); + return response; + } catch (HNWebException hwe) { + switch (hwe.getType()) { + case DOWNSTREAM_FAILURE: + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, hwe.getMessage(), hwe); + default: + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad /cancel-dependent request", hwe); + } + } catch (WebClientException wce) { + throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, wce.getMessage(), wce); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Bad /cancel-dependent request", e); + } + + } + private UpdateNumberAndDeptResponse handleUpdateGroupMemberResponse(UpdateNumberAndDeptResponse deptNumberResponse, UpdateNumberAndDeptResponse empNumberResponse) { UpdateNumberAndDeptResponse response = new UpdateNumberAndDeptResponse(); diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/converter/rapid/RPBSPWP0Converter.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/converter/rapid/RPBSPWP0Converter.java new file mode 100644 index 00000000..7ed1b8ce --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/converter/rapid/RPBSPWP0Converter.java @@ -0,0 +1,50 @@ +package ca.bc.gov.hlth.hnweb.converter.rapid; + +import ca.bc.gov.hlth.hnweb.model.rapid.RPBSHeader; +import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPWP0; +import ca.bc.gov.hlth.hnweb.model.rapid.WP0; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentRequest; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentResponse;; + +public class RPBSPWP0Converter extends BaseRapidConverter { + private static final String TRAN_CODE = "RPBSPWP0"; + + public RPBSPWP0Converter() { + super(); + } + + public RPBSPWP0 convertRequest(CancelDependentRequest request) { + RPBSHeader rpbsHeader = new RPBSHeader(); + rpbsHeader.setOrganization(userInfo.getOrganization()); + rpbsHeader.setTranCode(getTranCode()); + + WP0 wp0 = new WP0(); + wp0.setGroupNumber(request.getGroupNumber()); + wp0.setSubscriberPhn(request.getPhn()); + wp0.setBeneficiaryPhn(request.getDependentPhn()); + wp0.setCoverageCancelDate(formatDate(request.getCoverageCancelDate())); + wp0.setPayerCancelReason(request.getCancelReason()); + + RPBSPWP0 rpbspwp0 = new RPBSPWP0(); + rpbspwp0.setRpbsHeader(rpbsHeader); + rpbspwp0.setWp0(wp0); + + return rpbspwp0; + } + + public CancelDependentResponse convertResponse(RPBSPWP0 rpbspwp0) { + CancelDependentResponse response = new CancelDependentResponse(); + RPBSHeader header = rpbspwp0.getRpbsHeader(); + + handleStatus(header, response); + response.setPhn(rpbspwp0.getWp0().getSubscriberPhn()); + + return response; + } + + @Override + public String getTranCode() { + return TRAN_CODE; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/RPBSPWP0.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/RPBSPWP0.java new file mode 100644 index 00000000..edfd2649 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/RPBSPWP0.java @@ -0,0 +1,42 @@ +package ca.bc.gov.hlth.hnweb.model.rapid; + +import org.apache.commons.lang3.StringUtils; + +public class RPBSPWP0 { + private RPBSHeader rpbsHeader = new RPBSHeader(); + private WP0 wp0 = new WP0(); + + public RPBSPWP0() { + super(); + } + + public RPBSPWP0(String message) { + String headerText = StringUtils.substring(message, 0, RPBSHeader.SEGMENT_LENGTH); + String bodyText = StringUtils.substring(message, RPBSHeader.SEGMENT_LENGTH); + + rpbsHeader = new RPBSHeader(headerText); + + wp0 = new WP0(bodyText); + } + + public WP0 getWp0() { + return wp0; + } + + public void setWp0(WP0 wp0) { + this.wp0 = wp0; + } + + public RPBSHeader getRpbsHeader() { + return rpbsHeader; + } + + public void setRpbsHeader(RPBSHeader rpbsHeader) { + this.rpbsHeader = rpbsHeader; + } + + public String serialize() { + return rpbsHeader.serialize() + wp0.serialize(); + } + +} diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/WP0.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/WP0.java new file mode 100644 index 00000000..946a6e38 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rapid/WP0.java @@ -0,0 +1,84 @@ +package ca.bc.gov.hlth.hnweb.model.rapid; + +import org.apache.commons.lang3.StringUtils; + +public class WP0 { + + /** 1 SubscriberPHN String No 0...10 */ + private String subscriberPhn; + /** 2 GroupNumber String No 0...7 */ + private String groupNumber; + /** 3 BeneficiaryPHN String No 0...10 */ + private String beneficiaryPhn; + /** 4 CoverageCancelDate String No 0...10 */ + private String coverageCancelDate; + /** 5 PayerCancelReason String No 0...1 */ + private String payerCancelReason; + + public WP0() { + super(); + } + + public WP0(String message) { + super(); + subscriberPhn = StringUtils.substring(message, 0, 10); + groupNumber = StringUtils.substring(message, 10, 17); + beneficiaryPhn = StringUtils.substring(message, 17, 27); + coverageCancelDate = StringUtils.substring(message, 27, 37); + // I , E or P + payerCancelReason = StringUtils.substring(message, 37, 38); + } + + public String serialize() { + // Serialize is only used in when creating the request + StringBuilder sb = new StringBuilder(); + sb.append(StringUtils.rightPad(subscriberPhn, 10)); + sb.append(StringUtils.rightPad(groupNumber, 7)); + sb.append(StringUtils.rightPad(beneficiaryPhn, 10)); + sb.append(StringUtils.rightPad(coverageCancelDate, 10)); + sb.append(StringUtils.rightPad(payerCancelReason, 1)); + + return sb.toString(); + } + + public String getSubscriberPhn() { + return subscriberPhn; + } + + public void setSubscriberPhn(String subscriberPhn) { + this.subscriberPhn = subscriberPhn; + } + + public String getGroupNumber() { + return groupNumber; + } + + public void setGroupNumber(String groupNumber) { + this.groupNumber = groupNumber; + } + + public String getBeneficiaryPhn() { + return beneficiaryPhn; + } + + public void setBeneficiaryPhn(String beneficiaryPhn) { + this.beneficiaryPhn = beneficiaryPhn; + } + + public String getCoverageCancelDate() { + return coverageCancelDate; + } + + public void setCoverageCancelDate(String coverageCancelDate) { + this.coverageCancelDate = coverageCancelDate; + } + + public String getPayerCancelReason() { + return payerCancelReason; + } + + public void setPayerCancelReason(String payerCancelReason) { + this.payerCancelReason = payerCancelReason; + } + +} diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentRequest.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentRequest.java new file mode 100644 index 00000000..8b7363f9 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentRequest.java @@ -0,0 +1,59 @@ +package ca.bc.gov.hlth.hnweb.model.rest.groupmember; + +import java.time.LocalDate; + +public class CancelDependentRequest { + private String phn; + private String groupNumber; + private String dependentPhn; + private LocalDate coverageCancelDate; + // Valid values are I, E and P + private String cancelReason; + + public String getPhn() { + return phn; + } + + public void setPhn(String phn) { + this.phn = phn; + } + + public String getGroupNumber() { + return groupNumber; + } + + public void setGroupNumber(String groupNumber) { + this.groupNumber = groupNumber; + } + + public String getDependentPhn() { + return dependentPhn; + } + + public void setDependentPhn(String dependentPhn) { + this.dependentPhn = dependentPhn; + } + + public LocalDate getCoverageCancelDate() { + return coverageCancelDate; + } + + public void setCoverageCancelDate(LocalDate coverageCancelDate) { + this.coverageCancelDate = coverageCancelDate; + } + + public String getCancelReason() { + return cancelReason; + } + + public void setCancelReason(String cancelReason) { + this.cancelReason = cancelReason; + } + + @Override + public String toString() { + return "CancelGroupMemberRequest [phn=" + phn + ", groupNumber=" + groupNumber + ",dependentPhn=" + dependentPhn + ", coverageCancelDate=" + coverageCancelDate + + ", cancelReason=" + cancelReason + "]"; + } + +} diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentResponse.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentResponse.java new file mode 100644 index 00000000..7a48cb97 --- /dev/null +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/model/rest/groupmember/CancelDependentResponse.java @@ -0,0 +1,21 @@ +package ca.bc.gov.hlth.hnweb.model.rest.groupmember; + +import ca.bc.gov.hlth.hnweb.model.rest.BaseResponse; + +public class CancelDependentResponse extends BaseResponse { + private String phn; + + public String getPhn() { + return phn; + } + + public void setPhn(String phn) { + this.phn = phn; + } + + @Override + public String toString() { + return "CancelGroupMemberResponse [phn=" + phn + ", status=" + status + ", message=" + message + "]"; + } + +} diff --git a/backend/src/main/java/ca/bc/gov/hlth/hnweb/service/GroupMemberService.java b/backend/src/main/java/ca/bc/gov/hlth/hnweb/service/GroupMemberService.java index daa8715f..0257a853 100644 --- a/backend/src/main/java/ca/bc/gov/hlth/hnweb/service/GroupMemberService.java +++ b/backend/src/main/java/ca/bc/gov/hlth/hnweb/service/GroupMemberService.java @@ -17,6 +17,7 @@ import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPED0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPEE0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPWC0; +import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPWP0; import ca.bc.gov.hlth.hnweb.model.rapid.RPBSPXP0; /** @@ -39,6 +40,9 @@ public class GroupMemberService { @Value("${rapid.r35Path}") private String r35Path; + @Value("${rapid.r36Path}") + private String r36Path; + @Autowired private WebClient rapidWebClient; @@ -145,6 +149,32 @@ public RPBSPWC0 cancelGroupMember(RPBSPWC0 rpbspwc0) throws HNWebException { return rpbspwc0Response; } + + /** + * Cancels the group member's dependent coverage based on the R36/RPBSPWP0. + * + * @param rpbspwp0 + * @return The RPBSPWP0 response. + * @throws HNWebException + */ + public RPBSPWP0 cancelDependent(RPBSPWP0 rpbspwp0) throws HNWebException { + String rpbspwp0Str = rpbspwp0.serialize(); + + logger.info("Request {}", rpbspwp0Str); + + ResponseEntity response = postRapidRequest(r36Path, rpbspwp0Str); + + logger.debug("Response Status: {} ; Message:\n{}", response.getStatusCode(), response.getBody()); + + if (response.getStatusCode() != HttpStatus.OK) { + logger.error("Could not connect to downstream service. Service returned {}", response.getStatusCode()); + throw new HNWebException(ExceptionType.DOWNSTREAM_FAILURE); + } + + RPBSPWP0 rpbspwp0Response = new RPBSPWP0(response.getBody()); + + return rpbspwp0Response; + } private ResponseEntity postRapidRequest(String path, String body) { return rapidWebClient diff --git a/backend/src/test/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberControllerTest.java b/backend/src/test/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberControllerTest.java index d622c02b..c641fccf 100644 --- a/backend/src/test/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberControllerTest.java +++ b/backend/src/test/java/ca/bc/gov/hlth/hnweb/controller/GroupMemberControllerTest.java @@ -27,6 +27,8 @@ import ca.bc.gov.hlth.hnweb.model.rest.groupmember.UpdateNumberAndDeptResponse; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.AddGroupMemberRequest; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.AddGroupMemberResponse; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentRequest; +import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelDependentResponse; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelGroupMemberRequest; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.CancelGroupMemberResponse; import ca.bc.gov.hlth.hnweb.model.rest.groupmember.MemberAddress; @@ -54,6 +56,13 @@ public class GroupMemberControllerTest { private static final String RPBSPXP0_ERROR_COVERAGE_ALREADY_EXISTS = " RPBSPXP000000010 ERRORMSGRPBS0065COVERAGE ALREADY EXISTS FOR THE PHN/GROUP NUMBER SPECIFIED 63371091111 222 2022-01-01 6045551234 9873895927 9873895927"; private static final String RPBSPXP0_SUCCESS = " RPBSPXP000000010 RESPONSERPBS9014TRANSACTION SUCCESSFUL 62431091111 222 2022-01-01123 main st V1V1V1 6045551234 9873895902 "; + private static final String RPBSPWP0_SUCCESS = " RPBSPWP000000010 RESPONSERPBS9014TRANSACTION SUCCESSFUL 9873895927633710998828072772022-12-31P"; + private static final String RPBSPWP0_ERROR_SAME_DEPENDENT_PHN = " RPBSPWP000000010 ERRORMSGRPBS0097SUBSCRIBER AND DEPENDENT PHN MUST BE DIFFERENT. 9882807277624310998828072772022-01-31P"; + private static final String RPBSPWP0_ERROR_PHN_NOT_IN_GROUP = " RPBSPWP000000010 ERRORMSGRPBS0105PHN MUST BE A SUBSCRIBER IN GROUP 9340338122624310993290908952023-02-28I"; + private static final String RPBSPWP0_ERROR_DEPENDENT_HAS_NO_COVERAGE_UNDER_SUBSCRIBER= " RPBSPWP000000010 ERRORMSGRPBS0104DEPENDENT MUST HAVE COVERAGE UNDER SUBSCRIBER. 9331926919633710998828072772022-01-31P"; + private static final String RPBSPWP0_ERROR_NO_ACTIVE_COVERAGES_FOUND= " RPBSPWP000000010 ERRORMSGRPBS0047NO ACTIVE COVERAGES FOUND. PLS FORWARD SOURCE DOCS TO MSP 9340338122633710993290908952022-01-31I"; + private static final String RPBSPWP0_ERROR_FUTURE_CANCEL_DATE= " RPBSPWP000000010 ERRORMSGRPBS0090DEPENDENT HAS A FUTURE CANCEL DATE. PLS FORWARD DOCS TO MSP 9340338122633710993290908952023-02-28I"; + private static MockWebServer mockBackEnd; private static MockedStatic mockStatic; @@ -351,6 +360,156 @@ public void testAddGroupMember_success() throws InterruptedException { assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); } + + @Test + public void testCancelDependent_dependentNotUnderSubcriberCoverage() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody(RPBSPWP0_ERROR_DEPENDENT_HAS_NO_COVERAGE_UNDER_SUBSCRIBER) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9331926919"); + cancelDependentRequest.setGroupNumber("6337109"); + cancelDependentRequest.setDependentPhn("9882807277"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2022, 01, 31)); + cancelDependentRequest.setCancelReason("I"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.ERROR, cancelDependentResponse.getStatus()); + assertEquals("RPBS0104 DEPENDENT MUST HAVE COVERAGE UNDER SUBSCRIBER.", cancelDependentResponse.getMessage()); + assertEquals("9331926919", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + + @Test + public void testCancelDependent_subscriberPhnNotInGroup() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody( RPBSPWP0_ERROR_PHN_NOT_IN_GROUP) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9340338122"); + cancelDependentRequest.setGroupNumber("6243109"); + cancelDependentRequest.setDependentPhn("9329090895"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2023, 02, 28)); + cancelDependentRequest.setCancelReason("I"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.ERROR, cancelDependentResponse.getStatus()); + assertEquals("RPBS0105 PHN MUST BE A SUBSCRIBER IN GROUP", cancelDependentResponse.getMessage()); + assertEquals("9340338122", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + + @Test + public void testCancelDependent_subscriberAndDependentPhnAreSame() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody(RPBSPWP0_ERROR_SAME_DEPENDENT_PHN) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9882807277"); + cancelDependentRequest.setGroupNumber("6337109"); + cancelDependentRequest.setDependentPhn("9882807277"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2022, 01, 31)); + cancelDependentRequest.setCancelReason("I"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.ERROR, cancelDependentResponse.getStatus()); + assertEquals("RPBS0097 SUBSCRIBER AND DEPENDENT PHN MUST BE DIFFERENT.", cancelDependentResponse.getMessage()); + assertEquals("9882807277", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + + @Test + public void testCancelDependent_noActiveCoverage() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody(RPBSPWP0_ERROR_NO_ACTIVE_COVERAGES_FOUND) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9340338122"); + cancelDependentRequest.setGroupNumber("6337109"); + cancelDependentRequest.setDependentPhn("9329090895"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2022, 01, 31)); + cancelDependentRequest.setCancelReason("I"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.ERROR, cancelDependentResponse.getStatus()); + assertEquals("RPBS0047 NO ACTIVE COVERAGES FOUND. PLS FORWARD SOURCE DOCS TO MSP", cancelDependentResponse.getMessage()); + assertEquals("9340338122", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + @Test + public void testCancelDependent_futureCancelDate() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody(RPBSPWP0_ERROR_FUTURE_CANCEL_DATE) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9340338122"); + cancelDependentRequest.setGroupNumber("6337109"); + cancelDependentRequest.setDependentPhn("9329090895"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2023, 02, 28)); + cancelDependentRequest.setCancelReason("I"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.ERROR, cancelDependentResponse.getStatus()); + assertEquals("RPBS0090 DEPENDENT HAS A FUTURE CANCEL DATE. PLS FORWARD DOCS TO MSP", cancelDependentResponse.getMessage()); + assertEquals("9340338122", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + @Test + public void testCancelDependent_success() throws InterruptedException { + mockBackEnd.enqueue(new MockResponse() + .setBody(RPBSPWP0_SUCCESS) + .addHeader(CONTENT_TYPE, MediaType.TEXT_PLAIN.toString())); + + + CancelDependentRequest cancelDependentRequest = new CancelDependentRequest(); + cancelDependentRequest.setPhn("9873895927"); + cancelDependentRequest.setGroupNumber("6337109"); + cancelDependentRequest.setDependentPhn("9397105575"); + cancelDependentRequest.setCoverageCancelDate(LocalDate.of(2022, 01, 31)); + cancelDependentRequest.setCancelReason("K"); + + ResponseEntity response = groupMemberController.cancelDependent(cancelDependentRequest); + + CancelDependentResponse cancelDependentResponse = response.getBody(); + assertEquals(StatusEnum.SUCCESS, cancelDependentResponse.getStatus()); + assertEquals("TRANSACTION SUCCESSFUL", cancelDependentResponse.getMessage()); + assertEquals("9873895927", cancelDependentResponse.getPhn()); + + // Check the client request is sent as expected + RecordedRequest recordedRequest = mockBackEnd.takeRequest(); + assertEquals(HttpMethod.POST.name(), recordedRequest.getMethod()); + } + /** * The URL property used by the mocked endpoint needs to be set after the MockWebServer starts as the port it uses is * created dynamically on start up to ensure it uses an available port so it is not known before then. diff --git a/backend/src/test/resources/application-dev.yaml b/backend/src/test/resources/application-dev.yaml index e8ddd6cd..67da9bf7 100644 --- a/backend/src/test/resources/application-dev.yaml +++ b/backend/src/test/resources/application-dev.yaml @@ -13,7 +13,8 @@ rapid: url: r30Path: r34Path: - r35Path: + r35Path: + r36Path: r41Path: r42Path: user: diff --git a/frontend/src/components/template/TheNavBar.vue b/frontend/src/components/template/TheNavBar.vue index b35a408a..dc42e210 100644 --- a/frontend/src/components/template/TheNavBar.vue +++ b/frontend/src/components/template/TheNavBar.vue @@ -9,10 +9,10 @@ @@ -30,11 +30,12 @@ @@ -89,7 +90,7 @@ export default { const hasPermission = this.$store.getters['auth/hasPermission'] return hasPermission('E45') || hasPermission('R15') || hasPermission('R41') }, - } + }, } diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index d28ffc44..408b2131 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -1,23 +1,24 @@ import { createRouter, createWebHistory } from 'vue-router' -import Help from './../views/Help.vue' -import Home from './../views/Home.vue' -import CheckEligibility from './../views/eligibility/CheckEligibility.vue' -import CoverageStatusCheck from './../views/eligibility/CoverageStatusCheck.vue' -import store from '../store' -import NotFound from '../views/NotFound.vue' -import Unauthorized from '../views/Unauthorized.vue' +import AddGroupMember from '../views/groupmember/AddGroupMember.vue' import AddVisaResidentWithPHN from '../views/coverage/enrollment/AddVisaResidentWithPHN.vue' import AddVisaResidentWithoutPHN from '../views/coverage/enrollment/AddVisaResidentWithoutPHN.vue' +import CancelDependent from '../views/groupmember/CancelDependent.vue' +import CancelGroupMember from '../views/groupmember/CancelGroupMember.vue' +import CheckEligibility from './../views/eligibility/CheckEligibility.vue' import CoverageEnrollmentHome from '../views/coverage/enrollment/CoverageEnrollmentHome.vue' import CoverageMaintenanceHome from '../views/coverage/maintenance/CoverageMaintenanceHome.vue' +import CoverageStatusCheck from './../views/eligibility/CoverageStatusCheck.vue' import EligibilityHome from '../views/eligibility/EligibilityHome.vue' +import GroupMemberHome from '../views/groupmember/GroupMemberHome.vue' +import Help from './../views/Help.vue' +import Home from './../views/Home.vue' +import NotFound from '../views/NotFound.vue' import PhnInquiry from '../views/eligibility/PhnInquiry.vue' import PhnLookup from '../views/eligibility/PhnLookup.vue' -import AddGroupMember from '../views/groupmember/AddGroupMember.vue' -import CancelGroupMember from '../views/groupmember/CancelGroupMember.vue' -import GroupMemberHome from '../views/groupmember/GroupMemberHome.vue' +import Unauthorized from '../views/Unauthorized.vue' import UpdateNumberAndDept from '../views/groupmember/UpdateNumberAndDept.vue' +import store from '../store' const routes = [ { @@ -103,20 +104,24 @@ const routes = [ { path: 'addGroupMember', name: 'AddGroupMember', - component: AddGroupMember, + component: AddGroupMember, }, { path: 'updateNumberAndDept', name: 'UpdateNumberAndDept', - component: UpdateNumberAndDept, + component: UpdateNumberAndDept, }, { path: 'cancelGroupMember', name: 'CancelGroupMember', component: CancelGroupMember, }, - - ] + { + path: 'cancelDependent', + name: 'CancelDependent', + component: CancelDependent, + }, + ], }, { path: '/help', diff --git a/frontend/src/services/BaseService.js b/frontend/src/services/BaseService.js index d7563694..3c421701 100644 --- a/frontend/src/services/BaseService.js +++ b/frontend/src/services/BaseService.js @@ -17,9 +17,10 @@ export const resources = { addGroupMember: '/group-member/add-group-member', updateNumberAndDept: '/group-member/update-number-and-dept', cancelGroupMember: '/group-member/cancel-group-member', + cancelDependent: '/group-member/cancel-dependent', }, user: { - permissions: '/user/permissions' + permissions: '/user/permissions', }, } diff --git a/frontend/src/services/GroupMemberService.js b/frontend/src/services/GroupMemberService.js index 93105431..f053a799 100644 --- a/frontend/src/services/GroupMemberService.js +++ b/frontend/src/services/GroupMemberService.js @@ -1,15 +1,16 @@ import { apiRequest, resources } from './BaseService' export default { - - updateNumberAndDept(request) { - return apiRequest().then(axiosInstance => axiosInstance.post(resources.groupMember.updateNumberAndDept, request)) + updateNumberAndDept(request) { + return apiRequest().then((axiosInstance) => axiosInstance.post(resources.groupMember.updateNumberAndDept, request)) }, cancelGroupMember(request) { - return apiRequest().then(axiosInstance => axiosInstance.post(resources.groupMember.cancelGroupMember, request)) + return apiRequest().then((axiosInstance) => axiosInstance.post(resources.groupMember.cancelGroupMember, request)) }, - addGroupMember(request) { - return apiRequest().then(axiosInstance => axiosInstance.post(resources.groupMember.addGroupMember, request)) - }, - -} \ No newline at end of file + addGroupMember(request) { + return apiRequest().then((axiosInstance) => axiosInstance.post(resources.groupMember.addGroupMember, request)) + }, + cancelDependent(request) { + return apiRequest().then((axiosInstance) => axiosInstance.post(resources.groupMember.cancelDependent, request)) + }, +} diff --git a/frontend/src/views/groupmember/CancelDependent.vue b/frontend/src/views/groupmember/CancelDependent.vue new file mode 100644 index 00000000..80084481 --- /dev/null +++ b/frontend/src/views/groupmember/CancelDependent.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/frontend/src/views/groupmember/GroupMemberHome.vue b/frontend/src/views/groupmember/GroupMemberHome.vue index ab69511f..db596410 100644 --- a/frontend/src/views/groupmember/GroupMemberHome.vue +++ b/frontend/src/views/groupmember/GroupMemberHome.vue @@ -5,9 +5,10 @@ import SubNavTab from '../../components/template/SubNavTab.vue' @@ -17,5 +18,3 @@ export default { name: 'GroupMemberHome', } - - diff --git a/frontend/tests/e2e/pages/groupmember/CancelDependent.js b/frontend/tests/e2e/pages/groupmember/CancelDependent.js new file mode 100644 index 00000000..ee3d69d5 --- /dev/null +++ b/frontend/tests/e2e/pages/groupmember/CancelDependent.js @@ -0,0 +1,18 @@ +import { Selector } from 'testcafe' + +class CancelDependent { + constructor() { + this.phnInput = Selector('#phn') + this.dependentPhnInput = Selector('#dependentPhn') + this.groupNumberInput = Selector('#groupNumber') + this.relationshipInput = Selector('#relationship') + this.cancelDateInput = Selector('#dp-input-cancelDate') + this.cancelReasonInput = Selector('#cancelReason') + this.submitButton = Selector('button[type="submit"]') + this.clearButton = Selector('button[type="button"]') + this.divSelectedDate = Selector('div').withAttribute('class', 'dp__overlay_cell_active dp__overlay_cell_pad') + this.errorText = Selector('div').withAttribute('class', 'error-text') + } +} + +export default new CancelDependent() diff --git a/frontend/tests/e2e/tests/groupmember/CancelDependentTest.js b/frontend/tests/e2e/tests/groupmember/CancelDependentTest.js new file mode 100644 index 00000000..56985ee1 --- /dev/null +++ b/frontend/tests/e2e/tests/groupmember/CancelDependentTest.js @@ -0,0 +1,108 @@ +import AlertPage from '../../pages/AlertPage' +import CancelDependent from '../../pages/groupmember/CancelDependent' +import { SITE_UNDER_TEST } from '../../configuration' +import { regularAccUser } from '../../roles/roles' + +const ERROR_MESSAGE = 'Please correct errors before submitting' +const PHN_REQUIRED_MESSAGE = 'PHN is required' +const INVALID_PHN_ERROR_MESSAGE = 'PHN format is invalid' +const GROUP_NUMBER_REQUIRED_MESSAGE = 'Group Number is required' +const INVALID_GROUP_NUMBER_ERROR_MESSAGE = 'Group Number is invalid' +const COVERAGE_CANCEL_DATE_REQUIRED_MESSAGE = 'Coverage Cancel Date is required' +const DEPENDENT_PHN_REQUIRED_MESSAGE = 'PHN is required' +const CANCEL_REASON_REQUIRED_MESSAGE = 'Cancel Reason is required' +const RAPID_RESPONSE = 'RPBS0097 SUBSCRIBER AND DEPENDENT PHN MUST BE DIFFERENT.' + +const PAGE_TO_TEST = SITE_UNDER_TEST + '/groupmember/CancelDependent' + +fixture(`CancelDependent Page`).disablePageCaching`Test CancelDependent` + .beforeEach(async (t) => { + await t.useRole(regularAccUser) + }) + .page(PAGE_TO_TEST) + +test('Check required fields validation', async (t) => { + await t + // Given required fields aren't filled out (phn, dependent phn, group member, group member number, department) + // When I click the submit button + .click(CancelDependent.submitButton) + // I expect an error message stating the page had errors and individual error messages for each required field + .expect(AlertPage.alertBannerText.textContent) + .contains(ERROR_MESSAGE) + .expect(CancelDependent.errorText.nth(0).textContent) + .contains(GROUP_NUMBER_REQUIRED_MESSAGE) + .expect(CancelDependent.errorText.nth(1).textContent) + .contains(PHN_REQUIRED_MESSAGE) + .expect(CancelDependent.errorText.nth(2).textContent) + .contains(DEPENDENT_PHN_REQUIRED_MESSAGE) + .expect(CancelDependent.errorText.nth(3).textContent) + .contains(COVERAGE_CANCEL_DATE_REQUIRED_MESSAGE) + .expect(CancelDependent.errorText.nth(4).textContent) + .contains(CANCEL_REASON_REQUIRED_MESSAGE) +}) + +test('Check invalid formats validation', async (t) => { + await t + // Given I enter PHN, Dependent PHN, Group Number, and Cancel dates with invalid formats + .typeText(CancelDependent.phnInput, '9000448000') + .typeText(CancelDependent.dependentPhnInput, '9000448000') + .typeText(CancelDependent.groupNumberInput, '123') + // When I click the submit button + .click(CancelDependent.submitButton) + // I expect an error message stating the page had errors and an individual error message for the PHN and Group and Cancel Date formats + .expect(CancelDependent.errorText.nth(0).textContent) + .contains(INVALID_GROUP_NUMBER_ERROR_MESSAGE) + .expect(CancelDependent.errorText.nth(1).textContent) + .contains(INVALID_PHN_ERROR_MESSAGE) + .expect(CancelDependent.errorText.nth(2).textContent) + .contains(INVALID_PHN_ERROR_MESSAGE) + .expect(AlertPage.alertBannerText.textContent) + .contains(ERROR_MESSAGE) +}) + +test('Check properly filled form passes validation', async (t) => { + await t + // Given I have a form filled out with data + .typeText(CancelDependent.groupNumberInput, '6243109') + .typeText(CancelDependent.phnInput, '9397105575') + .typeText(CancelDependent.dependentPhnInput, '9397105575') + .click(CancelDependent.cancelDateInput) + .click(CancelDependent.divSelectedDate) + .click(CancelDependent.cancelReasonInput) + .pressKey('down') + .pressKey('enter') + // When I click the submit button + .click(CancelDependent.submitButton) + // I expect a response from RAPID + .expect(AlertPage.alertBannerText.textContent) + .contains(RAPID_RESPONSE) +}) + +test('Check clear button clears the form', async (t) => { + await t + // Given I have a form filled out with data + .typeText(CancelDependent.phnInput, '9882807277') + .typeText(CancelDependent.dependentPhnInput, '9882807277') + .typeText(CancelDependent.groupNumberInput, '6337109') + .click(CancelDependent.relationshipInput) + .pressKey('down') + .pressKey('enter') + .click(CancelDependent.cancelReasonInput) + .pressKey('down') + .pressKey('enter') + .click(CancelDependent.cancelDateInput) + .click(CancelDependent.divSelectedDate) + // When I click the clear button + .click(CancelDependent.clearButton) + // I expect the form to be cleared + .expect(CancelDependent.phnInput.value) + .eql('') + .expect(CancelDependent.dependentPhnInput.value) + .eql('') + .expect(CancelDependent.groupNumberInput.value) + .eql('') + .expect(CancelDependent.cancelReasonInput.value) + .eql('') + .expect(CancelDependent.cancelDateInput.value) + .eql('') +})