diff --git a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/ExtSubmissionClient.kt b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/ExtSubmissionClient.kt index e55e33dc75..d844678e78 100644 --- a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/ExtSubmissionClient.kt +++ b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/ExtSubmissionClient.kt @@ -1,8 +1,8 @@ package ac.uk.ebi.biostd.client.api import ac.uk.ebi.biostd.client.dto.ExtPageQuery -import ac.uk.ebi.biostd.client.extensions.linkedMultiValueMapOf import ac.uk.ebi.biostd.client.integration.web.ExtSubmissionOperations +import ebi.ac.uk.commons.http.builder.linkedMultiValueMapOf import ebi.ac.uk.commons.http.ext.RequestParams import ebi.ac.uk.commons.http.ext.getForObject import ebi.ac.uk.commons.http.ext.post diff --git a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/StatsClient.kt b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/StatsClient.kt index 4256ae8ddc..4ec0c5575d 100644 --- a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/StatsClient.kt +++ b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/api/StatsClient.kt @@ -1,8 +1,8 @@ package ac.uk.ebi.biostd.client.api -import ac.uk.ebi.biostd.client.extensions.httpHeadersOf -import ac.uk.ebi.biostd.client.extensions.linkedMultiValueMapOf import ac.uk.ebi.biostd.client.integration.web.StatsOperations +import ebi.ac.uk.commons.http.builder.httpHeadersOf +import ebi.ac.uk.commons.http.builder.linkedMultiValueMapOf import ebi.ac.uk.commons.http.ext.RequestParams import ebi.ac.uk.commons.http.ext.getForObject import ebi.ac.uk.commons.http.ext.postForObject diff --git a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/common/BioWebClientRequestBuilder.kt b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/common/BioWebClientRequestBuilder.kt index dfb5887ba4..009ebf0e84 100644 --- a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/common/BioWebClientRequestBuilder.kt +++ b/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/common/BioWebClientRequestBuilder.kt @@ -1,8 +1,8 @@ package ac.uk.ebi.biostd.client.common -import ac.uk.ebi.biostd.client.extensions.linkedMultiValueMapOf import ac.uk.ebi.biostd.client.integration.web.SubmissionFilesConfig import ebi.ac.uk.api.STORAGE_MODE +import ebi.ac.uk.commons.http.builder.linkedMultiValueMapOf import ebi.ac.uk.model.constants.FILES import ebi.ac.uk.model.constants.PREFERRED_SOURCES import ebi.ac.uk.model.constants.SUBMISSION diff --git a/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/constants/Fields.kt b/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/constants/Fields.kt index 37cc5fc6a2..82ba72541e 100644 --- a/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/constants/Fields.kt +++ b/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/constants/Fields.kt @@ -46,6 +46,7 @@ enum class SubFields(override val value: String) : Fields { TITLE("Title"), ROOT_PATH("RootPath"), PUBLIC_ACCESS_TAG("Public"), + DOI("DOI"), RELEASE_DATE("ReleaseDate"), RELEASE_TIME("rtime"), diff --git a/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/extensions/SubExt.kt b/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/extensions/SubExt.kt index b9aa269aac..43de0b7b99 100644 --- a/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/extensions/SubExt.kt +++ b/commons/commons-bio/src/main/kotlin/ebi/ac/uk/model/extensions/SubExt.kt @@ -88,6 +88,15 @@ var Submission.title: String? value?.let { this[SubFields.TITLE] = it } } +/** + * Obtain the submission DOI attribute if present. + */ +var Submission.doi: String? + get() = find(SubFields.DOI) + set(value) { + value?.let { this[SubFields.DOI] = it } + } + /** * Obtain the submission accession number template if present. */ diff --git a/commons/commons-bio/src/test/kotlin/ebi/ac/uk/model/extensions/SubExtTest.kt b/commons/commons-bio/src/test/kotlin/ebi/ac/uk/model/extensions/SubExtTest.kt index 181f0a50cf..8d4763cd97 100644 --- a/commons/commons-bio/src/test/kotlin/ebi/ac/uk/model/extensions/SubExtTest.kt +++ b/commons/commons-bio/src/test/kotlin/ebi/ac/uk/model/extensions/SubExtTest.kt @@ -92,6 +92,15 @@ class SubExtTest { assertExtendedAttribute(submission, SubFields.TITLE, "Title") } + @Test + fun doi() { + val submission = submission("ABC-123") {} + submission.doi = "10.6019/ABC-123" + + assertThat(submission.doi).isEqualTo("10.6019/ABC-123") + assertExtendedAttribute(submission, SubFields.DOI, "10.6019/ABC-123") + } + @Test fun `root path`() { val submission = submission("ABC-123") {} diff --git a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/extensions/Common.kt b/commons/commons-http/src/main/kotlin/ebi/ac/uk/commons/http/builder/HttpParamBuilders.kt similarity index 94% rename from client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/extensions/Common.kt rename to commons/commons-http/src/main/kotlin/ebi/ac/uk/commons/http/builder/HttpParamBuilders.kt index 49da7dee9c..248071bc4f 100644 --- a/client/bio-webclient/src/main/kotlin/ac/uk/ebi/biostd/client/extensions/Common.kt +++ b/commons/commons-http/src/main/kotlin/ebi/ac/uk/commons/http/builder/HttpParamBuilders.kt @@ -1,4 +1,4 @@ -package ac.uk.ebi.biostd.client.extensions +package ebi.ac.uk.commons.http.builder import org.springframework.http.HttpHeaders import org.springframework.util.LinkedMultiValueMap diff --git a/commons/commons-model-extended-mapping/src/main/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapper.kt b/commons/commons-model-extended-mapping/src/main/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapper.kt index bf07da4765..462c99a832 100644 --- a/commons/commons-model-extended-mapping/src/main/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapper.kt +++ b/commons/commons-model-extended-mapping/src/main/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapper.kt @@ -5,6 +5,7 @@ import ebi.ac.uk.model.Attribute import ebi.ac.uk.model.Submission import ebi.ac.uk.model.constants.SubFields.ATTACH_TO import ebi.ac.uk.model.constants.SubFields.COLLECTION_VALIDATOR +import ebi.ac.uk.model.constants.SubFields.DOI import ebi.ac.uk.model.constants.SubFields.PUBLIC_ACCESS_TAG import ebi.ac.uk.model.constants.SubFields.RELEASE_DATE import ebi.ac.uk.model.constants.SubFields.ROOT_PATH @@ -25,6 +26,7 @@ class ToSubmissionMapper(private val toSectionMapper: ToSectionMapper) { private fun ExtSubmission.simpleAttributes(): List = buildSet { addAll(attributes.filter { it.name != COLLECTION_VALIDATOR.value }.map { it.toAttribute() }) title?.let { add(Attribute(TITLE.value, it)) } + doi?.let { add(Attribute(DOI.value, it)) } releaseTime?.let { add(Attribute(RELEASE_DATE.value, it.toLocalDate().toString())) } rootPath?.let { add(Attribute(ROOT_PATH.value, it)) } addAll(collections.filter { it.accNo != PUBLIC_ACCESS_TAG.value }.map { Attribute(ATTACH_TO.value, it.accNo) }) diff --git a/commons/commons-model-extended-mapping/src/test/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapperTest.kt b/commons/commons-model-extended-mapping/src/test/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapperTest.kt index 5b77e70f2c..84922e21ac 100644 --- a/commons/commons-model-extended-mapping/src/test/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapperTest.kt +++ b/commons/commons-model-extended-mapping/src/test/kotlin/ebi/ac/uk/extended/mapping/to/ToSubmissionMapperTest.kt @@ -6,6 +6,7 @@ import ebi.ac.uk.model.Attribute import ebi.ac.uk.model.Section import ebi.ac.uk.model.Submission import ebi.ac.uk.model.extensions.attachTo +import ebi.ac.uk.model.extensions.doi import ebi.ac.uk.model.extensions.releaseDate import ebi.ac.uk.model.extensions.rootPath import ebi.ac.uk.model.extensions.title @@ -24,6 +25,7 @@ class ToSubmissionMapperTest( @MockK val toSectionMapper: ToSectionMapper, ) { private val extSubmission = basicExtSubmission.copy( + doi = "10.983/S-TEST123", rootPath = "/a/root/path", collections = listOf(ExtCollection("BioImages")), releaseTime = OffsetDateTime.of(2019, 9, 21, 0, 0, 0, 0, UTC), @@ -46,18 +48,11 @@ class ToSubmissionMapperTest( assertSubmissionAttributes(submission) } - private fun assertSection(submission: Submission) { - assertThat(submission.section.type).isEqualTo("Study") - assertThat(submission.section.attributes).isEmpty() - assertThat(submission.section.files).isEmpty() - assertThat(submission.section.links).isEmpty() - assertThat(submission.section.sections).isEmpty() - } - private fun assertSubmissionAttributes(submission: Submission) { - assertThat(submission.attributes).hasSize(5) + assertThat(submission.attributes).hasSize(6) assertThat(submission.attributes).contains(Attribute("Type", "Experiment")) assertThat(submission.title).isEqualTo("Test Submission") + assertThat(submission.doi).isEqualTo("10.983/S-TEST123") assertThat(submission.attachTo).isEqualTo("BioImages") assertThat(submission.releaseDate).isEqualTo("2019-09-21") assertThat(submission.rootPath).isEqualTo("/a/root/path") diff --git a/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/constants/ExtSerializationFields.kt b/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/constants/ExtSerializationFields.kt index 66a8b3e859..f3b0b66341 100644 --- a/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/constants/ExtSerializationFields.kt +++ b/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/constants/ExtSerializationFields.kt @@ -38,6 +38,7 @@ object ExtSerializationFields { const val OWNER = "owner" const val SUBMITTER = "submitter" const val TITLE = "title" + const val DOI = "doi" const val METHOD = "method" const val REL_PATH = "relPath" const val ROOT_PATH = "rootPath" diff --git a/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializer.kt b/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializer.kt index 514068d114..533f5f16a0 100644 --- a/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializer.kt +++ b/commons/commons-model-extended-serialization/src/main/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializer.kt @@ -11,6 +11,7 @@ import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.ACC_NO import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.ATTRIBUTES import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.COLLECTIONS import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.CREATION_TIME +import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.DOI import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.METHOD import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.MOD_TIME import uk.ac.ebi.extended.serialization.constants.ExtSerializationFields.OWNER @@ -43,6 +44,7 @@ class ExtSubmissionSerializer : JsonSerializer() { gen.writeStringField(OWNER, submission.owner) gen.writeStringField(SUBMITTER, submission.submitter) gen.writeStringField(TITLE, submission.title) + gen.writeStringField(DOI, submission.doi) gen.writeStringField(METHOD, submission.method.name) gen.writeStringField(REL_PATH, submission.relPath) gen.writeStringField(ROOT_PATH, submission.rootPath) diff --git a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializerTest.kt b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializerTest.kt index 73822f026d..6197f6cf33 100644 --- a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializerTest.kt +++ b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/serializers/ExtSubmissionSerializerTest.kt @@ -73,6 +73,7 @@ class ExtSubmissionSerializerTest { "owner" to "owner@mail.org" "submitter" to "submitter@mail.org" "title" to "Test Submission" + "doi" to "10.983/S-TEST1" "method" to ExtSubmissionMethod.PAGE_TAB "relPath" to "/a/rel/path" "rootPath" to "/a/root/path" @@ -142,6 +143,7 @@ class ExtSubmissionSerializerTest { owner = "owner@mail.org", submitter = "submitter@mail.org", title = "TestSubmission", + doi = "10.983/S-TEST1", method = ExtSubmissionMethod.PAGE_TAB, relPath = "/a/rel/path", rootPath = "/a/root/path", diff --git a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceExtTest.kt b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceExtTest.kt index be25e5b779..46e2e10f0e 100644 --- a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceExtTest.kt +++ b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceExtTest.kt @@ -25,7 +25,6 @@ import java.time.OffsetDateTime internal class ExtSerializationServiceExtTest( private val tmpFolder: TemporaryFolder, ) { - private val testInstance: ExtSerializationService = ExtSerializationService() @Test @@ -73,6 +72,7 @@ internal class ExtSerializationServiceExtTest( owner = "owner@mail.org", submitter = "submitter@mail.org", title = "TestSubmission", + doi = "10.983/S-TEST1", method = ExtSubmissionMethod.PAGE_TAB, relPath = "/a/rel/path", rootPath = "/a/root/path", diff --git a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceTest.kt b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceTest.kt index e3b87f5d13..0ccc793bc1 100644 --- a/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceTest.kt +++ b/commons/commons-model-extended-serialization/src/test/kotlin/uk/ac/ebi/extended/serialization/service/ExtSerializationServiceTest.kt @@ -15,7 +15,6 @@ import io.github.glytching.junit.extension.folder.TemporaryFolderExtension import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith -import uk.ac.ebi.extended.test.AttributeFactory.defaultAttribute import java.time.OffsetDateTime import java.time.ZoneOffset @@ -24,7 +23,6 @@ class ExtSerializationServiceTest(private val tempFolder: TemporaryFolder) { private val testInstance = ExtSerializationService() private val testFile = tempFolder.createFile("results.txt") private val nfsFile = tempFolder.createFile("file.txt") - private val attributes = (1..4).map { defaultAttribute(name = "name$it") } @Test fun `serialize - deserialize`() { @@ -37,6 +35,7 @@ class ExtSerializationServiceTest(private val tempFolder: TemporaryFolder) { owner = "owner@mail.org", submitter = "submitter@mail.org", title = "Test Submission", + doi = "10.983/S-TEST123", method = PAGE_TAB, relPath = "/a/rel/path", rootPath = "/a/root/path", diff --git a/commons/commons-model-extended-serialization/src/testFixtures/kotlin/uk/ac/ebi/extended/test/SubmissionFactory.kt b/commons/commons-model-extended-serialization/src/testFixtures/kotlin/uk/ac/ebi/extended/test/SubmissionFactory.kt index c5a0c86926..6c3f031cb8 100644 --- a/commons/commons-model-extended-serialization/src/testFixtures/kotlin/uk/ac/ebi/extended/test/SubmissionFactory.kt +++ b/commons/commons-model-extended-serialization/src/testFixtures/kotlin/uk/ac/ebi/extended/test/SubmissionFactory.kt @@ -1,11 +1,9 @@ -@file:Suppress("LongParameterList", "MagicNumber") +@file:Suppress("LongParameterList") package uk.ac.ebi.extended.test import arrow.core.Either import ebi.ac.uk.extended.model.ExtAttribute -import ebi.ac.uk.extended.model.ExtAttributeDetail -import ebi.ac.uk.extended.model.ExtCollection import ebi.ac.uk.extended.model.ExtFile import ebi.ac.uk.extended.model.ExtFileList import ebi.ac.uk.extended.model.ExtFileTable @@ -14,80 +12,8 @@ import ebi.ac.uk.extended.model.ExtLink import ebi.ac.uk.extended.model.ExtLinkTable import ebi.ac.uk.extended.model.ExtSection import ebi.ac.uk.extended.model.ExtSectionTable -import ebi.ac.uk.extended.model.ExtSubmission -import ebi.ac.uk.extended.model.ExtSubmissionMethod -import ebi.ac.uk.extended.model.ExtTag import ebi.ac.uk.extended.model.FireFile -import ebi.ac.uk.extended.model.StorageMode import uk.ac.ebi.extended.serialization.service.createExtFileList -import uk.ac.ebi.extended.test.SectionFactory.defaultSection -import java.time.OffsetDateTime -import java.time.ZoneOffset.UTC - -object SubmissionFactory { - fun defaultSubmission( - accNo: String = ACC_NO, - version: Int = VERSION, - schemaVersion: String = SCHEMA_VERSION, - owner: String = OWNER, - submitter: String = SUBMITTER, - title: String? = TITLE, - method: ExtSubmissionMethod = METHOD, - relPath: String = REL_PATH, - rootPath: String? = ROOT_PATH, - released: Boolean = RELEASED, - secretKey: String = SECRET_KEY, - releaseTime: OffsetDateTime? = RELEASE_TIME, - modificationTime: OffsetDateTime = MODIFICATION_TIME, - creationTime: OffsetDateTime = CREATION_TIME, - section: ExtSection = SECTION, - attributes: List = ATTRIBUTES, - tags: List = TAGS, - collections: List = COLLECTIONS, - pageTabFiles: List = PAGE_TAG_FILES, - ) = ExtSubmission( - accNo = accNo, - version = version, - schemaVersion = schemaVersion, - owner = owner, - submitter = submitter, - title = title, - method = method, - relPath = relPath, - rootPath = rootPath, - released = released, - secretKey = secretKey, - releaseTime = releaseTime, - modificationTime = modificationTime, - creationTime = creationTime, - section = section, - attributes = attributes, - tags = tags, - collections = collections, - pageTabFiles = pageTabFiles, - storageMode = StorageMode.NFS - ) - - const val ACC_NO = "S-TEST123" - const val VERSION = 1 - const val SCHEMA_VERSION = "1.0" - const val OWNER = "owner@email.org" - const val SUBMITTER = "submitter@email.org" - const val TITLE = "Default Submission Title" - val METHOD = ExtSubmissionMethod.PAGE_TAB - const val REL_PATH = "S-TEST/123/S-TEST123" - const val ROOT_PATH = "SUBMISSION_ROOT_PATH" - const val RELEASED = false - const val SECRET_KEY = "SUBMISSION_SECRET_KEY" - val RELEASE_TIME: OffsetDateTime = OffsetDateTime.of(2019, 9, 21, 0, 0, 0, 0, UTC) - val MODIFICATION_TIME: OffsetDateTime = OffsetDateTime.of(2020, 9, 21, 0, 0, 0, 0, UTC) - val CREATION_TIME: OffsetDateTime = OffsetDateTime.of(2018, 9, 21, 0, 0, 0, 0, UTC) - val ATTRIBUTES = emptyList() - val TAGS = emptyList() - val COLLECTIONS = emptyList() - val SECTION = defaultSection() - val PAGE_TAG_FILES = emptyList() -} object SectionFactory { fun defaultSection( @@ -165,18 +91,3 @@ object FileListFactory { const val FILES_URL = "filesUrl" val PAGE_TAG_FILES = emptyList() } - -object AttributeFactory { - fun defaultAttribute( - name: String = NAME, - value: String = VALUE, - reference: Boolean = REFERENCE, - nameAttrs: List = listOf(), - valueAttrs: List = listOf(), - - ) = ExtAttribute(name, value, reference, nameAttrs, valueAttrs) - - const val NAME = "name" - const val VALUE = "value" - const val REFERENCE = false -} diff --git a/commons/commons-model-extended/src/main/kotlin/ebi/ac/uk/extended/model/ExtendedModel.kt b/commons/commons-model-extended/src/main/kotlin/ebi/ac/uk/extended/model/ExtendedModel.kt index a235c680e1..7769203da0 100644 --- a/commons/commons-model-extended/src/main/kotlin/ebi/ac/uk/extended/model/ExtendedModel.kt +++ b/commons/commons-model-extended/src/main/kotlin/ebi/ac/uk/extended/model/ExtendedModel.kt @@ -112,6 +112,7 @@ data class ExtSubmission( var schemaVersion: String, val submitter: String, val title: String?, + val doi: String?, val method: ExtSubmissionMethod, val relPath: String, val rootPath: String?, diff --git a/commons/commons-model-extended/src/test/kotlin/ebi/ac/uk/extended/model/ExtSubmissionExtensionsTest.kt b/commons/commons-model-extended/src/test/kotlin/ebi/ac/uk/extended/model/ExtSubmissionExtensionsTest.kt index f5a9b9c78d..99d5a76168 100644 --- a/commons/commons-model-extended/src/test/kotlin/ebi/ac/uk/extended/model/ExtSubmissionExtensionsTest.kt +++ b/commons/commons-model-extended/src/test/kotlin/ebi/ac/uk/extended/model/ExtSubmissionExtensionsTest.kt @@ -67,6 +67,7 @@ class ExtSubmissionExtensionsTest( storageMode = StorageMode.FIRE, submitter = "submitter@mail.org", title = subTitle, + doi = "10.983/S-TEST1", method = ExtSubmissionMethod.PAGE_TAB, relPath = "/a/rel/path", rootPath = null, diff --git a/commons/commons-test/src/main/kotlin/ebi/ac/uk/test/ExtSubmissionFactory.kt b/commons/commons-test/src/main/kotlin/ebi/ac/uk/test/ExtSubmissionFactory.kt index aaff5e7c67..32f7b12174 100644 --- a/commons/commons-test/src/main/kotlin/ebi/ac/uk/test/ExtSubmissionFactory.kt +++ b/commons/commons-test/src/main/kotlin/ebi/ac/uk/test/ExtSubmissionFactory.kt @@ -12,6 +12,7 @@ val basicExtSubmission = ExtSubmission( version = 1, schemaVersion = "1.0", title = "Test Submission", + doi = null, owner = "owner@email.org", submitter = "submitter@email.org", method = PAGE_TAB, diff --git a/scheduler/tasks/stats-reporter-task/src/test/kotlin/uk/ac/ebi/scheduler/stats/persistence/StatsReporterDataRepositoryTest.kt b/scheduler/tasks/stats-reporter-task/src/test/kotlin/uk/ac/ebi/scheduler/stats/persistence/StatsReporterDataRepositoryTest.kt index 8b0ff1cd6e..31e7c9428c 100644 --- a/scheduler/tasks/stats-reporter-task/src/test/kotlin/uk/ac/ebi/scheduler/stats/persistence/StatsReporterDataRepositoryTest.kt +++ b/scheduler/tasks/stats-reporter-task/src/test/kotlin/uk/ac/ebi/scheduler/stats/persistence/StatsReporterDataRepositoryTest.kt @@ -95,6 +95,7 @@ class StatsReporterDataRepositoryTest( owner = "biostudies-dev@ebi.ac.uk", submitter = "biostudies-dev@ebi.ac.uk", title = "Test Stats Submission", + doi = "10.983/S-BSST1", method = PAGE_TAB, rootPath = null, relPath = "S-BSST/001/S-BSST1", diff --git a/submission/notifications/src/test/kotlin/ebi/ac/uk/notifications/service/RtNotificationServiceTest.kt b/submission/notifications/src/test/kotlin/ebi/ac/uk/notifications/service/RtNotificationServiceTest.kt index ea6807f757..9d5409a36d 100644 --- a/submission/notifications/src/test/kotlin/ebi/ac/uk/notifications/service/RtNotificationServiceTest.kt +++ b/submission/notifications/src/test/kotlin/ebi/ac/uk/notifications/service/RtNotificationServiceTest.kt @@ -309,6 +309,7 @@ internal class RtNotificationServiceTest( owner = "owner@mail.org", submitter = "submitter@mail.org", title = title, + doi = "10.983/S-TEST1", method = ExtSubmissionMethod.PAGE_TAB, relPath = "/a/rel/path", rootPath = "/a/root/path", diff --git a/submission/persistence-filesystem/src/test/kotlin/ac/uk/ebi/biostd/persistence/filesystem/ExtSubmissionFactory.kt b/submission/persistence-filesystem/src/test/kotlin/ac/uk/ebi/biostd/persistence/filesystem/ExtSubmissionFactory.kt deleted file mode 100644 index 7331018c23..0000000000 --- a/submission/persistence-filesystem/src/test/kotlin/ac/uk/ebi/biostd/persistence/filesystem/ExtSubmissionFactory.kt +++ /dev/null @@ -1,55 +0,0 @@ -package ac.uk.ebi.biostd.persistence.filesystem - -import arrow.core.Either.Companion.left -import ebi.ac.uk.extended.model.ExtFileList -import ebi.ac.uk.extended.model.ExtSection -import ebi.ac.uk.extended.model.ExtSubmission -import ebi.ac.uk.extended.model.ExtSubmissionMethod -import ebi.ac.uk.extended.model.StorageMode -import ebi.ac.uk.extended.model.createNfsFile -import uk.ac.ebi.extended.serialization.service.createExtFileList -import java.io.File -import java.time.OffsetDateTime -import java.time.ZoneOffset - -fun extSubmissionWithFileList(files: List, referencedFiles: List) = - ExtSubmission( - accNo = "ABC-123", - version = 1, - schemaVersion = "1.0", - storageMode = StorageMode.NFS, - title = "A Test Submission", - owner = "owner", - submitter = "submitter", - method = ExtSubmissionMethod.PAGE_TAB, - relPath = "ABC/ABCxxx123/ABC-123", - rootPath = null, - released = false, - secretKey = "a-secret-key", - releaseTime = null, - modificationTime = OffsetDateTime.of(2018, 10, 10, 0, 0, 0, 0, ZoneOffset.UTC), - creationTime = OffsetDateTime.of(2018, 10, 10, 0, 0, 0, 0, ZoneOffset.UTC), - attributes = emptyList(), - tags = emptyList(), - collections = emptyList(), - section = extSectionWithFileList(files, referencedFiles) - ) - -fun extSectionWithFileList(files: List, referencedFiles: List) = - ExtSection( - type = "Study", - files = files.map { left(createNfsFile(it.name, "relPath", it, emptyList())) }, - fileList = ExtFileList( - "fileList", - file = createExtFileList( - referencedFiles.map { - createNfsFile( - it.name, - "relPath", - it, - emptyList() - ) - } - ) - ) - ) diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverter.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverter.kt index bc5904a7de..fbe46551b8 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverter.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverter.kt @@ -7,6 +7,7 @@ import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_ATTRIBUTES import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_COLLECTIONS import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_CREATION_TIME +import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_DOI import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_ID import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_METHOD import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_MODIFICATION_TIME @@ -45,6 +46,7 @@ class DocSubmissionConverter( owner = source.getString(SUB_OWNER), submitter = source.getString(SUB_SUBMITTER), title = source.getString(SUB_TITLE), + doi = source.getString(SUB_DOI), method = DocSubmissionMethod.fromString(source.getString(SUB_METHOD)), relPath = source.getString(SUB_REL_PATH), rootPath = source.getString(SUB_ROOT_PATH), diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/shared/ConverterCommonsFields.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/shared/ConverterCommonsFields.kt index 54d0a2c179..7adf755ae8 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/shared/ConverterCommonsFields.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/shared/ConverterCommonsFields.kt @@ -111,7 +111,7 @@ object DocSectionFields { object DocSubmissionFields { val DOC_SUBMISSION_CLASS: String = DocSubmission::class.java.canonicalName val DOC_TAG_CLASS: String = DocTag::class.java.canonicalName - val DOC_PROJECT_CLASS: String = DocCollection::class.java.canonicalName + val DOC_COLLECTION_CLASS: String = DocCollection::class.java.canonicalName const val CLASS_FIELD = "_class" const val SUB = "submission" @@ -122,6 +122,7 @@ object DocSubmissionFields { const val SUB_OWNER = "owner" const val SUB_SUBMITTER = "submitter" const val SUB_TITLE = "title" + const val SUB_DOI = "doi" const val SUB_METHOD = "method" const val SUB_REL_PATH = "relPath" const val SUB_ROOT_PATH = "rootPath" diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverter.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverter.kt index b28db1452e..0e122257a8 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverter.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverter.kt @@ -1,7 +1,7 @@ package ac.uk.ebi.biostd.persistence.doc.db.converters.to import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.COLLECTION_ACC_NO -import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.DOC_PROJECT_CLASS +import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.DOC_COLLECTION_CLASS import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.DOC_SUBMISSION_CLASS import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.DOC_TAG_CLASS import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.PAGE_TAB_FILES @@ -10,6 +10,7 @@ import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_ATTRIBUTES import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_COLLECTIONS import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_CREATION_TIME +import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_DOI import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_ID import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_METHOD import ac.uk.ebi.biostd.persistence.doc.db.converters.shared.DocSubmissionFields.SUB_MODIFICATION_TIME @@ -49,6 +50,7 @@ class SubmissionConverter( submissionDoc[SUB_OWNER] = submission.owner submissionDoc[SUB_SUBMITTER] = submission.submitter submissionDoc[SUB_TITLE] = submission.title + submissionDoc[SUB_DOI] = submission.doi submissionDoc[SUB_METHOD] = submission.method.value submissionDoc[SUB_REL_PATH] = submission.relPath submissionDoc[SUB_ROOT_PATH] = submission.rootPath @@ -76,7 +78,7 @@ class SubmissionConverter( private fun collectionToDocument(docCollection: DocCollection): Document { val projectDoc = Document() - projectDoc[classField] = DOC_PROJECT_CLASS + projectDoc[classField] = DOC_COLLECTION_CLASS projectDoc[COLLECTION_ACC_NO] = docCollection.accNo return projectDoc } diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/data/SubmissionDraftDocDataRepository.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/data/SubmissionDraftDocDataRepository.kt index e703774c8f..c0f0aaf154 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/data/SubmissionDraftDocDataRepository.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/db/data/SubmissionDraftDocDataRepository.kt @@ -60,7 +60,7 @@ class SubmissionDraftDocDataRepository( status: DraftStatus, pageRequest: PageRequest = PageRequest(), ): Flow { - val pageRequest = pageRequest.asDataPageRequest() - return submissionDraftRepository.findAllByUserIdAndStatus(userId, status, pageRequest) + val request = pageRequest.asDataPageRequest() + return submissionDraftRepository.findAllByUserIdAndStatus(userId, status, request) } } diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/from/ToDocSubmissionMapper.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/from/ToDocSubmissionMapper.kt index ae8966164b..a319e0fa7e 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/from/ToDocSubmissionMapper.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/from/ToDocSubmissionMapper.kt @@ -23,6 +23,7 @@ class ToDocSubmissionMapper(private val toDocSectionMapper: ToDocSectionMapper) id = submissionId, accNo = accNo, title = title, + doi = doi, method = getMethod(method), version = version, schemaVersion = schemaVersion, diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/to/ToExtSubmissionMapper.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/to/ToExtSubmissionMapper.kt index 65b5429cae..dbedd3082e 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/to/ToExtSubmissionMapper.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/mapping/to/ToExtSubmissionMapper.kt @@ -19,6 +19,7 @@ class ToExtSubmissionMapper( owner = sub.owner, submitter = sub.submitter, title = sub.title, + doi = sub.doi, version = sub.version, schemaVersion = sub.schemaVersion, method = getMethod(sub.method), diff --git a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/model/SubmissionModel.kt b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/model/SubmissionModel.kt index 3a09d8adfb..8211f3bf64 100644 --- a/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/model/SubmissionModel.kt +++ b/submission/persistence-mongo/src/main/kotlin/ac/uk/ebi/biostd/persistence/doc/model/SubmissionModel.kt @@ -27,6 +27,7 @@ data class DocSubmission( val owner: String, val submitter: String, val title: String?, + val doi: String?, val method: DocSubmissionMethod, val relPath: String, val rootPath: String?, diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverterTest.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverterTest.kt index c47b3a239b..96bd8031d9 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverterTest.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/from/DocSubmissionConverterTest.kt @@ -48,25 +48,25 @@ internal class DocSubmissionConverterTest( private fun assertThatAll(result: DocSubmission) { assertThat(result).isInstanceOf(DocSubmission::class.java) assertThat(result.id).isEqualTo(subId) - assertThat(result.accNo).isEqualTo(subAccNo) - assertThat(result.version).isEqualTo(subVersion) - assertThat(result.schemaVersion).isEqualTo(subSchemaVersion) - assertThat(result.owner).isEqualTo(subOwner) - assertThat(result.submitter).isEqualTo(subSubmitter) - assertThat(result.title).isEqualTo(subTitle) - assertThat(result.method).isEqualTo(DocSubmissionMethod.fromString(subMethod)) - assertThat(result.relPath).isEqualTo(subRelPath) - assertThat(result.rootPath).isEqualTo(subRootPath) - assertThat(result.released).isEqualTo(subReleased) - assertThat(result.secretKey).isEqualTo(subSecretKey) + assertThat(result.accNo).isEqualTo(SUB_ACC_NO) + assertThat(result.version).isEqualTo(VERSION) + assertThat(result.schemaVersion).isEqualTo(SCHEMA_VERSION) + assertThat(result.owner).isEqualTo(OWNER) + assertThat(result.submitter).isEqualTo(SUBMITTER) + assertThat(result.title).isEqualTo(TITLE) + assertThat(result.method).isEqualTo(DocSubmissionMethod.fromString(METHOD)) + assertThat(result.relPath).isEqualTo(REL_PATH) + assertThat(result.rootPath).isEqualTo(ROOT_PATH) + assertThat(result.released).isEqualTo(RELEASED) + assertThat(result.secretKey).isEqualTo(SECRET_KEY) assertThat(result.releaseTime).isEqualTo(subReleaseTime.toInstant()) assertThat(result.modificationTime).isEqualTo(subModificationTime.toInstant()) assertThat(result.creationTime).isEqualTo(subCreationTime.toInstant()) assertThat(result.section).isEqualTo(docSection) assertThat(result.attributes).isEqualTo(listOf(docAttribute)) - assertThat(result.tags[0].name).isEqualTo(tagDocName) - assertThat(result.tags[0].value).isEqualTo(tagDocValue) - assertThat(result.collections[0].accNo).isEqualTo(projectDocAccNo) + assertThat(result.tags[0].name).isEqualTo(TAG_DOC_NAME) + assertThat(result.tags[0].value).isEqualTo(TAG_DOC_VALUE) + assertThat(result.collections[0].accNo).isEqualTo(COLLECTION_DOC_ACC_NO) assertThat(result.pageTabFiles).isEqualTo(listOf(docFile)) assertThat(result.storageMode).isEqualTo(StorageMode.NFS) } @@ -79,63 +79,64 @@ internal class DocSubmissionConverterTest( val subDocument = Document() subDocument[DocSubmissionFields.CLASS_FIELD] = docSubmissionClass subDocument[DocSubmissionFields.SUB_ID] = subId - subDocument[DocSubmissionFields.SUB_ACC_NO] = subAccNo - subDocument[DocSubmissionFields.SUB_VERSION] = subVersion - subDocument[DocSubmissionFields.SUB_SCHEMA_VERSION] = subSchemaVersion - subDocument[DocSubmissionFields.SUB_OWNER] = subOwner - subDocument[DocSubmissionFields.SUB_SUBMITTER] = subSubmitter - subDocument[DocSubmissionFields.SUB_TITLE] = subTitle - subDocument[DocSubmissionFields.SUB_METHOD] = subMethod - subDocument[DocSubmissionFields.SUB_REL_PATH] = subRelPath - subDocument[DocSubmissionFields.SUB_ROOT_PATH] = subRootPath - subDocument[DocSubmissionFields.SUB_RELEASED] = subReleased - subDocument[DocSubmissionFields.SUB_SECRET_KEY] = subSecretKey - subDocument[DocSubmissionFields.SUB_STATUS] = subStatus + subDocument[DocSubmissionFields.SUB_ACC_NO] = SUB_ACC_NO + subDocument[DocSubmissionFields.SUB_VERSION] = VERSION + subDocument[DocSubmissionFields.SUB_SCHEMA_VERSION] = SCHEMA_VERSION + subDocument[DocSubmissionFields.SUB_OWNER] = OWNER + subDocument[DocSubmissionFields.SUB_SUBMITTER] = SUBMITTER + subDocument[DocSubmissionFields.SUB_TITLE] = TITLE + subDocument[DocSubmissionFields.SUB_METHOD] = METHOD + subDocument[DocSubmissionFields.SUB_REL_PATH] = REL_PATH + subDocument[DocSubmissionFields.SUB_ROOT_PATH] = ROOT_PATH + subDocument[DocSubmissionFields.SUB_RELEASED] = RELEASED + subDocument[DocSubmissionFields.SUB_SECRET_KEY] = SECRET_KEY + subDocument[DocSubmissionFields.SUB_STATUS] = STATUS subDocument[DocSubmissionFields.SUB_RELEASE_TIME] = subReleaseTime subDocument[DocSubmissionFields.SUB_MODIFICATION_TIME] = subModificationTime subDocument[DocSubmissionFields.SUB_CREATION_TIME] = subCreationTime subDocument[DocSubmissionFields.SUB_SECTION] = sectionDocument subDocument[DocSubmissionFields.SUB_ATTRIBUTES] = listOf(attributeDocument) subDocument[DocSubmissionFields.SUB_TAGS] = listOf(createTagDocument()) - subDocument[DocSubmissionFields.SUB_COLLECTIONS] = listOf(createProjectDocument()) + subDocument[DocSubmissionFields.SUB_COLLECTIONS] = listOf(createCollectionDocument()) subDocument[DocSubmissionFields.PAGE_TAB_FILES] = listOf(fileDocument) - subDocument[DocSubmissionFields.STORAGE_MODE] = storageMode + subDocument[DocSubmissionFields.STORAGE_MODE] = STORAGE_MODE return subDocument } private fun createTagDocument(): Document { val tagDoc = Document() - tagDoc[DocSubmissionFields.TAG_DOC_NAME] = tagDocName - tagDoc[DocSubmissionFields.TAG_DOC_VALUE] = tagDocValue + tagDoc[DocSubmissionFields.TAG_DOC_NAME] = TAG_DOC_NAME + tagDoc[DocSubmissionFields.TAG_DOC_VALUE] = TAG_DOC_VALUE return tagDoc } - private fun createProjectDocument(): Document { - val projectDoc = Document() - projectDoc[DocSubmissionFields.COLLECTION_ACC_NO] = projectDocAccNo - return projectDoc + private fun createCollectionDocument(): Document { + val collectionDoc = Document() + collectionDoc[DocSubmissionFields.COLLECTION_ACC_NO] = COLLECTION_DOC_ACC_NO + return collectionDoc } companion object { - val subId = ObjectId(1, 1) - const val subAccNo = "accNo" - const val projectDocAccNo = "accNo" - const val subVersion = 1 - const val subSchemaVersion = "1.0" - const val subOwner = "owner" - const val subSubmitter = "submitter" - const val subTitle = "title" - const val subMethod = "FILE" - const val subStatus = "PROCESSED" - const val subRelPath = "relPath" - const val subRootPath = "rootPath" - const val subReleased = false - const val subSecretKey = "secretKey" - val subReleaseTime: Date = Date(110) - val subModificationTime: Date = Date(220) - val subCreationTime: Date = Date(330) - const val tagDocName = "name" - const val tagDocValue = "value" - const val storageMode = "NFS" + private val subId = ObjectId(1, 1) + private val subReleaseTime: Date = Date(110) + private val subModificationTime: Date = Date(220) + private val subCreationTime: Date = Date(330) + + private const val SUB_ACC_NO = "accNo" + private const val COLLECTION_DOC_ACC_NO = "accNo" + private const val VERSION = 1 + private const val SCHEMA_VERSION = "1.0" + private const val OWNER = "owner" + private const val SUBMITTER = "submitter" + private const val TITLE = "title" + private const val METHOD = "FILE" + private const val STATUS = "PROCESSED" + private const val REL_PATH = "relPath" + private const val ROOT_PATH = "rootPath" + private const val RELEASED = false + private const val SECRET_KEY = "secretKey" + private const val TAG_DOC_NAME = "name" + private const val TAG_DOC_VALUE = "value" + private const val STORAGE_MODE = "NFS" } } diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverterTest.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverterTest.kt index 67e97292db..f10fbca9ab 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverterTest.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/db/converters/to/SubmissionConverterTest.kt @@ -65,17 +65,17 @@ internal class SubmissionConverterTest( val result = testInstance.convert(docSubmission) assertThat(result[SUB_ID]).isEqualTo(submissionId) - assertThat(result[SUB_ACC_NO]).isEqualTo(submissionAccNo) - assertThat(result[SUB_VERSION]).isEqualTo(submissionVersion) - assertThat(result[SUB_SCHEMA_VERSION]).isEqualTo(submissionSchemaVersion) - assertThat(result[SUB_OWNER]).isEqualTo(submissionOwner) - assertThat(result[SUB_SUBMITTER]).isEqualTo(submissionSubmitter) - assertThat(result[SUB_TITLE]).isEqualTo(submissionTitle) + assertThat(result[SUB_ACC_NO]).isEqualTo(ACC_NO) + assertThat(result[SUB_VERSION]).isEqualTo(VERSION) + assertThat(result[SUB_SCHEMA_VERSION]).isEqualTo(SCHEMA_VERSION) + assertThat(result[SUB_OWNER]).isEqualTo(OWNER) + assertThat(result[SUB_SUBMITTER]).isEqualTo(SUBMITTER) + assertThat(result[SUB_TITLE]).isEqualTo(TITLE) assertThat(result[SUB_METHOD]).isEqualTo(DocSubmissionMethod.PAGE_TAB.value) - assertThat(result[SUB_REL_PATH]).isEqualTo(submissionRelPath) - assertThat(result[SUB_ROOT_PATH]).isEqualTo(submissionRootPath) - assertThat(result[SUB_RELEASED]).isEqualTo(submissionReleased) - assertThat(result[SUB_SECRET_KEY]).isEqualTo(submissionSecretKey) + assertThat(result[SUB_REL_PATH]).isEqualTo(REL_PATH) + assertThat(result[SUB_ROOT_PATH]).isEqualTo(ROOT_PATH) + assertThat(result[SUB_RELEASED]).isEqualTo(RELEASED) + assertThat(result[SUB_SECRET_KEY]).isEqualTo(SECRET_KEY) assertThat(result[SUB_RELEASE_TIME]).isEqualTo(submissionReleaseTime) assertThat(result[SUB_MODIFICATION_TIME]).isEqualTo(submissionModificationTime) assertThat(result[SUB_CREATION_TIME]).isEqualTo(submissionCreationTime) @@ -86,12 +86,12 @@ internal class SubmissionConverterTest( val tags = result.getAs>(DocSubmissionFields.SUB_TAGS) val tag = tags.first() - assertThat(tag[DocSubmissionFields.TAG_DOC_NAME]).isEqualTo(docTagName) - assertThat(tag[DocSubmissionFields.TAG_DOC_VALUE]).isEqualTo(docTagValue) + assertThat(tag[DocSubmissionFields.TAG_DOC_NAME]).isEqualTo(TAG_NAME) + assertThat(tag[DocSubmissionFields.TAG_DOC_VALUE]).isEqualTo(TAG_VALUE) - val projects = result.getAs>(DocSubmissionFields.SUB_COLLECTIONS) - val project = projects.first() - assertThat(project[DocSubmissionFields.COLLECTION_ACC_NO]).isEqualTo(docProjectAccNo) + val collections = result.getAs>(DocSubmissionFields.SUB_COLLECTIONS) + val collection = collections.first() + assertThat(collection[DocSubmissionFields.COLLECTION_ACC_NO]).isEqualTo(COLLECTION_ACC_NO) } private fun createDocSubmission( @@ -101,50 +101,52 @@ internal class SubmissionConverterTest( ): DocSubmission { return DocSubmission( id = submissionId, - accNo = submissionAccNo, - version = submissionVersion, - schemaVersion = submissionSchemaVersion, - owner = submissionOwner, - submitter = submissionSubmitter, - title = submissionTitle, + accNo = ACC_NO, + version = VERSION, + schemaVersion = SCHEMA_VERSION, + owner = OWNER, + submitter = SUBMITTER, + title = TITLE, + doi = DOI, method = DocSubmissionMethod.PAGE_TAB, - relPath = submissionRelPath, - rootPath = submissionRootPath, - released = submissionReleased, - secretKey = submissionSecretKey, + relPath = REL_PATH, + rootPath = ROOT_PATH, + released = RELEASED, + secretKey = SECRET_KEY, releaseTime = submissionReleaseTime, modificationTime = submissionModificationTime, creationTime = submissionCreationTime, section = docSection, attributes = listOf(docAttribute), tags = submissionTags, - collections = submissionProjects, + collections = submissionCollections, pageTabFiles = listOf(docFile), storageMode = StorageMode.NFS ) } private companion object { - val submissionId = ObjectId() - const val submissionAccNo = "S-TEST1" - const val submissionVersion = 1 - const val submissionSchemaVersion = "1.0" - const val submissionOwner = "owner@mail.org" - const val submissionSubmitter = "submitter@mail.org" - const val submissionTitle = "TestSubmission" - const val submissionRelPath = "/a/rel/path" - const val submissionRootPath = "/a/root/path" - const val submissionReleased = false - const val submissionSecretKey = "a-secret-key" - val submissionReleaseTime: Instant = Instant.ofEpochSecond(1) - val submissionModificationTime: Instant = Instant.ofEpochSecond(2) - val submissionCreationTime: Instant = Instant.ofEpochSecond(3) + private val submissionId = ObjectId() + private const val ACC_NO = "S-TEST1" + private const val VERSION = 1 + private const val SCHEMA_VERSION = "1.0" + private const val OWNER = "owner@mail.org" + private const val SUBMITTER = "submitter@mail.org" + private const val TITLE = "TestSubmission" + private const val DOI = "10.983/S-TEST1" + private const val REL_PATH = "/a/rel/path" + private const val ROOT_PATH = "/a/root/path" + private const val RELEASED = false + private const val SECRET_KEY = "a-secret-key" + private val submissionReleaseTime: Instant = Instant.ofEpochSecond(1) + private val submissionModificationTime: Instant = Instant.ofEpochSecond(2) + private val submissionCreationTime: Instant = Instant.ofEpochSecond(3) - private const val docTagName = "component" - private const val docTagValue = "web" - val submissionTags = listOf(DocTag(docTagName, docTagValue)) + private const val TAG_NAME = "component" + private const val TAG_VALUE = "web" + private val submissionTags = listOf(DocTag(TAG_NAME, TAG_VALUE)) - private const val docProjectAccNo = "BioImages" - val submissionProjects = listOf(DocCollection(docProjectAccNo)) + private const val COLLECTION_ACC_NO = "BioImages" + private val submissionCollections = listOf(DocCollection(COLLECTION_ACC_NO)) } } diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/ExtSubmissionRepositoryTest.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/ExtSubmissionRepositoryTest.kt index 9d284523a7..82dfb6729d 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/ExtSubmissionRepositoryTest.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/ExtSubmissionRepositoryTest.kt @@ -14,6 +14,7 @@ import ac.uk.ebi.biostd.persistence.doc.test.SubmissionTestHelper.docSubmission import ac.uk.ebi.biostd.persistence.doc.test.beans.TestConfig import ebi.ac.uk.db.MINIMUM_RUNNING_TIME import ebi.ac.uk.db.MONGO_VERSION +import ebi.ac.uk.test.basicExtSubmission import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -34,8 +35,6 @@ import uk.ac.ebi.extended.test.FileListFactory.FILE_PATH import uk.ac.ebi.extended.test.FileListFactory.defaultFileList import uk.ac.ebi.extended.test.FireFileFactory.defaultFireFile import uk.ac.ebi.extended.test.SectionFactory.defaultSection -import uk.ac.ebi.extended.test.SubmissionFactory.ACC_NO -import uk.ac.ebi.extended.test.SubmissionFactory.defaultSubmission import uk.ac.ebi.serialization.common.FilesResolver import java.time.Duration @@ -68,7 +67,7 @@ class ExtSubmissionRepositoryTest( @Test fun `save submission`() = runTest { val section = defaultSection(fileList = defaultFileList(files = listOf(defaultFireFile()))) - val submission = defaultSubmission(section = section, version = 1) + val submission = basicExtSubmission.copy(section = section) val result = testInstance.saveSubmission(submission) @@ -93,12 +92,12 @@ class ExtSubmissionRepositoryTest( @Test fun `expire previous versions`() = runTest { - subDataRepository.save(docSubmission.copy(accNo = ACC_NO, version = 1)) + subDataRepository.save(docSubmission.copy(accNo = "S-TEST123", version = 1)) assertThat(subDataRepository.findAll().toList()).hasSize(1) - testInstance.expirePreviousVersions(ACC_NO) + testInstance.expirePreviousVersions("S-TEST123") - assertThat(subDataRepository.getSubmission(ACC_NO, -1)).isNotNull() + assertThat(subDataRepository.getSubmission("S-TEST123", -1)).isNotNull() assertThat(subDataRepository.findAll().toList()).hasSize(1) } diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/StatsMongoDataServiceTest.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/StatsMongoDataServiceTest.kt index 32613df4e1..c3b4472c83 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/StatsMongoDataServiceTest.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/service/StatsMongoDataServiceTest.kt @@ -19,6 +19,7 @@ import ebi.ac.uk.util.collections.third import io.mockk.coEvery import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest @@ -43,6 +44,7 @@ import java.time.Duration.ofSeconds @ExtendWith(MockKExtension::class, SpringExtension::class) @Testcontainers @SpringBootTest(classes = [MongoDbReposConfig::class]) +@OptIn(ExperimentalCoroutinesApi::class) class StatsMongoDataServiceTest( @MockK private val submissionsRepository: SubmissionMongoRepository, @Autowired private val submissionStatsDataRepository: SubmissionStatsDataRepository, diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/SubmissionTestHelper.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/SubmissionTestHelper.kt index b18e77d3d3..af1e7474a3 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/SubmissionTestHelper.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/SubmissionTestHelper.kt @@ -25,9 +25,8 @@ internal const val SECRET_KEY = "a-secret-key" internal const val TAG_NAME = "component" internal const val TAG_VALUE = "web" internal const val PROJECT_ACC_NO = "BioImages" -internal const val STAT_TYPE = "VIEWS" -internal const val STAT_VALUE = 123L internal const val REL_PATH = "S-TEST/123/S-TEST123" +internal const val DOI = "10.6019/S-TEST123" val fireDocFile = FireDocFile("filename", "filePath", "relPath", "fireId", listOf(), "md5", 1L, FILE.value) val fireDocDirectory = FireDocFile("filename", "filePath", "relPath", "dirFireId", listOf(), "md5", 1L, DIR.value) @@ -45,6 +44,7 @@ object SubmissionTestHelper { owner = OWNER, submitter = SUBMITTER, title = SUB_TITLE, + doi = DOI, method = PAGE_TAB, relPath = REL_PATH, rootPath = ROOT_PATH, diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/SubmissionDocTestHelper.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/SubmissionDocTestHelper.kt index d9dd28829d..056f9d4f03 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/SubmissionDocTestHelper.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/SubmissionDocTestHelper.kt @@ -39,6 +39,7 @@ internal const val TAG_VALUE = "web" internal const val COLLECTION_ACC_NO = "BioImages" internal const val STAT_VALUE = 123L internal const val REL_PATH = "S-TEST/123/S-TEST123" +internal const val DOI = "10.6019/S-TEST123" internal val CREATION_TIME = Instant.now() internal val MODIFICATION_TIME = CREATION_TIME.plus(1, ChronoUnit.HOURS) internal val RELEASE_TIME = CREATION_TIME.plus(1, ChronoUnit.DAYS) @@ -56,6 +57,7 @@ internal val testDocSubmission: DocSubmission owner = OWNER, submitter = SUBMITTER, title = SUB_TITLE, + doi = DOI, method = PAGE_TAB, relPath = REL_PATH, rootPath = ROOT_PATH, diff --git a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/ext/ExtSubmissionFactory.kt b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/ext/ExtSubmissionFactory.kt index b6e91f9596..07bda7fa3a 100644 --- a/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/ext/ExtSubmissionFactory.kt +++ b/submission/persistence-mongo/src/test/kotlin/ac/uk/ebi/biostd/persistence/doc/test/doc/ext/ExtSubmissionFactory.kt @@ -22,6 +22,7 @@ const val SUBMISSION_REL_PATH = "/a/rel/path" const val SUBMISSION_ROOT_PATH = "/a/root/path" const val SUBMISSION_RELEASED = true const val SUBMISSION_SECRET_KEY = "a-secret-key" +const val SUBMISSION_DOI = "10.6019/S-TEST1" val RELEASE_TIME: OffsetDateTime = OffsetDateTime.of(2019, 9, 21, 10, 30, 34, 15, ZoneOffset.UTC) val MODIFICATION_TIME: OffsetDateTime = OffsetDateTime.of(2020, 9, 21, 10, 30, 34, 15, ZoneOffset.UTC) @@ -83,6 +84,7 @@ val fullExtSubmission = ExtSubmission( owner = SUBMISSION_OWNER, submitter = SUBMISSION_SUBMITTER, title = SUBMISSION_TITLE, + doi = SUBMISSION_DOI, method = SUBMISSION_METHOD, relPath = SUBMISSION_REL_PATH, rootPath = SUBMISSION_ROOT_PATH, diff --git a/submission/submission-config/src/main/kotlin/ac/uk/ebi/biostd/common/properties/ApplicationProperties.kt b/submission/submission-config/src/main/kotlin/ac/uk/ebi/biostd/common/properties/ApplicationProperties.kt index 4e048bb5b6..730aef777b 100644 --- a/submission/submission-config/src/main/kotlin/ac/uk/ebi/biostd/common/properties/ApplicationProperties.kt +++ b/submission/submission-config/src/main/kotlin/ac/uk/ebi/biostd/common/properties/ApplicationProperties.kt @@ -20,6 +20,7 @@ data class ApplicationProperties( val validator: ValidatorProperties, val persistence: PersistenceProperties, val notifications: NotificationsProperties, + val doi: DoiProperties, ) data class RetryProperties( @@ -59,3 +60,10 @@ data class NotificationsProperties( val requestQueue: String, val requestRoutingKey: String, ) + +data class DoiProperties( + val endpoint: String, + val uiUrl: String, + val user: String, + val password: String +) diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/itest/ITestListener.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/itest/ITestListener.kt index 102bed6b01..fd6a67a63f 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/itest/ITestListener.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/itest/ITestListener.kt @@ -9,6 +9,7 @@ import ac.uk.ebi.biostd.itest.wiremock.TestWireMockTransformer import ac.uk.ebi.biostd.itest.wiremock.TestWireMockTransformer.Companion.newTransformer import com.adobe.testing.s3mock.testcontainers.S3MockContainer import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder.okForJson import com.github.tomakehurst.wiremock.client.WireMock import com.github.tomakehurst.wiremock.client.WireMock.post import com.github.tomakehurst.wiremock.core.WireMockConfiguration @@ -17,7 +18,6 @@ import ebi.ac.uk.db.MONGO_VERSION import ebi.ac.uk.db.MYSQL_SCHEMA import ebi.ac.uk.db.MYSQL_VERSION import ebi.ac.uk.extended.model.StorageMode -import mu.KotlinLogging import org.junit.platform.launcher.TestExecutionListener import org.junit.platform.launcher.TestPlan import org.testcontainers.containers.MongoDBContainer @@ -27,8 +27,6 @@ import java.io.File import java.nio.file.Files import java.time.Duration.ofSeconds -private val logger = KotlinLogging.logger {} - class ITestListener : TestExecutionListener { override fun testPlanExecutionStarted(testPlan: TestPlan) { mongoSetup() @@ -36,6 +34,7 @@ class ITestListener : TestExecutionListener { fireSetup() ftpSetup() appPropertiesSetup() + doiSetup() } override fun testPlanExecutionFinished(testPlan: TestPlan) { @@ -61,8 +60,8 @@ class ITestListener : TestExecutionListener { private fun ftpSetup() { ftpServer.start() - System.setProperty("app.security.filesProperties.ftpUser", ftpUser) - System.setProperty("app.security.filesProperties.ftpPassword", ftpPassword) + System.setProperty("app.security.filesProperties.ftpUser", FTP_USER) + System.setProperty("app.security.filesProperties.ftpPassword", FTP_PASSWORD) System.setProperty("app.security.filesProperties.ftpUrl", ftpServer.getUrl()) System.setProperty("app.security.filesProperties.ftpPort", ftpServer.ftpPort.toString()) System.setProperty("app.security.filesProperties.ftpDirPath", ftpServer.fileSystemDirectory.absolutePath) @@ -70,11 +69,11 @@ class ITestListener : TestExecutionListener { private fun fireSetup() { s3Container.start() - System.setProperty("app.fire.s3.accessKey", awsAccessKey) - System.setProperty("app.fire.s3.secretKey", awsSecretKey) - System.setProperty("app.fire.s3.region", awsRegion) + System.setProperty("app.fire.s3.accessKey", AWS_ACCESS_KEY) + System.setProperty("app.fire.s3.secretKey", AWS_SECRET_KEY) + System.setProperty("app.fire.s3.region", AWS_REGION) System.setProperty("app.fire.s3.endpoint", s3Container.httpEndpoint) - System.setProperty("app.fire.s3.bucket", defaultBucket) + System.setProperty("app.fire.s3.bucket", DEFAULT_BUCKET) fireServer.stubFor( post(WireMock.urlMatching("/objects")) @@ -95,26 +94,34 @@ class ITestListener : TestExecutionListener { System.setProperty("app.requestFilesPath", requestFilesPath.absolutePath) System.setProperty("app.security.filesProperties.filesDirPath", dropboxPath.absolutePath) System.setProperty("app.security.filesProperties.magicDirPath", magicDirPath.absolutePath) - System.setProperty("app.persistence.concurrency", persistenceConcurrency) + System.setProperty("app.persistence.concurrency", PERSISTENCE_CONCURRENCY) System.setProperty("app.persistence.enableFire", "${System.getProperty("enableFire").toBoolean()}") } + private fun doiSetup() { + doiServer.start() + System.setProperty("app.doi.endpoint", "${doiServer.baseUrl()}/deposit") + System.setProperty("app.doi.uiUrl", "https://www.ebi.ac.uk/biostudies/") + System.setProperty("app.doi.user", "a-user") + System.setProperty("app.doi.password", "a-password") + } + companion object { private val testAppFolder = Files.createTempDirectory("test-app-folder").toFile() - private const val defaultBucket = "bio-fire-bucket" - private const val awsAccessKey = "anyKey" - private const val awsSecretKey = "anySecret" - private const val awsRegion = "anyRegion" - private const val failFactorEnv = "ITEST_FAIL_FACTOR" - private const val persistenceConcurrency = "10" + private const val DEFAULT_BUCKET = "bio-fire-bucket" + private const val AWS_ACCESS_KEY = "anyKey" + private const val AWS_SECRET_KEY = "anySecret" + private const val AWS_REGION = "anyRegion" + private const val FAIL_FACTOR_ENV = "ITEST_FAIL_FACTOR" + private const val PERSISTENCE_CONCURRENCY = "10" - private const val ftpUser = "ftpUser" - private const val ftpPassword = "ftpPassword" + private const val FTP_USER = "ftpUser" + private const val FTP_PASSWORD = "ftpPassword" - internal const val fixedDelayEnv = "ITEST_FIXED_DELAY" + internal const val FIXED_DELAY_ENV = "ITEST_FIXED_DELAY" internal val nfsSubmissionPath = testAppFolder.createDirectory("submission") internal val fireSubmissionPath = testAppFolder.createDirectory("submission-fire") - internal val firePath = testAppFolder.createDirectory("fire-db") + private val firePath = testAppFolder.createDirectory("fire-db") internal val fireTempFolder = testAppFolder.createDirectory("fire-temp") internal val nfsFtpPath = testAppFolder.createDirectory("ftpPath") @@ -126,7 +133,8 @@ class ITestListener : TestExecutionListener { internal val magicDirPath = testAppFolder.createDirectory("magic") internal val dropboxPath = testAppFolder.createDirectory("dropbox") - private val fireServer: WireMockServer by lazy { createFireApiMock(s3Container) } + private val fireServer: WireMockServer by lazy { createFireApiMock() } + private val doiServer: WireMockServer by lazy { createDoiApiMock() } private val ftpServer = createFtpServer() private val mongoContainer = createMongoContainer() @@ -149,29 +157,36 @@ class ITestListener : TestExecutionListener { .withStartupCheckStrategy(MinimumDurationRunningStartupCheckStrategy(ofSeconds(MINIMUM_RUNNING_TIME))) private fun createMockS3Container(): S3MockContainer = S3MockContainer("latest") - .withInitialBuckets(defaultBucket) + .withInitialBuckets(DEFAULT_BUCKET) private fun createFtpServer(): FtpServer { return FtpServer.createServer( FtpConfig( sslConfig = SslConfig(File(this::class.java.getResource("/mykeystore.jks").toURI()), "123456"), - userName = ftpUser, - password = ftpPassword + userName = FTP_USER, + password = FTP_PASSWORD ) ) } - private fun createFireApiMock(s3MockContainer: S3MockContainer): WireMockServer { - val factor = System.getenv(failFactorEnv)?.toInt() - val delay = System.getenv(fixedDelayEnv)?.toLong() ?: 0L + private fun createDoiApiMock(): WireMockServer { + val doiServer = WireMockServer(WireMockConfiguration().dynamicPort()) + doiServer.stubFor(post("/deposit").willReturn(okForJson("ok"))) + + return doiServer + } + + private fun createFireApiMock(): WireMockServer { + val factor = System.getenv(FAIL_FACTOR_ENV)?.toInt() + val delay = System.getenv(FIXED_DELAY_ENV)?.toLong() ?: 0L val transformer = newTransformer( subFolder = fireSubmissionPath.toPath(), ftpFolder = fireFtpPath.toPath(), dbFolder = firePath.toPath(), failFactor = factor, fixedDelay = delay, - httpEndpoint = s3MockContainer.httpEndpoint, - defaultBucket = defaultBucket + httpEndpoint = s3Container.httpEndpoint, + defaultBucket = DEFAULT_BUCKET ) return WireMockServer(WireMockConfiguration().dynamicPort().extensions(transformer)) } diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/files/UserFileApiTest.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/files/UserFileApiTest.kt index e334beb254..27db099120 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/files/UserFileApiTest.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/files/UserFileApiTest.kt @@ -33,7 +33,6 @@ class UserFileApiTest( @Autowired val securityTestService: SecurityTestService, @LocalServerPort val serverPort: Int, ) { - @BeforeAll fun init() { securityTestService.ensureUserRegistration(FilesUser) diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/FileListValidationTest.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/FileListValidationTest.kt index 763c93f71a..a02e4b36e5 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/FileListValidationTest.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/FileListValidationTest.kt @@ -19,6 +19,7 @@ import ebi.ac.uk.dsl.tsv.tsv import ebi.ac.uk.io.ext.createFile import ebi.ac.uk.io.ext.md5 import ebi.ac.uk.io.ext.size +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertThrows @@ -38,6 +39,7 @@ import uk.ac.ebi.fire.client.integration.web.FireClient @Import(FilePersistenceConfig::class) @ExtendWith(SpringExtension::class) +@OptIn(ExperimentalCoroutinesApi::class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class FileListValidationTest( @Autowired private val fireClient: FireClient, diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SpecialSubmissionAttributesTest.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SpecialSubmissionAttributesTest.kt index 62956b1d4d..11f707c9d6 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SpecialSubmissionAttributesTest.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SpecialSubmissionAttributesTest.kt @@ -11,6 +11,7 @@ import ac.uk.ebi.biostd.itest.itest.getWebClient import ac.uk.ebi.biostd.persistence.common.service.SubmissionPersistenceQueryService import ac.uk.ebi.biostd.persistence.model.DbTag import ac.uk.ebi.biostd.persistence.repositories.TagDataRepository +import ac.uk.ebi.biostd.submission.model.DoiRequest.Companion.BS_DOI_ID import arrow.core.Either import ebi.ac.uk.asserts.assertThat import ebi.ac.uk.dsl.section @@ -334,4 +335,33 @@ class SpecialSubmissionAttributesTest( assertFiles(section.files, fileName) assertSubSections(section.sections) } + + @Test + fun `15-6 submission with DOI`() = runTest { + val submission = tsv { + line("Submission", "S-STBL125") + line("Title", "Submission with DOI") + line("DOI") + + line("Study", "SECT-001") + line() + + line("Author") + line("Name", "Jane Doe") + line("ORCID", "1234-5678-9101-1121") + line("Affiliation", "o1") + line() + + line("Organization", "o1") + line("Name", "EMBL") + line() + }.toString() + + assertThat(webClient.submitSingle(submission, TSV)).isSuccessful() + + val savedSubmission = submissionRepository.getExtByAccNo("S-STBL125") + assertThat(savedSubmission.accNo).isEqualTo("S-STBL125") + assertThat(savedSubmission.title).isEqualTo("Submission with DOI") + assertThat(savedSubmission.doi).isEqualTo("$BS_DOI_ID/S-STBL125") + } } diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SubmissionPerformanceTest.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SubmissionPerformanceTest.kt index 52a203a895..4fb9e3b2df 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SubmissionPerformanceTest.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/test/submission/submit/SubmissionPerformanceTest.kt @@ -5,7 +5,7 @@ import ac.uk.ebi.biostd.client.integration.web.BioWebClient import ac.uk.ebi.biostd.common.config.FilePersistenceConfig import ac.uk.ebi.biostd.itest.common.SecurityTestService import ac.uk.ebi.biostd.itest.entities.SuperUser -import ac.uk.ebi.biostd.itest.itest.ITestListener.Companion.fixedDelayEnv +import ac.uk.ebi.biostd.itest.itest.ITestListener.Companion.FIXED_DELAY_ENV import ac.uk.ebi.biostd.itest.itest.ITestListener.Companion.tempFolder import ac.uk.ebi.biostd.itest.itest.getWebClient import ebi.ac.uk.dsl.tsv.line @@ -42,11 +42,11 @@ class SubmissionPerformanceTest( } @Test - @EnabledIfEnvironmentVariable(named = fixedDelayEnv, matches = "\\d+") + @EnabledIfEnvironmentVariable(named = FIXED_DELAY_ENV, matches = "\\d+") @EnabledIfSystemProperty(named = "enableFire", matches = "true") fun `With many files`() { val files = 100 - val delay = System.getenv(fixedDelayEnv).toLong() + val delay = System.getenv(FIXED_DELAY_ENV).toLong() val subFiles = (1..files).map { tempFolder.createFile("$it.txt") } webClient.uploadFiles(subFiles) diff --git a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/wiremock/TestWireMockTransformer.kt b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/wiremock/TestWireMockTransformer.kt index 8ad9a23768..51fe2e1bfd 100644 --- a/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/wiremock/TestWireMockTransformer.kt +++ b/submission/submission-webapp/src/itest/kotlin/ac/uk/ebi/biostd/itest/wiremock/TestWireMockTransformer.kt @@ -25,7 +25,7 @@ import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR import java.nio.file.Path import kotlin.random.Random -class TestWireMockTransformer constructor( +class TestWireMockTransformer( private val failFactor: Int?, private val fixedDelay: Long, private val handlers: List, diff --git a/submission/submission-webapp/src/itest/resources/itestsInventory/itestsInventory.md b/submission/submission-webapp/src/itest/resources/itestsInventory/itestsInventory.md index 829924bf31..b0f0af15a7 100644 --- a/submission/submission-webapp/src/itest/resources/itestsInventory/itestsInventory.md +++ b/submission/submission-webapp/src/itest/resources/itestsInventory/itestsInventory.md @@ -95,6 +95,7 @@ | SpecialSubmissionAttributesTest | 15-3 | new submission with empty accNo subsection table | | | SpecialSubmissionAttributesTest | 15-4 | new submission with empty-null attributes | | | SpecialSubmissionAttributesTest | 15-5 | new submission with empty-null table attributes | | +| SpecialSubmissionAttributesTest | 15-6 | submission with DOI | | | SubmissionApiTest | 16-1 | submit with submission object | Performs different simple submission cases, no accNo, invalid link url, rootPath, and others. | | SubmissionApiTest | 16-2 | empty accNo | | | SubmissionApiTest | 16-3 | submission with root path | | diff --git a/submission/submission-webapp/src/main/kotlin/ac/uk/ebi/biostd/common/config/SubmitterConfig.kt b/submission/submission-webapp/src/main/kotlin/ac/uk/ebi/biostd/common/config/SubmitterConfig.kt index 6e2fc597ee..aa45b95ad0 100644 --- a/submission/submission-webapp/src/main/kotlin/ac/uk/ebi/biostd/common/config/SubmitterConfig.kt +++ b/submission/submission-webapp/src/main/kotlin/ac/uk/ebi/biostd/common/config/SubmitterConfig.kt @@ -16,6 +16,7 @@ import ac.uk.ebi.biostd.persistence.filesystem.api.FileStorageService import ac.uk.ebi.biostd.persistence.filesystem.pagetab.PageTabService import ac.uk.ebi.biostd.submission.service.AccNoService import ac.uk.ebi.biostd.submission.service.CollectionProcessor +import ac.uk.ebi.biostd.submission.service.DoiService import ac.uk.ebi.biostd.submission.service.FileSourcesService import ac.uk.ebi.biostd.submission.service.TimesService import ac.uk.ebi.biostd.submission.submitter.ExtSubmissionSubmitter @@ -193,6 +194,7 @@ class SubmitterConfig( @Bean fun submissionProcessor( + doiService: DoiService, persistenceService: SubmissionPersistenceService, timesService: TimesService, accNoService: AccNoService, @@ -201,6 +203,7 @@ class SubmitterConfig( toExtSectionMapper: ToExtSectionMapper, ): SubmissionProcessor = SubmissionProcessor( + doiService, persistenceService, timesService, accNoService, @@ -235,7 +238,6 @@ class SubmitterConfig( } @Configuration - @Suppress("MagicNumber") @EnableConfigurationProperties class ServiceConfig( private val service: PersistenceService, @@ -246,6 +248,9 @@ class SubmitterConfig( @Bean fun accNoPatternUtil() = AccNoPatternUtil() + @Bean + fun doiService(webClient: WebClient) = DoiService(webClient, properties.doi) + @Bean fun accNoService() = AccNoService(service, accNoPatternUtil(), userPrivilegesService, properties.subBasePath) diff --git a/submission/submission-webapp/src/main/resources/application-local.yml b/submission/submission-webapp/src/main/resources/application-local.yml index 313fdabc26..d7da485332 100644 --- a/submission/submission-webapp/src/main/resources/application-local.yml +++ b/submission/submission-webapp/src/main/resources/application-local.yml @@ -56,3 +56,8 @@ app: requireActivation: false persistence: concurrency: 5 + doi: + endpoint: https://test-endpoint.org + uiUrl: https://www.biostudies.ac.uk + user: a-user + password: a-password diff --git a/submission/submission-webapp/src/main/resources/application.yml b/submission/submission-webapp/src/main/resources/application.yml index 880436deaa..89046428ae 100644 --- a/submission/submission-webapp/src/main/resources/application.yml +++ b/submission/submission-webapp/src/main/resources/application.yml @@ -116,3 +116,8 @@ app: notifications: requestQueue: submission-request-submitter-queue # queue used to handle request stages requestRoutingKey: bio.submission.requested # request messages routing key + doi: + endpoint: # DOI registration endpoint + uiUrl: # The BioStudies UI URL which will be associated to the DOI record + user: # User for the DOI service + password: # Password for the DOI service diff --git a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/stats/domain/service/SubmissionStatsServiceTest.kt b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/stats/domain/service/SubmissionStatsServiceTest.kt index 6b11f9a50e..52a7d03fbf 100644 --- a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/stats/domain/service/SubmissionStatsServiceTest.kt +++ b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/stats/domain/service/SubmissionStatsServiceTest.kt @@ -18,6 +18,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import io.mockk.mockkStatic import io.mockk.slot +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest @@ -31,6 +32,7 @@ import uk.ac.ebi.extended.serialization.service.fileSequence import java.io.File @ExtendWith(MockKExtension::class) +@OptIn(ExperimentalCoroutinesApi::class) class SubmissionStatsServiceTest( @MockK private val statsFileHandler: StatsFileHandler, @MockK private val tempFileGenerator: TempFileGenerator, diff --git a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/domain/service/SubmissionStagesHandlerTest.kt b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/domain/service/SubmissionStagesHandlerTest.kt index 4361a03ded..3354f7107d 100644 --- a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/domain/service/SubmissionStagesHandlerTest.kt +++ b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/domain/service/SubmissionStagesHandlerTest.kt @@ -19,6 +19,7 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test @@ -26,6 +27,7 @@ import org.junit.jupiter.api.extension.ExtendWith import uk.ac.ebi.events.service.EventsPublisherService @ExtendWith(MockKExtension::class) +@OptIn(ExperimentalCoroutinesApi::class) class SubmissionStagesHandlerTest( @MockK private val statsService: SubmissionStatsService, @MockK private val submissionSubmitter: SubmissionSubmitter, diff --git a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitterTest.kt b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitterTest.kt index a0c0f6c380..a9c48aec69 100644 --- a/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitterTest.kt +++ b/submission/submission-webapp/src/test/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitterTest.kt @@ -9,6 +9,7 @@ import ebi.ac.uk.test.basicExtSubmission import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import io.mockk.slot @@ -16,6 +17,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -23,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith @ExtendWith(MockKExtension::class) @OptIn(ExperimentalCoroutinesApi::class) class SubmissionSubmitterTest( + @MockK private val request: SubmitRequest, @MockK private val submissionSubmitter: ExtSubmissionSubmitter, @MockK private val submissionProcessor: SubmissionProcessor, @MockK private val collectionValidationService: CollectionValidationService, @@ -38,21 +41,19 @@ class SubmissionSubmitterTest( @AfterEach fun afterEach() = clearAllMocks() + @BeforeEach + fun beforeEach() { + setUpRequest() + setUpDraftService() + } + @Test - fun `create request`(@MockK request: SubmitRequest) = runTest { + fun `create request`() = runTest { val submission = basicExtSubmission val extRequestSlot = slot() - coEvery { request.draftKey } returns "TMP_123" - coEvery { request.owner } returns submission.owner - coEvery { request.accNo } returns submission.accNo coEvery { submissionProcessor.processSubmission(request) } returns submission - coEvery { draftService.setAcceptedStatus("TMP_123") } answers { nothing } coEvery { collectionValidationService.executeCollectionValidators(submission) } answers { nothing } - coEvery { draftService.setActiveStatus("TMP_123") } answers { nothing } - coEvery { draftService.setProcessingStatus(submission.owner, "TMP_123") } answers { nothing } - coEvery { draftService.setAcceptedStatus("S-TEST123") } answers { nothing } - coEvery { draftService.deleteSubmissionDraft(submission.submitter, "S-TEST123") } answers { nothing } coEvery { submissionSubmitter.createRequest(capture(extRequestSlot)) } returns (submission.accNo to submission.version) @@ -75,16 +76,10 @@ class SubmissionSubmitterTest( } @Test - fun `create with failure on validation`(@MockK request: SubmitRequest) = runTest { + fun `create with failure on validation`() = runTest { val submission = basicExtSubmission val extRequestSlot = slot() - coEvery { request.draftKey } returns "TMP_123" - coEvery { request.owner } returns submission.owner - coEvery { request.accNo } returns submission.accNo - coEvery { draftService.setActiveStatus("TMP_123") } answers { nothing } - coEvery { draftService.setProcessingStatus(submission.owner, "TMP_123") } answers { nothing } - coEvery { draftService.setAcceptedStatus("TMP_123") } answers { nothing } coEvery { submissionProcessor.processSubmission(request) } throws RuntimeException("validation error") assertThrows { testInstance.createRequest(request) } @@ -100,4 +95,19 @@ class SubmissionSubmitterTest( draftService.setAcceptedStatus("TMP_123") } } + + private fun setUpRequest() { + every { request.draftKey } returns "TMP_123" + every { request.owner } returns basicExtSubmission.owner + every { request.accNo } returns basicExtSubmission.accNo + every { request.previousVersion } returns null + } + + private fun setUpDraftService() { + coEvery { draftService.setAcceptedStatus("TMP_123") } answers { nothing } + coEvery { draftService.setActiveStatus("TMP_123") } answers { nothing } + coEvery { draftService.setAcceptedStatus("S-TEST123") } answers { nothing } + coEvery { draftService.setProcessingStatus(basicExtSubmission.owner, "TMP_123") } answers { nothing } + coEvery { draftService.deleteSubmissionDraft(basicExtSubmission.submitter, "S-TEST123") } answers { nothing } + } } diff --git a/submission/submitter/build.gradle.kts b/submission/submitter/build.gradle.kts index 6fe1c5ce9e..c7ea68b548 100644 --- a/submission/submitter/build.gradle.kts +++ b/submission/submitter/build.gradle.kts @@ -21,6 +21,7 @@ import SpringBootDependencies.SpringBootStarterWeb import TestDependencies.BaseTestCompileDependencies import TestDependencies.BaseTestRuntimeDependencies import TestDependencies.KotlinCoroutinesTest +import TestDependencies.KotlinXmlBuilder import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension import org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES @@ -53,6 +54,7 @@ dependencies { implementation(KotlinReflect) implementation(KotlinStdLib) implementation(KotlinLogging) + implementation(KotlinXmlBuilder) implementation(SpringBootStarterDataJpa) implementation(SpringBootStarterWeb) diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/exceptions/DoiExceptions.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/exceptions/DoiExceptions.kt new file mode 100644 index 0000000000..00704f3732 --- /dev/null +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/exceptions/DoiExceptions.kt @@ -0,0 +1,25 @@ +package ac.uk.ebi.biostd.submission.exceptions + +class MissingDoiFieldException(field: String) : RuntimeException("The required DOI field '$field' could not be found") + +class MissingTitleException : RuntimeException("A title is required for DOI registration") + +class InvalidOrgNameException(org: String) : RuntimeException("The following organization name is empty: '$org'") + +class InvalidOrgException : RuntimeException("Organizations are required to have an accession") + +class InvalidAuthorNameException : RuntimeException("Authors are required to have a name") + +class MissingAuthorAffiliationException : RuntimeException("Authors are required to have an affiliation") + +class InvalidAuthorAffiliationException( + author: String, + organization: String, +) : RuntimeException("The organization '$organization' affiliated to the author '$author' could not be found") + +class RemovedDoiException(previous: String) : RuntimeException("The previous DOI: '$previous' cannot be removed") + +class InvalidDoiException( + given: String, + previous: String, +) : RuntimeException("The given DOI '$given' should match the previous DOI '$previous'") diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/model/DoiRequest.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/model/DoiRequest.kt new file mode 100644 index 0000000000..dd97a715d9 --- /dev/null +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/model/DoiRequest.kt @@ -0,0 +1,91 @@ +package ac.uk.ebi.biostd.submission.model + +import ebi.ac.uk.util.collections.ifNotEmpty +import org.redundent.kotlin.xml.PrintOptions +import org.redundent.kotlin.xml.xml + +data class Contributor( + val name: String, + val surname: String, + val affiliation: String, + val orcid: String? +) + +class DoiRequest( + private val accNo: String, + private val title: String, + private val timestamp: String, + private val instanceUrl: String, + private val contributors: List, +) { + val doi: String + get() = "$BS_DOI_ID/$accNo" + + fun asXmlRequest(): String { + return xml("doi_batch") { + xmlns = XML_NAMESPACE + attribute("xmlns:xsi", XML_SCHEMA_INSTANCE) + attribute("version", XML_SCHEMA_VERSION) + attribute("xsi:schemaLocation", "$XML_SCHEMA_LOCATION_1 $XML_SCHEMA_LOCATION_2") + "head" { + "doi_batch_id" { -timestamp } + "timestamp" { -timestamp } + "depositor" { + "depositor_name" { -DEPOSITOR } + "email_address" { -EMAIL } + } + "registrant" { -DEPOSITOR } + } + "body" { + "database" { + "database_metadata" { + attribute("language", "en") + "titles" { + "title" { -BS_TITLE } + } + } + "dataset" { + contributors.ifNotEmpty { + "contributors" { + contributors.forEach { contributor -> + "person_name" { + attribute("contributor_role", "author") + attribute("sequence", "first") + "given_name" { -contributor.name } + "surname" { -contributor.surname } + "affiliation" { -contributor.affiliation } + contributor.orcid?.let { orcid -> + "ORCID" { + attribute("authenticated", "false") + -orcid + } + } + } + } + } + } + "titles" { + "title" { -title } + } + "doi_data" { + "doi" { -doi } + "resource" { -"$instanceUrl/studies/$accNo" } + } + } + } + } + }.toString(PrintOptions(pretty = true, singleLineTextElements = true, indent = " ")) + } + + companion object { + const val BS_DOI_ID = "10.6019" + const val BS_TITLE = "BioStudies Database" + const val DEPOSITOR = "EMBL-EBI" + const val EMAIL = "biostudies@ebi.ac.uk" + const val XML_NAMESPACE = "http://www.crossref.org/schema/4.4.1" + const val XML_SCHEMA_VERSION = "4.4.1" + const val XML_SCHEMA_INSTANCE = "http://www.w3.org/2001/XMLSchema-instance" + const val XML_SCHEMA_LOCATION_1 = "http://www.crossref.org/schema/4.4.1" + const val XML_SCHEMA_LOCATION_2 = "http://www.crossref.org/schema/deposit/crossref4.4.1.xsd" + } +} diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/service/DoiService.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/service/DoiService.kt new file mode 100644 index 0000000000..a4dc792dca --- /dev/null +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/service/DoiService.kt @@ -0,0 +1,143 @@ +package ac.uk.ebi.biostd.submission.service + +import ac.uk.ebi.biostd.common.properties.DoiProperties +import ac.uk.ebi.biostd.submission.exceptions.InvalidAuthorAffiliationException +import ac.uk.ebi.biostd.submission.exceptions.InvalidAuthorNameException +import ac.uk.ebi.biostd.submission.exceptions.InvalidDoiException +import ac.uk.ebi.biostd.submission.exceptions.InvalidOrgException +import ac.uk.ebi.biostd.submission.exceptions.InvalidOrgNameException +import ac.uk.ebi.biostd.submission.exceptions.MissingAuthorAffiliationException +import ac.uk.ebi.biostd.submission.exceptions.MissingDoiFieldException +import ac.uk.ebi.biostd.submission.exceptions.MissingTitleException +import ac.uk.ebi.biostd.submission.exceptions.RemovedDoiException +import ac.uk.ebi.biostd.submission.model.Contributor +import ac.uk.ebi.biostd.submission.model.DoiRequest +import ac.uk.ebi.biostd.submission.model.SubmitRequest +import ebi.ac.uk.commons.http.builder.httpHeadersOf +import ebi.ac.uk.commons.http.builder.linkedMultiValueMapOf +import ebi.ac.uk.commons.http.ext.RequestParams +import ebi.ac.uk.commons.http.ext.post +import ebi.ac.uk.io.FileUtils +import ebi.ac.uk.model.Section +import ebi.ac.uk.model.Submission +import ebi.ac.uk.model.constants.SubFields.DOI +import ebi.ac.uk.model.extensions.allSections +import ebi.ac.uk.model.extensions.title +import mu.KotlinLogging +import org.springframework.core.io.FileSystemResource +import org.springframework.http.HttpHeaders.CONTENT_TYPE +import org.springframework.http.MediaType.MULTIPART_FORM_DATA +import org.springframework.web.reactive.function.client.WebClient +import java.nio.file.Files +import java.time.Instant + +private val logger = KotlinLogging.logger {} + +@Suppress("ThrowsCount", "ReturnCount") +class DoiService( + private val webClient: WebClient, + private val properties: DoiProperties, +) { + fun calculateDoi(accNo: String, rqt: SubmitRequest): String? { + val doi = rqt.submission.attributes.find { it.name == DOI.name } ?: return null + val previousDoi = rqt.previousVersion?.doi + + if (previousDoi != null) { + val value = doi.value + requireNotNull(value) { throw RemovedDoiException(previousDoi) } + require(value == previousDoi) { throw InvalidDoiException(value, previousDoi) } + return previousDoi + } + + return registerDoi(accNo, rqt) + } + + private fun registerDoi(accNo: String, rqt: SubmitRequest): String { + val sub = rqt.submission + val timestamp = Instant.now().epochSecond.toString() + val title = requireNotNull(sub.title) { throw MissingTitleException() } + val request = DoiRequest(accNo, title, timestamp, properties.uiUrl, getContributors(sub)) + val requestFile = Files.createTempFile("${TEMP_FILE_NAME}_$accNo", ".xml").toFile() + FileUtils.writeContent(requestFile, request.asXmlRequest()) + + val headers = httpHeadersOf(CONTENT_TYPE to MULTIPART_FORM_DATA) + val body = linkedMultiValueMapOf( + USER_PARAM to properties.user, + PASSWORD_PARAM to properties.password, + OPERATION_PARAM to OPERATION_PARAM_VALUE, + FILE_PARAM to FileSystemResource(requestFile), + ) + + webClient.post(properties.endpoint, RequestParams(headers, body)) + logger.info { "$accNo ${rqt.owner} Registered DOI: '${request.doi}'" } + + return request.doi + } + + private fun getContributors(sub: Submission): List { + val organizations = getOrganizations(sub) + val contributors = sub.allSections().filter { it.type == AUTHOR_TYPE } + validateContributors(contributors, organizations) + + return contributors.map { it.asContributor(organizations) } + } + + private fun validateContributors(contributors: List
, organizations: Map) { + fun validate(contributor: Section) { + val names = requireNotNull(contributor.find(NAME_ATTR)) { throw InvalidAuthorNameException() } + val org = requireNotNull(contributor.find(AFFILIATION_ATTR)) { throw MissingAuthorAffiliationException() } + requireNotNull(organizations[org]) { throw InvalidAuthorAffiliationException(names, org) } + } + + contributors + .ifEmpty { throw MissingDoiFieldException(AUTHOR_TYPE) } + .forEach(::validate) + } + + private fun Section.asContributor(organizations: Map): Contributor { + val names = find(NAME_ATTR)!! + val affiliation = find(AFFILIATION_ATTR)!! + + return Contributor( + name = names.substringBeforeLast(" ", ""), + surname = names.substringAfterLast(" "), + affiliation = organizations[affiliation]!!, + orcid = find(ORCID_ATTR) + ) + } + + private fun getOrganizations(sub: Submission): Map { + val organizations = sub.allSections().filter { it.type == ORG_TYPE } + validateOrganizations(organizations) + + return organizations.associateBy({ it.accNo!! }, { it.find(NAME_ATTR)!! }) + } + + private fun validateOrganizations(organizations: List
) { + fun validate(org: Section) { + val accNo = org.accNo + requireNotNull(accNo) { throw InvalidOrgException() } + requireNotNull(org.find(NAME_ATTR)) { throw InvalidOrgNameException(accNo) } + } + + organizations + .ifEmpty { throw MissingDoiFieldException(ORG_TYPE) } + .forEach(::validate) + } + + companion object { + internal const val AFFILIATION_ATTR = "Affiliation" + internal const val NAME_ATTR = "Name" + internal const val ORCID_ATTR = "ORCID" + + internal const val ORG_TYPE = "Organization" + internal const val AUTHOR_TYPE = "Author" + + internal const val FILE_PARAM = "fname" + internal const val OPERATION_PARAM = "operation" + internal const val OPERATION_PARAM_VALUE = "doMDUpload" + internal const val PASSWORD_PARAM = "login_password" + internal const val USER_PARAM = "login_id" + internal const val TEMP_FILE_NAME = "doi-request" + } +} diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionProcessor.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionProcessor.kt index 4fcba18aff..2e348c1b33 100644 --- a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionProcessor.kt +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionProcessor.kt @@ -5,6 +5,7 @@ import ac.uk.ebi.biostd.persistence.common.service.SubmissionPersistenceService import ac.uk.ebi.biostd.submission.model.SubmitRequest import ac.uk.ebi.biostd.submission.service.AccNoService import ac.uk.ebi.biostd.submission.service.CollectionProcessor +import ac.uk.ebi.biostd.submission.service.DoiService import ac.uk.ebi.biostd.submission.service.TimesService import ebi.ac.uk.extended.mapping.from.ToExtSectionMapper import ebi.ac.uk.extended.mapping.from.toExtAttributes @@ -27,6 +28,7 @@ private val logger = KotlinLogging.logger {} @Suppress("LongParameterList") class SubmissionProcessor( + private val doiService: DoiService, private val persistenceService: SubmissionPersistenceService, private val timesService: TimesService, private val accNoService: AccNoService, @@ -42,6 +44,7 @@ class SubmissionProcessor( logger.info { "${rqt.accNo} ${rqt.owner} Assigned accNo '$accNoString' to draft with key '${rqt.draftKey}'" } + val doi = doiService.calculateDoi(accNoString, rqt) val version = persistenceService.getNextVersion(accNoString) val secretKey = previousVersion?.secretKey ?: UUID.randomUUID().toString() val relPath = accNoService.getRelPath(accNo) @@ -55,6 +58,7 @@ class SubmissionProcessor( schemaVersion = DEFAULT_SCHEMA_VERSION, method = getMethod(method), title = submission.title, + doi = doi, relPath = relPath, rootPath = submission.rootPath, released = released, diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitter.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitter.kt index ac89a691d6..56227b18dc 100644 --- a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitter.kt +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/SubmissionSubmitter.kt @@ -70,7 +70,6 @@ class SubmissionSubmitter( private suspend fun processRequest(rqt: SubmitRequest): ExtSubmission { try { logger.info { "${rqt.accNo} ${rqt.owner} Started processing submission request" } - rqt.draftKey?.let { startProcessingDraft(rqt.accNo, rqt.owner, it) } val processed = submissionProcessor.processSubmission(rqt) collectionValidationService.executeCollectionValidators(processed) diff --git a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/request/SubmissionRequestSaver.kt b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/request/SubmissionRequestSaver.kt index a5edeb59e7..381117f200 100644 --- a/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/request/SubmissionRequestSaver.kt +++ b/submission/submitter/src/main/kotlin/ac/uk/ebi/biostd/submission/submitter/request/SubmissionRequestSaver.kt @@ -24,9 +24,9 @@ class SubmissionRequestSaver( val sub = request.submission logger.info { "$accNo ${sub.owner} Started saving submission '${sub.accNo}', version={${sub.version}}" } - val assemble = assembleSubmission(sub) + val assembled = assembleSubmission(sub) persistenceService.expirePreviousVersions(sub.accNo) - val saved = persistenceService.saveSubmission(assemble) + val saved = persistenceService.saveSubmission(assembled) logger.info { "$accNo ${sub.owner} Finished saving submission '${sub.accNo}', version={${sub.version}}" } requestService.saveSubmissionRequest(request.withNewStatus(PERSISTED)) diff --git a/submission/submitter/src/test/kotlin/ac/uk/ebi/biostd/submission/service/DoiServiceTest.kt b/submission/submitter/src/test/kotlin/ac/uk/ebi/biostd/submission/service/DoiServiceTest.kt new file mode 100644 index 0000000000..a2b56f98a7 --- /dev/null +++ b/submission/submitter/src/test/kotlin/ac/uk/ebi/biostd/submission/service/DoiServiceTest.kt @@ -0,0 +1,432 @@ +package ac.uk.ebi.biostd.submission.service + +import ac.uk.ebi.biostd.common.properties.DoiProperties +import ac.uk.ebi.biostd.submission.exceptions.InvalidAuthorAffiliationException +import ac.uk.ebi.biostd.submission.exceptions.InvalidAuthorNameException +import ac.uk.ebi.biostd.submission.exceptions.InvalidDoiException +import ac.uk.ebi.biostd.submission.exceptions.InvalidOrgException +import ac.uk.ebi.biostd.submission.exceptions.InvalidOrgNameException +import ac.uk.ebi.biostd.submission.exceptions.MissingAuthorAffiliationException +import ac.uk.ebi.biostd.submission.exceptions.MissingDoiFieldException +import ac.uk.ebi.biostd.submission.exceptions.MissingTitleException +import ac.uk.ebi.biostd.submission.exceptions.RemovedDoiException +import ac.uk.ebi.biostd.submission.model.DoiRequest.Companion.BS_DOI_ID +import ac.uk.ebi.biostd.submission.model.SubmitRequest +import ac.uk.ebi.biostd.submission.service.DoiService.Companion.FILE_PARAM +import ac.uk.ebi.biostd.submission.service.DoiService.Companion.OPERATION_PARAM +import ac.uk.ebi.biostd.submission.service.DoiService.Companion.OPERATION_PARAM_VALUE +import ac.uk.ebi.biostd.submission.service.DoiService.Companion.PASSWORD_PARAM +import ac.uk.ebi.biostd.submission.service.DoiService.Companion.USER_PARAM +import ebi.ac.uk.dsl.attribute +import ebi.ac.uk.dsl.section +import ebi.ac.uk.dsl.submission +import ebi.ac.uk.extended.model.ExtSubmission +import ebi.ac.uk.model.constants.MULTIPART_FORM_DATA +import ebi.ac.uk.model.extensions.title +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.verify +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.core.io.FileSystemResource +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpHeaders.CONTENT_TYPE +import org.springframework.util.LinkedMultiValueMap +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec +import java.time.Instant +import java.time.OffsetDateTime +import java.time.ZoneOffset.UTC +import java.util.function.Consumer + +@ExtendWith(MockKExtension::class) +class DoiServiceTest( + @MockK private val webClient: WebClient, + @MockK private val submitRequest: SubmitRequest, + @MockK private val previousVersion: ExtSubmission, +) { + private val testInstance = DoiService(webClient, properties) + private val mockNow = OffsetDateTime.of(2020, 9, 21, 10, 11, 0, 0, UTC).toInstant() + + @AfterEach + fun afterEach() = clearAllMocks() + + @BeforeEach + fun beforeEach() { + mockkStatic(Instant::class) + every { Instant.now() } returns mockNow + every { submitRequest.previousVersion } returns null + } + + @Test + fun `doi registration`( + @MockK requestSpec: RequestBodySpec, + ) { + val headersSlot = slot>() + val bodySlot = slot>() + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Organization") { + accNo = "o1" + attribute("Name", "EMBL") + } + + section("Author") { + attribute("Name", "John Doe") + attribute("ORCID", "12-32-45-82") + attribute("Affiliation", "o1", ref = true) + } + } + } + + every { submitRequest.submission } returns submission + every { webClient.post().uri(properties.endpoint) } returns requestSpec + every { requestSpec.bodyValue(capture(bodySlot)) } returns requestSpec + every { requestSpec.headers(capture(headersSlot)) } returns requestSpec + every { requestSpec.retrieve().bodyToMono(String::class.java).block() } returns "OK" + + val doi = testInstance.calculateDoi(TEST_ACC_NO, submitRequest) + val body = bodySlot.captured + val headers = headersSlot.captured + val requestFile = body[FILE_PARAM]!!.first() as FileSystemResource + + assertThat(doi).isEqualTo("$BS_DOI_ID/$TEST_ACC_NO") + assertThat(requestFile.file.readText()).isEqualToIgnoringWhitespace(EXPECTED_DOI_REQUEST) + assertThat(body[USER_PARAM]!!.first()).isEqualTo(properties.user) + assertThat(body[PASSWORD_PARAM]!!.first()).isEqualTo(properties.password) + assertThat(body[OPERATION_PARAM]!!.first()).isEqualTo(OPERATION_PARAM_VALUE) + headers.andThen { assertThat(it[CONTENT_TYPE]!!.first()).isEqualTo(MULTIPART_FORM_DATA) } + verify(exactly = 1) { + webClient.post().uri(properties.endpoint) + requestSpec.bodyValue(body) + requestSpec.retrieve().bodyToMono(String::class.java).block() + } + } + + @Test + fun `doi not requested`() { + val submission = submission { + title = "Test Submission" + + section("Study") { + attribute("Type", "Experiment") + } + } + + every { submitRequest.submission } returns submission + + assertThat(testInstance.calculateDoi(TEST_ACC_NO, submitRequest)).isNull() + verify(exactly = 0) { webClient.post() } + } + + @Test + fun `already existing DOI`() { + val previousVersionDoi = "$BS_DOI_ID/$TEST_ACC_NO" + val submission = submission { + title = "Test Submission" + attribute("DOI", "$BS_DOI_ID/$TEST_ACC_NO") + + section("Study") { + attribute("Type", "Experiment") + } + } + + every { submitRequest.submission } returns submission + every { previousVersion.doi } returns previousVersionDoi + every { submitRequest.previousVersion } returns previousVersion + + val doi = testInstance.calculateDoi(TEST_ACC_NO, submitRequest) + + assertThat(doi).isEqualTo(previousVersionDoi) + verify(exactly = 0) { webClient.post() } + } + + @Test + fun `invalid given DOI`() { + val doi = "10.287.71/$TEST_ACC_NO" + val previousDoi = "$BS_DOI_ID/$TEST_ACC_NO" + val submission = submission { + title = "Test Submission" + attribute("DOI", doi) + + section("Study") { + attribute("Type", "Experiment") + } + } + + every { submitRequest.submission } returns submission + every { previousVersion.doi } returns previousDoi + every { submitRequest.previousVersion } returns previousVersion + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + assertThat(exception.message).isEqualTo("The given DOI '$doi' should match the previous DOI '$previousDoi'") + verify(exactly = 0) { webClient.post() } + } + + @Test + fun `removed DOI`() { + val previousDoi = "$BS_DOI_ID/$TEST_ACC_NO" + val submission = submission { + title = "Test Submission" + attribute("DOI", null) + + section("Study") { + attribute("Type", "Experiment") + } + } + + every { submitRequest.submission } returns submission + every { previousVersion.doi } returns previousDoi + every { submitRequest.previousVersion } returns previousVersion + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + assertThat(exception.message).isEqualTo("The previous DOI: '$previousDoi' cannot be removed") + verify(exactly = 0) { webClient.post() } + } + + @Test + fun `missing title`() { + val submission = submission { + attribute("DOI", "") + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("A title is required for DOI registration") + } + + @Test + fun `missing organization`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Author") { + attribute("Name", "John Doe") + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("The required DOI field 'Organization' could not be found") + } + + @Test + fun `missing organization accNo`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Organization") { + attribute("Name", "EMBL") + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("Organizations are required to have an accession") + } + + @Test + fun `missing organization name`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Organization") { + accNo = "o1" + attribute("Name", "American Society") + } + + section("Organization") { + accNo = "o2" + attribute("Name", "EMBL") + } + + section("Organization") { + accNo = "o3" + attribute("Research Associate", "Astrazeneca") + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { testInstance.calculateDoi(TEST_ACC_NO, submitRequest) } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("The following organization name is empty: 'o3'") + } + + @Test + fun `missing author name`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Organization") { + accNo = "o1" + attribute("Name", "EMBL") + } + + section("Author") { + attribute("P.I.", "John Doe") + attribute("ORCID", "12-32-45-82") + attribute("Affiliation", "o1", ref = true) + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { + testInstance.calculateDoi(TEST_ACC_NO, submitRequest) + } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("Authors are required to have a name") + } + + @Test + fun `missing affiliation`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", "") + + section("Study") { + section("Organization") { + accNo = "o1" + attribute("Name", "EMBL") + } + + section("Author") { + attribute("Name", "John Doe") + attribute("ORCID", "12-32-45-82") + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { + testInstance.calculateDoi(TEST_ACC_NO, submitRequest) + } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message).isEqualTo("Authors are required to have an affiliation") + } + + @Test + fun `invalid affiliation`() { + val submission = submission { + title = "Test Submission" + attribute("DOI", null) + + section("Study") { + section("Organization") { + accNo = "o1" + attribute("Name", "EMBL") + } + + section("Author") { + attribute("Name", "John Doe") + attribute("ORCID", "12-32-45-82") + attribute("Affiliation", "o2", ref = true) + } + } + } + + every { submitRequest.submission } returns submission + + val exception = assertThrows { + testInstance.calculateDoi(TEST_ACC_NO, submitRequest) + } + + verify(exactly = 0) { webClient.post() } + assertThat(exception.message) + .isEqualTo("The organization 'o2' affiliated to the author 'John Doe' could not be found") + } + + companion object { + private const val TEST_ACC_NO = "S-TEST123" + + private val properties = DoiProperties( + endpoint = "https://test-endpoint.org", + uiUrl = "https://www.biostudies.ac.uk", + user = "a-user", + password = "a-password", + ) + + private const val EXPECTED_DOI_REQUEST = """ + + + 1600683060 + 1600683060 + + EMBL-EBI + biostudies@ebi.ac.uk + + EMBL-EBI + + + + + + BioStudies Database + + + + + + John + Doe + EMBL + 12-32-45-82 + + + + Test Submission + + + 10.6019/S-TEST123 + https://www.biostudies.ac.uk/studies/S-TEST123 + + + + + + """ + } +}