Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement streamDocumentBinaryContentByDocumentId #684

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ task functional(type: Test, description: 'Runs the functional tests.', group: 'V
}

finalizedBy {
generateCucumberReports.enabled = true
generateCucumberReports {
doLast{
delete "${rootDir}/BEFTA Report for Functional Tests/"
Expand Down
63 changes: 53 additions & 10 deletions src/functionalTest/resources/features/F-002/F-002.feature
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,6 @@ Feature: F-002: Get Document Binary Content by Document ID
Then a negative response is received,
And the response has all other details as expected.

#Generic Scenarios for Security
@S-024 @Ignore
Scenario: generic scenario for Unauthorized

@S-025 @Ignore
Scenario: generic scenario for Forbidden

@S-026 @Ignore
Scenario: generic scenario for Unsupported Media Type

@S-114 @Ignore #this scenario is not valid anymore after CCD-3138.
Scenario: must receive an error response when CCD Data Store tries to access Get Document Binary Content API
Given a user with [an active caseworker profile in CCD with full permissions on a document field],
Expand Down Expand Up @@ -108,3 +98,56 @@ Feature: F-002: Get Document Binary Content by Document ID
And it is submitted to call the [Get Binary Content by Document ID] operation of [CCD Case Document AM API],
Then a negative response is received
And the response has all other details as expected.

@S-024
Scenario: must successfully stream a document's binary content
Given a user with [an active caseworker profile in CCD with full permissions on a document field],
And a successful call [by another privileged user to upload a document with mandatory metadata] as in [Default_Document_Upload_Data],
And another successful call [to create a token for case creation] as in [Befta_Jurisdiction2_Default_Token_Creation_Data_For_Case_Creation]
And another successful call [to create a case of this case type] as in [S-020_Case_Create]
When a request is prepared with appropriate values,
And it is submitted to call the [Stream Binary Content by Document ID] operation of [CCD Case Document AM API],
Then a positive response is received,
And the response [contains the binary content for the uploaded document],
And the response has all other details as expected.

@S-025
Scenario: must receive an error response from streaming endpoint for a non existing document id
Given a user with [an active caseworker profile in CCD with full permissions on a document field],
When a request is prepared with appropriate values,
And the request [contains a non existing document id],
And it is submitted to call the [Stream Binary Content by Document ID] operation of [CCD Case Document AM API],
Then a negative response is received,
And the response has all other details as expected.

@S-026
Scenario: must receive an error response from streaming endpoint for a malformed document ID
Given a user with [an active caseworker profile in CCD with full permissions on a document field],
When a request is prepared with appropriate values,
And the request [contains a malformed document ID],
And it is submitted to call the [Stream Binary Content by Document ID] operation of [CCD Case Document AM API],
Then a negative response is received,
And the response has all the details as expected.

@S-027
Scenario: must receive an error response from streaming endpoint for an active caseworker who does not have document access
Given a user with [an active caseworker profile in CCD with limited permissions on a document field],
And a successful call [by another privileged user to upload a document with mandatory metadata] as in [Default_Document_Upload_Data],
And another successful call [to create a token for case creation] as in [Befta_Jurisdiction2_Default_Token_Creation_Data_For_Case_Creation]
And another successful call [to create a case of this case type] as in [S-027_Case_Create]
When a request is prepared with appropriate values,
And the request [contains an active caseworker who does not have document access],
And it is submitted to call the [Stream Binary Content by Document ID] operation of [CCD Case Document AM API],
Then a negative response is received,
And the response has all other details as expected.


#Generic Scenarios for Security
@S-028 @Ignore
Scenario: generic scenario for Unauthorized

@S-029 @Ignore
Scenario: generic scenario for Forbidden

@S-030 @Ignore
Scenario: generic scenario for Unsupported Media Type
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"_guid_": "F-002_Test_Stream_Base",
"_extends_": "F-002-Base-Root",
"productName": "CCD Case Document AM API",
"operationName": "Stream Binary Content by Document ID",
"s2sClientId": "xui_webapp",
"specs": [
"an active caseworker profile in CCD with full permissions on a document field"
],
"method": "GET",
"uri": "/v2/cases/documents/{documentId}/binary"
}
32 changes: 32 additions & 0 deletions src/functionalTest/resources/features/F-002/S-024.td.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"title": "must successfully stream a document's binary content",
"_guid_": "S-024",
"_extends_": "F-002_Test_Stream_Base",
"specs": [
"contains the binary content for the uploaded document"
],
"request": {
"pathVariables": {
"documentId": "${[scenarioContext][childContexts][Default_Document_Upload_Data][customValues][documentIdInTheResponse]}"
}
},
"expectedResponse": {
"_extends_": "Common_200_Response",
"headers": {
"Accept-Ranges": "bytes",
"Content-Disposition": "[[ANYTHING_PRESENT]]",
"Content-Type": "[[ANYTHING_PRESENT]]",
"data-source": "contentURI",
"OriginalFileName": "[[ANYTHING_PRESENT]]",
"Content-Encoding": "gzip",
"Transfer-Encoding": "chunked"
},
"body": {
"__fileInBody__": {
"fullPath": "file",
"size": "[[ANYTHING_PRESENT]]",
"contentHash": "hash"
}
}
}
}
23 changes: 23 additions & 0 deletions src/functionalTest/resources/features/F-002/S-025.td.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"title": "must receive an error response from streaming endpoint for a non existing document id",
"_guid_": "S-025",
"_extends_": "F-002_Test_Stream_Base",
"specs": [
"contains a non existing document id"
],
"request": {
"pathVariables": {
"documentId": "00000000-0000-0000-0000-000000000000"
}
},
"expectedResponse": {
"_extends_": "AM_404_Response",
"headers": {
"Content-Type": "application/json",
"Content-Length": "[[ANYTHING_PRESENT]]"
},
"body": {
"error": "Meta data does not exist for documentId: 00000000-0000-0000-0000-000000000000"
}
}
}
23 changes: 23 additions & 0 deletions src/functionalTest/resources/features/F-002/S-026.td.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"title": "must receive an error response from streaming endpoint for a malformed document ID",
"_guid_": "S-026",
"_extends_": "F-002_Test_Stream_Base",
"specs": [
"contains a malformed document ID"
],
"request": {
"pathVariables": {
"documentId": "8de9957b-d7a6-40a1-@£$&-6aac30d33644"
}
},
"expectedResponse": {
"_extends_": "AM_400_Response",
"headers": {
"Content-Type": "application/json",
"Content-Length": "[[ANYTHING_PRESENT]]"
},
"body": {
"error": "[[ANYTHING_PRESENT]]"
}
}
}
30 changes: 30 additions & 0 deletions src/functionalTest/resources/features/F-002/S-027.td.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"title": "must receive an error response from streaming endpoint for an active caseworker who does not have document access",
"_guid_": "S-027",
"_extends_": "F-002_Test_Stream_Base",
"specs": [
"an active caseworker profile in CCD with limited permissions on a document field",
"contains an active caseworker who does not have document access"
],
"users": {
"invokingUser": {
"username": "[email protected]",
"password": "[[$CCD_BEFTA_CASEWORKER_2_SOLICITOR_1_PWD]]"
}
},
"request": {
"pathVariables": {
"documentId": "${[scenarioContext][childContexts][Default_Document_Upload_Data][customValues][documentIdInTheResponse]}"
}
},
"expectedResponse": {
"_extends_": "AM_404_Response",
"headers": {
"Content-Type": "application/json",
"Content-Length": "[[ANYTHING_PRESENT]]"
},
"body": {
"error": "Case reference ${[scenarioContext][childContexts][S-027_Case_Create][testData][actualResponse][body][id]} not found for requested document."
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"_guid_": "S-027_Case_Create",
"_extends_": "Befta_Jurisdiction2_Default_Full_Case_Creation_Data"
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,8 @@ public class CaseDocumentAmControllerIT extends BaseTest implements TestFixture

private static final String SUCCESS = "Success";
private static final int ERROR_403 = 403;
public static final String PATCH_ERROR_DESCRIPTION_BAD_REQUEST = "Document metadata exists for %s but the "
+ "case type is not a moving case type: %s";
private static final String MAIN_URL = "/cases/documents";
private static final String MAIN_URL_V2 = "/v2/cases/documents";
private static final String ATTACH_TO_CASE_URL = "/attachToCase";
private static final String SERVICE_NAME_CCD_DATA = "ccd_data";
private static final String SERVICE_NAME_CCD_GW = "ccd_gw";
Expand Down Expand Up @@ -369,6 +368,26 @@ void shouldSuccessfullyGetDocumentBinaryContent() throws Exception {
null));
}

