diff --git a/examples/doc-scan/src/main/java/com/yoti/docscan/demo/controller/DocScanController.java b/examples/doc-scan/src/main/java/com/yoti/docscan/demo/controller/DocScanController.java index 1a2d73af2..c651135a3 100644 --- a/examples/doc-scan/src/main/java/com/yoti/docscan/demo/controller/DocScanController.java +++ b/examples/doc-scan/src/main/java/com/yoti/docscan/demo/controller/DocScanController.java @@ -126,6 +126,23 @@ public ResponseEntity getMedia( return new ResponseEntity<>(media.getContent(), headers, HttpStatus.OK); } + @RequestMapping(value = "/session-overview-pdf", method = RequestMethod.GET) + public ResponseEntity getSessionOverviewPdf(@RequestParam(value = "sessionId") String sessionId) { + Media media; + try { + media = docScanService.createSessionOverviewPdf(sessionId); + } catch (DocScanException e) { + LOG.error(e.getMessage()); + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(media.getMimeType())); + headers.setContentDispositionFormData("attachment", "session-overview.pdf"); + + return new ResponseEntity<>(media.getContent(), headers, HttpStatus.OK); + } + @RequestMapping(value = "/tracked-devices", method = RequestMethod.GET) public @ResponseBody List getTrackedDevices(@RequestParam(value = "sessionId") String sessionId) throws DocScanException { return docScanService.getTrackedDevices(sessionId); diff --git a/examples/doc-scan/src/main/java/com/yoti/docscan/demo/service/DocScanService.java b/examples/doc-scan/src/main/java/com/yoti/docscan/demo/service/DocScanService.java index 233f02180..2957466b7 100644 --- a/examples/doc-scan/src/main/java/com/yoti/docscan/demo/service/DocScanService.java +++ b/examples/doc-scan/src/main/java/com/yoti/docscan/demo/service/DocScanService.java @@ -21,6 +21,7 @@ import com.yoti.api.client.docs.session.create.filters.RequiredIdDocument; import com.yoti.api.client.docs.session.create.filters.RequiredSupplementaryDocument; import com.yoti.api.client.docs.session.create.objective.ProofOfAddressObjective; +import com.yoti.api.client.docs.session.create.pdf.CreateSessionOverviewPdfPayload; import com.yoti.api.client.docs.session.create.task.RequestedIdDocTextExtractionTask; import com.yoti.api.client.docs.session.create.task.RequestedSupplementaryDocTextExtractionTask; import com.yoti.api.client.docs.session.devicemetadata.MetadataResponse; @@ -151,6 +152,16 @@ public Media getMedia(String sessionId, String mediaId) throws DocScanException return docScanClient.getMediaContent(sessionId, mediaId); } + public Media createSessionOverviewPdf(String sessionId) throws DocScanException { + CreateSessionOverviewPdfPayload payload = CreateSessionOverviewPdfPayload.builder() + .withIncludeImages(true) + .withIncludeExtractedText(true) + .withIncludeDeviceTracking(true) + .build(); + + return docScanClient.createSessionOverviewPdf(sessionId, payload); + } + public List getTrackedDevices(String sessionId) throws DocScanException { return docScanClient.getTrackedDevices(sessionId); } diff --git a/examples/doc-scan/src/main/resources/templates/success.html b/examples/doc-scan/src/main/resources/templates/success.html index 2ddeb6f4a..3ca1f6046 100644 --- a/examples/doc-scan/src/main/resources/templates/success.html +++ b/examples/doc-scan/src/main/resources/templates/success.html @@ -1063,6 +1063,18 @@

+ +
+
+

+ Session Overview PDF + + + + +

