From fbc7539e288c8b3a2711809fcb1c7b440831d6a4 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 10:38:56 +0200 Subject: [PATCH 01/14] Initial write enabled flag --- .../backend/service/crossref/CrossRefService.kt | 9 +++++++++ .../backend/service/crossref/CrossRefServiceTest.kt | 13 +++++++++++++ backend/src/test/resources/application.properties | 1 + kubernetes/loculus/templates/loculus-backend.yaml | 3 +++ kubernetes/loculus/values.schema.json | 6 ++++++ 5 files changed, 32 insertions(+) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt index 57cb1c6ec3..130d106392 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt @@ -35,6 +35,7 @@ data class CrossRefServiceProperties( val email: String?, val organization: String?, val hostUrl: String?, + val writeEnabled: Boolean?, ) data class DoiEntry( @@ -62,6 +63,7 @@ class CrossRefService(private val properties: CrossRefServiceProperties, private properties.email != null && properties.organization != null && properties.hostUrl != null + val isWriteEnabled = properties.writeEnabled == true val doiPrefix: String? = properties.doiPrefix val dateTimeFormatterMM: DateTimeFormatter = DateTimeFormatter.ofPattern("MM") val dateTimeFormatterdd: DateTimeFormatter = DateTimeFormatter.ofPattern("dd") @@ -73,6 +75,12 @@ class CrossRefService(private val properties: CrossRefServiceProperties, private } } + private fun checkIsWriteEnabled() { + if (!isWriteEnabled) { + throw RuntimeException("The CrossRefService is read-only so this action is not permitted.") + } + } + fun parseCrossRefCitedByXML(citedByXML: String): CrossRefCitedByResult { val parser = Parser.xmlParser().setTrackErrors(1) val doc = Jsoup.parse(citedByXML, "", parser) @@ -280,6 +288,7 @@ class CrossRefService(private val properties: CrossRefServiceProperties, private fun postCrossRefXML(XML: String): String { checkIsActive() + checkIsWriteEnabled() // This is needed per their API specification val formData = mapOf( diff --git a/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt index 7aedc46fe2..5040442604 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt @@ -1,6 +1,7 @@ package org.loculus.backend.service.crossref import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -270,6 +271,18 @@ class CrossRefServiceTest(@Autowired private val crossRefService: CrossRefServic ) } + @Test + fun `postCrossRefXML throws when write is not enabled`() { + // crossref.write-enabled is false in the test configuration, so posting must be rejected + // before any request is made to CrossRef. + assertFalse(crossRefService.isWriteEnabled) + + val ex = assertThrows { + crossRefService.postCrossRefXML(crossRefXMLReference) + } + assertTrue(ex.message!!.contains("read-only", ignoreCase = true)) + } + @Test fun `Create an XML metadata string complying with CrossRef's schema`() { val crossRefXML = crossRefService.generateCrossRefXML( diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index 4961ec5b3e..31e637746f 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -10,6 +10,7 @@ crossref.database-name=Loculus Database crossref.email=dois@loculus.org crossref.organization=loculus.org crossref.host-url=https://main.loculus.org +crossref.write-enabled=false keycloak.user=dummy keycloak.password=dummy diff --git a/kubernetes/loculus/templates/loculus-backend.yaml b/kubernetes/loculus/templates/loculus-backend.yaml index a6d0bedad3..1a0db9b700 100644 --- a/kubernetes/loculus/templates/loculus-backend.yaml +++ b/kubernetes/loculus/templates/loculus-backend.yaml @@ -58,6 +58,7 @@ spec: - "--crossref.email=$(CROSSREF_EMAIL)" - "--crossref.organization=$(CROSSREF_ORGANIZATION)" - "--crossref.host-url=$(CROSSREF_HOST_URL)" + - "--crossref.write-enabled=$(CROSSREF_WRITE_ENABLED)" {{- end }} - "--keycloak.password=$(BACKEND_KEYCLOAK_PASSWORD)" - "--keycloak.realm=loculus" @@ -108,6 +109,8 @@ spec: value: {{$.Values.seqSets.crossRef.organization | quote }} - name: CROSSREF_HOST_URL value: {{$.Values.seqSets.crossRef.hostUrl | quote }} + - name: CROSSREF_WRITE_ENABLED + value: {{$.Values.seqSets.crossRef.writeEnabled | quote }} {{- end }} - name: BACKEND_KEYCLOAK_PASSWORD valueFrom: diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index fcb96fd10c..cc90a01bb5 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -1286,6 +1286,12 @@ "groups": ["general"], "type": "string", "description": "The host URL for CrossRef callbacks" + }, + "writeEnabled": { + "groups": ["general"], + "type": "boolean", + "default": false, + "description": "Whether to enable Crossref write functionality, such as generating DOIs." } } }, From e37199ccaeffdbe3a1ffd66b47921dfe7df332ae Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 11:51:39 +0200 Subject: [PATCH 02/14] Change crossref active test to check for null or blank --- .../backend/service/crossref/CrossRefService.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt index 130d106392..d890dca9b9 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt @@ -55,14 +55,14 @@ data class CrossRefCitedByResult( @Service class CrossRefService(private val properties: CrossRefServiceProperties, private val dateProvider: DateProvider) { - val isActive = properties.endpoint != null && - properties.username != null && - properties.password != null && - properties.doiPrefix != null && - properties.databaseName != null && - properties.email != null && - properties.organization != null && - properties.hostUrl != null + val isActive = !properties.endpoint.isNullOrBlank() && + !properties.username.isNullOrBlank() != null && + !properties.password.isNullOrBlank() && + !properties.doiPrefix.isNullOrBlank() && + !properties.databaseName.isNullOrBlank() && + !properties.email.isNullOrBlank() && + !properties.organization.isNullOrBlank() && + !properties.hostUrl.isNullOrBlank() val isWriteEnabled = properties.writeEnabled == true val doiPrefix: String? = properties.doiPrefix val dateTimeFormatterMM: DateTimeFormatter = DateTimeFormatter.ofPattern("MM") From d60398144034881b18a91592b75c5408fa38dfbb Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 11:52:48 +0200 Subject: [PATCH 03/14] Check crossref service active and writeable before creating seqset doi in db --- .../SeqSetCitationsDatabaseService.kt | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt index 55fe3cd92e..650775d436 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt @@ -35,6 +35,7 @@ import org.loculus.backend.api.Status.APPROVED_FOR_RELEASE import org.loculus.backend.api.SubmittedSeqSetRecord import org.loculus.backend.auth.AuthenticatedUser import org.loculus.backend.config.BackendConfig +import org.loculus.backend.controller.ForbiddenException import org.loculus.backend.controller.NotFoundException import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.service.crossref.CrossRefService @@ -342,6 +343,14 @@ class SeqSetCitationsDatabaseService( val username = authenticatedUser.username log.info { "Create DOI for seqSet $seqSetId, version $version, user $username" } + if (!crossRefService.isActive) { + throw ForbiddenException("The crossref service is not active, so creating DOIs is forbidden.") + } + + if (!crossRefService.isWriteEnabled) { + throw ForbiddenException("The crossref service is not write-enabled, so creating DOIs is forbidden.") + } + validateCreateSeqSetDOI(username, seqSetId, version) val doiPrefix = crossRefService.doiPrefix ?: "no-prefix-configured" @@ -359,18 +368,16 @@ class SeqSetCitationsDatabaseService( it[SeqSetsTable.seqSetDOI] = seqSetDOI } - if (crossRefService.isActive) { - val crossRefXml = crossRefService.generateCrossRefXML( - DoiEntry( - LocalDate.now(), - "SeqSet: ${seqSet[0].name}", - seqSetDOI, - "/seqsets/$seqSetId.$version", - null, - ), - ) - crossRefService.postCrossRefXML(crossRefXml) - } + val crossRefXml = crossRefService.generateCrossRefXML( + DoiEntry( + LocalDate.now(), + "SeqSet: ${seqSet[0].name}", + seqSetDOI, + "/seqsets/$seqSetId.$version", + null, + ), + ) + crossRefService.postCrossRefXML(crossRefXml) return ResponseSeqSet( seqSetId, From 14e549b8c6c835ac631ca908a7d2be510bcc97b6 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 11:58:01 +0200 Subject: [PATCH 04/14] Update tests to use mock crossref as active, with crossref generate and post xml functions mocked --- .../controller/seqsetcitations/CitationEndpointsTest.kt | 9 ++++----- .../controller/seqsetcitations/SeqSetEndpointsTest.kt | 5 ++++- .../seqsetcitations/SeqSetValidationEndpointsTest.kt | 5 ++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/CitationEndpointsTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/CitationEndpointsTest.kt index de4fe7456c..0684d3c15a 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/CitationEndpointsTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/CitationEndpointsTest.kt @@ -50,7 +50,10 @@ class CitationEndpointsTest( } returns listOf(AccessionVersion(MOCK_SEQ_ACCESSION, MOCK_SEQ_VERSION)) every { accessionPreconditionValidator.validate(any()) } returns Unit every { crossRefService.doiPrefix } returns MOCK_DOI_PREFIX - every { crossRefService.isActive } returns false + every { crossRefService.isActive } returns true + every { crossRefService.isWriteEnabled } returns true + every { crossRefService.generateCrossRefXML(any()) } returns "" + every { crossRefService.postCrossRefXML(any()) } returns "Crossref API response" } @ParameterizedTest @@ -117,7 +120,6 @@ class CitationEndpointsTest( val seqSetVersion = JsonPath.read(seqSetResult.response.contentAsString, "$.seqSetVersion").toLong() // Simulate running the crossref citations task - every { crossRefService.isActive } returns true every { crossRefService.getCrossRefCitedBy(MOCK_DOI_PREFIX) } returns CrossRefCitedByResult(emptyList(), emptyList()) seqSetCrossRefCitationsTask.task() @@ -157,7 +159,6 @@ class CitationEndpointsTest( ), seqSetDOIs = setOf(seqSetDOI), ) - every { crossRefService.isActive } returns true every { crossRefService.getCrossRefCitedBy(MOCK_DOI_PREFIX) } returns CrossRefCitedByResult(listOf(seqSetCitationSource), emptyList()) seqSetCrossRefCitationsTask.task() @@ -204,8 +205,6 @@ class CitationEndpointsTest( val (seqSetIdA, seqSetVersionA, seqSetDOIA) = createSeqSetWithDOI() val (seqSetIdB, seqSetVersionB, seqSetDOIB) = createSeqSetWithDOI() - every { crossRefService.isActive } returns true - val citationSource = SeqSetCitationSource( CitationSource( sourceDOI = "10.5678/citing-paper", diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt index f09b6f73b8..b44ac391c4 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt @@ -31,7 +31,10 @@ class SeqSetEndpointsTest(@Autowired private val client: SeqSetCitationsControll fun setup() { every { accessionPreconditionValidator.validate(any()) } returns Unit every { crossRefService.doiPrefix } returns MOCK_DOI_PREFIX - every { crossRefService.isActive } returns false + every { crossRefService.isActive } returns true + every { crossRefService.isWriteEnabled } returns true + every { crossRefService.generateCrossRefXML(any()) } returns "" + every { crossRefService.postCrossRefXML(any()) } returns "Crossref API response" } @ParameterizedTest diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetValidationEndpointsTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetValidationEndpointsTest.kt index 3c4fd8e1fa..3c2f59a932 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetValidationEndpointsTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetValidationEndpointsTest.kt @@ -30,7 +30,10 @@ class SeqSetValidationEndpointsTest( @BeforeEach fun setup() { every { crossRefService.doiPrefix } returns MOCK_DOI_PREFIX - every { crossRefService.isActive } returns false + every { crossRefService.isActive } returns true + every { crossRefService.isWriteEnabled } returns true + every { crossRefService.generateCrossRefXML(any()) } returns "" + every { crossRefService.postCrossRefXML(any()) } returns "Crossref API response" } @Test From 8f3a4637eda15bf85dbf9bf85cb64720247453d0 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 11:58:18 +0200 Subject: [PATCH 05/14] Update schema description --- kubernetes/loculus/values.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index cc90a01bb5..9f0b4ac884 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -1291,7 +1291,7 @@ "groups": ["general"], "type": "boolean", "default": false, - "description": "Whether to enable Crossref write functionality, such as generating DOIs." + "description": "Enable/disable Crossref write functionality, such as generating DOIs" } } }, From bc7eb8d4a35a5051848b702037f5c74ad880d0f9 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 12:01:02 +0200 Subject: [PATCH 06/14] Fixed typo --- .../org/loculus/backend/service/crossref/CrossRefService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt index d890dca9b9..f64c1072af 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/crossref/CrossRefService.kt @@ -56,7 +56,7 @@ data class CrossRefCitedByResult( @Service class CrossRefService(private val properties: CrossRefServiceProperties, private val dateProvider: DateProvider) { val isActive = !properties.endpoint.isNullOrBlank() && - !properties.username.isNullOrBlank() != null && + !properties.username.isNullOrBlank() && !properties.password.isNullOrBlank() && !properties.doiPrefix.isNullOrBlank() && !properties.databaseName.isNullOrBlank() && From 369396c24e02e74ebb711d48c9fce265d6f56bcb Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 12:27:30 +0200 Subject: [PATCH 07/14] Fixed tests --- .../service/crossref/CrossRefServiceTest.kt | 31 +++++++++++++++---- .../src/test/resources/application.properties | 2 +- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt index 5040442604..d5d7b055f6 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/crossref/CrossRefServiceTest.kt @@ -11,6 +11,7 @@ import org.loculus.backend.SpringBootTestWithoutDatabase import org.loculus.backend.api.CitationContributor import org.loculus.backend.api.CitationSource import org.loculus.backend.api.SeqSetCitationSource +import org.loculus.backend.utils.DateProvider import org.springframework.beans.factory.annotation.Autowired import java.time.Instant import java.time.LocalDate @@ -31,6 +32,21 @@ class CrossRefServiceTest(@Autowired private val crossRefService: CrossRefServic $doiBatchID1711411200000Loculus Databasedois@loculus.orgLoculus DatabaseLoculus Databaseloculus.orgSeqSet: My test set03262024$doihttps://main.loculus.org/seqsets/LOC_SS_1.1 """.trimIndent() + private fun crossRefService(writeEnabled: Boolean?) = CrossRefService( + CrossRefServiceProperties( + endpoint = "dummy", + username = "dummy", + password = "dummy", + doiPrefix = "placeholder", + databaseName = "Loculus Database", + email = "dois@loculus.org", + organization = "loculus.org", + hostUrl = "https://main.loculus.org", + writeEnabled = writeEnabled, + ), + DateProvider(), + ) + @Test fun `parseCrossRefCitedByXML returns citations from valid XML across different citation types`() { val xml = """ @@ -272,17 +288,20 @@ class CrossRefServiceTest(@Autowired private val crossRefService: CrossRefServic } @Test - fun `postCrossRefXML throws when write is not enabled`() { - // crossref.write-enabled is false in the test configuration, so posting must be rejected - // before any request is made to CrossRef. - assertFalse(crossRefService.isWriteEnabled) - + fun `postCrossRefXML is rejected when write is not enabled`() { + val readOnlyService = crossRefService(writeEnabled = false) val ex = assertThrows { - crossRefService.postCrossRefXML(crossRefXMLReference) + readOnlyService.postCrossRefXML(crossRefXMLReference) } assertTrue(ex.message!!.contains("read-only", ignoreCase = true)) } + @Test + fun `crossref write-enabled=true property string is coerced to the boolean flag`() { + // Application properties sets crossref.write-enabled=true + assertTrue(crossRefService.isWriteEnabled) + } + @Test fun `Create an XML metadata string complying with CrossRef's schema`() { val crossRefXML = crossRefService.generateCrossRefXML( diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index 31e637746f..2eae6c6bda 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -10,7 +10,7 @@ crossref.database-name=Loculus Database crossref.email=dois@loculus.org crossref.organization=loculus.org crossref.host-url=https://main.loculus.org -crossref.write-enabled=false +crossref.write-enabled=true keycloak.user=dummy keycloak.password=dummy From 36f88f9ca0f24a430f4792d9b61cd2f8063e5a31 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:32:11 +0200 Subject: [PATCH 08/14] feat: get rid of unnecessary toString() conversion --- .../seqsetcitations/SeqSetCitationsDatabaseService.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt index 650775d436..93a23233fb 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/seqsetcitations/SeqSetCitationsDatabaseService.kt @@ -108,7 +108,7 @@ class SeqSetCitationsDatabaseService( } return ResponseSeqSet( - insertedSet[SeqSetsTable.seqSetId].toString(), + insertedSet[SeqSetsTable.seqSetId], insertedSet[SeqSetsTable.seqSetVersion], ) } @@ -187,7 +187,7 @@ class SeqSetCitationsDatabaseService( } return ResponseSeqSet( - insertedSet[SeqSetsTable.seqSetId].toString(), + insertedSet[SeqSetsTable.seqSetId], insertedSet[SeqSetsTable.seqSetVersion], ) } @@ -481,10 +481,10 @@ class SeqSetCitationsDatabaseService( mutableListOf(), ) - val uniqueSeqSetIds = latestSeqSetWithUserAccession.map { it.seqSetId.toString() }.toSet() + val uniqueSeqSetIds = latestSeqSetWithUserAccession.map { it.seqSetId }.toSet() for (seqSetId in uniqueSeqSetIds) { val year = latestSeqSetWithUserAccession - .first { it.seqSetId.toString() == seqSetId } + .first { it.seqSetId == seqSetId } .createdAt.toLocalDateTime().year.toLong() if (citedBy.years.contains(year)) { val index = citedBy.years.indexOf(year) From e073d08a59e23a0eb275ac399d3b2fa5f6792a72 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 13:28:43 +0200 Subject: [PATCH 09/14] Remove use of non-existing github env vars that were set to blank and causing failing tests with null or blank check --- .github/workflows/backend-tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 2b8ad89139..7617869a13 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -16,10 +16,6 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 env: - CROSSREF_USERNAME: ${{ secrets.CROSSREF_USERNAME }} - CROSSREF_PASSWORD: ${{ secrets.CROSSREF_PASSWORD }} - CROSSREF_ENDPOINT: ${{ secrets.CROSSREF_ENDPOINT }} - CROSSREF_DOI_PREFIX: ${{ secrets.CROSSREF_DOI_PREFIX }} RUN_EXTRA_TESTS: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && 'true' || 'false' }} steps: - name: Login to Docker Hub From d33d50f7b327dbbbbf9a283bc94a0e2202e03a14 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 14:04:25 +0200 Subject: [PATCH 10/14] Add tests for forbidden when creating seqset DOI when crossref is not active or not enabled --- .../seqsetcitations/SeqSetEndpointsTest.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt index b44ac391c4..9acf9f9662 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/seqsetcitations/SeqSetEndpointsTest.kt @@ -125,6 +125,36 @@ class SeqSetEndpointsTest(@Autowired private val client: SeqSetCitationsControll .andExpect(jsonPath("\$[0].seqSetDOI").isString) } + @Test + fun `WHEN calling create seqSet DOI while write is disabled THEN returns forbidden`() { + every { crossRefService.isWriteEnabled } returns false + + val seqSetResult = client.createSeqSet() + .andExpect(status().isOk) + .andReturn() + val seqSetId = JsonPath.read(seqSetResult.response.contentAsString, "$.seqSetId") + + client.createSeqSetDOI(seqSetId) + .andExpect(status().isForbidden) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("\$.detail", containsString("not write-enabled"))) + } + + @Test + fun `WHEN calling create seqSet DOI while crossref is inactive THEN returns forbidden`() { + every { crossRefService.isActive } returns false + + val seqSetResult = client.createSeqSet() + .andExpect(status().isOk) + .andReturn() + val seqSetId = JsonPath.read(seqSetResult.response.contentAsString, "$.seqSetId") + + client.createSeqSetDOI(seqSetId) + .andExpect(status().isForbidden) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("\$.detail", containsString("not active"))) + } + @Test fun `WHEN calling update seqSet THEN returns updated seqSet`() { val seqSetResult = client.createSeqSet() From 1842c76e6b6609232ca3042f6c6b6d00bec77852 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 14:37:34 +0200 Subject: [PATCH 11/14] To revert: testing active crossref but write disabled --- kubernetes/loculus/values.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index c4217a8006..fa8b0f943e 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -13,7 +13,13 @@ seqSets: enabled: true crossRef: DOIPrefix: "placeholder" - endpoint: "https://test.crossref.org" + endpoint: "dummy" + databaseName: "dummy" + email: "email@dummy" + organization: "dummy" + hostUrl: "dummy" + username: "dummy" + password: "dummy" fieldsToDisplay: - field: "geoLocCountry" displayName: "Country" From 962c75b118c59a1bce3b204050c3278c94dd3a35 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 14:50:47 +0200 Subject: [PATCH 12/14] Fixing dummy username/password --- kubernetes/loculus/values.yaml | 2 -- kubernetes/loculus/values_preview_server.yaml | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index fa8b0f943e..26f71db0c0 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -18,8 +18,6 @@ seqSets: email: "email@dummy" organization: "dummy" hostUrl: "dummy" - username: "dummy" - password: "dummy" fieldsToDisplay: - field: "geoLocCountry" displayName: "Country" diff --git a/kubernetes/loculus/values_preview_server.yaml b/kubernetes/loculus/values_preview_server.yaml index c6a1ea97d2..777ee7e4be 100644 --- a/kubernetes/loculus/values_preview_server.yaml +++ b/kubernetes/loculus/values_preview_server.yaml @@ -34,6 +34,12 @@ secrets: encryptedData: accessKey: AgCm73j1g21DnqeLjC/b3Dvp18fvqn384l5KhGW99NgYBvDB96PYi9VOy4NEQhtRx/EkSCKv1v+Q64Gz39OUaO0Eb5nH3DfKlGdKiSH4SZLVZhOziH7u8QjTHp37JQ/mpdS3bwFOkjweWd8uFdY7vBFiYDp//hJmDPAf/nJVdafBhu2UuCXUduTyIugn9SiP19O0BVWUvndrNvncJ9t7kqBmjh2aVosC2uDAvqmQG7tiBDn68SuJrsgy98PteBqWFc2fGlsyprbGaRfsTdHCz2+/X73YN2jE+Jc7zLWyTtd8WoRdNZdUtLazN6gJeLbHNq/LC1q3u8o2ILO5mruvRqgSIQMoBzdYThbJmPwU+b3UcaKgjZ6lyb7hykGr1JxWNElPl/zz2oaQ/WsjtbZC/SkLy9tUur0nENeeu6uOQQIxqdjqQyeDqb8u8jiYtWWrlzZhhVx4pn1FatELCPmdiqs72plsDG9RVyLLamDQb2ICp7XWM+NpCIihzaI+FXjk7lPu0HC2SXL8xNaw+DC4PlayfSEst9X9/4VOVzCPGSV/NAemYTw4Evozl2Enh1vYft+W/JreS0aI5qPJQ1FBXds5yreRDT0PAnIaXNtHbz8AybTi50TLFnrNb9AZCehI/hNSpD3PnUNa7jqTcRUZLbOty6exlZBDE1WIO0qRX5IVzvGy3W09BDdvpvM27j9IRHuL/2spw1SkFztpqRQcmoLgrPBVzQ== secretKey: AgAS8a/ldlEderUmlXih5P2EKDQiuPX0CW5nRMcxGhrHCslm3YVrOSdkdkz7y7yuPC/eJ34c9TcDPFINSYmx4TGkJ1FA6yRroIs0gWc2JYQlRlcT+AuvhWsgMAUHiRwFmydAqjTn9nGrPXuhg/r1EtFJM13uiwM9MXzM14EAXlFoijwqkAO1NZpVMhB4zWP2NwXAknCZtNWqTaZoasWOOr83vrHbKW+C7oQtFrzrUxP83U1qPrWiwJO90eHv6Rao30rXUjwCgOJwQHzrhgZaJg+ak5OMsxbpc3Tmc8H2j3RJMsUhbC11BC9OCk75V4YY2XZ/RmIcJWS6NwVMLJxK4+at/zImAEFobf0igVWeW8oaNnDtu6zhLPjuG/V0RGZ8mLPS4o3/IXNWcvuXUiG5nt0sxqh/YNOr+SFKoW9iU9fVjHK5k5++sT+rixkTqbSs8DNXU25z2oETzF0/s7AKh0N1vokOg3CHGdMlwqBH26rtSDzqf9q00vozq2DxwJWyfdq5fiImYlAoT0sHqV8gG8kfMnnYbnL+TPhkPqdL4x29vWIMZNJqcQuTJmtszkFhmF8/HJSB5Y6kmHN+xml2gm2TJ7BnVdqk0U01Dudbt1xCLY925K4D4cgoVpQYMkA+TZyhlGr6UaEnaZn2KQ7YJvh0q3sgnrwbxhXxgA04gCjXqc9Dcpc52/GUMBBsChM3Ywxph2Mfcx5ycO5DLU3/jsJfYs5YLQk/z576tQsXm89830k9helncrEb + crossref: + type: sealedsecret + clusterWide: "true" + encryptedData: + username: dummy-secret-username + password: dummy-secret-password previewDocs: true robotsNoindexHeader: true disableEnaSubmission: false From 3653a74a94160c1055b3ccb5ce4a1aee29619cb0 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 15:07:30 +0200 Subject: [PATCH 13/14] There is already a dummy secret --- kubernetes/loculus/values_preview_server.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/kubernetes/loculus/values_preview_server.yaml b/kubernetes/loculus/values_preview_server.yaml index 777ee7e4be..c6a1ea97d2 100644 --- a/kubernetes/loculus/values_preview_server.yaml +++ b/kubernetes/loculus/values_preview_server.yaml @@ -34,12 +34,6 @@ secrets: encryptedData: accessKey: AgCm73j1g21DnqeLjC/b3Dvp18fvqn384l5KhGW99NgYBvDB96PYi9VOy4NEQhtRx/EkSCKv1v+Q64Gz39OUaO0Eb5nH3DfKlGdKiSH4SZLVZhOziH7u8QjTHp37JQ/mpdS3bwFOkjweWd8uFdY7vBFiYDp//hJmDPAf/nJVdafBhu2UuCXUduTyIugn9SiP19O0BVWUvndrNvncJ9t7kqBmjh2aVosC2uDAvqmQG7tiBDn68SuJrsgy98PteBqWFc2fGlsyprbGaRfsTdHCz2+/X73YN2jE+Jc7zLWyTtd8WoRdNZdUtLazN6gJeLbHNq/LC1q3u8o2ILO5mruvRqgSIQMoBzdYThbJmPwU+b3UcaKgjZ6lyb7hykGr1JxWNElPl/zz2oaQ/WsjtbZC/SkLy9tUur0nENeeu6uOQQIxqdjqQyeDqb8u8jiYtWWrlzZhhVx4pn1FatELCPmdiqs72plsDG9RVyLLamDQb2ICp7XWM+NpCIihzaI+FXjk7lPu0HC2SXL8xNaw+DC4PlayfSEst9X9/4VOVzCPGSV/NAemYTw4Evozl2Enh1vYft+W/JreS0aI5qPJQ1FBXds5yreRDT0PAnIaXNtHbz8AybTi50TLFnrNb9AZCehI/hNSpD3PnUNa7jqTcRUZLbOty6exlZBDE1WIO0qRX5IVzvGy3W09BDdvpvM27j9IRHuL/2spw1SkFztpqRQcmoLgrPBVzQ== secretKey: AgAS8a/ldlEderUmlXih5P2EKDQiuPX0CW5nRMcxGhrHCslm3YVrOSdkdkz7y7yuPC/eJ34c9TcDPFINSYmx4TGkJ1FA6yRroIs0gWc2JYQlRlcT+AuvhWsgMAUHiRwFmydAqjTn9nGrPXuhg/r1EtFJM13uiwM9MXzM14EAXlFoijwqkAO1NZpVMhB4zWP2NwXAknCZtNWqTaZoasWOOr83vrHbKW+C7oQtFrzrUxP83U1qPrWiwJO90eHv6Rao30rXUjwCgOJwQHzrhgZaJg+ak5OMsxbpc3Tmc8H2j3RJMsUhbC11BC9OCk75V4YY2XZ/RmIcJWS6NwVMLJxK4+at/zImAEFobf0igVWeW8oaNnDtu6zhLPjuG/V0RGZ8mLPS4o3/IXNWcvuXUiG5nt0sxqh/YNOr+SFKoW9iU9fVjHK5k5++sT+rixkTqbSs8DNXU25z2oETzF0/s7AKh0N1vokOg3CHGdMlwqBH26rtSDzqf9q00vozq2DxwJWyfdq5fiImYlAoT0sHqV8gG8kfMnnYbnL+TPhkPqdL4x29vWIMZNJqcQuTJmtszkFhmF8/HJSB5Y6kmHN+xml2gm2TJ7BnVdqk0U01Dudbt1xCLY925K4D4cgoVpQYMkA+TZyhlGr6UaEnaZn2KQ7YJvh0q3sgnrwbxhXxgA04gCjXqc9Dcpc52/GUMBBsChM3Ywxph2Mfcx5ycO5DLU3/jsJfYs5YLQk/z576tQsXm89830k9helncrEb - crossref: - type: sealedsecret - clusterWide: "true" - encryptedData: - username: dummy-secret-username - password: dummy-secret-password previewDocs: true robotsNoindexHeader: true disableEnaSubmission: false From a506be9127f1d21e1405bc69992f4939b9f05934 Mon Sep 17 00:00:00 2001 From: tombch Date: Tue, 16 Jun 2026 16:17:25 +0200 Subject: [PATCH 14/14] Reverting dummy crossref values --- kubernetes/loculus/values.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 26f71db0c0..c4217a8006 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -13,11 +13,7 @@ seqSets: enabled: true crossRef: DOIPrefix: "placeholder" - endpoint: "dummy" - databaseName: "dummy" - email: "email@dummy" - organization: "dummy" - hostUrl: "dummy" + endpoint: "https://test.crossref.org" fieldsToDisplay: - field: "geoLocCountry" displayName: "Country"