@Test
void shouldSuccessfullyStreamDocumentBinaryContent() throws Exception {
final Document document = buildDocument();

stubDocumentUrlWithReadPermissions();
stubGetDocumentMetaData(document);
stubDocumentBinaryContent();

mockMvc.perform(get(MAIN_URL_V2 + "/" + DOCUMENT_ID + BINARY)
.headers(createHttpHeaders(SERVICE_NAME_XUI_WEBAPP)))
.andExpect(status().isOk())
.andExpect(hasGeneratedLogAudit(
AuditOperationType.DOWNLOAD_STREAMED_DOCUMENT_BINARY_CONTENT_BY_ID,
SERVICE_NAME_XUI_WEBAPP,
List.of(DOCUMENT_ID.toString()),
null,
null,
null));
}

@Test
void shouldSuccessfullyGetDocumentBinaryContentNoCaseIdTTLInFuture() throws Exception {
final Document document = buildDocumentWithoutCaseId(Date.from(Instant.now().plus(1, ChronoUnit.HOURS)));
Expand All @@ -389,6 +408,26 @@ void shouldSuccessfullyGetDocumentBinaryContentNoCaseIdTTLInFuture() throws Exce
null));
}

@Test
void shouldSuccessfullyStreamDocumentBinaryContentNoCaseIdTTLInFuture() throws Exception {
final Document document = buildDocumentWithoutCaseId(Date.from(Instant.now().plus(1, ChronoUnit.HOURS)));

stubDocumentUrlWithReadPermissions();
stubGetDocumentMetaData(document);
stubDocumentBinaryContent();

mockMvc.perform(get(MAIN_URL_V2 + "/" + DOCUMENT_ID + BINARY)
.headers(createHttpHeaders(SERVICE_NAME_XUI_WEBAPP)))
.andExpect(status().isOk())
.andExpect(hasGeneratedLogAudit(
AuditOperationType.DOWNLOAD_STREAMED_DOCUMENT_BINARY_CONTENT_BY_ID,
SERVICE_NAME_XUI_WEBAPP,
List.of(DOCUMENT_ID.toString()),
null,
null,
null));
}