+
+
diff --git a/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanClient.java b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanClient.java index c1deb1014..70252954c 100644 --- a/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanClient.java +++ b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanClient.java @@ -12,6 +12,7 @@ import com.yoti.api.client.docs.session.create.SessionSpec; import com.yoti.api.client.docs.session.create.facecapture.CreateFaceCaptureResourcePayload; import com.yoti.api.client.docs.session.create.facecapture.UploadFaceCaptureImagePayload; +import com.yoti.api.client.docs.session.create.pdf.CreateSessionOverviewPdfPayload; import com.yoti.api.client.docs.session.devicemetadata.MetadataResponse; import com.yoti.api.client.docs.session.instructions.Instructions; import com.yoti.api.client.docs.session.retrieve.CreateFaceCaptureResourceResponse; @@ -141,6 +142,19 @@ public Media getIbvInstructionsPdf(String sessionId) throws DocScanException { return docScanService.getIbvInstructionsPdf(authStrategy, sessionId); } + /** + * Creates a PDF overview of the given session. + * + * @param sessionId the session ID + * @param createSessionOverviewPdfPayload the {@link CreateSessionOverviewPdfPayload} describing what to include + * @return the PDF media + * @throws DocScanException if an error has occurred + */ + public Media createSessionOverviewPdf(String sessionId, CreateSessionOverviewPdfPayload createSessionOverviewPdfPayload) throws DocScanException { + LOG.debug("Creating session overview PDF for session '{}'", sessionId); + return docScanService.createSessionOverviewPdf(authStrategy, sessionId, createSessionOverviewPdfPayload); + } + /** * Fetches the associated instructions contact profile for the given In-Branch Verification session * diff --git a/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanService.java b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanService.java index 3799b760f..535a28ab0 100644 --- a/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanService.java +++ b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/DocScanService.java @@ -23,6 +23,7 @@ import com.yoti.api.client.docs.session.create.SessionSpec; import com.yoti.api.client.docs.session.create.facecapture.CreateFaceCaptureResourcePayload; import com.yoti.api.client.docs.session.create.facecapture.UploadFaceCaptureImagePayload; +import com.yoti.api.client.docs.session.create.pdf.CreateSessionOverviewPdfPayload; import com.yoti.api.client.docs.session.devicemetadata.MetadataResponse; import com.yoti.api.client.docs.session.instructions.Instructions; import com.yoti.api.client.docs.session.retrieve.CreateFaceCaptureResourceResponse; @@ -383,6 +384,38 @@ Media getIbvInstructionsPdf(AuthStrategy authStrategy, String sessionId) throws } } + Media createSessionOverviewPdf(AuthStrategy authStrategy, + String sessionId, + CreateSessionOverviewPdfPayload createSessionOverviewPdfPayload) throws DocScanException { + notNull(authStrategy, "authStrategy"); + notNullOrEmpty(sessionId, "sessionId"); + notNull(createSessionOverviewPdfPayload, "createSessionOverviewPdfPayload"); + + String path = unsignedPathFactory.createSessionOverviewPdfPath(sessionId); + LOG.info("Creating session overview PDF at '{}'", path); + + try { + byte[] payload = objectMapper.writeValueAsBytes(createSessionOverviewPdfPayload); + + YotiHttpRequest yotiHttpRequest = yotiHttpRequestBuilderFactory.create() + .withAuthStrategy(authStrategy) + .withBaseUrl(apiUrl) + .withEndpoint(path) + .withPayload(payload) + .withHttpMethod(HTTP_POST) + .build(); + YotiHttpResponse response = yotiHttpRequest.execute(); + + return new MediaValue(findContentType(response), response.getResponseBody()); + } catch (GeneralSecurityException ex) { + throw new DocScanException("Error signing the request: " + ex.getMessage(), ex); + } catch (ResourceException ex) { + throw new DocScanException("Error executing the POST: " + ex.getMessage(), ex); + } catch (IOException | URISyntaxException ex) { + throw new DocScanException("Error building the request: " + ex.getMessage(), ex); + } + } + SessionConfigurationResponse fetchSessionConfiguration(AuthStrategy authStrategy, String sessionId) throws DocScanException { notNull(authStrategy, "authStrategy"); notNullOrEmpty(sessionId, "sessionId"); diff --git a/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayload.java b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayload.java new file mode 100644 index 000000000..e01d0db45 --- /dev/null +++ b/yoti-sdk-api/src/main/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayload.java @@ -0,0 +1,85 @@ +package com.yoti.api.client.docs.session.create.pdf; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CreateSessionOverviewPdfPayload { + + @JsonProperty("include_images") + private final Boolean includeImages; + + @JsonProperty("include_extracted_text") + private final Boolean includeExtractedText; + + @JsonProperty("include_device_tracking") + private final Boolean includeDeviceTracking; + + private CreateSessionOverviewPdfPayload(Boolean includeImages, Boolean includeExtractedText, Boolean includeDeviceTracking) { + this.includeImages = includeImages; + this.includeExtractedText = includeExtractedText; + this.includeDeviceTracking = includeDeviceTracking; + } + + public static CreateSessionOverviewPdfPayload.Builder builder() { + return new CreateSessionOverviewPdfPayload.Builder(); + } + + public Boolean getIncludeImages() { + return includeImages; + } + + public Boolean getIncludeExtractedText() { + return includeExtractedText; + } + + public Boolean getIncludeDeviceTracking() { + return includeDeviceTracking; + } + + public static class Builder { + + private Boolean includeImages; + private Boolean includeExtractedText; + private Boolean includeDeviceTracking; + + private Builder() { } + + /** + * Sets whether images should be included in the PDF. + * + * @param includeImages whether to include images + * @return the builder + */ + public Builder withIncludeImages(boolean includeImages) { + this.includeImages = includeImages; + return this; + } + + /** + * Sets whether extracted text should be included in the PDF. + * + * @param includeExtractedText whether to include extracted text + * @return the builder + */ + public Builder withIncludeExtractedText(boolean includeExtractedText) { + this.includeExtractedText = includeExtractedText; + return this; + } + + /** + * Sets whether device tracking information should be included in the PDF. + * + * @param includeDeviceTracking whether to include device tracking information + * @return the builder + */ + public Builder withIncludeDeviceTracking(boolean includeDeviceTracking) { + this.includeDeviceTracking = includeDeviceTracking; + return this; + } + + public CreateSessionOverviewPdfPayload build() { + return new CreateSessionOverviewPdfPayload(includeImages, includeExtractedText, includeDeviceTracking); + } + + } + +} diff --git a/yoti-sdk-api/src/main/java/com/yoti/api/client/spi/remote/call/factory/UnsignedPathFactory.java b/yoti-sdk-api/src/main/java/com/yoti/api/client/spi/remote/call/factory/UnsignedPathFactory.java index 56e3440ab..bc2131890 100644 --- a/yoti-sdk-api/src/main/java/com/yoti/api/client/spi/remote/call/factory/UnsignedPathFactory.java +++ b/yoti-sdk-api/src/main/java/com/yoti/api/client/spi/remote/call/factory/UnsignedPathFactory.java @@ -36,6 +36,7 @@ public class UnsignedPathFactory { private static final String DOCS_UPLOAD_FACE_CAPTURE_IMAGE = "/sessions/%s/resources/face-capture/%s/image"; private static final String DOCS_TRIGGER_IBV_NOTIFICATION = "/sessions/%s/instructions/email"; private static final String DOCS_TRACKED_DEVICES = "/sessions/%s/tracked-devices"; + private static final String DOCS_SESSION_OVERVIEW_PDF = "/sessions/%s/pdf"; public String createAmlPath() { return format(AML); @@ -105,6 +106,10 @@ public String createFetchIbvInstructionsPdfPath(String sessionId) { return format(DOCS_FETCH_IBV_INSTRUCTIONS_PDF, sessionId); } + public String createSessionOverviewPdfPath(String sessionId) { + return format(DOCS_SESSION_OVERVIEW_PDF, sessionId); + } + public String createGetSupportedDocumentsPath(boolean includeNonLatin) { return format(DOCS_SUPPORTED_DOCUMENTS, includeNonLatin); } diff --git a/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanClientTest.java b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanClientTest.java index d85ec9184..03ddc39b1 100644 --- a/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanClientTest.java +++ b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanClientTest.java @@ -7,6 +7,7 @@ import static org.mockito.Mockito.when; import com.yoti.api.client.docs.session.create.SessionSpec; +import com.yoti.api.client.docs.session.create.pdf.CreateSessionOverviewPdfPayload; import com.yoti.api.client.docs.session.instructions.Instructions; import com.yoti.api.client.spi.remote.call.factory.AuthStrategy; @@ -110,6 +111,17 @@ public void getIbvInstructionsPdf_shouldFailWithExceptionFromYotiDocsService() t assertThat(exception, is(original)); } + @Test + public void createSessionOverviewPdf_shouldFailWithExceptionFromYotiDocsService() throws Exception { + CreateSessionOverviewPdfPayload payload = CreateSessionOverviewPdfPayload.builder().build(); + DocScanException original = new DocScanException("Test exception"); + doThrow(original).when(docScanServiceMock).createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, payload); + + DocScanException exception = assertThrows(DocScanException.class, () -> testObj.createSessionOverviewPdf(SOME_SESSION_ID, payload)); + + assertThat(exception, is(original)); + } + @Test public void fetchInstructionsContactProfile_shouldFailWithExceptionFromYotiDocsService() throws Exception { DocScanException original = new DocScanException("Test exception"); diff --git a/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanServiceTest.java b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanServiceTest.java index f889cbe20..79993262d 100644 --- a/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanServiceTest.java +++ b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/DocScanServiceTest.java @@ -36,6 +36,7 @@ import com.yoti.api.client.docs.session.create.SessionSpec; import com.yoti.api.client.docs.session.create.facecapture.CreateFaceCaptureResourcePayload; import com.yoti.api.client.docs.session.create.facecapture.UploadFaceCaptureImagePayload; +import com.yoti.api.client.docs.session.create.pdf.CreateSessionOverviewPdfPayload; import com.yoti.api.client.docs.session.devicemetadata.MetadataResponse; import com.yoti.api.client.docs.session.instructions.Instructions; import com.yoti.api.client.docs.session.retrieve.AuthenticityCheckResponse; @@ -98,6 +99,7 @@ public class DocScanServiceTest { @Mock Instructions instructionsMock; @Mock CreateFaceCaptureResourcePayload createFaceCaptureResourcePayloadMock; @Mock UploadFaceCaptureImagePayload uploadFaceCaptureImagePayloadMock; + @Mock CreateSessionOverviewPdfPayload createSessionOverviewPdfPayloadMock; @Before public void setUp() { @@ -870,6 +872,100 @@ public void getIbvInstructionsPdf_shouldReturnNullForNoContent() throws Exceptio assertThat(result, is(nullValue())); } + @Test + public void createSessionOverviewPdf_shouldThrowExceptionWhenAuthStrategyIsNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> docScanService.createSessionOverviewPdf( + null, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("'authStrategy' must not be null.")); + } + + @Test + public void createSessionOverviewPdf_shouldThrowExceptionWhenSessionIdIsNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> docScanService.createSessionOverviewPdf( + authStrategyMock, null, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("sessionId")); + } + + @Test + public void createSessionOverviewPdf_shouldThrowExceptionWhenSessionIdIsEmpty() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> docScanService.createSessionOverviewPdf( + authStrategyMock, "", createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("sessionId")); + } + + @Test + public void createSessionOverviewPdf_shouldThrowExceptionWhenPayloadIsNull() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> docScanService.createSessionOverviewPdf( + authStrategyMock, SOME_SESSION_ID, null)); + + assertThat(ex.getMessage(), containsString("createSessionOverviewPdfPayload")); + } + + @Test + public void createSessionOverviewPdf_shouldWrapGeneralSecurityException() throws Exception { + GeneralSecurityException gse = new GeneralSecurityException("some gse"); + when(yotiHttpRequestBuilderMock.build()).thenThrow(gse); + + DocScanException ex = assertThrows(DocScanException.class, () -> docScanService.createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("Error signing the request: some gse")); + } + + @Test + public void createSessionOverviewPdf_shouldWrapResourceException() throws Exception { + ResourceException resourceException = new ResourceException(400, "Failed Request", "Some response from API"); + when(yotiHttpRequestBuilderMock.build()).thenReturn(yotiHttpRequestMock); + when(yotiHttpRequestMock.execute()).thenThrow(resourceException); + + DocScanException ex = assertThrows(DocScanException.class, () -> docScanService.createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("Error executing the POST: Failed Request")); + } + + @Test + public void createSessionOverviewPdf_shouldWrapIOException() throws Exception { + IOException ioException = new IOException("Some io exception"); + when(yotiHttpRequestBuilderMock.build()).thenReturn(yotiHttpRequestMock); + when(yotiHttpRequestMock.execute()).thenThrow(ioException); + + DocScanException ex = assertThrows(DocScanException.class, () -> docScanService.createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("Error building the request: Some io exception")); + } + + @Test + public void createSessionOverviewPdf_shouldWrapURISyntaxException() throws Exception { + URISyntaxException uriSyntaxException = new URISyntaxException("someUrl", "Failed to build URI"); + when(yotiHttpRequestBuilderMock.build()).thenThrow(uriSyntaxException); + + DocScanException ex = assertThrows(DocScanException.class, () -> docScanService.createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock)); + + assertThat(ex.getMessage(), containsString("Error building the request: Failed to build URI: someUrl")); + } + + @Test + public void createSessionOverviewPdf_shouldPostPayloadAndReturnMedia() throws Exception { + when(objectMapperMock.writeValueAsBytes(createSessionOverviewPdfPayloadMock)).thenReturn(SOME_SESSION_SPEC_BYTES); + when(yotiHttpRequestBuilderMock.build()).thenReturn(yotiHttpRequestMock); + when(yotiHttpRequestMock.execute()).thenReturn(yotiHttpResponseMock); + when(yotiHttpResponseMock.getResponseHeaders()).thenReturn(createHeadersMap(CONTENT_TYPE, "application/pdf")); + when(yotiHttpResponseMock.getResponseBody()).thenReturn(IMAGE_BODY); + when(unsignedPathFactoryMock.createSessionOverviewPdfPath(SOME_SESSION_ID)).thenReturn(SOME_PATH); + + Media result = docScanService.createSessionOverviewPdf(authStrategyMock, SOME_SESSION_ID, createSessionOverviewPdfPayloadMock); + + assertThat(result.getMimeType(), is("application/pdf")); + assertThat(result.getContent(), is(IMAGE_BODY)); + verify(yotiHttpRequestBuilderMock).withAuthStrategy(authStrategyMock); + verify(yotiHttpRequestBuilderMock).withEndpoint(SOME_PATH); + verify(yotiHttpRequestBuilderMock).withBaseUrl(SOME_API_URL); + verify(yotiHttpRequestBuilderMock).withHttpMethod(HttpMethod.HTTP_POST); + verify(yotiHttpRequestBuilderMock).withPayload(SOME_SESSION_SPEC_BYTES); + } + @Test public void fetchInstructionsContactProfile_shouldThrowExceptionWhenAuthStrategyIsNull() { IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> docScanService.fetchInstructionsContactProfile(null, SOME_SESSION_ID)); diff --git a/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayloadTest.java b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayloadTest.java new file mode 100644 index 000000000..bc53a625d --- /dev/null +++ b/yoti-sdk-api/src/test/java/com/yoti/api/client/docs/session/create/pdf/CreateSessionOverviewPdfPayloadTest.java @@ -0,0 +1,46 @@ +package com.yoti.api.client.docs.session.create.pdf; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +public class CreateSessionOverviewPdfPayloadTest { + + @Test + public void build_shouldBuildCreateSessionOverviewPdfPayloadCorrectly() { + CreateSessionOverviewPdfPayload result = CreateSessionOverviewPdfPayload.builder() + .withIncludeImages(true) + .withIncludeExtractedText(false) + .withIncludeDeviceTracking(true) + .build(); + + assertThat(result.getIncludeImages(), is(true)); + assertThat(result.getIncludeExtractedText(), is(false)); + assertThat(result.getIncludeDeviceTracking(), is(true)); + } + + @Test + public void build_shouldLeaveUnsetValuesNull() { + CreateSessionOverviewPdfPayload result = CreateSessionOverviewPdfPayload.builder().build(); + + assertThat(result.getIncludeImages(), is(nullValue())); + assertThat(result.getIncludeExtractedText(), is(nullValue())); + assertThat(result.getIncludeDeviceTracking(), is(nullValue())); + } + + @Test + public void shouldOnlySerializeProvidedValues() throws Exception { + ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL); + + CreateSessionOverviewPdfPayload payload = CreateSessionOverviewPdfPayload.builder() + .withIncludeExtractedText(false) + .build(); + + assertThat(objectMapper.writeValueAsString(payload), is("{\"include_extracted_text\":false}")); + } + +}