From 00b6b99f5f96821462e54f822a40018bda723692 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 10:50:59 +0200 Subject: [PATCH 01/16] Emit pretty json for new (bigger) result object --- .../java/nl/knaw/huc/textrepo/task/TestIndexFilesByType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByType.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByType.java index fe665afd..acd75b0e 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByType.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByType.java @@ -64,7 +64,7 @@ public ImportResult importDocs(String fileType) { var result = new ImportResult(); result.status = results.stream().map(String::valueOf).collect(Collectors.joining(", ")); - result.body = asCodeBlock(body.equals("") ? " " : body); + result.body = asPrettyJson(body.equals("") ? " " : body); return result; } From 3654de81b5d771094d044fab4ff6f9a52778ca55 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 10:52:27 +0200 Subject: [PATCH 02/16] Beef up Swagger details for import task --- .../nl/knaw/huc/api/ResultImportDocument.java | 6 ++++ .../huc/resources/task/ImportResource.java | 31 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/textrepo-app/src/main/java/nl/knaw/huc/api/ResultImportDocument.java b/textrepo-app/src/main/java/nl/knaw/huc/api/ResultImportDocument.java index 0781eae7..513c451f 100644 --- a/textrepo-app/src/main/java/nl/knaw/huc/api/ResultImportDocument.java +++ b/textrepo-app/src/main/java/nl/knaw/huc/api/ResultImportDocument.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.MoreObjects; +import io.swagger.annotations.ApiModelProperty; import nl.knaw.huc.core.Document; import nl.knaw.huc.core.Version; @@ -24,21 +25,26 @@ public UUID getDocumentId() { } @JsonProperty + @ApiModelProperty(position = 1) public UUID getFileId() { return version.getFileId(); } @JsonProperty + @ApiModelProperty(position = 2) public UUID getVersionId() { return version.getId(); } @JsonProperty + @ApiModelProperty(position = 3, value = "hash value of version contents", + example = "4177ad5c5ababb0d56005cad513e9854735ed8979a0c404a73f3e9c7") public String getContentsSha() { return version.getContentsSha(); } @JsonProperty + @ApiModelProperty(position = 4, value = "true iff this version was created in this request") public boolean isNewVersion() { return isNewVersion; } diff --git a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java index 7087cad7..04887296 100644 --- a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java +++ b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java @@ -2,8 +2,11 @@ import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.ResponseHeader; +import nl.knaw.huc.api.ResultImportDocument; import nl.knaw.huc.service.task.TaskBuilderFactory; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; @@ -22,8 +25,11 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import java.io.InputStream; +import java.net.URI; import static java.util.Objects.requireNonNull; +import static javax.ws.rs.core.HttpHeaders.LINK; +import static javax.ws.rs.core.HttpHeaders.LOCATION; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA; import static nl.knaw.huc.resources.HeaderLink.Uri.CONTENTS; @@ -34,8 +40,12 @@ @Api(tags = {"task", "import"}) @Path("task/import") public class ImportResource { - private static final Logger log = LoggerFactory.getLogger(ImportResource.class); + + private static final String LINK_DESCRIPTION = + "REST URIs of document, file, version, and contents used in this request"; + private static final String LOCATION_DESCRIPTION = "(absolute) URL of newly created version"; + private final TaskBuilderFactory factory; public ImportResource(TaskBuilderFactory factory) { @@ -46,12 +56,21 @@ public ImportResource(TaskBuilderFactory factory) { @Path("documents/{externalId}/{typeName}") @Consumes(MULTIPART_FORM_DATA) @Produces(APPLICATION_JSON) - @ApiOperation("Import file as the current version for {typeName} of document referenced by {externalId} " + - "without indexing") - @ApiResponses(value = {@ApiResponse(code = 201, message = "CREATED")}) + @ApiOperation(value = + "Import file contents to create version for type {typeName} of document {externalDocumentId} (without indexing)") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Contents found in earlier version", + response = ResultImportDocument.class, + responseHeaders = {@ResponseHeader(name = LINK, response = URI.class, description = LINK_DESCRIPTION)}), + @ApiResponse(code = 201, message = "New version created for contents", + response = ResultImportDocument.class, + responseHeaders = { + @ResponseHeader(name = LINK, response = URI.class, description = LINK_DESCRIPTION), + @ResponseHeader(name = LOCATION, response = URI.class, description = LOCATION_DESCRIPTION)}), + @ApiResponse(code = 404, message = "When allowNewDocument=false and no document is found for externalId")}) public Response importDocumentContentsForFileWithType( - @NotBlank @PathParam("externalId") String externalId, - @NotBlank @PathParam("typeName") String typeName, + @NotBlank @PathParam("externalId") @ApiParam(example = "document_1234") String externalId, + @NotBlank @PathParam("typeName") @ApiParam(example = "plaintext") String typeName, @QueryParam("allowNewDocument") @DefaultValue("false") boolean allowNewDocument, @QueryParam("asLatestVersion") @DefaultValue("false") boolean asLatestVersion, @NotNull @FormDataParam("contents") InputStream uploadedInputStream, From d5695c7300339e3eb31eead8b97d8a3e93b529ec Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 11:16:11 +0200 Subject: [PATCH 03/16] Add notes for import task --- .../java/nl/knaw/huc/resources/task/ImportResource.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java index 04887296..f42e63e8 100644 --- a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java +++ b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java @@ -57,7 +57,12 @@ public ImportResource(TaskBuilderFactory factory) { @Consumes(MULTIPART_FORM_DATA) @Produces(APPLICATION_JSON) @ApiOperation(value = - "Import file contents to create version for type {typeName} of document {externalDocumentId} (without indexing)") + "Import file contents to create version for type {typeName} of document {externalDocumentId} (without indexing)", + notes = "Use allowNewDocument=true if document {externalId} is not yet in use and you want it to be created. " + + "Use asLatestVersion=true if contents you are uploading are already present as an earlier version, " + + "but you need those contents to represent that latest version; a new version node will be added. " + + "See also the Concordion Integration Tests under \"Task API\"") + @ApiResponses(value = { @ApiResponse(code = 200, message = "Contents found in earlier version", response = ResultImportDocument.class, From 448024bcd0deda82562cd28fd233c0ecd9828d5b Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 11:27:13 +0200 Subject: [PATCH 04/16] Emit pretty json for bigger result object --- .../nl/knaw/huc/textrepo/task/TestIndexFilesByIndexName.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByIndexName.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByIndexName.java index e152ce68..ebfb3149 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByIndexName.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestIndexFilesByIndexName.java @@ -65,7 +65,7 @@ public ImportResult importDocs(String fileType) { var result = new ImportResult(); result.status = results.stream().map(String::valueOf).collect(Collectors.joining(", ")); - result.body = asCodeBlock(body.equals("") ? " " : body); + result.body = asPrettyJson(body.equals("") ? " " : body); return result; } From feaadb3c1c87263a3bf44d91d467067a5a4dc7d3 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 11:28:13 +0200 Subject: [PATCH 05/16] Ruffle documentation feathers --- .../java/nl/knaw/huc/resources/task/ImportResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java index f42e63e8..b993a7a3 100644 --- a/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java +++ b/textrepo-app/src/main/java/nl/knaw/huc/resources/task/ImportResource.java @@ -59,9 +59,9 @@ public ImportResource(TaskBuilderFactory factory) { @ApiOperation(value = "Import file contents to create version for type {typeName} of document {externalDocumentId} (without indexing)", notes = "Use allowNewDocument=true if document {externalId} is not yet in use and you want it to be created. " + - "Use asLatestVersion=true if contents you are uploading are already present as an earlier version, " + - "but you need those contents to represent that latest version; a new version node will be added. " + - "See also the Concordion Integration Tests under \"Task API\"") + "Use asLatestVersion=true if contents you are uploading are already present as an *earlier* version, " + + "but you need those contents to represent the *latest* version; a new version will be created for it. " + + "See also the Concordion Integration Tests under \"Task API\".") @ApiResponses(value = { @ApiResponse(code = 200, message = "Contents found in earlier version", From a568912ef269ba3717bef80aa70bf24a10d31d47 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 12:22:24 +0200 Subject: [PATCH 06/16] Add explanatory text for now. Tests to be added! --- .../huc/textrepo/task/TestImportDocument.java | 9 ++++ .../nl/knaw/huc/textrepo/TextRepo.md | 45 ++++++++++--------- .../huc/textrepo/task/TestImportDocument.md | 20 +++++++++ 3 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java create mode 100644 concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java new file mode 100644 index 00000000..d80ee897 --- /dev/null +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java @@ -0,0 +1,9 @@ +package nl.knaw.huc.textrepo.task; + +import org.concordion.integration.junit4.ConcordionRunner; +import org.junit.runner.RunWith; + +@RunWith(ConcordionRunner.class) +public class TestImportDocument { + +} diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/TextRepo.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/TextRepo.md index ec466ad6..35cd5c6c 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/TextRepo.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/TextRepo.md @@ -1,41 +1,42 @@ # Text Repository Acceptance Tests - - [Health checks](TestHealthChecks.md "c:run") +- [Health checks](TestHealthChecks.md "c:run") ## Rest API A user of the Text Repository can retrieve, create, update and delete resources using a REST-ful API: - - [Document collection](rest/TestRestDocumentCollection.md "c:run") - - [Documents](rest/TestRestDocuments.md "c:run") - - [Document metadata](rest/TestRestDocumentMetadata.md "c:run") - - [Document files](rest/TestRestDocumentFiles.md "c:run") - - [Types](rest/TestRestTypes.md "c:run") - - [Files](rest/TestRestFiles.md "c:run") - - [File metadata](rest/TestRestFileMetadata.md "c:run") - - [File versions](rest/TestRestFileVersions.md "c:run") - - [Versions](rest/TestRestVersions.md "c:run") - - [Version metadata](rest/TestRestVersionMetadata.md "c:run") - - [Version contents](rest/TestRestVersionContents.md "c:run") - - [Contents](rest/TestRestContents.md "c:run") +- [Document collection](rest/TestRestDocumentCollection.md "c:run") +- [Documents](rest/TestRestDocuments.md "c:run") +- [Document metadata](rest/TestRestDocumentMetadata.md "c:run") +- [Document files](rest/TestRestDocumentFiles.md "c:run") +- [Types](rest/TestRestTypes.md "c:run") +- [Files](rest/TestRestFiles.md "c:run") +- [File metadata](rest/TestRestFileMetadata.md "c:run") +- [File versions](rest/TestRestFileVersions.md "c:run") +- [Versions](rest/TestRestVersions.md "c:run") +- [Version metadata](rest/TestRestVersionMetadata.md "c:run") +- [Version contents](rest/TestRestVersionContents.md "c:run") +- [Contents](rest/TestRestContents.md "c:run") ## Task API The Text Repository offers `task`-endpoints to perform a single, complex task within a single request: - - [Get document metadata by external ID](task/TestFindDocumentMetadataByExternalId.md "c:run") - - [Get file metadata by external ID and file type](task/TestFindFileMetadataByExternalId.md "c:run") - - [Get latest file contents by external ID and file type](task/TestFindFileContentsByExternalId.md "c:run") - - [Index files by type](task/TestIndexFilesByType.md "c:run") - - [Index files by index name](task/TestIndexFilesByIndexName.md "c:run") - +- [Get document metadata by external ID](task/TestFindDocumentMetadataByExternalId.md "c:run") +- [Get file metadata by external ID and file type](task/TestFindFileMetadataByExternalId.md "c:run") +- [Get latest file contents by external ID and file type](task/TestFindFileContentsByExternalId.md "c:run") +- [Import document](task/TestImportDocument.md "c:run") +- [Index files by type](task/TestIndexFilesByType.md "c:run") +- [Index files by index name](task/TestIndexFilesByIndexName.md "c:run") + ## Dashboard The Text Repository offers various diagnostics about the state of documents: - - [Show count of documents lacking files and / or metadata](dashboard/TestDashboard.md "c:run") +- [Show count of documents lacking files and / or metadata](dashboard/TestDashboard.md "c:run") ## Indexers - - [File indexer](index/TestFileIndexer.md "c:run") - - [Autocomplete indexer](index/TestAutocompleteIndexer.md "c:run") +- [File indexer](index/TestFileIndexer.md "c:run") +- [Autocomplete indexer](index/TestAutocompleteIndexer.md "c:run") diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md new file mode 100644 index 00000000..66e3301d --- /dev/null +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -0,0 +1,20 @@ +# Test `/task/import/documents/{externalId}/{typeName}` + +File contents can be directly uploaded to a document, referenced by its `externalId`. This yields a (possibly new) +version of a `typeName` typed file for that document. + +If the document with `externalId` does not yet exist, you can have the repository create it during import by passing +`allowNewDocuments=true`. + +Otherwise, using `allowNewDocuments=false`, the import will verify that you are uploading to an already existing +document, or deny the request. Note that this is also the default if you leave `allowNewDocuments` out. + +If you offer the same contents of a version already registered for this document during another import, by +default (`asLatestVersion=false`), no new version will be created as it already exists for that particular document and +type. This _idempotent_ behavior ensures that if, e.g., versions `1-2-3-4` have been imported so far, and you offer the +contents of `2` in subsequent import requests with `asLatestVersion=false`, there will be no changes to the versions. + +However, if you _do_ want to have an **earlier** version, that was superseded by another import, to become the +**latest** version again, you can pass `asLatestVersion=true` with your import request. In the above example of +versions `1-2-3-4` being in the repository, if you were to import the contents of `2` again with `asLatestVersion=true`, +the versions in the repository will look like `1-2-3-4-2` designating version `2` as the latest version again. From 1e7503d0145c80e31a6ee91025b7d982f5fab3ee Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 13:10:33 +0200 Subject: [PATCH 07/16] Fix typeName/name mismatch --- .../task/TestFindFileContentsByExternalId.md | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.md index be535e47..c56c1aa4 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.md @@ -2,35 +2,37 @@ The contents of the latest file version can be retrieved with the `find` task, an external ID and file type. -To retrieve file metadata we first create: +To retrieve file metadata we first create: - - a document with external ID: [`test-external-id`](- "#externalId"); - - with a file of type: [`text`](- "#fileType"); - - and a file version containing: [`example content`](- "#contents"); +- a document with external ID: [`test-external-id`](- "#externalId"); +- with a file of type: [`text`](- "#fileType"); +- and a file version containing: [`example content`](- "#contents"); [ ](- "#docId=createDocument(#externalId)") [ ](- "#fileId=createFile(#docId)") [ ](- "createVersion(#fileId, #contents)") ### Retrieve file contents -When retrieving the contents of a file with a `GET` to [`/task/find/{externalId}/file/contents?type={name}`](- "#findEndpoint") - - where `{externalId}` is [ ](- "c:echo=#externalId"); - - where `{typeName}` is [ ](- "c:echo=#fileType"); +When retrieving the contents of a file with a `GET` +to [`/task/find/{externalId}/file/contents?type={typeName}`](- "#findEndpoint") + +- where `{externalId}` is [ ](- "c:echo=#externalId"); +- where `{typeName}` is [ ](- "c:echo=#fileType"); [ ](- "#retrieveResult=retrieve(#findEndpoint, #externalId, #fileType)") Then: - - The response status should be: [200](- "?=#retrieveResult.status"); - - Contents should be: [`example content`](- "?=#retrieveResult.body"); - - Headers should contain link to [version history](- "?=#retrieveResult.versionHistory"); - - Headers should contain link to [parent resource](- "?=#retrieveResult.parent"); - - Headers should contain link to [type resource](- "?=#retrieveResult.type"); - - Link headers: +- The response status should be: [200](- "?=#retrieveResult.status"); +- Contents should be: [`example content`](- "?=#retrieveResult.body"); +- Headers should contain link to [version history](- "?=#retrieveResult.versionHistory"); +- Headers should contain link to [parent resource](- "?=#retrieveResult.parent"); +- Headers should contain link to [type resource](- "?=#retrieveResult.type"); +- Link headers: [ ](- "ext:embed=#retrieveResult.headers") - - Full response: +- Full response: [ ](- "ext:embed=#retrieveResult.body") From ad6fd443ec97a49da4f8c6cee8ddf08eced0cf69 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 13:30:45 +0200 Subject: [PATCH 08/16] Fix typeName/name mismatch --- .../textrepo/task/TestFindFileContentsByExternalId.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java index 1b175963..b618bf3f 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java @@ -6,7 +6,6 @@ import static java.util.Map.of; import static nl.knaw.huc.textrepo.util.TestUtils.asCodeBlock; -import static nl.knaw.huc.textrepo.util.TestUtils.asPrettyJson; import static nl.knaw.huc.textrepo.util.TestUtils.replaceInUrlAndQueryParams; public class TestFindFileContentsByExternalId extends AbstractConcordionTest { @@ -35,7 +34,8 @@ public static class RetrieveResult { public RetrieveResult retrieve(String endpoint, String externalId, String fileType) { final var response = client - .target(replaceInUrlAndQueryParams(endpoint, of("{externalId}", externalId, "{name}", fileType))) + .target(replaceInUrlAndQueryParams(endpoint, + of("{externalId}", externalId, "{typeName}", fileType))) .request() .get(); @@ -46,7 +46,9 @@ public RetrieveResult retrieve(String endpoint, String externalId, String fileTy result.headers = ""; var links = response.getHeaders().get("Link"); links.forEach((l) -> { - System.out.println("link:" + l.toString());result.headers += asHeaderLink(l.toString());}); + System.out.println("link:" + l.toString()); + result.headers += asHeaderLink(l.toString()); + }); result.versionHistory = links .stream() From 819270b5a12b1da6d40fad2f4284924394b93f63 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 14:24:13 +0200 Subject: [PATCH 09/16] Eliminate multiple copies of asHeaderLink --- .../TestFindDocumentMetadataByExternalId.java | 6 +----- .../task/TestFindFileContentsByExternalId.java | 5 +---- .../task/TestFindFileMetadataByExternalId.java | 6 +----- .../java/nl/knaw/huc/textrepo/util/TestUtils.java | 15 ++++----------- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindDocumentMetadataByExternalId.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindDocumentMetadataByExternalId.java index bef8ab05..4e2d9ce4 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindDocumentMetadataByExternalId.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindDocumentMetadataByExternalId.java @@ -2,13 +2,12 @@ import nl.knaw.huc.textrepo.AbstractConcordionTest; import nl.knaw.huc.textrepo.util.RestUtils; -import org.apache.commons.text.StringEscapeUtils; import static javax.ws.rs.client.Entity.entity; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static nl.knaw.huc.textrepo.Config.HOST; import static nl.knaw.huc.textrepo.util.TestUtils.asCodeBlock; +import static nl.knaw.huc.textrepo.util.TestUtils.asHeaderLink; import static nl.knaw.huc.textrepo.util.TestUtils.asPrettyJson; import static nl.knaw.huc.textrepo.util.TestUtils.replaceUrlParams; @@ -70,7 +69,4 @@ public RetrieveResult retrieve(Object endpoint, Object docId, Object externalId, return result; } - private String asHeaderLink(String header) { - return StringEscapeUtils.escapeHtml4("Link: " + header + "\n"); - } } diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java index b618bf3f..c28fd886 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileContentsByExternalId.java @@ -2,10 +2,10 @@ import nl.knaw.huc.textrepo.AbstractConcordionTest; import nl.knaw.huc.textrepo.util.RestUtils; -import org.apache.commons.text.StringEscapeUtils; import static java.util.Map.of; import static nl.knaw.huc.textrepo.util.TestUtils.asCodeBlock; +import static nl.knaw.huc.textrepo.util.TestUtils.asHeaderLink; import static nl.knaw.huc.textrepo.util.TestUtils.replaceInUrlAndQueryParams; public class TestFindFileContentsByExternalId extends AbstractConcordionTest { @@ -76,7 +76,4 @@ public RetrieveResult retrieve(String endpoint, String externalId, String fileTy return result; } - private String asHeaderLink(String header) { - return StringEscapeUtils.escapeHtml4("Link: " + header + "\n"); - } } diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileMetadataByExternalId.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileMetadataByExternalId.java index 00d9fa0d..fe6dbaa3 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileMetadataByExternalId.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestFindFileMetadataByExternalId.java @@ -2,14 +2,13 @@ import nl.knaw.huc.textrepo.AbstractConcordionTest; import nl.knaw.huc.textrepo.util.RestUtils; -import org.apache.commons.text.StringEscapeUtils; import static java.util.Map.of; import static javax.ws.rs.client.Entity.entity; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static javax.ws.rs.core.MediaType.TEXT_PLAIN; import static nl.knaw.huc.textrepo.Config.HOST; import static nl.knaw.huc.textrepo.util.TestUtils.asCodeBlock; +import static nl.knaw.huc.textrepo.util.TestUtils.asHeaderLink; import static nl.knaw.huc.textrepo.util.TestUtils.asPrettyJson; import static nl.knaw.huc.textrepo.util.TestUtils.replaceInUrlAndQueryParams; @@ -83,7 +82,4 @@ public RetrieveResult retrieve(String endpoint, String externalId, String fileTy return result; } - private String asHeaderLink(String header) { - return StringEscapeUtils.escapeHtml4("Link: " + header + "\n"); - } } diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/util/TestUtils.java b/concordion/src/test/java/nl/knaw/huc/textrepo/util/TestUtils.java index 767913e5..f7fd0d00 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/util/TestUtils.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/util/TestUtils.java @@ -2,12 +2,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.text.StringSubstitutor; +import org.apache.commons.text.StringEscapeUtils; import org.glassfish.jersey.client.JerseyClientBuilder; -import org.glassfish.jersey.media.multipart.FormDataBodyPart; -import org.glassfish.jersey.media.multipart.FormDataContentDisposition; -import org.glassfish.jersey.media.multipart.FormDataMultiPart; -import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,18 +12,12 @@ import javax.ws.rs.core.UriBuilder; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; import java.util.Map; -import java.util.Optional; import java.util.UUID; -import java.util.regex.Pattern; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static javax.ws.rs.client.Entity.entity; -import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; import static nl.knaw.huc.textrepo.Config.HOST; -import static nl.knaw.huc.textrepo.Config.TEXT_TYPE; public class TestUtils { @@ -99,4 +89,7 @@ public static URI replaceInUrlAndQueryParams(Object endpoint, Map Date: Wed, 31 Mar 2021 14:26:17 +0200 Subject: [PATCH 10/16] Test importing document 404 and 201 casees --- .../huc/textrepo/task/TestImportDocument.java | 77 ++++++++++++++++++- .../huc/textrepo/task/TestImportDocument.md | 50 +++++++++++- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java index d80ee897..a8081fc5 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java @@ -1,9 +1,78 @@ package nl.knaw.huc.textrepo.task; -import org.concordion.integration.junit4.ConcordionRunner; -import org.junit.runner.RunWith; +import nl.knaw.huc.textrepo.AbstractConcordionTest; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; -@RunWith(ConcordionRunner.class) -public class TestImportDocument { +import javax.ws.rs.core.Response; +import java.util.Optional; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Map.of; +import static javax.ws.rs.client.Entity.entity; +import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; +import static nl.knaw.huc.textrepo.util.TestUtils.asCodeBlock; +import static nl.knaw.huc.textrepo.util.TestUtils.asHeaderLink; +import static nl.knaw.huc.textrepo.util.TestUtils.asPrettyJson; +import static nl.knaw.huc.textrepo.util.TestUtils.replaceInUrlAndQueryParams; + +public class TestImportDocument extends AbstractConcordionTest { + public static class ImportResult { + public int status; + public String headers; + public String body; + } + + public ImportResult retrieve(String endpoint, + String externalId, + String typeName, + String content + ) { + final var response = importDocument(endpoint, externalId, content.getBytes(), typeName); + + final var result = new ImportResult(); + result.status = response.getStatus(); + + final var body = response.readEntity(String.class); + result.body = asPrettyJson(body.equals("") ? " " : body); + + result.headers = ""; + Optional.ofNullable(response.getHeaders().get("Link")) + .ifPresent(links -> links.forEach(l -> { + System.out.println("link:" + l.toString()); + result.headers += asHeaderLink(l.toString()); + })); + result.headers = asCodeBlock(result.headers); + + return result; + } + + private Response importDocument(String endpoint, String externalId, byte[] content, String typeName) { + var contentDisposition = FormDataContentDisposition + .name("contents") + .fileName(externalId + ".txt") + .size(content.length) + .build(); + + final var multiPart = new FormDataMultiPart() + .bodyPart(new FormDataBodyPart( + contentDisposition, + new String(content, UTF_8), + APPLICATION_OCTET_STREAM_TYPE) + ); + + var importUri = replaceInUrlAndQueryParams(endpoint, of( + "{externalId}", externalId, + "{typeName}", typeName)); + var request = client + .register(MultiPartFeature.class) + .target(importUri) + .request(); + + var entity = entity(multiPart, multiPart.getMediaType()); + return request.post(entity); + + } } diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 66e3301d..6c7d4bfa 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -4,10 +4,10 @@ File contents can be directly uploaded to a document, referenced by its `externa version of a `typeName` typed file for that document. If the document with `externalId` does not yet exist, you can have the repository create it during import by passing -`allowNewDocuments=true`. +`allowNewDocument=true`. -Otherwise, using `allowNewDocuments=false`, the import will verify that you are uploading to an already existing -document, or deny the request. Note that this is also the default if you leave `allowNewDocuments` out. +Otherwise, using `allowNewDocument=false`, the import will verify that you are uploading to an already existing +document, or deny the request. Note that this is also the default if you leave `allowNewDocument` out. If you offer the same contents of a version already registered for this document during another import, by default (`asLatestVersion=false`), no new version will be created as it already exists for that particular document and @@ -18,3 +18,47 @@ However, if you _do_ want to have an **earlier** version, that was superseded by **latest** version again, you can pass `asLatestVersion=true` with your import request. In the above example of versions `1-2-3-4` being in the repository, if you were to import the contents of `2` again with `asLatestVersion=true`, the versions in the repository will look like `1-2-3-4-2` designating version `2` as the latest version again. + +## Importing contents for a document that does not exist yet + +We try to import a document + +- with external ID: [`document_1234`](- "#externalId"); +- using type: [`text`](- "#typeName"); +- uploading a file containing [`example content`](- "#contents"); + +### 1. Refusing new documents to be made + +When we `POST` the file to +[`/task/import/documents/dochment_1234/text`](- "#importEndpoint") + +(or equivalently by explicitly appending the query param `?allowNewDocument=false`) + +[ ](- "#result=retrieve(#importEndpoint, #externalId, #typeName, #contents)") + +Then: + +- The response status should be: [404](- "?=#result.status"); +- Full response: + +[ ](- "ext:embed=#result.body") + +### 2. Allowing new documents to be made + +However, when we `POST` our file to + +[`/task/import/documents/document_1234/text?allowNewDocument=true`](- "#importEndpoint") + +[ ](- "#result=retrieve(#importEndpoint, #externalId, #typeName, #contents)") + +Then: + +- The response status should be: [201](- "?=#result.status"); +- Link headers: + +[ ](- "ext:embed=#result.headers") + +- Full response: + +[ ](- "ext:embed=#result.body") + From 3c65ad9915ef8e40573faff36e2ad9247b5ec8ff Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 31 Mar 2021 16:38:15 +0200 Subject: [PATCH 11/16] Tix fypo in dochment --- .../resources/nl/knaw/huc/textrepo/task/TestImportDocument.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 6c7d4bfa..9e8b68de 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -30,7 +30,7 @@ We try to import a document ### 1. Refusing new documents to be made When we `POST` the file to -[`/task/import/documents/dochment_1234/text`](- "#importEndpoint") +[`/task/import/documents/document_1234/text`](- "#importEndpoint") (or equivalently by explicitly appending the query param `?allowNewDocument=false`) From 527a21403410d97392a9c8a34cbbf9fd3cdb3beb Mon Sep 17 00:00:00 2001 From: HDJ Date: Tue, 6 Apr 2021 12:28:25 +0200 Subject: [PATCH 12/16] Add test for 'asLatestVersion' cases --- .../huc/textrepo/task/TestImportDocument.md | 89 +++++++++++++++++-- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 9e8b68de..34606d1c 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -19,22 +19,22 @@ However, if you _do_ want to have an **earlier** version, that was superseded by versions `1-2-3-4` being in the repository, if you were to import the contents of `2` again with `asLatestVersion=true`, the versions in the repository will look like `1-2-3-4-2` designating version `2` as the latest version again. -## Importing contents for a document that does not exist yet +## Importing contents for a document that does not exist (yet) We try to import a document - with external ID: [`document_1234`](- "#externalId"); - using type: [`text`](- "#typeName"); -- uploading a file containing [`example content`](- "#contents"); +- uploading a file containing [`example content`](- "#originalContents"); -### 1. Refusing new documents to be made +### 1. Using `allowNewDocument=false` (default) When we `POST` the file to [`/task/import/documents/document_1234/text`](- "#importEndpoint") (or equivalently by explicitly appending the query param `?allowNewDocument=false`) -[ ](- "#result=retrieve(#importEndpoint, #externalId, #typeName, #contents)") +[ ](- "#result=retrieve(#importEndpoint, #externalId, #typeName, #originalContents)") Then: @@ -43,13 +43,15 @@ Then: [ ](- "ext:embed=#result.body") -### 2. Allowing new documents to be made +This helps prevent you from accidentally create new documents when you know the document should already exist. + +### 2. Using `allowNewDocument=true` However, when we `POST` our file to -[`/task/import/documents/document_1234/text?allowNewDocument=true`](- "#importEndpoint") +[`/task/import/documents/document_1234/text?allowNewDocument=true`](- "#importEndpoint2") -[ ](- "#result=retrieve(#importEndpoint, #externalId, #typeName, #contents)") +[ ](- "#result=retrieve(#importEndpoint2, #externalId, #typeName, #originalContents)") Then: @@ -62,3 +64,76 @@ Then: [ ](- "ext:embed=#result.body") +## Importing a version which may already be in the version history + +### 1. Using `asLatestVersion=false (default)` + +Now that `document_1234` has a version created in the test above, when we `POST` the same file +***again*** to + +[`/task/import/documents/document_1234/text`](- "#importEndpoint3") + +[ ](- "#result=retrieve(#importEndpoint3, #externalId, #typeName, #originalContents)") + +Then: + +- The response status should be: [200](- "?=#result.status"); + +And we get the same headers and body. + +- Link headers: + +[ ](- "ext:embed=#result.headers") + +- Full response: + +[ ](- "ext:embed=#result.body") + +This means that you can upload the same contents however often you like, no new version(s) will be created for contents +already present for a document. + +### 2. Using `asLatestVersion=true` when uploaded contents is already latest version + +When we `POST` our file again to + +[`/task/import/documents/document_1234/text?asLatestVersion=true`](- "#importEndpoint4") + +[ ](- "#result=retrieve(#importEndpoint4, #externalId, #typeName, #originalContents)") + +Then: + +- The response status should be: [200](- "?=#result.status"); + +Nothing was created, because the current version is ***already the latest version***. + +- Link headers: + +[ ](- "ext:embed=#result.headers") + +- Full response: + +[ ](- "ext:embed=#result.body") + +### 3. Using `asLatestVersion=true` when uploaded contents is an older version + +When we `POST` some [`other content`](- "#otherContents") to + +[`/task/import/documents/document_1234/text`](- "#importEndpoint5") + +[ ](- "#result=retrieve(#importEndpoint5, #externalId, #typeName, #otherContents)") + +Then: + +- The response status should be: [201](- "?=#result.status") + +And if we then `POST` our original contents again to + +[`/task/import/documents/document_1234/text?asLatestVersion=true`](- "#importEndpoint6") + +[ ](- "#result=retrieve(#importEndpoint6, #externalId, #typeName, #originalContents)") + +Then, finally,: + +- The reponse status should be: [201](- "?=#result.status") + +because a new version was created for our older, existing, contents. From 2204abf82251ce6a5fa668b5921de5b1ba864591 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 7 Apr 2021 10:27:49 +0200 Subject: [PATCH 13/16] Add more thorough Link header testing --- .../huc/textrepo/task/TestImportDocument.java | 33 ++++++++++++++++--- .../huc/textrepo/task/TestImportDocument.md | 22 ++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java index a8081fc5..448b10ea 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java @@ -7,6 +7,8 @@ import org.glassfish.jersey.media.multipart.MultiPartFeature; import javax.ws.rs.core.Response; +import java.util.Collections; +import java.util.List; import java.util.Optional; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,6 +23,10 @@ public class TestImportDocument extends AbstractConcordionTest { public static class ImportResult { public int status; + public String versionLink; + public String fileLink; + public String documentLink; + public String contentsLink; public String headers; public String body; } @@ -39,16 +45,33 @@ public ImportResult retrieve(String endpoint, result.body = asPrettyJson(body.equals("") ? " " : body); result.headers = ""; - Optional.ofNullable(response.getHeaders().get("Link")) - .ifPresent(links -> links.forEach(l -> { - System.out.println("link:" + l.toString()); - result.headers += asHeaderLink(l.toString()); - })); + final var links = Optional.ofNullable(response.getHeaders().get("Link")) + .orElse(Collections.emptyList()); + links.forEach(l -> result.headers += asHeaderLink(l.toString())); result.headers = asCodeBlock(result.headers); + result.versionLink = findLink(links, "version"); + result.fileLink = findLink(links, "file"); + result.documentLink = findLink(links, "document"); + result.contentsLink = findLink(links, "contents"); + return result; } + private String findLink(List links, String type) { + return links.stream() + .map(Object::toString) + .filter(l -> isRelationshipLinkTo(l, type)) + .findFirst() + .map(l -> "restful relationship link to " + type) + .orElse("header link to " + type + " missing"); + } + + private boolean isRelationshipLinkTo(String suspect, String type) { + return suspect.contains("/rest/" + type) + && suspect.endsWith("rel=\"" + type + "\""); + } + private Response importDocument(String endpoint, String externalId, byte[] content, String typeName) { var contentDisposition = FormDataContentDisposition .name("contents") diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 34606d1c..09bdbf9e 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -56,6 +56,11 @@ However, when we `POST` our file to Then: - The response status should be: [201](- "?=#result.status"); +- Headers should contain [restful relationship link to version](- "?=#result.versionLink"); +- Headers should contain [restful relationship link to file](- "?=#result.fileLink"); +- Headers should contain [restful relationship link to document](- "?=#result.documentLink"); +- Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); + - Link headers: [ ](- "ext:embed=#result.headers") @@ -81,6 +86,11 @@ Then: And we get the same headers and body. +- Headers should contain [restful relationship link to version](- "?=#result.versionLink"); +- Headers should contain [restful relationship link to file](- "?=#result.fileLink"); +- Headers should contain [restful relationship link to document](- "?=#result.documentLink"); +- Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); + - Link headers: [ ](- "ext:embed=#result.headers") @@ -106,6 +116,11 @@ Then: Nothing was created, because the current version is ***already the latest version***. +- Headers should contain [restful relationship link to version](- "?=#result.versionLink"); +- Headers should contain [restful relationship link to file](- "?=#result.fileLink"); +- Headers should contain [restful relationship link to document](- "?=#result.documentLink"); +- Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); + - Link headers: [ ](- "ext:embed=#result.headers") @@ -132,8 +147,13 @@ And if we then `POST` our original contents again to [ ](- "#result=retrieve(#importEndpoint6, #externalId, #typeName, #originalContents)") -Then, finally,: +Then, finally, we get: - The reponse status should be: [201](- "?=#result.status") because a new version was created for our older, existing, contents. + +- Headers should contain [restful relationship link to version](- "?=#result.versionLink"); +- Headers should contain [restful relationship link to file](- "?=#result.fileLink"); +- Headers should contain [restful relationship link to document](- "?=#result.documentLink"); +- Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); From 3ebe203c5ad325442821c5086b17f104e8fbe46e Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 7 Apr 2021 10:28:25 +0200 Subject: [PATCH 14/16] fix typo --- .../resources/nl/knaw/huc/textrepo/task/TestImportDocument.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 09bdbf9e..4b2b93e2 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -43,7 +43,7 @@ Then: [ ](- "ext:embed=#result.body") -This helps prevent you from accidentally create new documents when you know the document should already exist. +This helps prevent you from accidentally creating new documents when you know the document should already exist. ### 2. Using `allowNewDocument=true` From dd33836a398d2aabd6760b4776ca97ac5d0bd38c Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 7 Apr 2021 11:35:52 +0200 Subject: [PATCH 15/16] Add more thorough body (json) testing --- .../huc/textrepo/task/TestImportDocument.java | 51 ++++++++++++++++++- .../huc/textrepo/task/TestImportDocument.md | 32 ++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java index 448b10ea..e4023790 100644 --- a/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java +++ b/concordion/src/test/java/nl/knaw/huc/textrepo/task/TestImportDocument.java @@ -1,5 +1,6 @@ package nl.knaw.huc.textrepo.task; +import com.jayway.jsonpath.DocumentContext; import nl.knaw.huc.textrepo.AbstractConcordionTest; import org.glassfish.jersey.media.multipart.FormDataBodyPart; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; @@ -10,6 +11,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Map.of; @@ -23,12 +25,20 @@ public class TestImportDocument extends AbstractConcordionTest { public static class ImportResult { public int status; + + public String headers; public String versionLink; public String fileLink; public String documentLink; public String contentsLink; - public String headers; + public String body; + public String fileId; + public String documentId; + public String versionId; + public String contentsSha; + + public String isNewVersion; } public ImportResult retrieve(String endpoint, @@ -55,9 +65,48 @@ public ImportResult retrieve(String endpoint, result.documentLink = findLink(links, "document"); result.contentsLink = findLink(links, "contents"); + final var json = jsonPath.parse(body); + result.fileId = findValidUUID(json, "fileId"); + result.documentId = findValidUUID(json, "documentId"); + result.versionId = findValidUUID(json, "versionId"); + result.contentsSha = findValidSha(json, "contentsSha"); + result.isNewVersion = findNewVersionInfo(json, "newVersion"); + return result; } + private String findValidUUID(DocumentContext json, String field) { + final String str = json.read("$." + field); + try { + final var uuid = UUID.fromString(str); + return "valid " + field; + } catch (Exception e) { + return "invalid " + field + " (not a UUID)"; + } + } + + private String findValidSha(DocumentContext json, String field) { + final String str = json.read("$." + field); + if (str == null) { + return "missing " + field; + } + if (str.matches("[0-9a-f]{56}")) { + return "valid " + field; + } + return "invalid " + field + " (not a SHA224)"; + } + + private String findNewVersionInfo(DocumentContext json, String field) { + final Boolean isNewVersion = json.read("$." + field); + if (isNewVersion == null) { + return "missing " + field; + } + if (isNewVersion) { + return "a new version was created"; + } + return "no new version was created"; + } + private String findLink(List links, String type) { return links.stream() .map(Object::toString) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index 4b2b93e2..c461d5b8 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -60,11 +60,15 @@ Then: - Headers should contain [restful relationship link to file](- "?=#result.fileLink"); - Headers should contain [restful relationship link to document](- "?=#result.documentLink"); - Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); - - Link headers: [ ](- "ext:embed=#result.headers") +- Body should contain [valid fileId](- "?=#result.fileId"); +- Body should contain [valid documentId](- "?=#result.documentId"); +- Body should contain [valid versionId](- "?=#result.versionId"); +- Body should contain [valid contentsSha](- "?=#result.contentsSha"); +- Body should indicate [a new version was created](- "?=#result.isNewVersion"); - Full response: [ ](- "ext:embed=#result.body") @@ -90,11 +94,15 @@ And we get the same headers and body. - Headers should contain [restful relationship link to file](- "?=#result.fileLink"); - Headers should contain [restful relationship link to document](- "?=#result.documentLink"); - Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); - - Link headers: [ ](- "ext:embed=#result.headers") +- Body should contain [valid fileId](- "?=#result.fileId"); +- Body should contain [valid documentId](- "?=#result.documentId"); +- Body should contain [valid versionId](- "?=#result.versionId"); +- Body should contain [valid contentsSha](- "?=#result.contentsSha"); +- Body should indicate [no new version was created](- "?=#result.isNewVersion"); - Full response: [ ](- "ext:embed=#result.body") @@ -120,11 +128,15 @@ Nothing was created, because the current version is ***already the latest versio - Headers should contain [restful relationship link to file](- "?=#result.fileLink"); - Headers should contain [restful relationship link to document](- "?=#result.documentLink"); - Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); - - Link headers: [ ](- "ext:embed=#result.headers") +- Body should contain [valid fileId](- "?=#result.fileId"); +- Body should contain [valid documentId](- "?=#result.documentId"); +- Body should contain [valid versionId](- "?=#result.versionId"); +- Body should contain [valid contentsSha](- "?=#result.contentsSha"); +- Body should indicate [no new version was created](- "?=#result.isNewVersion"); - Full response: [ ](- "ext:embed=#result.body") @@ -141,6 +153,8 @@ Then: - The response status should be: [201](- "?=#result.status") +for brevity, we omit testing headers and body for this intermediate version. + And if we then `POST` our original contents again to [`/task/import/documents/document_1234/text?asLatestVersion=true`](- "#importEndpoint6") @@ -157,3 +171,15 @@ because a new version was created for our older, existing, contents. - Headers should contain [restful relationship link to file](- "?=#result.fileLink"); - Headers should contain [restful relationship link to document](- "?=#result.documentLink"); - Headers should contain [restful relationship link to contents](- "?=#result.contentsLink"); +- Link headers: + +[ ](- "ext:embed=#result.headers") + +- Body should contain [valid fileId](- "?=#result.fileId"); +- Body should contain [valid documentId](- "?=#result.documentId"); +- Body should contain [valid versionId](- "?=#result.versionId"); +- Body should contain [valid contentsSha](- "?=#result.contentsSha"); +- Body should indicate [a new version was created](- "?=#result.isNewVersion"); +- Full response: + +[ ](- "ext:embed=#result.body") From a6c8a11003fdef0e110b7cfad829fcf9e7c92da7 Mon Sep 17 00:00:00 2001 From: HDJ Date: Wed, 7 Apr 2021 13:32:34 +0200 Subject: [PATCH 16/16] Improve readability of some prolix paragraphs --- .../huc/textrepo/task/TestImportDocument.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md index c461d5b8..69d2ad20 100644 --- a/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md +++ b/concordion/src/test/resources/nl/knaw/huc/textrepo/task/TestImportDocument.md @@ -3,21 +3,21 @@ File contents can be directly uploaded to a document, referenced by its `externalId`. This yields a (possibly new) version of a `typeName` typed file for that document. -If the document with `externalId` does not yet exist, you can have the repository create it during import by passing +If the document with `externalId` does not exist yet, you can have the repository create it by passing `allowNewDocument=true`. -Otherwise, using `allowNewDocument=false`, the import will verify that you are uploading to an already existing +Otherwise, using `allowNewDocument=false`, the import task will verify that you are uploading to an already existing document, or deny the request. Note that this is also the default if you leave `allowNewDocument` out. -If you offer the same contents of a version already registered for this document during another import, by -default (`asLatestVersion=false`), no new version will be created as it already exists for that particular document and -type. This _idempotent_ behavior ensures that if, e.g., versions `1-2-3-4` have been imported so far, and you offer the -contents of `2` in subsequent import requests with `asLatestVersion=false`, there will be no changes to the versions. +If you upload the same contents of a version already registered for a particular document and type, no new version will +be created by default (`asLatestVersion=false`). This _idempotent_ behavior ensures that if, e.g., versions `1-2-3-4` +have been imported, and you offer the contents of `2` with `asLatestVersion=false`, there will be no changes to the +version trail. However, if you _do_ want to have an **earlier** version, that was superseded by another import, to become the -**latest** version again, you can pass `asLatestVersion=true` with your import request. In the above example of -versions `1-2-3-4` being in the repository, if you were to import the contents of `2` again with `asLatestVersion=true`, -the versions in the repository will look like `1-2-3-4-2` designating version `2` as the latest version again. +**latest** version again, you can pass `asLatestVersion=true`. In the above example of versions `1-2-3-4` being in the +repository, if you were to import the contents of `2` with `asLatestVersion=true`, the version trail will be +`1-2-3-4-2` designating `2` as the latest version. ## Importing contents for a document that does not exist (yet)