@Test
void shouldErrorForbiddenGetDocumentBinaryContentNoCaseIdTTLInPast() throws Exception {
final Document document = buildDocumentWithoutCaseId(Date.from(Instant.now().minus(1, ChronoUnit.HOURS)));
Expand All @@ -413,6 +452,30 @@ void shouldErrorForbiddenGetDocumentBinaryContentNoCaseIdTTLInPast() throws Exce
null));
}

@Test
void shouldErrorForbiddenStreamDocumentBinaryContentNoCaseIdTTLInPast() throws Exception {
final Document document = buildDocumentWithoutCaseId(Date.from(Instant.now().minus(1, ChronoUnit.HOURS)));

stubDocumentUrlWithReadPermissions();
stubGetDocumentMetaData(document);
stubDocumentBinaryContent();

mockMvc.perform(get(MAIN_URL_V2 + "/" + DOCUMENT_ID + BINARY)
.headers(createHttpHeaders(SERVICE_NAME_XUI_WEBAPP)))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error",
is("Forbidden: Insufficient permissions: Document "
+ DOCUMENT_ID
+ " can not be downloaded as TTL has expired")))
.andExpect(hasGeneratedLogAudit(
AuditOperationType.DOWNLOAD_STREAMED_DOCUMENT_BINARY_CONTENT_BY_ID,
SERVICE_NAME_XUI_WEBAPP,
List.of(DOCUMENT_ID.toString()),
null,
null,
null));
}

