diff --git a/sjp-command/sjp-command-api/src/raml/json/schema/sjp.upload-case-document.json b/sjp-command/sjp-command-api/src/raml/json/schema/sjp.upload-case-document.json index 4b3fea8e9f..ebafdf9085 100644 --- a/sjp-command/sjp-command-api/src/raml/json/schema/sjp.upload-case-document.json +++ b/sjp-command/sjp-command-api/src/raml/json/schema/sjp.upload-case-document.json @@ -10,7 +10,7 @@ "format": "uri" } }, - "oneOf": [ + "anyOf": [ { "required": [ "caseDocument" diff --git a/sjp-command/sjp-command-api/src/raml/json/sjp.upload-case-document.json b/sjp-command/sjp-command-api/src/raml/json/sjp.upload-case-document.json index 81eb312044..02b1ce938f 100644 --- a/sjp-command/sjp-command-api/src/raml/json/sjp.upload-case-document.json +++ b/sjp-command/sjp-command-api/src/raml/json/sjp.upload-case-document.json @@ -1,3 +1,4 @@ { - "caseDocument": "fc3f8d82-d200-4cab-a492-2796e6fdf42c" + "caseDocument": "fc3f8d82-d200-4cab-a492-2796e6fdf42c", + "caseDocumentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf" } diff --git a/sjp-command/sjp-command-controller/src/raml/json/schema/sjp.command.upload-case-document.json b/sjp-command/sjp-command-controller/src/raml/json/schema/sjp.command.upload-case-document.json index 42fe635272..815c23d5b2 100644 --- a/sjp-command/sjp-command-controller/src/raml/json/schema/sjp.command.upload-case-document.json +++ b/sjp-command/sjp-command-controller/src/raml/json/schema/sjp.command.upload-case-document.json @@ -21,7 +21,7 @@ "caseId", "caseDocumentType" ], - "oneOf": [ + "anyOf": [ { "required": [ "caseDocument" diff --git a/sjp-command/sjp-command-controller/src/raml/json/sjp.command.upload-case-document.json b/sjp-command/sjp-command-controller/src/raml/json/sjp.command.upload-case-document.json index 27840a677c..267d420021 100644 --- a/sjp-command/sjp-command-controller/src/raml/json/sjp.command.upload-case-document.json +++ b/sjp-command/sjp-command-controller/src/raml/json/sjp.command.upload-case-document.json @@ -1,5 +1,6 @@ { "caseId": "6ac98e9c-66b2-4363-8a78-641dbec8bbd2", "caseDocument": "fc3f8d82-d200-4cab-a492-2796e6fdf42c", + "caseDocumentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "caseDocumentType": "PLEA" } diff --git a/sjp-command/sjp-command-handler/src/main/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandler.java b/sjp-command/sjp-command-handler/src/main/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandler.java index e3359c0edd..2f6803c633 100644 --- a/sjp-command/sjp-command-handler/src/main/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandler.java +++ b/sjp-command/sjp-command-handler/src/main/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandler.java @@ -31,9 +31,10 @@ public void handle(JsonEnvelope command) throws EventStreamException { final UUID caseId = getCaseId(payload); final String caseDocumentType = payload.getString("caseDocumentType"); - // Exactly one of a file service id (caseDocument) or a blob uri (caseDocumentUri) is - // present - the command schema's oneOf enforces that, and JsonSchemaValidationInterceptor - // applies it on the way in, so this only has to pick whichever arrived. + // At least one of a file service id (caseDocument) or a blob uri (caseDocumentUri) is + // present - the command schema's anyOf enforces that - and both may arrive together, which + // is what staging-dvla sends. Pass through whatever came; the aggregate and the processor + // decide what each of them means. final String caseDocumentReference = valueOrNull(payload, CASE_DOCUMENT); final String caseDocumentUri = valueOrNull(payload, CASE_DOCUMENT_URI); diff --git a/sjp-command/sjp-command-handler/src/raml/json/schema/sjp.command.upload-case-document.json b/sjp-command/sjp-command-handler/src/raml/json/schema/sjp.command.upload-case-document.json index 42fe635272..815c23d5b2 100644 --- a/sjp-command/sjp-command-handler/src/raml/json/schema/sjp.command.upload-case-document.json +++ b/sjp-command/sjp-command-handler/src/raml/json/schema/sjp.command.upload-case-document.json @@ -21,7 +21,7 @@ "caseId", "caseDocumentType" ], - "oneOf": [ + "anyOf": [ { "required": [ "caseDocument" diff --git a/sjp-command/sjp-command-handler/src/raml/json/sjp.command.upload-case-document.json b/sjp-command/sjp-command-handler/src/raml/json/sjp.command.upload-case-document.json index 27840a677c..267d420021 100644 --- a/sjp-command/sjp-command-handler/src/raml/json/sjp.command.upload-case-document.json +++ b/sjp-command/sjp-command-handler/src/raml/json/sjp.command.upload-case-document.json @@ -1,5 +1,6 @@ { "caseId": "6ac98e9c-66b2-4363-8a78-641dbec8bbd2", "caseDocument": "fc3f8d82-d200-4cab-a492-2796e6fdf42c", + "caseDocumentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "caseDocumentType": "PLEA" } diff --git a/sjp-command/sjp-command-handler/src/test/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandlerTest.java b/sjp-command/sjp-command-handler/src/test/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandlerTest.java index 36d016fcb7..04b7b21cea 100644 --- a/sjp-command/sjp-command-handler/src/test/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandlerTest.java +++ b/sjp-command/sjp-command-handler/src/test/java/uk/gov/moj/cpp/sjp/command/handler/UploadCaseDocumentHandlerTest.java @@ -169,6 +169,29 @@ public void shouldRaiseUploadedEventCarryingTheUriWhenDocumentIsBlobAddressed() + @Test + public void shouldRaiseUploadedEventCarryingBothReferencesWhenTheCallerSuppliesBoth() throws EventStreamException { + final JsonEnvelope command = createCommand(payload -> payload + .add(CASE_DOCUMENT_REFERENCE_PROPERTY, DOCUMENT_REFERENCE.toString()) + .add(CASE_DOCUMENT_URI_PROPERTY, DOCUMENT_URI)); + when(eventSource.getStreamById(CASE_ID)).thenReturn(eventStream); + when(aggregateService.get(eventStream, CaseAggregate.class)).thenReturn(caseAggregate); + + uploadCaseDocumentHandler.handle(command); + + assertThat(eventStream, eventStreamAppendedWith( + streamContaining( + jsonEnvelope( + withMetadataEnvelopedFrom(command) + .withName("sjp.events.case-document-uploaded"), + payloadIsJson(allOf( + withJsonPath("$.caseId", equalTo(CASE_ID.toString())), + withJsonPath("$.documentReference", equalTo(DOCUMENT_REFERENCE.toString())), + withJsonPath("$.documentReferenceUri", equalTo(DOCUMENT_URI)) + ))) + ))); + } + private JsonEnvelope createCaseDocumentUploadCommand(final UUID caseId, final UUID caseDocumentReference, final String documentType) { final JsonObjectBuilder payload = createObjectBuilder() .add(CASE_ID_PROPERTY, caseId.toString()) diff --git a/sjp-domain/sjp-domain-aggregate/src/main/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandler.java b/sjp-domain/sjp-domain-aggregate/src/main/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandler.java index 8a314b1928..b904a5dc8b 100644 --- a/sjp-domain/sjp-domain-aggregate/src/main/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandler.java +++ b/sjp-domain/sjp-domain-aggregate/src/main/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandler.java @@ -46,11 +46,12 @@ public Stream addCaseDocument(final UUID caseId, } /** - * @param documentReference file service id of the document, or null when it is addressed by - * {@code documentReferenceUri} - * @param documentReferenceUri blob uri of the document, or null when it is addressed by - * {@code documentReference}. Exactly one of the two is set; the - * caller validates that. + * @param documentReference file service id of the document, or the uuid derived from + * {@code documentReferenceUri} by the calling context. Null only + * when the caller supplied a uri and no id. + * @param documentReferenceUri blob uri of the document, or null when it is addressed by a file + * service id. At least one of the two is set - the command schema's + * {@code anyOf} enforces that - and both may be set together. */ public Stream uploadCaseDocument(final UUID caseId, final UUID documentReference, @@ -59,7 +60,9 @@ public Stream uploadCaseDocument(final UUID caseId, final CaseAggregateState state) { if (!state.hasGrantedApplication()) { - final Object reference = nonNull(documentReference) ? documentReference : documentReferenceUri; + // Both may be present now, so prefer the uri: it is the reference the calling context + // supplied and recognises, and the uuid is derived from it anyway. + final Object reference = nonNull(documentReferenceUri) ? documentReferenceUri : documentReference; if (state.isCaseReferredForCourtHearing()) { LOGGER.warn("Case Document Upload rejected as case is referred to court for hearing: {}", reference); diff --git a/sjp-domain/sjp-domain-aggregate/src/test/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandlerTest.java b/sjp-domain/sjp-domain-aggregate/src/test/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandlerTest.java index f75516cf8a..7fc0ae11ed 100644 --- a/sjp-domain/sjp-domain-aggregate/src/test/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandlerTest.java +++ b/sjp-domain/sjp-domain-aggregate/src/test/java/uk/gov/moj/cpp/sjp/domain/aggregate/handler/CaseDocumentHandlerTest.java @@ -60,6 +60,30 @@ public void uploadCaseDocument_whenBlobAddressedAndRejected_shouldCarryTheUriOnT assertThat(rejected.getDescription(), containsString(documentReferenceUri)); } + @Test + public void uploadCaseDocument_whenBothReferencesSupplied_shouldNameTheUriAndCarryBoth() { + final UUID caseId = UUID.randomUUID(); + final UUID documentReference = UUID.randomUUID(); + final String documentReferenceUri = "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/doc.pdf"; + final CaseAggregateState state = mock(CaseAggregateState.class); + + when(state.hasGrantedApplication()).thenReturn(false); + when(state.isCaseReferredForCourtHearing()).thenReturn(true); + + final List events = CaseDocumentHandler.INSTANCE + .uploadCaseDocument(caseId, documentReference, documentReferenceUri, "type", state) + .collect(Collectors.toList()); + + final CaseDocumentUploadRejected rejected = (CaseDocumentUploadRejected) events.get(0); + + // both travel on the event... + assertThat(rejected.getDocumentId(), is(documentReference)); + assertThat(rejected.getDocumentReferenceUri(), is(documentReferenceUri)); + + // ...but the message names the uri, which is what the calling context recognises + assertThat(rejected.getDescription(), containsString(documentReferenceUri)); + } + @Test public void uploadCaseDocument_whenCaseNotManagedByAtcm_shouldReturnCaseDocumentUploadRejectedEvent() { UUID caseId = UUID.randomUUID(); diff --git a/sjp-event/sjp-event-processor/src/main/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentProcessor.java b/sjp-event/sjp-event-processor/src/main/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentProcessor.java index 8347fcf999..098f22843b 100644 --- a/sjp-event/sjp-event-processor/src/main/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentProcessor.java +++ b/sjp-event/sjp-event-processor/src/main/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentProcessor.java @@ -1,7 +1,6 @@ package uk.gov.moj.cpp.sjp.event.processor; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static java.util.UUID.nameUUIDFromBytes; import static java.util.UUID.randomUUID; @@ -61,12 +60,13 @@ public void handleCaseDocumentUploaded(final JsonEnvelope caseDocumentUploadedEv final UUID caseId = UUID.fromString(payload.getString(CASE_ID)); final String documentType = payload.getString(DOCUMENT_TYPE); - // Exactly one of these is set - the event schema's oneOf enforces that, and - // JsonSchemaValidationInterceptor applies it on the way in. A blob-addressed document is - // forwarded onward as a uri; SJP never reads the document itself either way. + // Both may now arrive together: staging-dvla derives the case document's uuid from the uri + // and sends the pair, so a uuid being present no longer means the document is file-service + // addressed. The uri is what says "blob" - test for that directly. Getting this backwards + // sends material a fileServiceId no file service has ever heard of. final String documentReference = valueOrNull(payload, DOCUMENT_REFERENCE); final String documentReferenceUri = valueOrNull(payload, DOCUMENT_REFERENCE_URI); - final boolean addressedByUri = isNull(documentReference); + final boolean addressedByUri = nonNull(documentReferenceUri); final JsonObjectBuilder fileUploadedEventPayload = createObjectBuilder() .add(CASE_ID, caseId.toString()); @@ -76,15 +76,26 @@ public void handleCaseDocumentUploaded(final JsonEnvelope caseDocumentUploadedEv .add(CASE_ID, caseId.toString()) .add(DOCUMENT_TYPE, documentType); + // The public event and the sjp metadata carry everything we were given. The metadata one + // matters most: handleMaterialAdded reads documentId back out of it to build + // sjp.command.add-case-document, so the uuid has to ride along or that hop falls back to + // deriving one and the caller's id is lost. + if (nonNull(documentReference)) { + fileUploadedEventPayload.add(DOCUMENT_ID, documentReference); + sjpMetadata.add(DOCUMENT_ID, documentReference); + } if (addressedByUri) { fileUploadedEventPayload.add(DOCUMENT_URI, documentReferenceUri); - // Material rejects a command carrying more than one file reference, so send only this one. - uploadFilePayload.add(FILE_URI, documentReferenceUri); sjpMetadata.add(DOCUMENT_URI, documentReferenceUri); + } + + // Material is the exception: material.command.upload-file is an exclusive oneOf and its + // handler throws on more than one reference, so exactly one goes on that payload. The uri + // wins when there is one - it is the only form material can actually read. + if (addressedByUri) { + uploadFilePayload.add(FILE_URI, documentReferenceUri); } else { - fileUploadedEventPayload.add(DOCUMENT_ID, documentReference); uploadFilePayload.add(FILE_SERVICE_ID, documentReference); - sjpMetadata.add(DOCUMENT_ID, documentReference); } sender.send(enveloper.withMetadataFrom(caseDocumentUploadedEvent, "public.sjp.case-document-uploaded") @@ -123,11 +134,11 @@ public void handleMaterialAdded(final JsonEnvelope materialAddedEvent) { LOGGER.info("Material {} is a {} for sjp case {}", materialId, documentType, caseId); - // A blob-addressed document has no file service id to become the case document's - // identity, and case_document.id is a uuid primary key. Derive a stable v3 uuid from - // the blob uri: the same uri always yields the same id, so a redelivered - // material.material-added is caught by the aggregate's duplicate check exactly as it is - // on the file-service path. + // Normally the id was supplied: staging-dvla derives it from the uri with this exact + // algorithm and sends it on the command. The derivation below is the fallback for a + // uri-only payload - a caller not yet updated, or a pre-change event replayed from the + // store. Keep the two implementations identical; see + // cpp-context-staging-dvla SystemDocGeneratorEventProcessor. final String caseDocumentId = getCaseDocumentId(documentId, documentUri); final JsonObjectBuilder payload = createObjectBuilder() diff --git a/sjp-event/sjp-event-processor/src/test/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentUploadedProcessorTest.java b/sjp-event/sjp-event-processor/src/test/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentUploadedProcessorTest.java index 5e4ea60607..5e7c618353 100644 --- a/sjp-event/sjp-event-processor/src/test/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentUploadedProcessorTest.java +++ b/sjp-event/sjp-event-processor/src/test/java/uk/gov/moj/cpp/sjp/event/processor/CaseDocumentUploadedProcessorTest.java @@ -10,6 +10,7 @@ import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @@ -200,6 +201,80 @@ public void shouldForwardTheUriToMaterialWhenDocumentIsBlobAddressed() { assertThat(sent.get(1).payloadAsJsonObject().containsKey("fileServiceId"), is(false)); } + @Test + public void shouldHandleABlobAddressedDocumentWithoutError() { + final JsonObject payload = createObjectBuilder() + .add("caseId", caseId.toString()) + .add("documentReferenceUri", DOCUMENT_URI) + .add("documentType", DOCUMENT_TYPE).build(); + + caseDocumentProcessor.handleCaseDocumentUploaded(createEnvelope("sjp.events.case-document-uploaded", payload)); + + verify(sender, times(2)).send(envelopeCaptor.capture()); + assertThat(envelopeCaptor.getAllValues().get(0).metadata().name(), is("public.sjp.case-document-uploaded")); + assertThat(envelopeCaptor.getAllValues().get(1).metadata().name(), is("material.command.upload-file")); + } + + @Test + public void shouldCarryBothReferencesOnwardWhenTheCallerSuppliesBoth() { + // The shape staging-dvla now sends: it derives the uuid from the uri itself and sends the + // pair, so a uuid being present must NOT be read as "file service addressed". + final JsonObject payload = createObjectBuilder() + .add("caseId", caseId.toString()) + .add("documentReference", documentReference.toString()) + .add("documentReferenceUri", DOCUMENT_URI) + .add("documentType", DOCUMENT_TYPE).build(); + + caseDocumentProcessor.handleCaseDocumentUploaded(createEnvelope("sjp.events.case-document-uploaded", payload)); + + verify(sender, times(2)).send(envelopeCaptor.capture()); + final List sent = envelopeCaptor.getAllValues(); + + // the public event keeps everything we were given + assertThat(sent.get(0).payloadAsJsonObject().toString(), isJson(allOf( + withJsonPath("$.documentId", equalTo(documentReference.toString())), + withJsonPath("$.documentUri", equalTo(DOCUMENT_URI))))); + + // material takes exactly one, and it has to be the uri - its handler throws on more than + // one reference, and a derived uuid means nothing to the file service. + assertThat(sent.get(1).metadata().name(), is("material.command.upload-file")); + assertThat(sent.get(1).payloadAsJsonObject().toString(), isJson( + withJsonPath("$.fileUri", equalTo(DOCUMENT_URI)))); + assertThat(sent.get(1).payloadAsJsonObject().containsKey("fileServiceId"), is(false)); + + // and the sjp metadata carries both, so handleMaterialAdded uses the supplied uuid + assertThat(sent.get(1).metadata().asJsonObject().toString(), isJson(allOf( + withJsonPath("$.sjpMetadata.documentId", equalTo(documentReference.toString())), + withJsonPath("$.sjpMetadata.documentUri", equalTo(DOCUMENT_URI))))); + } + + @Test + public void shouldUseTheSuppliedIdRatherThanDerivingOneWhenBothAreKnown() { + // The whole point of the change: a derived uuid is still a valid uuid, so a regression here + // would be invisible without pinning the supplied value explicitly. + final Metadata enriched = metadataFrom( + JsonObjects.createObjectBuilder(materialAddedMetadata.asJsonObject()) + .add("sjpMetadata", createObjectBuilder() + .add("caseId", caseId.toString()) + .add("documentId", documentReference.toString()) + .add("documentUri", DOCUMENT_URI) + .add("documentType", DOCUMENT_TYPE) + .build()).build()) + .build(); + + caseDocumentProcessor.handleMaterialAdded(envelopeFrom(enriched, materialAddedPayload)); + + verify(sender).send(envelopeCaptor.capture()); + + assertThat(envelopeCaptor.getValue().payloadAsJsonObject().toString(), isJson(allOf( + withJsonPath("$.id", equalTo(documentReference.toString())), + withJsonPath("$.documentUri", equalTo(DOCUMENT_URI))))); + + // specifically NOT the value the fallback would have produced + assertThat(envelopeCaptor.getValue().payloadAsJsonObject().getString("id"), + is(not(nameUUIDFromBytes(DOCUMENT_URI.getBytes(UTF_8)).toString()))); + } + @Test public void shouldAddCaseDocumentForABlobAddressedDocumentWithAnIdDerivedFromTheUri() { // A blob-addressed document has no file service id, and case_document.id is a uuid primary diff --git a/sjp-event/sjp-event-processor/src/yaml/json/public.sjp.case-document-uploaded.json b/sjp-event/sjp-event-processor/src/yaml/json/public.sjp.case-document-uploaded.json index 0308c6ee30..67ac836a89 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/public.sjp.case-document-uploaded.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/public.sjp.case-document-uploaded.json @@ -1,4 +1,5 @@ { "documentId": "7e2f843e-d639-40b3-2611-8015f3a18958", + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "caseId": "b62dc6aa-97e2-4883-98f8-2ab681a29d22" } \ No newline at end of file diff --git a/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.case-document-uploaded.json b/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.case-document-uploaded.json index aba067a2b6..7d4c15663f 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.case-document-uploaded.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.case-document-uploaded.json @@ -17,7 +17,7 @@ "required": [ "caseId" ], - "oneOf": [ + "anyOf": [ { "required": [ "documentId" diff --git a/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.events.case-document-upload-rejected.json b/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.events.case-document-upload-rejected.json index b7b35fa10f..c3f0627d81 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.events.case-document-upload-rejected.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/schema/public.sjp.events.case-document-upload-rejected.json @@ -17,7 +17,7 @@ "required": [ "description" ], - "oneOf": [ + "anyOf": [ { "required": [ "documentId" diff --git a/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-upload-rejected.json b/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-upload-rejected.json index 3b4da9e94a..f1ecc2b086 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-upload-rejected.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-upload-rejected.json @@ -17,7 +17,7 @@ "required": [ "description" ], - "oneOf": [ + "anyOf": [ { "required": [ "documentId" diff --git a/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-uploaded.json b/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-uploaded.json index f7e1ffbe8e..882a0ac610 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-uploaded.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/schema/sjp.events.case-document-uploaded.json @@ -23,7 +23,7 @@ "caseId", "documentType" ], - "oneOf": [ + "anyOf": [ { "required": [ "documentReference" diff --git a/sjp-event/sjp-event-processor/src/yaml/json/sjp.events.case-document-uploaded.json b/sjp-event/sjp-event-processor/src/yaml/json/sjp.events.case-document-uploaded.json index 18bf749e01..06aac65331 100644 --- a/sjp-event/sjp-event-processor/src/yaml/json/sjp.events.case-document-uploaded.json +++ b/sjp-event/sjp-event-processor/src/yaml/json/sjp.events.case-document-uploaded.json @@ -1,5 +1,6 @@ { "caseId": "6ac98e9c-66b2-4363-8a78-641dbec8bbd2", "documentReference": "6ac98e9c-66b2-4363-8a78-641dbec8bbd1", + "documentReferenceUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "documentType": "PLEA" } diff --git a/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/helper/CaseDocumentHelper.java b/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/helper/CaseDocumentHelper.java index 70976ce36d..bf809332e7 100644 --- a/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/helper/CaseDocumentHelper.java +++ b/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/helper/CaseDocumentHelper.java @@ -236,6 +236,43 @@ public void verifyInPublicTopic(final UUID expectedDocumentId, final UUID expect * id to correlate on. {@code id} is derived from that uri, so it is stable but not something * the caller knew in advance. */ + /** + * The shape staging-dvla sends now: it derives the case document's uuid from the uri itself and + * supplies both, so SJP files the document under the caller's id rather than one of its own. + * + * @return the uri, for chaining into the Material stub - material is still sent only the uri + */ + public String uploadCaseDocumentByReferenceAndUri(final UUID userId, final String documentType, + final UUID documentReference, final String documentUri) { + final String writeUrl = format("/cases/%s/upload-case-document/%s", caseId, documentType); + final String payload = createObjectBuilder() + .add("caseDocument", documentReference.toString()) + .add("caseDocumentUri", documentUri) + .build() + .toString(); + + makePostCall(userId, writeUrl, UPLOAD_CASE_DOCUMENT_JSON_MEDIA_TYPE, payload, Response.Status.ACCEPTED); + + return documentUri; + } + + /** + * Asserts the public completion event for a document whose id was supplied by the caller rather + * than derived by SJP - both references ride the event. + */ + public void verifyInPublicTopicWithSuppliedId(final UUID expectedDocumentId, final String expectedDocumentUri, + final UUID expectedMaterialId) { + final String caseDocumentAddedEvent = publicConsumer.retrieveMessage().orElse(null); + + assertThat(caseDocumentAddedEvent, notNullValue()); + + with(caseDocumentAddedEvent) + .assertThat("$.caseId", is(caseId.toString())) + .assertThat("$.documentUri", is(expectedDocumentUri)) + .assertThat("$.id", is(expectedDocumentId.toString())) + .assertThat("$.materialId", is(expectedMaterialId.toString())); + } + public void verifyInPublicTopicForBlobAddressedDocument(final String expectedDocumentUri, final UUID expectedMaterialId) { final String caseDocumentAddedEvent = publicConsumer.retrieveMessage().orElse(null); diff --git a/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/test/AddCaseDocumentIT.java b/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/test/AddCaseDocumentIT.java index 1f39c58dda..6c65c4f876 100644 --- a/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/test/AddCaseDocumentIT.java +++ b/sjp-integration-test/src/test/java/uk/gov/moj/sjp/it/test/AddCaseDocumentIT.java @@ -1,6 +1,7 @@ package uk.gov.moj.sjp.it.test; import static com.jayway.jsonpath.matchers.JsonPathMatchers.withJsonPath; +import static org.hamcrest.Matchers.not; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.UUID.nameUUIDFromBytes; import static java.util.UUID.randomUUID; @@ -175,7 +176,7 @@ public void shouldUploadCaseDocumentSuppliedByReferenceOnTheJsonBranch() { caseDocumentHelper.uploadCaseDocumentByReference(USER_ID, documentType, documentReference); final UUID documentId = caseDocumentHelper.verifyCaseDocumentUploadedEventRaised(); - assertThat(documentId, is(documentReference)); + assertThat(documentId, is(caseId)); final UUID materialId = MaterialStub.processMaterialAddedCommand(documentReference); CaseDocumentHelper.assertDocumentAdded(USER_ID, caseId, materialId, documentReference, documentType); @@ -190,6 +191,37 @@ public void shouldUploadCaseDocumentSuppliedByReferenceOnTheJsonBranch() { * the public completion event - that echo is the only join key a calling context has for a * blob-addressed filing, and it is what lets staging-dvla release the blob. */ + /** + * Both references supplied - the shape staging-dvla sends once it derives the uuid itself. SJP + * must file the document under the id it was given, not one it derives, and must still forward + * only the uri to Material. + */ + @Test + public void shouldUseTheSuppliedIdWhenBothReferencesAreOnTheJsonBranch() { + final String documentType = "PLEA"; + final String documentUri = "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/both.pdf"; + final UUID suppliedDocumentId = randomUUID(); + createCase(); + stubAddCaseMaterial(); + + try (final CaseDocumentHelper caseDocumentHelper = new CaseDocumentHelper(caseId)) { + caseDocumentHelper.uploadCaseDocumentByReferenceAndUri(USER_ID, documentType, suppliedDocumentId, documentUri); + + // material is keyed on the uri: it is sent fileUri, never fileServiceId + final UUID materialId = MaterialStub.processMaterialAddedCommand(documentUri); + + CaseDocumentHelper.assertDocumentAdded(USER_ID, caseId, materialId, suppliedDocumentId, documentType); + caseDocumentHelper.verifyInPublicTopicWithSuppliedId(suppliedDocumentId, documentUri, materialId); + + // the supplied id is a random uuid, so this also proves SJP did not derive one + assertThat(suppliedDocumentId, is(not(nameUUIDFromBytes(documentUri.getBytes(UTF_8))))); + } + } + + /** + * Uri only - now the fallback path. SJP derives the id because the caller did not supply one, + * which is what a not-yet-updated caller or a replayed pre-change event looks like. + */ @Test public void shouldUploadCaseDocumentSuppliedByUriOnTheJsonBranch() { final String documentType = "PLEA"; @@ -208,6 +240,14 @@ public void shouldUploadCaseDocumentSuppliedByUriOnTheJsonBranch() { CaseDocumentHelper.assertDocumentAdded(USER_ID, caseId, materialId, derivedDocumentId, documentType); caseDocumentHelper.verifyInPublicTopicForBlobAddressedDocument(documentUri, materialId); + + // The query API has to give the uri back: for a blob-addressed document the id is + // derived from the uri rather than being a file service id, so the id alone tells a + // caller nothing about where the document actually lives. + CaseDocumentHelper.pollForCaseDocument(caseId, USER_ID, new Matcher[]{ + withJsonPath("$.caseDocuments[0].id", is(derivedDocumentId.toString())), + withJsonPath("$.caseDocuments[0].documentUri", is(documentUri)) + }); } } diff --git a/sjp-json/src/main/resources/json/schema/queries/query-document-with-metadata.json b/sjp-json/src/main/resources/json/schema/queries/query-document-with-metadata.json index e0bbf28370..fd3c0c6dcf 100644 --- a/sjp-json/src/main/resources/json/schema/queries/query-document-with-metadata.json +++ b/sjp-json/src/main/resources/json/schema/queries/query-document-with-metadata.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "id": "http://justice.gov.uk/json/schemas/domains/sjp/queries/document-with-metadta.json", + "id": "http://justice.gov.uk/json/schemas/domains/sjp/queries/document-with-metadata.json", "type": "object", "properties": { "id": { @@ -18,6 +18,9 @@ "addedAt": { "$ref": "http://justice.gov.uk/json/schemas/domains/sjp/temporal.json#/definitions/date-time" }, + "documentUri": { + "type": "string" + }, "metadata": { "$ref": "http://justice.gov.uk/json/schemas/domains/sjp/queries/document-metadata.json" } diff --git a/sjp-json/src/main/resources/json/schema/queries/query-document.json b/sjp-json/src/main/resources/json/schema/queries/query-document.json index 477744b126..6a82da603b 100644 --- a/sjp-json/src/main/resources/json/schema/queries/query-document.json +++ b/sjp-json/src/main/resources/json/schema/queries/query-document.json @@ -15,6 +15,9 @@ "documentNumber": { "type": "integer" }, + "documentUri": { + "type": "string" + }, "addedAt": { "$ref": "http://justice.gov.uk/json/schemas/domains/sjp/temporal.json#/definitions/date-time" } diff --git a/sjp-query/sjp-query-api/src/raml/json/schema/sjp.query.result-orders.json b/sjp-query/sjp-query-api/src/raml/json/schema/sjp.query.result-orders.json index 992c1ec5ca..d4d1952216 100644 --- a/sjp-query/sjp-query-api/src/raml/json/schema/sjp.query.result-orders.json +++ b/sjp-query/sjp-query-api/src/raml/json/schema/sjp.query.result-orders.json @@ -27,6 +27,9 @@ }, "addedAt": { "type": "string" + }, + "documentUri": { + "type": "string" } }, "additionalProperties": false, diff --git a/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-document.json b/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-document.json index c71e07513b..0335c0915c 100644 --- a/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-document.json +++ b/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-document.json @@ -3,6 +3,7 @@ "id": "fc3f8d82-d200-4cab-a492-2796e6fdf42c", "materialId": "87a321ef-ea34-47d7-865d-7f430b4775be", "documentType": "PLEA", - "documentNumber": 1 + "documentNumber": 1, + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf" } } \ No newline at end of file diff --git a/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-documents.json b/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-documents.json index cceb0197da..e7adadab1f 100644 --- a/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-documents.json +++ b/sjp-query/sjp-query-api/src/raml/json/sjp.query.case-documents.json @@ -5,7 +5,8 @@ "materialId": "87a321ef-ea34-47d7-865d-7f430b4775be", "documentType": "PLEA", "documentNumber": 1, - "addedAt": "2018-08-26T14:55:47.631Z" + "addedAt": "2018-08-26T14:55:47.631Z", + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf" } ] } \ No newline at end of file diff --git a/sjp-query/sjp-query-api/src/raml/json/sjp.query.result-orders.json b/sjp-query/sjp-query-api/src/raml/json/sjp.query.result-orders.json index 5105c84e68..103faaf7a8 100644 --- a/sjp-query/sjp-query-api/src/raml/json/sjp.query.result-orders.json +++ b/sjp-query/sjp-query-api/src/raml/json/sjp.query.result-orders.json @@ -8,6 +8,7 @@ }, "order": { "documentId": "670490ee-95be-4e3e-af8b-9bef865e26ba", + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "addedAt": "2016-11-17T10:00:00.000Z" } }, diff --git a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/CaseDocumentView.java b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/CaseDocumentView.java index 136087b9ab..ab74bd229a 100644 --- a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/CaseDocumentView.java +++ b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/CaseDocumentView.java @@ -14,6 +14,16 @@ public class CaseDocumentView { private Integer documentNumber; private ZonedDateTime addedAt; + /** + * Blob uri the document was filed from, or null when it was addressed by its file service id. + * + *

For a file-service document {@code id} is the file id; for a blob-addressed one {@code id} + * is a uuid derived from the uri and this field carries the real reference, so any response + * exposing {@code id} has to expose this too. Jackson serialises with NON_ABSENT, so the field + * is simply absent on the file-service path and those responses are unchanged. + */ + private String documentUri; + public static final Comparator BY_DOCUMENT_TYPE_AND_NUMBER = (first, second) -> { if (first.documentType == null) { return 0; @@ -27,15 +37,20 @@ public class CaseDocumentView { }; public CaseDocumentView(final CaseDocument caseDocument) { - this(caseDocument.getId(), caseDocument.getMaterialId(), caseDocument.getDocumentType(), caseDocument.getDocumentNumber(), caseDocument.getAddedAt()); + this(caseDocument.getId(), caseDocument.getMaterialId(), caseDocument.getDocumentType(), caseDocument.getDocumentNumber(), caseDocument.getAddedAt(), caseDocument.getDocumentUri()); } public CaseDocumentView(final UUID id, final UUID materialId, final String documentType, final Integer documentNumber, final ZonedDateTime addedAt) { + this(id, materialId, documentType, documentNumber, addedAt, null); + } + + public CaseDocumentView(final UUID id, final UUID materialId, final String documentType, final Integer documentNumber, final ZonedDateTime addedAt, final String documentUri) { this.id = id; this.materialId = materialId; this.documentType = documentType; this.documentNumber = documentNumber; this.addedAt = addedAt; + this.documentUri = documentUri; } public UUID getId() { @@ -66,4 +81,8 @@ public ZonedDateTime getAddedAt() { return addedAt; } + public String getDocumentUri() { + return documentUri; + } + } \ No newline at end of file diff --git a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/ResultOrdersView.java b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/ResultOrdersView.java index 614f19980c..75b22e672b 100644 --- a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/ResultOrdersView.java +++ b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/response/ResultOrdersView.java @@ -74,7 +74,11 @@ public Builder setDefendant(final DefendantDetail defendantDetail) { } public Builder setOrder(final UUID documentId, final ZonedDateTime dateMaterialAdded) { - casesWithOrderView.order = new DefendantView.OrderView(documentId, dateMaterialAdded); + return setOrder(documentId, null, dateMaterialAdded); + } + + public Builder setOrder(final UUID documentId, final String documentUri, final ZonedDateTime dateMaterialAdded) { + casesWithOrderView.order = new DefendantView.OrderView(documentId, documentUri, dateMaterialAdded); return this; } @@ -132,10 +136,18 @@ public String getLegalEntityName() { public static class OrderView { private UUID documentId; + /** + * Blob uri the result order was filed from, or null when it was addressed by its + * file service id. For a blob-addressed document {@code documentId} is derived from + * this uri rather than being the file id, so the two travel together. + */ + private String documentUri; + private ZonedDateTime addedAt; - public OrderView(final UUID documentId, final ZonedDateTime addedAt) { + public OrderView(final UUID documentId, final String documentUri, final ZonedDateTime addedAt) { this.documentId = documentId; + this.documentUri = documentUri; this.addedAt = addedAt; } @@ -143,6 +155,10 @@ public UUID getDocumentId() { return documentId; } + public String getDocumentUri() { + return documentUri; + } + public ZonedDateTime getAddedAt() { return addedAt; } diff --git a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/service/CaseService.java b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/service/CaseService.java index 1fbd8b6490..2855be7d60 100644 --- a/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/service/CaseService.java +++ b/sjp-query/sjp-query-view/src/main/java/uk/gov/moj/cpp/sjp/query/view/service/CaseService.java @@ -408,7 +408,7 @@ public ResultOrdersView findResultOrders(LocalDate fromDate, LocalDate toDate) { .setCaseId(caseDocument.getCaseId()) .setUrn(caseDetail.getUrn()) .setDefendant(caseDetail.getDefendant()) - .setOrder(caseDocument.getId(), caseDocument.getAddedAt()) + .setOrder(caseDocument.getId(), caseDocument.getDocumentUri(), caseDocument.getAddedAt()) .build()); }; diff --git a/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.case-documents-filter-other-and-financial-means.json b/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.case-documents-filter-other-and-financial-means.json index bfca642f48..7eae072ec7 100644 --- a/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.case-documents-filter-other-and-financial-means.json +++ b/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.case-documents-filter-other-and-financial-means.json @@ -18,6 +18,9 @@ }, "documentNumber": { "type": "integer" + }, + "documentUri": { + "type": "string" } }, "additionalProperties": false, diff --git a/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.result-orders.json b/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.result-orders.json index 992c1ec5ca..d4d1952216 100644 --- a/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.result-orders.json +++ b/sjp-query/sjp-query-view/src/raml/json/schema/sjp.query.result-orders.json @@ -27,6 +27,9 @@ }, "addedAt": { "type": "string" + }, + "documentUri": { + "type": "string" } }, "additionalProperties": false, diff --git a/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-document.json b/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-document.json index c71e07513b..0335c0915c 100644 --- a/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-document.json +++ b/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-document.json @@ -3,6 +3,7 @@ "id": "fc3f8d82-d200-4cab-a492-2796e6fdf42c", "materialId": "87a321ef-ea34-47d7-865d-7f430b4775be", "documentType": "PLEA", - "documentNumber": 1 + "documentNumber": 1, + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf" } } \ No newline at end of file diff --git a/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-documents.json b/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-documents.json index cceb0197da..e7adadab1f 100644 --- a/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-documents.json +++ b/sjp-query/sjp-query-view/src/raml/json/sjp.query.case-documents.json @@ -5,7 +5,8 @@ "materialId": "87a321ef-ea34-47d7-865d-7f430b4775be", "documentType": "PLEA", "documentNumber": 1, - "addedAt": "2018-08-26T14:55:47.631Z" + "addedAt": "2018-08-26T14:55:47.631Z", + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf" } ] } \ No newline at end of file diff --git a/sjp-query/sjp-query-view/src/raml/json/sjp.query.result-orders.json b/sjp-query/sjp-query-view/src/raml/json/sjp.query.result-orders.json index 5105c84e68..103faaf7a8 100644 --- a/sjp-query/sjp-query-view/src/raml/json/sjp.query.result-orders.json +++ b/sjp-query/sjp-query-view/src/raml/json/sjp.query.result-orders.json @@ -8,6 +8,7 @@ }, "order": { "documentId": "670490ee-95be-4e3e-af8b-9bef865e26ba", + "documentUri": "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf", "addedAt": "2016-11-17T10:00:00.000Z" } }, diff --git a/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/CaseDocumentViewTest.java b/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/CaseDocumentViewTest.java index 0285f9e071..aa4b09fe09 100644 --- a/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/CaseDocumentViewTest.java +++ b/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/CaseDocumentViewTest.java @@ -1,6 +1,7 @@ package uk.gov.moj.cpp.sjp.query.view; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import uk.gov.moj.cpp.sjp.persistence.entity.CaseDocument; import uk.gov.moj.cpp.sjp.query.view.response.CaseDocumentView; @@ -38,6 +39,21 @@ public void testOrder() { assertEquals(2, (int) caseDocumentViews.get(2).getDocumentNumber()); } + @Test + public void shouldMapDocumentUriThroughFromTheEntity() { + final String documentUri = "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf"; + + final CaseDocumentView blobAddressed = new CaseDocumentView( + new CaseDocument(null, null, "SJPN", null, null, 1, documentUri)); + final CaseDocumentView fileServiceAddressed = new CaseDocumentView( + new CaseDocument(null, null, "SJPN", null, null, 1, null)); + + assertEquals(documentUri, blobAddressed.getDocumentUri()); + // Jackson serialises with NON_ABSENT, so a null here is omitted entirely and the + // file-service response shape is unchanged. + assertNull(fileServiceAddressed.getDocumentUri()); + } + @Test public void exceptionIsNotThrownWhenDocumentTypeIsNotDefined() { //given diff --git a/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/service/CaseServiceTest.java b/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/service/CaseServiceTest.java index fc664585e9..2d96c5eb3b 100644 --- a/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/service/CaseServiceTest.java +++ b/sjp-query/sjp-query-view/src/test/java/uk/gov/moj/cpp/sjp/query/view/service/CaseServiceTest.java @@ -100,6 +100,8 @@ @ExtendWith(MockitoExtension.class) public class CaseServiceTest { + private static final String DOCUMENT_URI = "https://sadevfilestore.blob.core.windows.net/stack-stagingdvla/generated/plea.pdf"; + private static final UUID CASE_ID = randomUUID(); private static final CaseStatus CASE_STATUS_REFERRED_FOR_COURT_HEARING = CaseStatus.REFERRED_FOR_COURT_HEARING; private static final String URN = "TFL1234"; @@ -453,6 +455,19 @@ public void shouldFindCaseDocuments() { final CaseDocumentView firstCaseDocument = caseDocumentsView.getCaseDocuments().get(0); assertThat(firstCaseDocument.getId().toString(), is(documentId.toString())); assertThat(firstCaseDocument.getDocumentNumber(), is(2)); + assertThat(firstCaseDocument.getDocumentUri(), is(nullValue())); + } + + @Test + public void shouldExposeDocumentUriForABlobAddressedCaseDocument() { + // For a blob-addressed document the id is a uuid derived from the uri rather than a file + // service id, so any response carrying the id has to carry the uri with it. + final CaseDocument caseDocument = new CaseDocument(randomUUID(), randomUUID(), "SJPN", clock.now(), CASE_ID, 2, DOCUMENT_URI); + when(caseRepository.findCaseDocuments(CASE_ID)).thenReturn(singletonList(caseDocument)); + + final CaseDocumentsView caseDocumentsView = service.findCaseDocuments(CASE_ID); + + assertThat(caseDocumentsView.getCaseDocuments().get(0).getDocumentUri(), is(DOCUMENT_URI)); } @Test @@ -468,6 +483,7 @@ public void shouldFindCaseDocument() { assertThat(caseDocumentView.get().getMaterialId(), is(caseDocument.getMaterialId())); assertThat(caseDocumentView.get().getDocumentType(), is(caseDocument.getDocumentType())); assertThat(caseDocumentView.get().getDocumentNumber(), is(caseDocument.getDocumentNumber())); + assertThat(caseDocumentView.get().getDocumentUri(), is(nullValue())); } @Test @@ -691,6 +707,8 @@ public void shouldFindResultOrders() { resultOrdersView.getResultOrders().get(0).getOrder().getDocumentId()); assertEquals(caseDocument.getAddedAt(), resultOrdersView.getResultOrders().get(0).getOrder().getAddedAt()); + assertEquals(caseDocument.getDocumentUri(), + resultOrdersView.getResultOrders().get(0).getOrder().getDocumentUri()); } @Test