@Test
void shouldErrorForbiddenGetDocumentBinaryContentNoCaseIdTTLIsNull() throws Exception {
final Document document = buildDocumentWithoutCaseId(null);
Expand All @@ -437,6 +500,30 @@ void shouldErrorForbiddenGetDocumentBinaryContentNoCaseIdTTLIsNull() throws Exce
null));
}

@Test
void shouldErrorForbiddenStreamDocumentBinaryContentNoCaseIdTTLIsNull() throws Exception {
final Document document = buildDocumentWithoutCaseId(null);

stubDocumentUrlWithReadPermissions();
stubGetDocumentMetaData(document);
stubDocumentBinaryContent();

mockMvc.perform(get(MAIN_URL_V2 + "/" + DOCUMENT_ID + BINARY)
.headers(createHttpHeaders(SERVICE_NAME_XUI_WEBAPP)))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error",
is("Forbidden: Insufficient permissions: Document "
+ DOCUMENT_ID
+ " can not be downloaded as TTL has expired")))
.andExpect(hasGeneratedLogAudit(
AuditOperationType.DOWNLOAD_STREAMED_DOCUMENT_BINARY_CONTENT_BY_ID,
SERVICE_NAME_XUI_WEBAPP,
List.of(DOCUMENT_ID.toString()),
null,
null,
null));
}

@Test
void testShouldRaiseBadRequestWhenGetDocumentBinaryWithInvalidUUID() throws Exception {
mockMvc.perform(get(MAIN_URL + "/" + INVALID_DOCUMENT_ID + BINARY)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.hmcts.reform.ccd.documentam;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

Expand All @@ -8,6 +9,7 @@

import static java.util.Collections.emptyList;

@Getter
@Component
public class ApplicationParams {

Expand All @@ -26,24 +28,10 @@ public class ApplicationParams {
@Value("${moving.case.types}")
private List<String> movingCaseTypes;

public String getDocumentURL() {
return documentURL;
}

public int getDocumentTtlInDays() {
return documentTtlInDays;
}

public String getSalt() {
return salt;
}

public boolean isHashCheckEnabled() {
return hashCheckEnabled;
}
@Value("#{'${request.forwarded_headers.from_client}'.split(',')}")
private List<String> clientRequestHeadersToForward;

public List<String> getMovingCaseTypes() {
return Optional.ofNullable(movingCaseTypes).orElse(emptyList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum AuditOperationType {
DOWNLOAD_DOCUMENT_BY_ID("DownloadDocumentById"),
PATCH_METADATA_ON_DOCUMENTS("PatchMetaDataOnDocuments"),
DOWNLOAD_DOCUMENT_BINARY_CONTENT_BY_ID("DownloadDocumentBinaryContentById"),
DOWNLOAD_STREAMED_DOCUMENT_BINARY_CONTENT_BY_ID("DownloadStreamedDocumentBinaryContentById"),
UPLOAD_DOCUMENTS("UploadDocuments"),
PATCH_DOCUMENT_BY_DOCUMENT_ID("PatchDocumentByDocumentId"),
DELETE_DOCUMENT_BY_DOCUMENT_ID("DeleteDocumentByDocumentId"),
Expand Down
Loading