diff --git a/.idea/runConfigurations/LapisV2Open.xml b/.idea/runConfigurations/LapisV2Open.xml
index e633be40..ee0c00c5 100644
--- a/.idea/runConfigurations/LapisV2Open.xml
+++ b/.idea/runConfigurations/LapisV2Open.xml
@@ -1,11 +1,11 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/LapisV2Protected.xml b/.idea/runConfigurations/LapisV2Protected.xml
index 7822c70b..adfe5de2 100644
--- a/.idea/runConfigurations/LapisV2Protected.xml
+++ b/.idea/runConfigurations/LapisV2Protected.xml
@@ -1,11 +1,11 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lapis2/build.gradle b/lapis2/build.gradle
index 82cd2441..5bd2ab09 100644
--- a/lapis2/build.gradle
+++ b/lapis2/build.gradle
@@ -79,8 +79,9 @@ openApi {
apiDocsUrl.set("http://localhost:8080/api-docs")
customBootRun {
args.set([
- "--silo.url=does.not.matter.here",
- "--lapis.databaseConfig.path=../siloLapisTests/testData/testDatabaseConfig.yaml"
+ "--silo.url=does.not.matter.here",
+ "--lapis.databaseConfig.path=../siloLapisTests/testData/testDatabaseConfig.yaml",
+ "--referenceGenomeFilename=../siloLapisTests/testData/reference-genomes.json"
])
}
}
diff --git a/lapis2/docker-compose.yml b/lapis2/docker-compose.yml
index 1ceb9567..62fe23d8 100644
--- a/lapis2/docker-compose.yml
+++ b/lapis2/docker-compose.yml
@@ -4,12 +4,16 @@ services:
image: ghcr.io/genspectrum/lapis-v2:${LAPIS_TAG}
ports:
- "8080:8080"
- command: --silo.url=http://silo:8081 --lapis.databaseConfig.path=databaseConfig.yaml
+ command: --silo.url=http://silo:8081 --lapis.databaseConfig.path=databaseConfig.yaml --referenceGenomeFilename=reference-genomes.json
volumes:
- type: bind
source: ../siloLapisTests/testData/testDatabaseConfig.yaml
target: /workspace/databaseConfig.yaml
read_only: true
+ - type: bind
+ source: ../siloLapisTests/testData/reference-genomes.json
+ target: /workspace/reference-genomes.json
+ read_only: true
silo:
image: ghcr.io/genspectrum/lapis-silo:${SILO_TAG}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisApplication.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisApplication.kt
index 9a254ed0..2096785d 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisApplication.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisApplication.kt
@@ -1,5 +1,6 @@
package org.genspectrum.lapis
+import org.genspectrum.lapis.config.ReferenceGenome
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@@ -7,5 +8,7 @@ import org.springframework.boot.runApplication
class Lapisv2Application
fun main(args: Array) {
- runApplication(*args)
+ val referenceGenomeArgs = ReferenceGenome.readFromFileFromProgramArgs(args).toSpringApplicationArgs()
+
+ runApplication(*(args + referenceGenomeArgs))
}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisSpringConfig.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisSpringConfig.kt
index 50714d59..7c7e355b 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisSpringConfig.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/LapisSpringConfig.kt
@@ -4,6 +4,9 @@ import com.fasterxml.jackson.module.kotlin.readValue
import mu.KotlinLogging
import org.genspectrum.lapis.auth.DataOpennessAuthorizationFilterFactory
import org.genspectrum.lapis.config.DatabaseConfig
+import org.genspectrum.lapis.config.NucleotideSequence
+import org.genspectrum.lapis.config.REFERENCE_GENOME_APPLICATION_ARG_PREFIX
+import org.genspectrum.lapis.config.ReferenceGenome
import org.genspectrum.lapis.config.SequenceFilterFields
import org.genspectrum.lapis.logging.RequestContext
import org.genspectrum.lapis.logging.RequestContextLogger
@@ -60,4 +63,11 @@ class LapisSpringConfig {
fun dataOpennessAuthorizationFilter(
dataOpennessAuthorizationFilterFactory: DataOpennessAuthorizationFilterFactory,
) = dataOpennessAuthorizationFilterFactory.create()
+
+ @Bean
+ fun referenceGenome(
+ @Value("\${$REFERENCE_GENOME_APPLICATION_ARG_PREFIX}") nucleotideSegments: List,
+ ): ReferenceGenome {
+ return ReferenceGenome(nucleotideSegments.map { NucleotideSequence(it) })
+ }
}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt
index cc7d0d49..441ae9d7 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/OpenApiDocs.kt
@@ -42,6 +42,7 @@ import org.genspectrum.lapis.controller.ORDER_BY_FIELDS_SCHEMA
import org.genspectrum.lapis.controller.ORDER_BY_PROPERTY
import org.genspectrum.lapis.controller.REQUEST_SCHEMA_WITH_MIN_PROPORTION
import org.genspectrum.lapis.controller.SEQUENCE_FILTERS_SCHEMA
+import org.genspectrum.lapis.controller.SEQUENCE_REQUEST_SCHEMA
import org.genspectrum.lapis.request.AminoAcidInsertion
import org.genspectrum.lapis.request.AminoAcidMutation
import org.genspectrum.lapis.request.NucleotideInsertion
@@ -64,8 +65,9 @@ fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields, databaseConfi
Pair(AMINO_ACID_INSERTIONS_PROPERTY, aminoAcidInsertions()) +
Pair(ORDER_BY_PROPERTY, orderByPostSchema()) +
Pair(LIMIT_PROPERTY, limitSchema()) +
- Pair(OFFSET_PROPERTY, offsetSchema()) +
- Pair(FORMAT_PROPERTY, formatSchema())
+ Pair(OFFSET_PROPERTY, offsetSchema())
+
+ val sequenceFiltersWithFormat = sequenceFilters + Pair(FORMAT_PROPERTY, formatSchema())
return OpenAPI()
.components(
@@ -82,18 +84,24 @@ fun buildOpenApiSchema(sequenceFilterFields: SequenceFilterFields, databaseConfi
Schema()
.type("object")
.description("valid filters for sequence data")
- .properties(sequenceFilters + Pair(MIN_PROPORTION_PROPERTY, Schema().type("number"))),
+ .properties(
+ sequenceFiltersWithFormat + Pair(MIN_PROPORTION_PROPERTY, Schema().type("number")),
+ ),
)
.addSchemas(
AGGREGATED_REQUEST_SCHEMA,
- requestSchemaWithFields(sequenceFilters, AGGREGATED_GROUP_BY_FIELDS_DESCRIPTION),
+ requestSchemaWithFields(sequenceFiltersWithFormat, AGGREGATED_GROUP_BY_FIELDS_DESCRIPTION),
)
.addSchemas(
DETAILS_REQUEST_SCHEMA,
- requestSchemaWithFields(sequenceFilters, DETAILS_FIELDS_DESCRIPTION),
+ requestSchemaWithFields(sequenceFiltersWithFormat, DETAILS_FIELDS_DESCRIPTION),
)
.addSchemas(
INSERTIONS_REQUEST_SCHEMA,
+ requestSchemaForCommonSequenceFilters(sequenceFiltersWithFormat),
+ )
+ .addSchemas(
+ SEQUENCE_REQUEST_SCHEMA,
requestSchemaForCommonSequenceFilters(sequenceFilters),
)
.addSchemas(
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
index c2b5a937..994305ea 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/auth/DataOpennessAuthorizationFilter.kt
@@ -9,8 +9,13 @@ import org.genspectrum.lapis.config.AccessKeysReader
import org.genspectrum.lapis.config.DatabaseConfig
import org.genspectrum.lapis.config.OpennessLevel
import org.genspectrum.lapis.controller.ACCESS_KEY_PROPERTY
+import org.genspectrum.lapis.controller.AGGREGATED_ROUTE
+import org.genspectrum.lapis.controller.AMINO_ACID_INSERTIONS_ROUTE
+import org.genspectrum.lapis.controller.AMINO_ACID_MUTATIONS_ROUTE
import org.genspectrum.lapis.controller.LapisError
import org.genspectrum.lapis.controller.LapisErrorResponse
+import org.genspectrum.lapis.controller.NUCLEOTIDE_INSERTIONS_ROUTE
+import org.genspectrum.lapis.controller.NUCLEOTIDE_MUTATIONS_ROUTE
import org.genspectrum.lapis.util.CachedBodyHttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
@@ -93,7 +98,13 @@ private class ProtectedDataAuthorizationFilter(
companion object {
private val WHITELISTED_PATHS = listOf("/swagger-ui", "/api-docs")
- private val ENDPOINTS_THAT_SERVE_AGGREGATED_DATA = listOf("/aggregated", "/nucleotideMutations")
+ private val ENDPOINTS_THAT_SERVE_AGGREGATED_DATA = listOf(
+ AGGREGATED_ROUTE,
+ NUCLEOTIDE_MUTATIONS_ROUTE,
+ AMINO_ACID_MUTATIONS_ROUTE,
+ NUCLEOTIDE_INSERTIONS_ROUTE,
+ AMINO_ACID_INSERTIONS_ROUTE,
+ )
}
override fun isAuthorizedForEndpoint(request: CachedBodyHttpServletRequest): AuthorizationResult {
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/ReferenceGenome.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/ReferenceGenome.kt
new file mode 100644
index 00000000..eb05b82d
--- /dev/null
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/ReferenceGenome.kt
@@ -0,0 +1,53 @@
+package org.genspectrum.lapis.config
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.fasterxml.jackson.module.kotlin.readValue
+import java.io.File
+
+const val REFERENCE_GENOME_APPLICATION_ARG_PREFIX = "referenceGenome.nucleotideSequences"
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+class ReferenceGenome(
+ @JsonProperty("nucleotide_sequences")
+ val nucleotideSequences: List,
+) {
+ fun isSingleSegmented(): Boolean {
+ return nucleotideSequences.size == 1
+ }
+
+ companion object {
+ fun readFromFile(filename: String): ReferenceGenome {
+ return jacksonObjectMapper().readValue(File(filename))
+ }
+
+ private fun readFilenameFromProgramArgs(args: Array): String {
+ val referenceGenomeArg = args.find { it.startsWith("--referenceGenomeFilename=") }
+ return referenceGenomeArg?.substringAfter("=") ?: throw IllegalArgumentException(
+ "No reference genome filename specified. Please specify a reference genome filename using the " +
+ "--referenceGenomeFilename argument.",
+ )
+ }
+
+ fun readFromFileFromProgramArgs(args: Array): ReferenceGenome {
+ return readFromFile(readFilenameFromProgramArgs(args))
+ }
+ }
+
+ fun toSpringApplicationArgs(): Array {
+ val nucleotideSequenceArgs =
+ "--$REFERENCE_GENOME_APPLICATION_ARG_PREFIX=" + this.nucleotideSequences.joinToString(
+ separator = ",",
+ ) {
+ it.name
+ }
+
+ return arrayOf(nucleotideSequenceArgs)
+ }
+}
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class NucleotideSequence(
+ val name: String,
+)
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt
deleted file mode 100644
index a02427bf..00000000
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeature.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.genspectrum.lapis.config
-
-import org.springframework.stereotype.Component
-
-const val IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE = "isSingleSegmentedSequence"
-
-@Component
-class SingleSegmentedSequenceFeature(private val databaseConfig: DatabaseConfig) {
- fun isEnabled() =
- databaseConfig.schema.features.any { it.name == IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE }
-}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/LapisController.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/LapisController.kt
index d26389e6..d736c1f7 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/LapisController.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/LapisController.kt
@@ -15,11 +15,11 @@ import org.genspectrum.lapis.model.SiloQueryModel
import org.genspectrum.lapis.request.AminoAcidInsertion
import org.genspectrum.lapis.request.AminoAcidMutation
import org.genspectrum.lapis.request.CommonSequenceFilters
-import org.genspectrum.lapis.request.InsertionsRequest
import org.genspectrum.lapis.request.MutationProportionsRequest
import org.genspectrum.lapis.request.NucleotideInsertion
import org.genspectrum.lapis.request.NucleotideMutation
import org.genspectrum.lapis.request.OrderByField
+import org.genspectrum.lapis.request.SequenceFiltersRequest
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
import org.genspectrum.lapis.response.AggregationData
import org.genspectrum.lapis.response.AminoAcidInsertionResponse
@@ -27,8 +27,10 @@ import org.genspectrum.lapis.response.AminoAcidMutationResponse
import org.genspectrum.lapis.response.DetailsData
import org.genspectrum.lapis.response.NucleotideInsertionResponse
import org.genspectrum.lapis.response.NucleotideMutationResponse
+import org.genspectrum.lapis.silo.SequenceType
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
@@ -39,6 +41,7 @@ const val REQUEST_SCHEMA_WITH_MIN_PROPORTION = "SequenceFiltersWithMinProportion
const val AGGREGATED_REQUEST_SCHEMA = "AggregatedPostRequest"
const val DETAILS_REQUEST_SCHEMA = "DetailsPostRequest"
const val INSERTIONS_REQUEST_SCHEMA = "InsertionsRequest"
+const val SEQUENCE_REQUEST_SCHEMA = "SequenceRequest"
const val AGGREGATED_RESPONSE_SCHEMA = "AggregatedResponse"
const val DETAILS_RESPONSE_SCHEMA = "DetailsResponse"
@@ -72,6 +75,10 @@ const val AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION =
"Returns a list of mutations along with the counts and proportions whose proportions are greater " +
"than or equal to the specified minProportion. Only sequences matching the specified " +
"sequence filters are considered."
+
+const val AMINO_ACID_SEQUENCE_ENDPOINT_DESCRIPTION =
+ "Returns a string of fasta formated amino acid sequences. Only sequences matching the specified " +
+ "sequence filters are considered."
const val AGGREGATED_GROUP_BY_FIELDS_DESCRIPTION =
"The fields to stratify by. If empty, only the overall count is returned"
const val AGGREGATED_ORDER_BY_FIELDS_DESCRIPTION =
@@ -88,13 +95,22 @@ const val FORMAT_DESCRIPTION = "The data format of the response. " +
"Alternatively, the data format can be specified by setting the \"Accept\"-header. When both are specified, " +
"this parameter takes precedence."
+const val AGGREGATED_ROUTE = "/aggregated"
+const val DETAILS_ROUTE = "/details"
+const val NUCLEOTIDE_MUTATIONS_ROUTE = "/nucleotideMutations"
+const val AMINO_ACID_MUTATIONS_ROUTE = "/aminoAcidMutations"
+const val NUCLEOTIDE_INSERTIONS_ROUTE = "/nucleotideInsertions"
+const val AMINO_ACID_INSERTIONS_ROUTE = "/aminoAcidInsertions"
+const val ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE = "/alignedNucleotideSequences"
+const val AMINO_ACID_SEQUENCES_ROUTE = "/aminoAcidSequences"
+
@RestController
class LapisController(
private val siloQueryModel: SiloQueryModel,
private val requestContext: RequestContext,
private val csvWriter: CsvWriter,
) {
- @GetMapping("/aggregated", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(AGGREGATED_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAggregatedResponse
fun aggregated(
@SequenceFilters
@@ -160,7 +176,7 @@ class LapisController(
return LapisResponse(siloQueryModel.getAggregated(request))
}
- @GetMapping("/aggregated", produces = [TEXT_CSV_HEADER])
+ @GetMapping(AGGREGATED_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "getAggregatedAsCsv",
@@ -228,7 +244,7 @@ class LapisController(
return getResponseAsCsv(request, COMMA, siloQueryModel::getAggregated)
}
- @GetMapping("/aggregated", produces = [TEXT_TSV_HEADER])
+ @GetMapping(AGGREGATED_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "getAggregatedAsTsv",
@@ -296,7 +312,7 @@ class LapisController(
return getResponseAsCsv(request, TAB, siloQueryModel::getAggregated)
}
- @PostMapping("/aggregated", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(AGGREGATED_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAggregatedResponse
@Operation(
operationId = "postAggregated",
@@ -311,7 +327,7 @@ class LapisController(
return LapisResponse(siloQueryModel.getAggregated(request))
}
- @PostMapping("/aggregated", produces = [TEXT_CSV_HEADER])
+ @PostMapping(AGGREGATED_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "postAggregatedAsCsv",
@@ -325,7 +341,7 @@ class LapisController(
return getResponseAsCsv(request, COMMA, siloQueryModel::getAggregated)
}
- @PostMapping("/aggregated", produces = [TEXT_TSV_HEADER])
+ @PostMapping(AGGREGATED_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AGGREGATED_ENDPOINT_DESCRIPTION,
operationId = "postAggregatedAsTsv",
@@ -339,7 +355,7 @@ class LapisController(
return getResponseAsCsv(request, TAB, siloQueryModel::getAggregated)
}
- @GetMapping("/nucleotideMutations", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisNucleotideMutationsResponse
fun getNucleotideMutations(
@Parameter(
@@ -406,7 +422,7 @@ class LapisController(
return LapisResponse(result)
}
- @GetMapping("/nucleotideMutations", produces = [TEXT_CSV_HEADER])
+ @GetMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "getNucleotideMutationsAsCsv",
@@ -468,7 +484,7 @@ class LapisController(
return getResponseAsCsv(request, COMMA, siloQueryModel::computeNucleotideMutationProportions)
}
- @GetMapping("/nucleotideMutations", produces = [TEXT_TSV_HEADER])
+ @GetMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "getNucleotideMutationsAsTsv",
@@ -530,7 +546,7 @@ class LapisController(
return getResponseAsCsv(request, TAB, siloQueryModel::computeNucleotideMutationProportions)
}
- @PostMapping("/nucleotideMutations", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisNucleotideMutationsResponse
@Operation(
operationId = "postNucleotideMutations",
@@ -546,7 +562,7 @@ class LapisController(
return LapisResponse(result)
}
- @PostMapping("/nucleotideMutations", produces = [TEXT_CSV_HEADER])
+ @PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideMutationsAsCsv",
@@ -560,7 +576,7 @@ class LapisController(
return getResponseAsCsv(mutationProportionsRequest, COMMA, siloQueryModel::computeNucleotideMutationProportions)
}
- @PostMapping("/nucleotideMutations", produces = [TEXT_TSV_HEADER])
+ @PostMapping(NUCLEOTIDE_MUTATIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = NUCLEOTIDE_MUTATION_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideMutationsAsTsv",
@@ -574,7 +590,7 @@ class LapisController(
return getResponseAsCsv(mutationProportionsRequest, TAB, siloQueryModel::computeNucleotideMutationProportions)
}
- @GetMapping("/aminoAcidMutations", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAminoAcidMutationsResponse
fun getAminoAcidMutations(
@Parameter(
@@ -634,7 +650,7 @@ class LapisController(
return LapisResponse(result)
}
- @GetMapping("/aminoAcidMutations", produces = [TEXT_CSV_HEADER])
+ @GetMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "getAminoAcidMutationsAsCsv",
@@ -696,7 +712,7 @@ class LapisController(
return getResponseAsCsv(mutationProportionsRequest, COMMA, siloQueryModel::computeAminoAcidMutationProportions)
}
- @GetMapping("/aminoAcidMutations", produces = [TEXT_TSV_HEADER])
+ @GetMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "getAminoAcidMutationsAsTsv",
@@ -762,7 +778,7 @@ class LapisController(
)
}
- @PostMapping("/aminoAcidMutations", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAminoAcidMutationsResponse
@Operation(
operationId = "postAminoAcidMutations",
@@ -778,7 +794,7 @@ class LapisController(
return LapisResponse(result)
}
- @PostMapping("/aminoAcidMutations", produces = [TEXT_CSV_HEADER])
+ @PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidMutationsAsCsv",
@@ -798,7 +814,7 @@ class LapisController(
)
}
- @PostMapping("/aminoAcidMutations", produces = [TEXT_TSV_HEADER])
+ @PostMapping(AMINO_ACID_MUTATIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AMINO_ACID_MUTATIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidMutationsAsCsv",
@@ -818,7 +834,7 @@ class LapisController(
)
}
- @GetMapping("/details", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(DETAILS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisDetailsResponse
fun getDetailsAsJson(
@SequenceFilters
@@ -881,7 +897,7 @@ class LapisController(
return LapisResponse(siloQueryModel.getDetails(request))
}
- @GetMapping("/details", produces = [TEXT_CSV_HEADER])
+ @GetMapping(DETAILS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "getDetailsAsCsv",
@@ -940,7 +956,7 @@ class LapisController(
return getResponseAsCsv(request, COMMA, siloQueryModel::getDetails)
}
- @GetMapping("/details", produces = [TEXT_TSV_HEADER])
+ @GetMapping(DETAILS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "getDetailsAsTsv",
@@ -999,7 +1015,7 @@ class LapisController(
return getResponseAsCsv(request, TAB, siloQueryModel::getDetails)
}
- @PostMapping("/details", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(DETAILS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisDetailsResponse
@Operation(
operationId = "postDetails",
@@ -1014,7 +1030,7 @@ class LapisController(
return LapisResponse(siloQueryModel.getDetails(request))
}
- @PostMapping("/details", produces = [TEXT_CSV_HEADER])
+ @PostMapping(DETAILS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "postDetailsAsCsv",
@@ -1028,7 +1044,7 @@ class LapisController(
return getResponseAsCsv(request, COMMA, siloQueryModel::getDetails)
}
- @PostMapping("/details", produces = [TEXT_TSV_HEADER])
+ @PostMapping(DETAILS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = DETAILS_ENDPOINT_DESCRIPTION,
operationId = "postDetailsAsTsv",
@@ -1042,7 +1058,7 @@ class LapisController(
return getResponseAsCsv(request, TAB, siloQueryModel::getDetails)
}
- @GetMapping("/nucleotideInsertions", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisNucleotideInsertionsResponse
fun getNucleotideInsertions(
@SequenceFilters
@@ -1064,8 +1080,10 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
aminoAcidInsertions: List?,
@Parameter(
schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
@@ -1086,7 +1104,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): LapisResponse> {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1097,13 +1115,13 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- val result = siloQueryModel.getNucleotideInsertions(insertionRequest)
+ val result = siloQueryModel.getNucleotideInsertions(request)
return LapisResponse(result)
}
- @GetMapping("/nucleotideInsertions", produces = [TEXT_CSV_HEADER])
+ @GetMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "getNucleotideInsertionsAsCsv",
@@ -1129,6 +1147,7 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
aminoAcidInsertions: List?,
@@ -1151,7 +1170,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): String {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1162,12 +1181,12 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- return getResponseAsCsv(insertionRequest, COMMA, siloQueryModel::getNucleotideInsertions)
+ return getResponseAsCsv(request, COMMA, siloQueryModel::getNucleotideInsertions)
}
- @GetMapping("/nucleotideInsertions", produces = [TEXT_TSV_HEADER])
+ @GetMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "getNucleotideInsertionsAsTsv",
@@ -1193,8 +1212,10 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
aminoAcidInsertions: List?,
@Parameter(
schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
@@ -1215,7 +1236,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): String {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1226,12 +1247,12 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- return getResponseAsCsv(insertionRequest, TAB, siloQueryModel::getNucleotideInsertions)
+ return getResponseAsCsv(request, TAB, siloQueryModel::getNucleotideInsertions)
}
- @PostMapping("/nucleotideInsertions", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisNucleotideInsertionsResponse
@Operation(
operationId = "postNucleotideInsertions",
@@ -1239,7 +1260,7 @@ class LapisController(
fun postNucleotideInsertions(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): LapisResponse> {
requestContext.filter = request
@@ -1247,7 +1268,7 @@ class LapisController(
return LapisResponse(result)
}
- @PostMapping("/nucleotideInsertions", produces = [TEXT_CSV_HEADER])
+ @PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideInsertionsAsCsv",
@@ -1256,14 +1277,14 @@ class LapisController(
fun postNucleotideInsertionsAsCsv(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): String {
requestContext.filter = request
return getResponseAsCsv(request, COMMA, siloQueryModel::getNucleotideInsertions)
}
- @PostMapping("/nucleotideInsertions", produces = [TEXT_TSV_HEADER])
+ @PostMapping(NUCLEOTIDE_INSERTIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = NUCLEOTIDE_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postNucleotideInsertionsAsTsv",
@@ -1272,14 +1293,14 @@ class LapisController(
fun postNucleotideInsertionsAsTsv(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): String {
requestContext.filter = request
return getResponseAsCsv(request, TAB, siloQueryModel::getNucleotideInsertions)
}
- @GetMapping("/aminoAcidInsertions", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @GetMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAminoAcidInsertionsResponse
fun getAminoAcidInsertions(
@SequenceFilters
@@ -1301,8 +1322,10 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
aminoAcidInsertions: List?,
@Parameter(
schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
@@ -1323,7 +1346,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): LapisResponse> {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1334,13 +1357,13 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- val result = siloQueryModel.getAminoAcidInsertions(insertionRequest)
+ val result = siloQueryModel.getAminoAcidInsertions(request)
return LapisResponse(result)
}
- @GetMapping("/aminoAcidInsertions", produces = [TEXT_CSV_HEADER])
+ @GetMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "getAminoAcidInsertionsAsCsv",
@@ -1366,8 +1389,10 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
aminoAcidInsertions: List?,
@Parameter(
schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
@@ -1388,7 +1413,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): String {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1399,12 +1424,12 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- return getResponseAsCsv(insertionRequest, COMMA, siloQueryModel::getAminoAcidInsertions)
+ return getResponseAsCsv(request, COMMA, siloQueryModel::getAminoAcidInsertions)
}
- @GetMapping("/aminoAcidInsertions", produces = [TEXT_TSV_HEADER])
+ @GetMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "getAminoAcidInsertionsAsTsv",
@@ -1430,8 +1455,10 @@ class LapisController(
@RequestParam
aminoAcidMutations: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
nucleotideInsertions: List?,
@RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
aminoAcidInsertions: List?,
@Parameter(
schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
@@ -1452,7 +1479,7 @@ class LapisController(
@RequestParam
dataFormat: String? = null,
): String {
- val insertionRequest = InsertionsRequest(
+ val request = SequenceFiltersRequest(
sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
nucleotideMutations ?: emptyList(),
aminoAcidMutations ?: emptyList(),
@@ -1463,12 +1490,12 @@ class LapisController(
offset,
)
- requestContext.filter = insertionRequest
+ requestContext.filter = request
- return getResponseAsCsv(insertionRequest, TAB, siloQueryModel::getAminoAcidInsertions)
+ return getResponseAsCsv(request, TAB, siloQueryModel::getAminoAcidInsertions)
}
- @PostMapping("/aminoAcidInsertions", produces = [MediaType.APPLICATION_JSON_VALUE])
+ @PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [MediaType.APPLICATION_JSON_VALUE])
@LapisAminoAcidInsertionsResponse
@Operation(
operationId = "postAminoAcidInsertions",
@@ -1476,7 +1503,7 @@ class LapisController(
fun postAminoAcidInsertions(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): LapisResponse> {
requestContext.filter = request
@@ -1484,7 +1511,7 @@ class LapisController(
return LapisResponse(result)
}
- @PostMapping("/aminoAcidInsertions", produces = [TEXT_CSV_HEADER])
+ @PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_CSV_HEADER])
@Operation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidInsertionsAsCsv",
@@ -1493,14 +1520,14 @@ class LapisController(
fun postAminoAcidInsertionsAsCsv(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): String {
requestContext.filter = request
return getResponseAsCsv(request, COMMA, siloQueryModel::getAminoAcidInsertions)
}
- @PostMapping("/aminoAcidInsertions", produces = [TEXT_TSV_HEADER])
+ @PostMapping(AMINO_ACID_INSERTIONS_ROUTE, produces = [TEXT_TSV_HEADER])
@Operation(
description = AMINO_ACID_INSERTIONS_ENDPOINT_DESCRIPTION,
operationId = "postAminoAcidInsertionsAsTsv",
@@ -1509,13 +1536,83 @@ class LapisController(
fun postAminoAcidInsertionsAsTsv(
@Parameter(schema = Schema(ref = "#/components/schemas/$INSERTIONS_REQUEST_SCHEMA"))
@RequestBody
- request: InsertionsRequest,
+ request: SequenceFiltersRequest,
): String {
requestContext.filter = request
return getResponseAsCsv(request, TAB, siloQueryModel::getAminoAcidInsertions)
}
+ @GetMapping("$AMINO_ACID_SEQUENCES_ROUTE/{gene}", produces = ["text/x-fasta"])
+ @LapisAminoAcidSequenceResponse
+ fun getAminoAcidSequence(
+ @PathVariable(name = "gene", required = true) gene: String,
+ @SequenceFilters
+ @RequestParam
+ sequenceFilters: Map?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$ORDER_BY_FIELDS_SCHEMA"),
+ description = AGGREGATED_ORDER_BY_FIELDS_DESCRIPTION,
+ )
+ @RequestParam
+ orderBy: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_MUTATIONS_SCHEMA"),
+ explode = Explode.TRUE,
+ )
+ @RequestParam
+ nucleotideMutations: List?,
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_MUTATIONS_SCHEMA"))
+ @RequestParam
+ aminoAcidMutations: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
+ nucleotideInsertions: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
+ aminoAcidInsertions: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
+ description = LIMIT_DESCRIPTION,
+ )
+ @RequestParam
+ limit: Int? = null,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$OFFSET_SCHEMA"),
+ description = OFFSET_DESCRIPTION,
+ )
+ @RequestParam
+ offset: Int? = null,
+ ): String {
+ val request = SequenceFiltersRequest(
+ sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
+ nucleotideMutations ?: emptyList(),
+ aminoAcidMutations ?: emptyList(),
+ nucleotideInsertions ?: emptyList(),
+ aminoAcidInsertions ?: emptyList(),
+ orderBy ?: emptyList(),
+ limit,
+ offset,
+ )
+
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(request, SequenceType.ALIGNED, gene)
+ }
+
+ @PostMapping("$AMINO_ACID_SEQUENCES_ROUTE/{gene}", produces = ["text/x-fasta"])
+ @LapisAminoAcidSequenceResponse
+ fun postAminoAcidSequence(
+ @PathVariable(name = "gene", required = true) gene: String,
+ @Parameter(schema = Schema(ref = "#/components/schemas/$SEQUENCE_REQUEST_SCHEMA"))
+ @RequestBody
+ request: SequenceFiltersRequest,
+ ): String {
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(request, SequenceType.ALIGNED, gene)
+ }
+
private fun getResponseAsCsv(
request: Request,
delimiter: Delimiter,
@@ -1612,6 +1709,17 @@ private annotation class LapisNucleotideInsertionsResponse
)
private annotation class LapisAminoAcidInsertionsResponse
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+@Operation(
+ description = AMINO_ACID_SEQUENCE_ENDPOINT_DESCRIPTION,
+)
+@ApiResponse(
+ responseCode = "200",
+ description = "OK",
+)
+private annotation class LapisAminoAcidSequenceResponse
+
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Parameter(
@@ -1620,4 +1728,4 @@ private annotation class LapisAminoAcidInsertionsResponse
explode = Explode.TRUE,
style = ParameterStyle.FORM,
)
-private annotation class SequenceFilters
+annotation class SequenceFilters
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceController.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceController.kt
new file mode 100644
index 00000000..0465edcf
--- /dev/null
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceController.kt
@@ -0,0 +1,126 @@
+package org.genspectrum.lapis.controller
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.Parameter
+import io.swagger.v3.oas.annotations.enums.Explode
+import io.swagger.v3.oas.annotations.media.Schema
+import io.swagger.v3.oas.annotations.responses.ApiResponse
+import org.genspectrum.lapis.config.REFERENCE_GENOME_APPLICATION_ARG_PREFIX
+import org.genspectrum.lapis.logging.RequestContext
+import org.genspectrum.lapis.model.SiloQueryModel
+import org.genspectrum.lapis.request.AminoAcidInsertion
+import org.genspectrum.lapis.request.AminoAcidMutation
+import org.genspectrum.lapis.request.NucleotideInsertion
+import org.genspectrum.lapis.request.NucleotideMutation
+import org.genspectrum.lapis.request.OrderByField
+import org.genspectrum.lapis.request.SequenceFiltersRequest
+import org.genspectrum.lapis.silo.SequenceType
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+private const val ALIGNED_MULTI_SEGMENTED_NUCLEOTIDE_SEQUENCE_ENDPOINT_DESCRIPTION =
+ "Returns a string of fasta formatted aligned nucleotide sequences of the requested segment. " +
+ "Only sequences matching the specified sequence filters are considered."
+
+const val isMultiSegmentSequenceExpression = "#{'\${$REFERENCE_GENOME_APPLICATION_ARG_PREFIX}'.split(',').length > 1}"
+
+@RestController
+@ConditionalOnExpression(isMultiSegmentSequenceExpression)
+class MultiSegmentedSequenceController(
+ private val siloQueryModel: SiloQueryModel,
+ private val requestContext: RequestContext,
+) {
+ @GetMapping("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}", produces = ["text/x-fasta"])
+ @LapisAlignedMultiSegmentedNucleotideSequenceResponse
+ fun getAlignedNucleotideSequence(
+ @PathVariable(name = "segment", required = true) segment: String,
+ @SequenceFilters
+ @RequestParam
+ sequenceFilters: Map?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$ORDER_BY_FIELDS_SCHEMA"),
+ description = AGGREGATED_ORDER_BY_FIELDS_DESCRIPTION,
+ )
+ @RequestParam
+ orderBy: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_MUTATIONS_SCHEMA"),
+ explode = Explode.TRUE,
+ )
+ @RequestParam
+ nucleotideMutations: List?,
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_MUTATIONS_SCHEMA"))
+ @RequestParam
+ aminoAcidMutations: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
+ nucleotideInsertions: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
+ aminoAcidInsertions: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
+ description = LIMIT_DESCRIPTION,
+ )
+ @RequestParam
+ limit: Int? = null,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$OFFSET_SCHEMA"),
+ description = OFFSET_DESCRIPTION,
+ )
+ @RequestParam
+ offset: Int? = null,
+ ): String {
+ val request = SequenceFiltersRequest(
+ sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
+ nucleotideMutations ?: emptyList(),
+ aminoAcidMutations ?: emptyList(),
+ nucleotideInsertions ?: emptyList(),
+ aminoAcidInsertions ?: emptyList(),
+ orderBy ?: emptyList(),
+ limit,
+ offset,
+ )
+
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(
+ request,
+ SequenceType.ALIGNED,
+ segment,
+ )
+ }
+
+ @PostMapping("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/{segment}", produces = ["text/x-fasta"])
+ @LapisAlignedMultiSegmentedNucleotideSequenceResponse
+ fun postAlignedNucleotideSequence(
+ @PathVariable(name = "segment", required = true) segment: String,
+ @Parameter(schema = Schema(ref = "#/components/schemas/$SEQUENCE_REQUEST_SCHEMA"))
+ @RequestBody
+ request: SequenceFiltersRequest,
+ ): String {
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(
+ request,
+ SequenceType.ALIGNED,
+ segment,
+ )
+ }
+}
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+@Operation(
+ description = ALIGNED_MULTI_SEGMENTED_NUCLEOTIDE_SEQUENCE_ENDPOINT_DESCRIPTION,
+)
+@ApiResponse(
+ responseCode = "200",
+ description = "OK",
+)
+private annotation class LapisAlignedMultiSegmentedNucleotideSequenceResponse
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceController.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceController.kt
new file mode 100644
index 00000000..4f6c75b4
--- /dev/null
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceController.kt
@@ -0,0 +1,126 @@
+package org.genspectrum.lapis.controller
+
+import io.swagger.v3.oas.annotations.Operation
+import io.swagger.v3.oas.annotations.Parameter
+import io.swagger.v3.oas.annotations.enums.Explode
+import io.swagger.v3.oas.annotations.media.Schema
+import io.swagger.v3.oas.annotations.responses.ApiResponse
+import org.genspectrum.lapis.config.REFERENCE_GENOME_APPLICATION_ARG_PREFIX
+import org.genspectrum.lapis.config.ReferenceGenome
+import org.genspectrum.lapis.logging.RequestContext
+import org.genspectrum.lapis.model.SiloQueryModel
+import org.genspectrum.lapis.request.AminoAcidInsertion
+import org.genspectrum.lapis.request.AminoAcidMutation
+import org.genspectrum.lapis.request.NucleotideInsertion
+import org.genspectrum.lapis.request.NucleotideMutation
+import org.genspectrum.lapis.request.OrderByField
+import org.genspectrum.lapis.request.SequenceFiltersRequest
+import org.genspectrum.lapis.silo.SequenceType
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestBody
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RestController
+
+private const val ALIGNED_SINGLE_SEGMENTED_NUCLEOTIDE_SEQUENCE_ENDPOINT_DESCRIPTION =
+ "Returns a string of fasta formatted aligned nucleotide sequences. Only sequences matching the " +
+ "specified sequence filters are considered."
+
+const val isSingleSegmentSequenceExpression = "#{'\${$REFERENCE_GENOME_APPLICATION_ARG_PREFIX}'.split(',').length == 1}"
+
+@RestController
+@ConditionalOnExpression(isSingleSegmentSequenceExpression)
+class SingleSegmentedSequenceController(
+ private val siloQueryModel: SiloQueryModel,
+ private val requestContext: RequestContext,
+ private val referenceGenome: ReferenceGenome,
+) {
+
+ @GetMapping(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE, produces = ["text/x-fasta"])
+ @LapisAlignedSingleSegmentedNucleotideSequenceResponse
+ fun getAlignedNucleotideSequences(
+ @SequenceFilters
+ @RequestParam
+ sequenceFilters: Map?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$ORDER_BY_FIELDS_SCHEMA"),
+ description = AGGREGATED_ORDER_BY_FIELDS_DESCRIPTION,
+ )
+ @RequestParam
+ orderBy: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_MUTATIONS_SCHEMA"),
+ explode = Explode.TRUE,
+ )
+ @RequestParam
+ nucleotideMutations: List?,
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_MUTATIONS_SCHEMA"))
+ @RequestParam
+ aminoAcidMutations: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$NUCLEOTIDE_INSERTIONS_SCHEMA"))
+ nucleotideInsertions: List?,
+ @RequestParam
+ @Parameter(schema = Schema(ref = "#/components/schemas/$AMINO_ACID_INSERTIONS_SCHEMA"))
+ aminoAcidInsertions: List?,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$LIMIT_SCHEMA"),
+ description = LIMIT_DESCRIPTION,
+ )
+ @RequestParam
+ limit: Int? = null,
+ @Parameter(
+ schema = Schema(ref = "#/components/schemas/$OFFSET_SCHEMA"),
+ description = OFFSET_DESCRIPTION,
+ )
+ @RequestParam
+ offset: Int? = null,
+ ): String {
+ val request = SequenceFiltersRequest(
+ sequenceFilters?.filter { !SPECIAL_REQUEST_PROPERTIES.contains(it.key) } ?: emptyMap(),
+ nucleotideMutations ?: emptyList(),
+ aminoAcidMutations ?: emptyList(),
+ nucleotideInsertions ?: emptyList(),
+ aminoAcidInsertions ?: emptyList(),
+ orderBy ?: emptyList(),
+ limit,
+ offset,
+ )
+
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(
+ request,
+ SequenceType.ALIGNED,
+ referenceGenome.nucleotideSequences[0].name,
+ )
+ }
+
+ @PostMapping(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE, produces = ["text/x-fasta"])
+ @LapisAlignedSingleSegmentedNucleotideSequenceResponse
+ fun postAlignedNucleotideSequence(
+ @Parameter(schema = Schema(ref = "#/components/schemas/$SEQUENCE_REQUEST_SCHEMA"))
+ @RequestBody
+ request: SequenceFiltersRequest,
+ ): String {
+ requestContext.filter = request
+
+ return siloQueryModel.getGenomicSequence(
+ request,
+ SequenceType.ALIGNED,
+ referenceGenome.nucleotideSequences[0].name,
+ )
+ }
+}
+
+@Target(AnnotationTarget.FUNCTION)
+@Retention(AnnotationRetention.RUNTIME)
+@Operation(
+ description = ALIGNED_SINGLE_SEGMENTED_NUCLEOTIDE_SEQUENCE_ENDPOINT_DESCRIPTION,
+)
+@ApiResponse(
+ responseCode = "200",
+ description = "OK",
+)
+private annotation class LapisAlignedSingleSegmentedNucleotideSequenceResponse
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt
index 1abb1249..15f08881 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/model/SiloQueryModel.kt
@@ -1,14 +1,15 @@
package org.genspectrum.lapis.model
-import org.genspectrum.lapis.config.SingleSegmentedSequenceFeature
-import org.genspectrum.lapis.request.InsertionsRequest
+import org.genspectrum.lapis.config.ReferenceGenome
import org.genspectrum.lapis.request.MutationProportionsRequest
+import org.genspectrum.lapis.request.SequenceFiltersRequest
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
import org.genspectrum.lapis.response.AminoAcidInsertionResponse
import org.genspectrum.lapis.response.AminoAcidMutationResponse
import org.genspectrum.lapis.response.DetailsData
import org.genspectrum.lapis.response.NucleotideInsertionResponse
import org.genspectrum.lapis.response.NucleotideMutationResponse
+import org.genspectrum.lapis.silo.SequenceType
import org.genspectrum.lapis.silo.SiloAction
import org.genspectrum.lapis.silo.SiloClient
import org.genspectrum.lapis.silo.SiloQuery
@@ -18,7 +19,7 @@ import org.springframework.stereotype.Component
class SiloQueryModel(
private val siloClient: SiloClient,
private val siloFilterExpressionMapper: SiloFilterExpressionMapper,
- private val singleSegmentedSequenceFeature: SingleSegmentedSequenceFeature,
+ private val referenceGenome: ReferenceGenome,
) {
fun getAggregated(sequenceFilters: SequenceFiltersRequestWithFields) = siloClient.sendQuery(
@@ -49,7 +50,7 @@ class SiloQueryModel(
)
return data.map { it ->
val sequenceName =
- if (singleSegmentedSequenceFeature.isEnabled()) it.mutation else "${it.sequenceName}:${it.mutation}"
+ if (referenceGenome.isSingleSegmented()) it.mutation else "${it.sequenceName}:${it.mutation}"
NucleotideMutationResponse(
sequenceName,
@@ -95,7 +96,7 @@ class SiloQueryModel(
),
)
- fun getNucleotideInsertions(sequenceFilters: InsertionsRequest): List {
+ fun getNucleotideInsertions(sequenceFilters: SequenceFiltersRequest): List {
val data = siloClient.sendQuery(
SiloQuery(
SiloAction.nucleotideInsertions(
@@ -108,7 +109,7 @@ class SiloQueryModel(
)
return data.map { it ->
- val sequenceName = if (singleSegmentedSequenceFeature.isEnabled()) "" else "${it.sequenceName}:"
+ val sequenceName = if (referenceGenome.isSingleSegmented()) "" else "${it.sequenceName}:"
NucleotideInsertionResponse(
"ins_${sequenceName}${it.position}:${it.insertions}",
@@ -117,7 +118,7 @@ class SiloQueryModel(
}
}
- fun getAminoAcidInsertions(sequenceFilters: InsertionsRequest): List {
+ fun getAminoAcidInsertions(sequenceFilters: SequenceFiltersRequest): List {
val data = siloClient.sendQuery(
SiloQuery(
SiloAction.aminoAcidInsertions(
@@ -136,4 +137,23 @@ class SiloQueryModel(
)
}
}
+
+ fun getGenomicSequence(
+ sequenceFilters: SequenceFiltersRequest,
+ sequenceType: SequenceType,
+ sequenceName: String,
+ ): String {
+ return siloClient.sendQuery(
+ SiloQuery(
+ SiloAction.genomicSequence(
+ sequenceType,
+ sequenceName,
+ sequenceFilters.orderByFields,
+ sequenceFilters.limit,
+ sequenceFilters.offset,
+ ),
+ siloFilterExpressionMapper.map(sequenceFilters),
+ ),
+ ).joinToString("\n") { ">${it.sequenceKey}\n${it.sequence}" }
+ }
}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/request/InsertionsRequest.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequest.kt
similarity index 87%
rename from lapis2/src/main/kotlin/org/genspectrum/lapis/request/InsertionsRequest.kt
rename to lapis2/src/main/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequest.kt
index 4c28389e..5ee5e336 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/request/InsertionsRequest.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/request/SequenceFiltersRequest.kt
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import org.springframework.boot.jackson.JsonComponent
-data class InsertionsRequest(
+data class SequenceFiltersRequest(
override val sequenceFilters: Map,
override val nucleotideMutations: List,
override val aaMutations: List,
@@ -18,14 +18,14 @@ data class InsertionsRequest(
) : CommonSequenceFilters
@JsonComponent
-class InsertionRequestDeserializer : JsonDeserializer() {
- override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): InsertionsRequest {
+class SequenceFiltersRequestDeserializer : JsonDeserializer() {
+ override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): SequenceFiltersRequest {
val node = jsonParser.readValueAsTree()
val codec = jsonParser.codec
val parsedCommonFields = parseCommonFields(node, codec)
- return InsertionsRequest(
+ return SequenceFiltersRequest(
parsedCommonFields.sequenceFilters,
parsedCommonFields.nucleotideMutations,
parsedCommonFields.aminoAcidMutations,
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/response/SiloResponse.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/response/SiloResponse.kt
index 86d08f30..d0edb1ab 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/response/SiloResponse.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/response/SiloResponse.kt
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import io.swagger.v3.oas.annotations.media.Schema
+import org.genspectrum.lapis.config.DatabaseConfig
import org.genspectrum.lapis.controller.CsvRecord
import org.springframework.boot.jackson.JsonComponent
@@ -64,3 +65,19 @@ data class InsertionData(
val position: Int,
val sequenceName: String,
)
+
+data class SequenceData(
+ val sequenceKey: String,
+ val sequence: String,
+)
+
+@JsonComponent
+class SequenceDataDeserializer(val databaseConfig: DatabaseConfig) : JsonDeserializer() {
+ override fun deserialize(p: JsonParser, ctxt: DeserializationContext): SequenceData {
+ val node = p.readValueAsTree()
+ val sequenceKey = node.get(databaseConfig.schema.primaryKey).asText()
+ val sequence =
+ node.fields().asSequence().first { it.key != databaseConfig.schema.primaryKey }.value.asText()
+ return SequenceData(sequenceKey, sequence)
+ }
+}
diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt
index 7365f285..ad497938 100644
--- a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt
+++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt
@@ -2,12 +2,14 @@ package org.genspectrum.lapis.silo
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.type.TypeReference
import org.genspectrum.lapis.request.OrderByField
import org.genspectrum.lapis.response.AggregationData
import org.genspectrum.lapis.response.DetailsData
import org.genspectrum.lapis.response.InsertionData
import org.genspectrum.lapis.response.MutationData
+import org.genspectrum.lapis.response.SequenceData
import java.time.LocalDate
data class SiloQuery(val action: SiloAction, val filterExpression: SiloFilterExpression)
@@ -17,6 +19,7 @@ class MutationDataTypeReference : TypeReference>>()
class DetailsDataTypeReference : TypeReference>>()
class InsertionDataTypeReference : TypeReference>>()
+class SequenceDataTypeReference : TypeReference>>()
interface CommonActionFields {
val orderByFields: List
@@ -78,6 +81,14 @@ sealed class SiloAction(
limit: Int? = null,
offset: Int? = null,
): SiloAction> = AminoAcidInsertionsAction(orderByFields, limit, offset)
+
+ fun genomicSequence(
+ type: SequenceType,
+ sequenceName: String,
+ orderByFields: List = emptyList(),
+ limit: Int? = null,
+ offset: Int? = null,
+ ): SiloAction> = SequenceAction(orderByFields, limit, offset, type, sequenceName)
}
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@@ -131,6 +142,15 @@ sealed class SiloAction(
override val offset: Int? = null,
val type: String = "AminoAcidInsertions",
) : SiloAction>(InsertionDataTypeReference())
+
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ private data class SequenceAction(
+ override val orderByFields: List = emptyList(),
+ override val limit: Int? = null,
+ override val offset: Int? = null,
+ val type: SequenceType,
+ val sequenceName: String,
+ ) : SiloAction>(SequenceDataTypeReference())
}
sealed class SiloFilterExpression(val type: String)
@@ -184,3 +204,11 @@ data class IntBetween(val column: String, val from: Int?, val to: Int?) : SiloFi
data class FloatEquals(val column: String, val value: Double) : SiloFilterExpression("FloatEquals")
data class FloatBetween(val column: String, val from: Double?, val to: Double?) : SiloFilterExpression("FloatBetween")
+
+enum class SequenceType {
+ @JsonProperty("Fasta")
+ UNALIGNED,
+
+ @JsonProperty("FastaAligned")
+ ALIGNED,
+}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt
index 97941942..a560a53e 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/auth/ProtectedDataAuthorizationTest.kt
@@ -4,6 +4,7 @@ import com.ninjasquad.springmockk.MockkBean
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.verify
+import org.genspectrum.lapis.controller.AGGREGATED_ROUTE
import org.genspectrum.lapis.model.SiloQueryModel
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
import org.junit.jupiter.api.BeforeEach
@@ -34,7 +35,7 @@ class ProtectedDataAuthorizationTest(@Autowired val mockMvc: MockMvc) {
@MockkBean
lateinit var siloQueryModelMock: SiloQueryModel
- private val validRoute = "/aggregated"
+ private val validRoute = AGGREGATED_ROUTE
@BeforeEach
fun setUp() {
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt
index 24c5baeb..e0e17ef7 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/DatabaseConfigTest.kt
@@ -34,7 +34,6 @@ class DatabaseConfigTest {
underTest.schema.features,
containsInAnyOrder(
DatabaseFeature(name = SARS_COV2_VARIANT_QUERY_FEATURE),
- DatabaseFeature(name = IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE),
),
)
}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/ReferenceGenomeTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/ReferenceGenomeTest.kt
new file mode 100644
index 00000000..7f3cd88d
--- /dev/null
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/ReferenceGenomeTest.kt
@@ -0,0 +1,54 @@
+package org.genspectrum.lapis.config
+
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import java.lang.IllegalArgumentException
+
+private const val REFERENCE_GENOME_DEFAULT_FILENAME = "src/test/resources/config/reference-genomes.json"
+
+class ReferenceGenomeTest {
+ @Test
+ fun `should read from file`() {
+ val referenceGenome = ReferenceGenome.readFromFile(REFERENCE_GENOME_DEFAULT_FILENAME)
+ assertThat(referenceGenome.nucleotideSequences.size, equalTo(1))
+ assertThat(referenceGenome.nucleotideSequences[0].name, equalTo("main"))
+ }
+
+ @Test
+ fun `should read from file through program args`() {
+ val args = arrayOf("--referenceGenomeFilename=$REFERENCE_GENOME_DEFAULT_FILENAME")
+ val referenceGenome = ReferenceGenome.readFromFileFromProgramArgs(args)
+ assertThat(referenceGenome.nucleotideSequences.size, equalTo(1))
+ assertThat(referenceGenome.nucleotideSequences[0].name, equalTo("main"))
+ }
+
+ @Test
+ fun `should throw if no reference genome filename is given in the args`() {
+ val args = emptyArray()
+ assertThrows { ReferenceGenome.readFromFileFromProgramArgs(args) }
+ }
+
+ @Test
+ fun `should generate spring application arguments`() {
+ val referenceGenome = ReferenceGenome(
+ listOf(NucleotideSequence("main"), NucleotideSequence("other_segment")),
+ )
+ val springArgs = referenceGenome.toSpringApplicationArgs()
+ assertThat(springArgs[0], equalTo("--$REFERENCE_GENOME_APPLICATION_ARG_PREFIX=main,other_segment"))
+ }
+
+ @Test
+ fun `should detect single segmented sequence`() {
+ val singleSegmented = ReferenceGenome(
+ listOf(NucleotideSequence("main")),
+ )
+ assertThat(singleSegmented.isSingleSegmented(), equalTo(true))
+
+ val multiSegmented = ReferenceGenome(
+ listOf(NucleotideSequence("main"), NucleotideSequence("other_segment")),
+ )
+ assertThat(multiSegmented.isSingleSegmented(), equalTo(false))
+ }
+}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt
deleted file mode 100644
index c7448293..00000000
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/config/SingleSegmentedSequenceFeatureTest.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package org.genspectrum.lapis.config
-
-import org.junit.jupiter.api.Assertions.assertFalse
-import org.junit.jupiter.api.Assertions.assertTrue
-import org.junit.jupiter.api.Test
-
-class SingleSegmentedSequenceFeatureTest {
- @Test
- fun `given a databaseConfig with a feature named isSingleSegmentedSequence then isEnabled returns true`() {
- val input = databaseConfigWithFeatures(listOf(DatabaseFeature(IS_SEQUENCE_SINGLE_SEGMENTED_FEATURE)))
-
- val underTest = SingleSegmentedSequenceFeature(input)
-
- assertTrue(underTest.isEnabled())
- }
-
- @Test
- fun `given a databaseConfig without a feature named isSingleSegmentedSequence then isEnabled returns false`() {
- val input = databaseConfigWithFeatures(listOf(DatabaseFeature("notTheRightFeature")))
-
- val underTest = SingleSegmentedSequenceFeature(input)
-
- assertFalse(underTest.isEnabled())
- }
-
- private fun databaseConfigWithFeatures(
- databaseFeatures: List = emptyList(),
- ) = DatabaseConfig(
- DatabaseSchema(
- "test config",
- OpennessLevel.OPEN,
- emptyList(),
- "test primary key",
- databaseFeatures,
- ),
- )
-}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCommonFieldsTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCommonFieldsTest.kt
index 58bf7ee0..d0bf0938 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCommonFieldsTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCommonFieldsTest.kt
@@ -27,7 +27,9 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
@SpringBootTest
@AutoConfigureMockMvc
-class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
+class LapisControllerCommonFieldsTest(
+ @Autowired val mockMvc: MockMvc,
+) {
@MockkBean
lateinit var siloQueryModelMock: SiloQueryModel
@@ -47,7 +49,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- mockMvc.perform(get("/aggregated?orderBy=country"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?orderBy=country"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -69,7 +71,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- mockMvc.perform(get("/aggregated?orderBy=country,date"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?orderBy=country,date"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -91,7 +93,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"orderBy": ["country", "date"]}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -120,7 +122,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content(
"""
{
@@ -141,7 +143,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
@Test
fun `POST aggregated with invalid orderBy fields`() {
- val request = post("/aggregated")
+ val request = post("$AGGREGATED_ROUTE")
.content("""{"orderBy": [ { "field": ["this is an array, not a string"] } ]}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -167,7 +169,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- mockMvc.perform(get("/aggregated?limit=100"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?limit=100"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -190,7 +192,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"limit": 100}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -201,7 +203,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
@Test
fun `POST aggregated with invalid limit`() {
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"limit": "this is not a number"}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -228,7 +230,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- mockMvc.perform(get("/aggregated?offset=5"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?offset=5"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -252,7 +254,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(0, mapOf("country" to TextNode("Switzerland"))))
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"offset": 5}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -263,7 +265,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
@Test
fun `POST aggregated with invalid offset`() {
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"offset": "this is not a number"}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -287,7 +289,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(5, emptyMap()))
- mockMvc.perform(get("/aggregated?nucleotideInsertions=ins_123:ABC,ins_segment:124:DEF"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?nucleotideInsertions=ins_123:ABC,ins_segment:124:DEF"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(5))
}
@@ -307,7 +309,7 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(5, emptyMap()))
- mockMvc.perform(get("/aggregated?aminoAcidInsertions=ins_S:123:ABC,ins_ORF1:124:DEF"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?aminoAcidInsertions=ins_S:123:ABC,ins_ORF1:124:DEF"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(5))
}
@@ -351,20 +353,23 @@ class LapisControllerCommonFieldsTest(@Autowired val mockMvc: MockMvc) {
}
private companion object {
- fun allEndpoints() = listOf(
- Arguments.of("/nucleotideMutations"),
- Arguments.of("/aminoAcidMutations"),
- Arguments.of("/aggregated"),
- Arguments.of("/details"),
+ fun endpointsOfController() = listOf(
+ Arguments.of(NUCLEOTIDE_MUTATIONS_ROUTE),
+ Arguments.of(AMINO_ACID_MUTATIONS_ROUTE),
+ Arguments.of(AGGREGATED_ROUTE),
+ Arguments.of(DETAILS_ROUTE),
+ Arguments.of(NUCLEOTIDE_INSERTIONS_ROUTE),
+ Arguments.of(AMINO_ACID_INSERTIONS_ROUTE),
+ Arguments.of("$AMINO_ACID_SEQUENCES_ROUTE/S"),
)
@JvmStatic
- fun getEndpointsWithInsertionFilter() = allEndpoints()
+ fun getEndpointsWithInsertionFilter() = endpointsOfController()
@JvmStatic
- fun getEndpointsWithNucleotideMutationFilter() = allEndpoints()
+ fun getEndpointsWithNucleotideMutationFilter() = endpointsOfController()
@JvmStatic
- fun getEndpointsWithAminoAcidMutationFilter() = allEndpoints()
+ fun getEndpointsWithAminoAcidMutationFilter() = endpointsOfController()
}
}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCsvTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCsvTest.kt
index 051d23b1..09026c63 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCsvTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerCsvTest.kt
@@ -221,34 +221,34 @@ class LapisControllerCsvTest(@Autowired val mockMvc: MockMvc) {
fun mockEndpointReturnEmptyList(endpoint: String) =
when (endpoint) {
- "/details" ->
+ DETAILS_ROUTE ->
every {
siloQueryModelMock.getDetails(sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")))
} returns emptyList()
- "/aggregated" ->
+ AGGREGATED_ROUTE ->
every {
siloQueryModelMock.getAggregated(
sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")),
)
} returns emptyList()
- "/nucleotideMutations" ->
+ NUCLEOTIDE_MUTATIONS_ROUTE ->
every {
siloQueryModelMock.computeNucleotideMutationProportions(any())
} returns emptyList()
- "/aminoAcidMutations" ->
+ AMINO_ACID_MUTATIONS_ROUTE ->
every {
siloQueryModelMock.computeAminoAcidMutationProportions(any())
} returns emptyList()
- "/nucleotideInsertions" ->
+ NUCLEOTIDE_INSERTIONS_ROUTE ->
every {
siloQueryModelMock.getNucleotideInsertions(any())
} returns emptyList()
- "/aminoAcidInsertions" ->
+ AMINO_ACID_INSERTIONS_ROUTE ->
every {
siloQueryModelMock.getAminoAcidInsertions(any())
} returns emptyList()
@@ -257,32 +257,32 @@ class LapisControllerCsvTest(@Autowired val mockMvc: MockMvc) {
}
fun mockEndpointReturnData(endpoint: String) = when (endpoint) {
- "/details" ->
+ DETAILS_ROUTE ->
every {
siloQueryModelMock.getDetails(sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")))
} returns detailsData
- "/aggregated" ->
+ AGGREGATED_ROUTE ->
every {
siloQueryModelMock.getAggregated(sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")))
} returns aggregationData
- "/nucleotideMutations" ->
+ NUCLEOTIDE_MUTATIONS_ROUTE ->
every {
siloQueryModelMock.computeNucleotideMutationProportions(any())
} returns nucleotideMutationData
- "/aminoAcidMutations" ->
+ AMINO_ACID_MUTATIONS_ROUTE ->
every {
siloQueryModelMock.computeAminoAcidMutationProportions(any())
} returns aminoAcidMutationData
- "/nucleotideInsertions" ->
+ NUCLEOTIDE_INSERTIONS_ROUTE ->
every {
siloQueryModelMock.getNucleotideInsertions(any())
} returns nucleotideInsertionData
- "/aminoAcidInsertions" ->
+ AMINO_ACID_INSERTIONS_ROUTE ->
every {
siloQueryModelMock.getAminoAcidInsertions(any())
} returns aminoAcidInsertionData
@@ -291,22 +291,22 @@ class LapisControllerCsvTest(@Autowired val mockMvc: MockMvc) {
}
fun returnedCsvData(endpoint: String) = when (endpoint) {
- "/details" -> detailsDataCsv
- "/aggregated" -> aggregationDataCsv
- "/nucleotideMutations" -> mutationDataCsv
- "/aminoAcidMutations" -> mutationDataCsv
- "/nucleotideInsertions" -> nucleotideInsertionDataCsv
- "/aminoAcidInsertions" -> aminoAcidInsertionDataCsv
+ DETAILS_ROUTE -> detailsDataCsv
+ AGGREGATED_ROUTE -> aggregationDataCsv
+ NUCLEOTIDE_MUTATIONS_ROUTE -> mutationDataCsv
+ AMINO_ACID_MUTATIONS_ROUTE -> mutationDataCsv
+ NUCLEOTIDE_INSERTIONS_ROUTE -> nucleotideInsertionDataCsv
+ AMINO_ACID_INSERTIONS_ROUTE -> aminoAcidInsertionDataCsv
else -> throw IllegalArgumentException("Unknown endpoint: $endpoint")
}
fun returnedTsvData(endpoint: String) = when (endpoint) {
- "/details" -> detailsDataTsv
- "/aggregated" -> aggregationDataTsv
- "/nucleotideMutations" -> mutationDataTsv
- "/aminoAcidMutations" -> mutationDataTsv
- "/nucleotideInsertions" -> nucleotideInsertionDataTsv
- "/aminoAcidInsertions" -> aminoAcidInsertionDataTsv
+ DETAILS_ROUTE -> detailsDataTsv
+ AGGREGATED_ROUTE -> aggregationDataTsv
+ NUCLEOTIDE_MUTATIONS_ROUTE -> mutationDataTsv
+ AMINO_ACID_MUTATIONS_ROUTE -> mutationDataTsv
+ NUCLEOTIDE_INSERTIONS_ROUTE -> nucleotideInsertionDataTsv
+ AMINO_ACID_INSERTIONS_ROUTE -> aminoAcidInsertionDataTsv
else -> throw IllegalArgumentException("Unknown endpoint: $endpoint")
}
@@ -419,12 +419,12 @@ class LapisControllerCsvTest(@Autowired val mockMvc: MockMvc) {
private companion object {
@JvmStatic
fun getEndpoints() = listOf(
- Arguments.of("/details"),
- Arguments.of("/aggregated"),
- Arguments.of("/nucleotideMutations"),
- Arguments.of("/aminoAcidMutations"),
- Arguments.of("/nucleotideInsertions"),
- Arguments.of("/aminoAcidInsertions"),
+ Arguments.of(DETAILS_ROUTE),
+ Arguments.of(AGGREGATED_ROUTE),
+ Arguments.of(NUCLEOTIDE_MUTATIONS_ROUTE),
+ Arguments.of(AMINO_ACID_MUTATIONS_ROUTE),
+ Arguments.of(NUCLEOTIDE_INSERTIONS_ROUTE),
+ Arguments.of(AMINO_ACID_INSERTIONS_ROUTE),
)
}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerTest.kt
index 98c4afec..e65fd7a2 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/LapisControllerTest.kt
@@ -6,9 +6,9 @@ import com.ninjasquad.springmockk.MockkBean
import io.mockk.every
import org.genspectrum.lapis.model.SiloQueryModel
import org.genspectrum.lapis.request.DataVersion
-import org.genspectrum.lapis.request.InsertionsRequest
import org.genspectrum.lapis.request.MutationProportionsRequest
import org.genspectrum.lapis.request.NucleotideMutation
+import org.genspectrum.lapis.request.SequenceFiltersRequest
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
import org.genspectrum.lapis.response.AggregationData
import org.genspectrum.lapis.response.AminoAcidInsertionResponse
@@ -59,7 +59,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
),
)
- mockMvc.perform(get("/aggregated?country=Switzerland"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?country=Switzerland"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -103,7 +103,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
),
)
- mockMvc.perform(get("/aggregated?country=Switzerland&fields=country,age"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?country=Switzerland&fields=country,age"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(0))
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
@@ -125,7 +125,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(AggregationData(5, emptyMap()))
- mockMvc.perform(get("/aggregated?nucleotideMutations=123A,124B"))
+ mockMvc.perform(get("$AGGREGATED_ROUTE?nucleotideMutations=123A,124B"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].count").value(5))
}
@@ -146,7 +146,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
),
)
- val request = post("/aggregated")
+ val request = post(AGGREGATED_ROUTE)
.content("""{"country": "Switzerland", "fields": ["country","age"]}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -299,15 +299,17 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
private fun setupInsertionMock(endpoint: String) {
when (endpoint) {
- "/nucleotideInsertions" -> {
+ NUCLEOTIDE_INSERTIONS_ROUTE -> {
every {
- siloQueryModelMock.getNucleotideInsertions(insertionRequest(mapOf("country" to "Switzerland")))
+ siloQueryModelMock.getNucleotideInsertions(
+ sequenceFiltersRequest(mapOf("country" to "Switzerland")),
+ )
} returns listOf(someNucleotideInsertion())
}
- "/aminoAcidInsertions" -> {
+ AMINO_ACID_INSERTIONS_ROUTE -> {
every {
- siloQueryModelMock.getAminoAcidInsertions(insertionRequest(mapOf("country" to "Switzerland")))
+ siloQueryModelMock.getAminoAcidInsertions(sequenceFiltersRequest(mapOf("country" to "Switzerland")))
} returns listOf(someAminoAcidInsertion())
}
@@ -318,14 +320,14 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
private companion object {
@JvmStatic
fun getMutationEndpoints() = listOf(
- Arguments.of("/nucleotideMutations"),
- Arguments.of("/aminoAcidMutations"),
+ Arguments.of(NUCLEOTIDE_MUTATIONS_ROUTE),
+ Arguments.of(AMINO_ACID_MUTATIONS_ROUTE),
)
@JvmStatic
fun getInsertionEndpoints() = listOf(
- Arguments.of("/nucleotideInsertions"),
- Arguments.of("/aminoAcidInsertions"),
+ Arguments.of(NUCLEOTIDE_INSERTIONS_ROUTE),
+ Arguments.of(AMINO_ACID_INSERTIONS_ROUTE),
)
}
@@ -335,7 +337,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
siloQueryModelMock.getDetails(sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")))
} returns listOf(DetailsData(mapOf("country" to TextNode("Switzerland"), "age" to IntNode(42))))
- mockMvc.perform(get("/details?country=Switzerland"))
+ mockMvc.perform(get("$DETAILS_ROUTE?country=Switzerland"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
.andExpect(jsonPath("\$.data[0].age").value(42))
@@ -353,7 +355,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(DetailsData(mapOf("country" to TextNode("Switzerland"), "age" to IntNode(42))))
- mockMvc.perform(get("/details?country=Switzerland&fields=country&fields=age"))
+ mockMvc.perform(get("$DETAILS_ROUTE?country=Switzerland&fields=country&fields=age"))
.andExpect(status().isOk)
.andExpect(jsonPath("\$.data[0].country").value("Switzerland"))
.andExpect(jsonPath("\$.data[0].age").value(42))
@@ -365,7 +367,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
siloQueryModelMock.getDetails(sequenceFiltersRequestWithFields(mapOf("country" to "Switzerland")))
} returns listOf(DetailsData(mapOf("country" to TextNode("Switzerland"), "age" to IntNode(42))))
- val request = post("/details")
+ val request = post(DETAILS_ROUTE)
.content("""{"country": "Switzerland"}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -387,7 +389,7 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
)
} returns listOf(DetailsData(mapOf("country" to TextNode("Switzerland"), "age" to IntNode(42))))
- val request = post("/details")
+ val request = post(DETAILS_ROUTE)
.content("""{"country": "Switzerland", "fields": ["country", "age"]}""")
.contentType(MediaType.APPLICATION_JSON)
@@ -410,9 +412,9 @@ class LapisControllerTest(@Autowired val mockMvc: MockMvc) {
emptyList(),
)
- private fun insertionRequest(
+ private fun sequenceFiltersRequest(
sequenceFilters: Map,
- ) = InsertionsRequest(
+ ) = SequenceFiltersRequest(
sequenceFilters,
emptyList(),
emptyList(),
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceControllerTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceControllerTest.kt
new file mode 100644
index 00000000..d4255003
--- /dev/null
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/MultiSegmentedSequenceControllerTest.kt
@@ -0,0 +1,161 @@
+package org.genspectrum.lapis.controller
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import org.genspectrum.lapis.config.REFERENCE_GENOME_APPLICATION_ARG_PREFIX
+import org.genspectrum.lapis.model.SiloQueryModel
+import org.genspectrum.lapis.request.DataVersion
+import org.genspectrum.lapis.request.SequenceFiltersRequest
+import org.genspectrum.lapis.silo.SequenceType
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@SpringBootTest(
+ properties = ["$REFERENCE_GENOME_APPLICATION_ARG_PREFIX=someSegment,otherSegment"],
+)
+@AutoConfigureMockMvc
+class MultiSegmentedSequenceControllerTest(@Autowired val mockMvc: MockMvc) {
+ @MockkBean
+ lateinit var siloQueryModelMock: SiloQueryModel
+
+ @MockkBean
+ lateinit var dataVersion: DataVersion
+
+ @BeforeEach
+ fun setup() {
+ every {
+ dataVersion.dataVersion
+ } returns "1234"
+ }
+
+ @Test
+ fun `should GET alignedNucleotideSequences with empty filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "otherSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/otherSegment"))
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should GET alignedNucleotideSequences with filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(mapOf("country" to "Switzerland")),
+ SequenceType.ALIGNED,
+ "otherSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/otherSegment?country=Switzerland"))
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should POST alignedNucleotideSequences with empty filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "otherSegment",
+ )
+ } returns returnedValue
+
+ val request = post("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/otherSegment")
+ .content("""{}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should POST alignedNucleotideSequences with filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(mapOf("country" to "Switzerland")),
+ SequenceType.ALIGNED,
+ "otherSegment",
+ )
+ } returns returnedValue
+
+ val request = post("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/otherSegment")
+ .content("""{"country":"Switzerland"}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should not GET alignedNucleotideSequence without segment`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE))
+ .andExpect(status().isNotFound)
+ }
+
+ @Test
+ fun `should not POST alignedNucleotideSequences without segment`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "otherSegment",
+ )
+ } returns returnedValue
+
+ val request = post(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE)
+ .content("""{}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isNotFound)
+ }
+
+ private fun sequenceFiltersRequest(
+ sequenceFilters: Map,
+ ) = SequenceFiltersRequest(
+ sequenceFilters,
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ )
+}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceControllerTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceControllerTest.kt
new file mode 100644
index 00000000..93c66dde
--- /dev/null
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/controller/SingleSegmentedSequenceControllerTest.kt
@@ -0,0 +1,161 @@
+package org.genspectrum.lapis.controller
+
+import com.ninjasquad.springmockk.MockkBean
+import io.mockk.every
+import org.genspectrum.lapis.config.REFERENCE_GENOME_APPLICATION_ARG_PREFIX
+import org.genspectrum.lapis.model.SiloQueryModel
+import org.genspectrum.lapis.request.DataVersion
+import org.genspectrum.lapis.request.SequenceFiltersRequest
+import org.genspectrum.lapis.silo.SequenceType
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+
+@SpringBootTest(
+ properties = ["$REFERENCE_GENOME_APPLICATION_ARG_PREFIX=someSegment"],
+)
+@AutoConfigureMockMvc
+class SingleSegmentedSequenceControllerTest(@Autowired val mockMvc: MockMvc) {
+ @MockkBean
+ lateinit var siloQueryModelMock: SiloQueryModel
+
+ @MockkBean
+ lateinit var dataVersion: DataVersion
+
+ @BeforeEach
+ fun setup() {
+ every {
+ dataVersion.dataVersion
+ } returns "1234"
+ }
+
+ @Test
+ fun `should GET alignedNucleotideSequences with empty filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE))
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should GET alignedNucleotideSequences with filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(mapOf("country" to "Switzerland")),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE?country=Switzerland"))
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should POST alignedNucleotideSequences with empty filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ val request = MockMvcRequestBuilders.post(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE)
+ .content("""{}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should POST alignedNucleotideSequences with filter`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(mapOf("country" to "Switzerland")),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ val request = MockMvcRequestBuilders.post(ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE)
+ .content("""{"country": "Switzerland"}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isOk)
+ .andExpect(content().string(returnedValue))
+ .andExpect(header().stringValues("Lapis-Data-Version", "1234"))
+ }
+
+ @Test
+ fun `should not GET alignedNucleotideSequence with segment`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ mockMvc.perform(get("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/someSegment"))
+ .andExpect(status().isNotFound)
+ }
+
+ @Test
+ fun `should not POST alignedNucleotideSequences with segment`() {
+ val returnedValue = "TestSequenceContent"
+ every {
+ siloQueryModelMock.getGenomicSequence(
+ sequenceFiltersRequest(emptyMap()),
+ SequenceType.ALIGNED,
+ "someSegment",
+ )
+ } returns returnedValue
+
+ val request = MockMvcRequestBuilders.post("$ALIGNED_NUCLEOTIDE_SEQUENCES_ROUTE/someSegment")
+ .content("""{}""")
+ .contentType(MediaType.APPLICATION_JSON)
+
+ mockMvc.perform(request)
+ .andExpect(status().isNotFound)
+ }
+
+ private fun sequenceFiltersRequest(
+ sequenceFilters: Map,
+ ) = SequenceFiltersRequest(
+ sequenceFilters,
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ )
+}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt
index 6b58cfc9..a96ae3d4 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/model/SiloQueryModelTest.kt
@@ -4,10 +4,10 @@ import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.verify
-import org.genspectrum.lapis.config.SingleSegmentedSequenceFeature
+import org.genspectrum.lapis.config.ReferenceGenome
import org.genspectrum.lapis.request.CommonSequenceFilters
-import org.genspectrum.lapis.request.InsertionsRequest
import org.genspectrum.lapis.request.MutationProportionsRequest
+import org.genspectrum.lapis.request.SequenceFiltersRequest
import org.genspectrum.lapis.request.SequenceFiltersRequestWithFields
import org.genspectrum.lapis.response.AggregationData
import org.genspectrum.lapis.response.AminoAcidInsertionResponse
@@ -16,6 +16,8 @@ import org.genspectrum.lapis.response.InsertionData
import org.genspectrum.lapis.response.MutationData
import org.genspectrum.lapis.response.NucleotideInsertionResponse
import org.genspectrum.lapis.response.NucleotideMutationResponse
+import org.genspectrum.lapis.response.SequenceData
+import org.genspectrum.lapis.silo.SequenceType
import org.genspectrum.lapis.silo.SiloAction
import org.genspectrum.lapis.silo.SiloClient
import org.genspectrum.lapis.silo.SiloQuery
@@ -30,7 +32,7 @@ class SiloQueryModelTest {
lateinit var siloClientMock: SiloClient
@MockK
- lateinit var singleSegmentedSequenceFeatureMock: SingleSegmentedSequenceFeature
+ lateinit var referenceGenomeMock: ReferenceGenome
@MockK
lateinit var siloFilterExpressionMapperMock: SiloFilterExpressionMapper
@@ -40,14 +42,14 @@ class SiloQueryModelTest {
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
- underTest = SiloQueryModel(siloClientMock, siloFilterExpressionMapperMock, singleSegmentedSequenceFeatureMock)
+ underTest = SiloQueryModel(siloClientMock, siloFilterExpressionMapperMock, referenceGenomeMock)
}
@Test
fun `aggregate calls the SILO client with an aggregated action`() {
every { siloClientMock.sendQuery(any>>()) } returns emptyList()
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true
+ every { referenceGenomeMock.isSingleSegmented() } returns true
underTest.getAggregated(
SequenceFiltersRequestWithFields(
@@ -71,7 +73,7 @@ class SiloQueryModelTest {
fun `computeNucleotideMutationProportions calls the SILO client with a mutations action`() {
every { siloClientMock.sendQuery(any>>()) } returns emptyList()
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true
+ every { referenceGenomeMock.isSingleSegmented() } returns true
underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList(), 0.5),
@@ -90,7 +92,7 @@ class SiloQueryModelTest {
MutationData("A1234B", 1234, 0.1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true
+ every { referenceGenomeMock.isSingleSegmented() } returns true
val result = underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList()),
@@ -105,7 +107,7 @@ class SiloQueryModelTest {
MutationData("A1234B", 1234, 0.1234, "someSegmentName"),
)
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns false
+ every { referenceGenomeMock.isSingleSegmented() } returns false
val result = underTest.computeNucleotideMutationProportions(
MutationProportionsRequest(emptyMap(), emptyList(), emptyList(), emptyList(), emptyList()),
@@ -129,15 +131,15 @@ class SiloQueryModelTest {
}
@Test
- fun `getNucleotideInsertions ignores the field sequenceName if if singleSegmentedSequenceFeature is enabled`() {
+ fun `getNucleotideInsertions ignores the field sequenceName if the nucleotide sequence has one segment`() {
every { siloClientMock.sendQuery(any>>()) } returns listOf(
InsertionData(42, "ABCD", 1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns true
+ every { referenceGenomeMock.isSingleSegmented() } returns true
val result = underTest.getNucleotideInsertions(
- InsertionsRequest(
+ SequenceFiltersRequest(
emptyMap(),
emptyList(),
emptyList(),
@@ -151,15 +153,15 @@ class SiloQueryModelTest {
}
@Test
- fun `getNucleotideInsertions includes the field sequenceName if singleSegmentedSequenceFeature is not enabled`() {
+ fun `getNucleotideInsertions includes the field sequenceName if the nucleotide sequence has multiple segments`() {
every { siloClientMock.sendQuery(any>>()) } returns listOf(
InsertionData(42, "ABCD", 1234, "someSequenceName"),
)
every { siloFilterExpressionMapperMock.map(any()) } returns True
- every { singleSegmentedSequenceFeatureMock.isEnabled() } returns false
+ every { referenceGenomeMock.isSingleSegmented() } returns false
val result = underTest.getNucleotideInsertions(
- InsertionsRequest(
+ SequenceFiltersRequest(
emptyMap(),
emptyList(),
emptyList(),
@@ -180,7 +182,7 @@ class SiloQueryModelTest {
every { siloFilterExpressionMapperMock.map(any()) } returns True
val result = underTest.getAminoAcidInsertions(
- InsertionsRequest(
+ SequenceFiltersRequest(
emptyMap(),
emptyList(),
emptyList(),
@@ -192,4 +194,29 @@ class SiloQueryModelTest {
assertThat(result, equalTo(listOf(AminoAcidInsertionResponse("ins_someGene:1234:ABCD", 42))))
}
+
+ @Test
+ fun `getGenomicSequence calls the SILO client with a sequence action`() {
+ every { siloClientMock.sendQuery(any>>()) } returns emptyList()
+ every { siloFilterExpressionMapperMock.map(any()) } returns True
+
+ underTest.getGenomicSequence(
+ SequenceFiltersRequest(
+ emptyMap(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ ),
+ SequenceType.ALIGNED,
+ "someSequenceName",
+ )
+
+ verify {
+ siloClientMock.sendQuery(
+ SiloQuery(SiloAction.genomicSequence(SequenceType.ALIGNED, "someSequenceName"), True),
+ )
+ }
+ }
}
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt
index 89e80ce6..f8f948af 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.TextNode
import org.genspectrum.lapis.response.AggregationData
import org.genspectrum.lapis.response.DetailsData
import org.genspectrum.lapis.response.MutationData
+import org.genspectrum.lapis.response.SequenceData
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsInAnyOrder
import org.hamcrest.Matchers.containsString
@@ -154,6 +155,43 @@ class SiloClientTest {
)
}
+ @Test
+ fun `given server returns sequence data then response can be deserialized`() {
+ expectQueryRequestAndRespondWith(
+ response()
+ .withContentType(MediaType.APPLICATION_JSON_UTF_8)
+ .withBody(
+ """{
+ "queryResult": [
+ {
+ "gisaid_epi_isl": "key1",
+ "someSequenceName": "ABCD"
+ },
+ {
+ "gisaid_epi_isl": "key2",
+ "someSequenceName": "DEFG"
+ }
+ ]
+ }""",
+ ),
+ )
+
+ val query = SiloQuery(
+ SiloAction.genomicSequence(SequenceType.ALIGNED, "someSequenceName"),
+ StringEquals("theColumn", "theValue"),
+ )
+ val result = underTest.sendQuery(query)
+
+ assertThat(result, hasSize(2))
+ assertThat(
+ result,
+ containsInAnyOrder(
+ SequenceData("key1", "ABCD"),
+ SequenceData("key2", "DEFG"),
+ ),
+ )
+ }
+
@Test
fun `given server returns details response then response can be deserialized`() {
expectQueryRequestAndRespondWith(
diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt
index 3b1ee3fc..3136fb11 100644
--- a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt
+++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt
@@ -196,6 +196,66 @@ class SiloQueryTest {
}
""",
),
+ Arguments.of(
+ SiloAction.genomicSequence(SequenceType.ALIGNED, "someSequenceName"),
+ """
+ {
+ "type": "FastaAligned",
+ "sequenceName": "someSequenceName"
+ }
+ """,
+ ),
+ Arguments.of(
+ SiloAction.genomicSequence(
+ SequenceType.ALIGNED,
+ "someSequenceName",
+ listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)),
+ 100,
+ 50,
+ ),
+ """
+ {
+ "type": "FastaAligned",
+ "sequenceName": "someSequenceName",
+ "orderByFields": [
+ {"field": "field3", "order": "ascending"},
+ {"field": "field4", "order": "descending"}
+ ],
+ "limit": 100,
+ "offset": 50
+ }
+ """,
+ ),
+ Arguments.of(
+ SiloAction.genomicSequence(SequenceType.UNALIGNED, "someSequenceName"),
+ """
+ {
+ "type": "Fasta",
+ "sequenceName": "someSequenceName"
+ }
+ """,
+ ),
+ Arguments.of(
+ SiloAction.genomicSequence(
+ SequenceType.UNALIGNED,
+ "someSequenceName",
+ listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)),
+ 100,
+ 50,
+ ),
+ """
+ {
+ "type": "Fasta",
+ "sequenceName": "someSequenceName",
+ "orderByFields": [
+ {"field": "field3", "order": "ascending"},
+ {"field": "field4", "order": "descending"}
+ ],
+ "limit": 100,
+ "offset": 50
+ }
+ """,
+ ),
)
@JvmStatic
diff --git a/lapis2/src/test/resources/application-test.properties b/lapis2/src/test/resources/application-test.properties
index 9665721e..cb4d3d20 100644
--- a/lapis2/src/test/resources/application-test.properties
+++ b/lapis2/src/test/resources/application-test.properties
@@ -1,3 +1,4 @@
silo.url=http://url.to.silo
lapis.databaseConfig.path=src/test/resources/config/testDatabaseConfig.yaml
lapis.accessKeys.path=src/test/resources/config/testAccessKeys.yaml
+referenceGenome.nucleotideSequences=main,other_segment
diff --git a/lapis2/src/test/resources/application-testWithoutAccessKeys.properties b/lapis2/src/test/resources/application-testWithoutAccessKeys.properties
index fd626d23..272aa699 100644
--- a/lapis2/src/test/resources/application-testWithoutAccessKeys.properties
+++ b/lapis2/src/test/resources/application-testWithoutAccessKeys.properties
@@ -1,4 +1,4 @@
spring.config.import=file:src/main/resources/application.properties
-
silo.url=http://url.to.silo
lapis.databaseConfig.path=src/test/resources/config/testDatabaseConfig.yaml
+referenceGenome.nucleotideSequences=main,other_segment
diff --git a/lapis2/src/test/resources/config/reference-genomes.json b/lapis2/src/test/resources/config/reference-genomes.json
new file mode 100644
index 00000000..aef3f62d
--- /dev/null
+++ b/lapis2/src/test/resources/config/reference-genomes.json
@@ -0,0 +1,58 @@
+{
+ "nucleotide_sequences": [
+ {
+ "name": "main",
+ "sequence": "ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTCGTCCGGGTGTGACCGAAAGGTAAGATGGAGAGCCTTGTCCCTGGTTTCAACGAGAAAACACACGTCCAACTCAGTTTGCCTGTTTTACAGGTTCGCGACGTGCTCGTACGTGGCTTTGGAGACTCCGTGGAGGAGGTCTTATCAGAGGCACGTCAACATCTTAAAGATGGCACTTGTGGCTTAGTAGAAGTTGAAAAAGGCGTTTTGCCTCAACTTGAACAGCCCTATGTGTTCATCAAACGTTCGGATGCTCGAACTGCACCTCATGGTCATGTTATGGTTGAGCTGGTAGCAGAACTCGAAGGCATTCAGTACGGTCGTAGTGGTGAGACACTTGGTGTCCTTGTCCCTCATGTGGGCGAAATACCAGTGGCTTACCGCAAGGTTCTTCTTCGTAAGAACGGTAATAAAGGAGCTGGTGGCCATAGTTACGGCGCCGATCTAAAGTCATTTGACTTAGGCGACGAGCTTGGCACTGATCCTTATGAAGATTTTCAAGAAAACTGGAACACTAAACATAGCAGTGGTGTTACCCGTGAACTCATGCGTGAGCTTAACGGAGGGGCATACACTCGCTATGTCGATAACAACTTCTGTGGCCCTGATGGCTACCCTCTTGAGTGCATTAAAGACCTTCTAGCACGTGCTGGTAAAGCTTCATGCACTTTGTCCGAACAACTGGACTTTATTGACACTAAGAGGGGTGTATACTGCTGCCGTGAACATGAGCATGAAATTGCTTGGTACACGGAACGTTCTGAAAAGAGCTATGAATTGCAGACACCTTTTGAAATTAAATTGGCAAAGAAATTTGACACCTTCAATGGGGAATGTCCAAATTTTGTATTTCCCTTAAATTCCATAATCAAGACTATTCAACCAAGGGTTGAAAAGAAAAAGCTTGATGGCTTTATGGGTAGAATTCGATCTGTCTATCCAGTTGCGTCACCAAATGAATGCAACCAAATGTGCCTTTCAACTCTCATGAAGTGTGATCATTGTGGTGAAACTTCATGGCAGACGGGCGATTTTGTTAAAGCCACTTGCGAATTTTGTGGCACTGAGAATTTGACTAAAGAAGGTGCCACTACTTGTGGTTACTTACCCCAAAATGCTGTTGTTAAAATTTATTGTCCAGCATGTCACAATTCAGAAGTAGGACCTGAGCATAGTCTTGCCGAATACCATAATGAATCTGGCTTGAAAACCATTCTTCGTAAGGGTGGTCGCACTATTGCCTTTGGAGGCTGTGTGTTCTCTTATGTTGGTTGCCATAACAAGTGTGCCTATTGGGTTCCACGTGCTAGCGCTAACATAGGTTGTAACCATACAGGTGTTGTTGGAGAAGGTTCCGAAGGTCTTAATGACAACCTTCTTGAAATACTCCAAAAAGAGAAAGTCAACATCAATATTGTTGGTGACTTTAAACTTAATGAAGAGATCGCCATTATTTTGGCATCTTTTTCTGCTTCCACAAGTGCTTTTGTGGAAACTGTGAAAGGTTTGGATTATAAAGCATTCAAACAAATTGTTGAATCCTGTGGTAATTTTAAAGTTACAAAAGGAAAAGCTAAAAAAGGTGCCTGGAATATTGGTGAACAGAAATCAATACTGAGTCCTCTTTATGCATTTGCATCAGAGGCTGCTCGTGTTGTACGATCAATTTTCTCCCGCACTCTTGAAACTGCTCAAAATTCTGTGCGTGTTTTACAGAAGGCCGCTATAACAATACTAGATGGAATTTCACAGTATTCACTGAGACTCATTGATGCTATGATGTTCACATCTGATTTGGCTACTAACAATCTAGTTGTAATGGCCTACATTACAGGTGGTGTTGTTCAGTTGACTTCGCAGTGGCTAACTAACATCTTTGGCACTGTTTATGAAAAACTCAAACCCGTCCTTGATTGGCTTGAAGAGAAGTTTAAGGAAGGTGTAGAGTTTCTTAGAGACGGTTGGGAAATTGTTAAATTTATCTCAACCTGTGCTTGTGAAATTGTCGGTGGACAAATTGTCACCTGTGCAAAGGAAATTAAGGAGAGTGTTCAGACATTCTTTAAGCTTGTAAATAAATTTTTGGCTTTGTGTGCTGACTCTATCATTATTGGTGGAGCTAAACTTAAAGCCTTGAATTTAGGTGAAACATTTGTCACGCACTCAAAGGGATTGTACAGAAAGTGTGTTAAATCCAGAGAAGAAACTGGCCTACTCATGCCTCTAAAAGCCCCAAAAGAAATTATCTTCTTAGAGGGAGAAACACTTCCCACAGAAGTGTTAACAGAGGAAGTTGTCTTGAAAACTGGTGATTTACAACCATTAGAACAACCTACTAGTGAAGCTGTTGAAGCTCCATTGGTTGGTACACCAGTTTGTATTAACGGGCTTATGTTGCTCGAAATCAAAGACACAGAAAAGTACTGTGCCCTTGCACCTAATATGATGGTAACAAACAATACCTTCACACTCAAAGGCGGTGCACCAACAAAGGTTACTTTTGGTGATGACACTGTGATAGAAGTGCAAGGTTACAAGAGTGTGAATATCACTTTTGAACTTGATGAAAGGATTGATAAAGTACTTAATGAGAAGTGCTCTGCCTATACAGTTGAACTCGGTACAGAAGTAAATGAGTTCGCCTGTGTTGTGGCAGATGCTGTCATAAAAACTTTGCAACCAGTATCTGAATTACTTACACCACTGGGCATTGATTTAGATGAGTGGAGTATGGCTACATACTACTTATTTGATGAGTCTGGTGAGTTTAAATTGGCTTCACATATGTATTGTTCTTTCTACCCTCCAGATGAGGATGAAGAAGAAGGTGATTGTGAAGAAGAAGAGTTTGAGCCATCAACTCAATATGAGTATGGTACTGAAGATGATTACCAAGGTAAACCTTTGGAATTTGGTGCCACTTCTGCTGCTCTTCAACCTGAAGAAGAGCAAGAAGAAGATTGGTTAGATGATGATAGTCAACAAACTGTTGGTCAACAAGACGGCAGTGAGGACAATCAGACAACTACTATTCAAACAATTGTTGAGGTTCAACCTCAATTAGAGATGGAACTTACACCAGTTGTTCAGACTATTGAAGTGAATAGTTTTAGTGGTTATTTAAAACTTACTGACAATGTATACATTAAAAATGCAGACATTGTGGAAGAAGCTAAAAAGGTAAAACCAACAGTGGTTGTTAATGCAGCCAATGTTTACCTTAAACATGGAGGAGGTGTTGCAGGAGCCTTAAATAAGGCTACTAACAATGCCATGCAAGTTGAATCTGATGATTACATAGCTACTAATGGACCACTTAAAGTGGGTGGTAGTTGTGTTTTAAGCGGACACAATCTTGCTAAACACTGTCTTCATGTTGTCGGCCCAAATGTTAACAAAGGTGAAGACATTCAACTTCTTAAGAGTGCTTATGAAAATTTTAATCAGCACGAAGTTCTACTTGCACCATTATTATCAGCTGGTATTTTTGGTGCTGACCCTATACATTCTTTAAGAGTTTGTGTAGATACTGTTCGCACAAATGTCTACTTAGCTGTCTTTGATAAAAATCTCTATGACAAACTTGTTTCAAGCTTTTTGGAAATGAAGAGTGAAAAGCAAGTTGAACAAAAGATCGCTGAGATTCCTAAAGAGGAAGTTAAGCCATTTATAACTGAAAGTAAACCTTCAGTTGAACAGAGAAAACAAGATGATAAGAAAATCAAAGCTTGTGTTGAAGAAGTTACAACAACTCTGGAAGAAACTAAGTTCCTCACAGAAAACTTGTTACTTTATATTGACATTAATGGCAATCTTCATCCAGATTCTGCCACTCTTGTTAGTGACATTGACATCACTTTCTTAAAGAAAGATGCTCCATATATAGTGGGTGATGTTGTTCAAGAGGGTGTTTTAACTGCTGTGGTTATACCTACTAAAAAGGCTGGTGGCACTACTGAAATGCTAGCGAAAGCTTTGAGAAAAGTGCCAACAGACAATTATATAACCACTTACCCGGGTCAGGGTTTAAATGGTTACACTGTAGAGGAGGCAAAGACAGTGCTTAAAAAGTGTAAAAGTGCCTTTTACATTCTACCATCTATTATCTCTAATGAGAAGCAAGAAATTCTTGGAACTGTTTCTTGGAATTTGCGAGAAATGCTTGCACATGCAGAAGAAACACGCAAATTAATGCCTGTCTGTGTGGAAACTAAAGCCATAGTTTCAACTATACAGCGTAAATATAAGGGTATTAAAATACAAGAGGGTGTGGTTGATTATGGTGCTAGATTTTACTTTTACACCAGTAAAACAACTGTAGCGTCACTTATCAACACACTTAACGATCTAAATGAAACTCTTGTTACAATGCCACTTGGCTATGTAACACATGGCTTAAATTTGGAAGAAGCTGCTCGGTATATGAGATCTCTCAAAGTGCCAGCTACAGTTTCTGTTTCTTCACCTGATGCTGTTACAGCGTATAATGGTTATCTTACTTCTTCTTCTAAAACACCTGAAGAACATTTTATTGAAACCATCTCACTTGCTGGTTCCTATAAAGATTGGTCCTATTCTGGACAATCTACACAACTAGGTATAGAATTTCTTAAGAGAGGTGATAAAAGTGTATATTACACTAGTAATCCTACCACATTCCACCTAGATGGTGAAGTTATCACCTTTGACAATCTTAAGACACTTCTTTCTTTGAGAGAAGTGAGGACTATTAAGGTGTTTACAACAGTAGACAACATTAACCTCCACACGCAAGTTGTGGACATGTCAATGACATATGGACAACAGTTTGGTCCAACTTATTTGGATGGAGCTGATGTTACTAAAATAAAACCTCATAATTCACATGAAGGTAAAACATTTTATGTTTTACCTAATGATGACACTCTACGTGTTGAGGCTTTTGAGTACTACCACACAACTGATCCTAGTTTTCTGGGTAGGTACATGTCAGCATTAAATCACACTAAAAAGTGGAAATACCCACAAGTTAATGGTTTAACTTCTATTAAATGGGCAGATAACAACTGTTATCTTGCCACTGCATTGTTAACACTCCAACAAATAGAGTTGAAGTTTAATCCACCTGCTCTACAAGATGCTTATTACAGAGCAAGGGCTGGTGAAGCTGCTAACTTTTGTGCACTTATCTTAGCCTACTGTAATAAGACAGTAGGTGAGTTAGGTGATGTTAGAGAAACAATGAGTTACTTGTTTCAACATGCCAATTTAGATTCTTGCAAAAGAGTCTTGAACGTGGTGTGTAAAACTTGTGGACAACAGCAGACAACCCTTAAGGGTGTAGAAGCTGTTATGTACATGGGCACACTTTCTTATGAACAATTTAAGAAAGGTGTTCAGATACCTTGTACGTGTGGTAAACAAGCTACAAAATATCTAGTACAACAGGAGTCACCTTTTGTTATGATGTCAGCACCACCTGCTCAGTATGAACTTAAGCATGGTACATTTACTTGTGCTAGTGAGTACACTGGTAATTACCAGTGTGGTCACTATAAACATATAACTTCTAAAGAAACTTTGTATTGCATAGACGGTGCTTTACTTACAAAGTCCTCAGAATACAAAGGTCCTATTACGGATGTTTTCTACAAAGAAAACAGTTACACAACAACCATAAAACCAGTTACTTATAAATTGGATGGTGTTGTTTGTACAGAAATTGACCCTAAGTTGGACAATTATTATAAGAAAGACAATTCTTATTTCACAGAGCAACCAATTGATCTTGTACCAAACCAACCATATCCAAACGCAAGCTTCGATAATTTTAAGTTTGTATGTGATAATATCAAATTTGCTGATGATTTAAACCAGTTAACTGGTTATAAGAAACCTGCTTCAAGAGAGCTTAAAGTTACATTTTTCCCTGACTTAAATGGTGATGTGGTGGCTATTGATTATAAACACTACACACCCTCTTTTAAGAAAGGAGCTAAATTGTTACATAAACCTATTGTTTGGCATGTTAACAATGCAACTAATAAAGCCACGTATAAACCAAATACCTGGTGTATACGTTGTCTTTGGAGCACAAAACCAGTTGAAACATCAAATTCGTTTGATGTACTGAAGTCAGAGGACGCGCAGGGAATGGATAATCTTGCCTGCGAAGATCTAAAACCAGTCTCTGAAGAAGTAGTGGAAAATCCTACCATACAGAAAGACGTTCTTGAGTGTAATGTGAAAACTACCGAAGTTGTAGGAGACATTATACTTAAACCAGCAAATAATAGTTTAAAAATTACAGAAGAGGTTGGCCACACAGATCTAATGGCTGCTTATGTAGACAATTCTAGTCTTACTATTAAGAAACCTAATGAATTATCTAGAGTATTAGGTTTGAAAACCCTTGCTACTCATGGTTTAGCTGCTGTTAATAGTGTCCCTTGGGATACTATAGCTAATTATGCTAAGCCTTTTCTTAACAAAGTTGTTAGTACAACTACTAACATAGTTACACGGTGTTTAAACCGTGTTTGTACTAATTATATGCCTTATTTCTTTACTTTATTGCTACAATTGTGTACTTTTACTAGAAGTACAAATTCTAGAATTAAAGCATCTATGCCGACTACTATAGCAAAGAATACTGTTAAGAGTGTCGGTAAATTTTGTCTAGAGGCTTCATTTAATTATTTGAAGTCACCTAATTTTTCTAAACTGATAAATATTATAATTTGGTTTTTACTATTAAGTGTTTGCCTAGGTTCTTTAATCTACTCAACCGCTGCTTTAGGTGTTTTAATGTCTAATTTAGGCATGCCTTCTTACTGTACTGGTTACAGAGAAGGCTATTTGAACTCTACTAATGTCACTATTGCAACCTACTGTACTGGTTCTATACCTTGTAGTGTTTGTCTTAGTGGTTTAGATTCTTTAGACACCTATCCTTCTTTAGAAACTATACAAATTACCATTTCATCTTTTAAATGGGATTTAACTGCTTTTGGCTTAGTTGCAGAGTGGTTTTTGGCATATATTCTTTTCACTAGGTTTTTCTATGTACTTGGATTGGCTGCAATCATGCAATTGTTTTTCAGCTATTTTGCAGTACATTTTATTAGTAATTCTTGGCTTATGTGGTTAATAATTAATCTTGTACAAATGGCCCCGATTTCAGCTATGGTTAGAATGTACATCTTCTTTGCATCATTTTATTATGTATGGAAAAGTTATGTGCATGTTGTAGACGGTTGTAATTCATCAACTTGTATGATGTGTTACAAACGTAATAGAGCAACAAGAGTCGAATGTACAACTATTGTTAATGGTGTTAGAAGGTCCTTTTATGTCTATGCTAATGGAGGTAAAGGCTTTTGCAAACTACACAATTGGAATTGTGTTAATTGTGATACATTCTGTGCTGGTAGTACATTTATTAGTGATGAAGTTGCGAGAGACTTGTCACTACAGTTTAAAAGACCAATAAATCCTACTGACCAGTCTTCTTACATCGTTGATAGTGTTACAGTGAAGAATGGTTCCATCCATCTTTACTTTGATAAAGCTGGTCAAAAGACTTATGAAAGACATTCTCTCTCTCATTTTGTTAACTTAGACAACCTGAGAGCTAATAACACTAAAGGTTCATTGCCTATTAATGTTATAGTTTTTGATGGTAAATCAAAATGTGAAGAATCATCTGCAAAATCAGCGTCTGTTTACTACAGTCAGCTTATGTGTCAACCTATACTGTTACTAGATCAGGCATTAGTGTCTGATGTTGGTGATAGTGCGGAAGTTGCAGTTAAAATGTTTGATGCTTACGTTAATACGTTTTCATCAACTTTTAACGTACCAATGGAAAAACTCAAAACACTAGTTGCAACTGCAGAAGCTGAACTTGCAAAGAATGTGTCCTTAGACAATGTCTTATCTACTTTTATTTCAGCAGCTCGGCAAGGGTTTGTTGATTCAGATGTAGAAACTAAAGATGTTGTTGAATGTCTTAAATTGTCACATCAATCTGACATAGAAGTTACTGGCGATAGTTGTAATAACTATATGCTCACCTATAACAAAGTTGAAAACATGACACCCCGTGACCTTGGTGCTTGTATTGACTGTAGTGCGCGTCATATTAATGCGCAGGTAGCAAAAAGTCACAACATTGCTTTGATATGGAACGTTAAAGATTTCATGTCATTGTCTGAACAACTACGAAAACAAATACGTAGTGCTGCTAAAAAGAATAACTTACCTTTTAAGTTGACATGTGCAACTACTAGACAAGTTGTTAATGTTGTAACAACAAAGATAGCACTTAAGGGTGGTAAAATTGTTAATAATTGGTTGAAGCAGTTAATTAAAGTTACACTTGTGTTCCTTTTTGTTGCTGCTATTTTCTATTTAATAACACCTGTTCATGTCATGTCTAAACATACTGACTTTTCAAGTGAAATCATAGGATACAAGGCTATTGATGGTGGTGTCACTCGTGACATAGCATCTACAGATACTTGTTTTGCTAACAAACATGCTGATTTTGACACATGGTTTAGCCAGCGTGGTGGTAGTTATACTAATGACAAAGCTTGCCCATTGATTGCTGCAGTCATAACAAGAGAAGTGGGTTTTGTCGTGCCTGGTTTGCCTGGCACGATATTACGCACAACTAATGGTGACTTTTTGCATTTCTTACCTAGAGTTTTTAGTGCAGTTGGTAACATCTGTTACACACCATCAAAACTTATAGAGTACACTGACTTTGCAACATCAGCTTGTGTTTTGGCTGCTGAATGTACAATTTTTAAAGATGCTTCTGGTAAGCCAGTACCATATTGTTATGATACCAATGTACTAGAAGGTTCTGTTGCTTATGAAAGTTTACGCCCTGACACACGTTATGTGCTCATGGATGGCTCTATTATTCAATTTCCTAACACCTACCTTGAAGGTTCTGTTAGAGTGGTAACAACTTTTGATTCTGAGTACTGTAGGCACGGCACTTGTGAAAGATCAGAAGCTGGTGTTTGTGTATCTACTAGTGGTAGATGGGTACTTAACAATGATTATTACAGATCTTTACCAGGAGTTTTCTGTGGTGTAGATGCTGTAAATTTACTTACTAATATGTTTACACCACTAATTCAACCTATTGGTGCTTTGGACATATCAGCATCTATAGTAGCTGGTGGTATTGTAGCTATCGTAGTAACATGCCTTGCCTACTATTTTATGAGGTTTAGAAGAGCTTTTGGTGAATACAGTCATGTAGTTGCCTTTAATACTTTACTATTCCTTATGTCATTCACTGTACTCTGTTTAACACCAGTTTACTCATTCTTACCTGGTGTTTATTCTGTTATTTACTTGTACTTGACATTTTATCTTACTAATGATGTTTCTTTTTTAGCACATATTCAGTGGATGGTTATGTTCACACCTTTAGTACCTTTCTGGATAACAATTGCTTATATCATTTGTATTTCCACAAAGCATTTCTATTGGTTCTTTAGTAATTACCTAAAGAGACGTGTAGTCTTTAATGGTGTTTCCTTTAGTACTTTTGAAGAAGCTGCGCTGTGCACCTTTTTGTTAAATAAAGAAATGTATCTAAAGTTGCGTAGTGATGTGCTATTACCTCTTACGCAATATAATAGATACTTAGCTCTTTATAATAAGTACAAGTATTTTAGTGGAGCAATGGATACAACTAGCTACAGAGAAGCTGCTTGTTGTCATCTCGCAAAGGCTCTCAATGACTTCAGTAACTCAGGTTCTGATGTTCTTTACCAACCACCACAAACCTCTATCACCTCAGCTGTTTTGCAGAGTGGTTTTAGAAAAATGGCATTCCCATCTGGTAAAGTTGAGGGTTGTATGGTACAAGTAACTTGTGGTACAACTACACTTAACGGTCTTTGGCTTGATGACGTAGTTTACTGTCCAAGACATGTGATCTGCACCTCTGAAGACATGCTTAACCCTAATTATGAAGATTTACTCATTCGTAAGTCTAATCATAATTTCTTGGTACAGGCTGGTAATGTTCAACTCAGGGTTATTGGACATTCTATGCAAAATTGTGTACTTAAGCTTAAGGTTGATACAGCCAATCCTAAGACACCTAAGTATAAGTTTGTTCGCATTCAACCAGGACAGACTTTTTCAGTGTTAGCTTGTTACAATGGTTCACCATCTGGTGTTTACCAATGTGCTATGAGGCCCAATTTCACTATTAAGGGTTCATTCCTTAATGGTTCATGTGGTAGTGTTGGTTTTAACATAGATTATGACTGTGTCTCTTTTTGTTACATGCACCATATGGAATTACCAACTGGAGTTCATGCTGGCACAGACTTAGAAGGTAACTTTTATGGACCTTTTGTTGACAGGCAAACAGCACAAGCAGCTGGTACGGACACAACTATTACAGTTAATGTTTTAGCTTGGTTGTACGCTGCTGTTATAAATGGAGACAGGTGGTTTCTCAATCGATTTACCACAACTCTTAATGACTTTAACCTTGTGGCTATGAAGTACAATTATGAACCTCTAACACAAGACCATGTTGACATACTAGGACCTCTTTCTGCTCAAACTGGAATTGCCGTTTTAGATATGTGTGCTTCATTAAAAGAATTACTGCAAAATGGTATGAATGGACGTACCATATTGGGTAGTGCTTTATTAGAAGATGAATTTACACCTTTTGATGTTGTTAGACAATGCTCAGGTGTTACTTTCCAAAGTGCAGTGAAAAGAACAATCAAGGGTACACACCACTGGTTGTTACTCACAATTTTGACTTCACTTTTAGTTTTAGTCCAGAGTACTCAATGGTCTTTGTTCTTTTTTTTGTATGAAAATGCCTTTTTACCTTTTGCTATGGGTATTATTGCTATGTCTGCTTTTGCAATGATGTTTGTCAAACATAAGCATGCATTTCTCTGTTTGTTTTTGTTACCTTCTCTTGCCACTGTAGCTTATTTTAATATGGTCTATATGCCTGCTAGTTGGGTGATGCGTATTATGACATGGTTGGATATGGTTGATACTAGTTTGTCTGGTTTTAAGCTAAAAGACTGTGTTATGTATGCATCAGCTGTAGTGTTACTAATCCTTATGACAGCAAGAACTGTGTATGATGATGGTGCTAGGAGAGTGTGGACACTTATGAATGTCTTGACACTCGTTTATAAAGTTTATTATGGTAATGCTTTAGATCAAGCCATTTCCATGTGGGCTCTTATAATCTCTGTTACTTCTAACTACTCAGGTGTAGTTACAACTGTCATGTTTTTGGCCAGAGGTATTGTTTTTATGTGTGTTGAGTATTGCCCTATTTTCTTCATAACTGGTAATACACTTCAGTGTATAATGCTAGTTTATTGTTTCTTAGGCTATTTTTGTACTTGTTACTTTGGCCTCTTTTGTTTACTCAACCGCTACTTTAGACTGACTCTTGGTGTTTATGATTACTTAGTTTCTACACAGGAGTTTAGATATATGAATTCACAGGGACTACTCCCACCCAAGAATAGCATAGATGCCTTCAAACTCAACATTAAATTGTTGGGTGTTGGTGGCAAACCTTGTATCAAAGTAGCCACTGTACAGTCTAAAATGTCAGATGTAAAGTGCACATCAGTAGTCTTACTCTCAGTTTTGCAACAACTCAGAGTAGAATCATCATCTAAATTGTGGGCTCAATGTGTCCAGTTACACAATGACATTCTCTTAGCTAAAGATACTACTGAAGCCTTTGAAAAAATGGTTTCACTACTTTCTGTTTTGCTTTCCATGCAGGGTGCTGTAGACATAAACAAGCTTTGTGAAGAAATGCTGGACAACAGGGCAACCTTACAAGCTATAGCCTCAGAGTTTAGTTCCCTTCCATCATATGCAGCTTTTGCTACTGCTCAAGAAGCTTATGAGCAGGCTGTTGCTAATGGTGATTCTGAAGTTGTTCTTAAAAAGTTGAAGAAGTCTTTGAATGTGGCTAAATCTGAATTTGACCGTGATGCAGCCATGCAACGTAAGTTGGAAAAGATGGCTGATCAAGCTATGACCCAAATGTATAAACAGGCTAGATCTGAGGACAAGAGGGCAAAAGTTACTAGTGCTATGCAGACAATGCTTTTCACTATGCTTAGAAAGTTGGATAATGATGCACTCAACAACATTATCAACAATGCAAGAGATGGTTGTGTTCCCTTGAACATAATACCTCTTACAACAGCAGCCAAACTAATGGTTGTCATACCAGACTATAACACATATAAAAATACGTGTGATGGTACAACATTTACTTATGCATCAGCATTGTGGGAAATCCAACAGGTTGTAGATGCAGATAGTAAAATTGTTCAACTTAGTGAAATTAGTATGGACAATTCACCTAATTTAGCATGGCCTCTTATTGTAACAGCTTTAAGGGCCAATTCTGCTGTCAAATTACAGAATAATGAGCTTAGTCCTGTTGCACTACGACAGATGTCTTGTGCTGCCGGTACTACACAAACTGCTTGCACTGATGACAATGCGTTAGCTTACTACAACACAACAAAGGGAGGTAGGTTTGTACTTGCACTGTTATCCGATTTACAGGATTTGAAATGGGCTAGATTCCCTAAGAGTGATGGAACTGGTACTATCTATACAGAACTGGAACCACCTTGTAGGTTTGTTACAGACACACCTAAAGGTCCTAAAGTGAAGTATTTATACTTTATTAAAGGATTAAACAACCTAAATAGAGGTATGGTACTTGGTAGTTTAGCTGCCACAGTACGTCTACAAGCTGGTAATGCAACAGAAGTGCCTGCCAATTCAACTGTATTATCTTTCTGTGCTTTTGCTGTAGATGCTGCTAAAGCTTACAAAGATTATCTAGCTAGTGGGGGACAACCAATCACTAATTGTGTTAAGATGTTGTGTACACACACTGGTACTGGTCAGGCAATAACAGTTACACCGGAAGCCAATATGGATCAAGAATCCTTTGGTGGTGCATCGTGTTGTCTGTACTGCCGTTGCCACATAGATCATCCAAATCCTAAAGGATTTTGTGACTTAAAAGGTAAGTATGTACAAATACCTACAACTTGTGCTAATGACCCTGTGGGTTTTACACTTAAAAACACAGTCTGTACCGTCTGCGGTATGTGGAAAGGTTATGGCTGTAGTTGTGATCAACTCCGCGAACCCATGCTTCAGTCAGCTGATGCACAATCGTTTTTAAACGGGTTTGCGGTGTAAGTGCAGCCCGTCTTACACCGTGCGGCACAGGCACTAGTACTGATGTCGTATACAGGGCTTTTGACATCTACAATGATAAAGTAGCTGGTTTTGCTAAATTCCTAAAAACTAATTGTTGTCGCTTCCAAGAAAAGGACGAAGATGACAATTTAATTGATTCTTACTTTGTAGTTAAGAGACACACTTTCTCTAACTACCAACATGAAGAAACAATTTATAATTTACTTAAGGATTGTCCAGCTGTTGCTAAACATGACTTCTTTAAGTTTAGAATAGACGGTGACATGGTACCACATATATCACGTCAACGTCTTACTAAATACACAATGGCAGACCTCGTCTATGCTTTAAGGCATTTTGATGAAGGTAATTGTGACACATTAAAAGAAATACTTGTCACATACAATTGTTGTGATGATGATTATTTCAATAAAAAGGACTGGTATGATTTTGTAGAAAACCCAGATATATTACGCGTATACGCCAACTTAGGTGAACGTGTACGCCAAGCTTTGTTAAAAACAGTACAATTCTGTGATGCCATGCGAAATGCTGGTATTGTTGGTGTACTGACATTAGATAATCAAGATCTCAATGGTAACTGGTATGATTTCGGTGATTTCATACAAACCACGCCAGGTAGTGGAGTTCCTGTTGTAGATTCTTATTATTCATTGTTAATGCCTATATTAACCTTGACCAGGGCTTTAACTGCAGAGTCACATGTTGACACTGACTTAACAAAGCCTTACATTAAGTGGGATTTGTTAAAATATGACTTCACGGAAGAGAGGTTAAAACTCTTTGACCGTTATTTTAAATATTGGGATCAGACATACCACCCAAATTGTGTTAACTGTTTGGATGACAGATGCATTCTGCATTGTGCAAACTTTAATGTTTTATTCTCTACAGTGTTCCCACCTACAAGTTTTGGACCACTAGTGAGAAAAATATTTGTTGATGGTGTTCCATTTGTAGTTTCAACTGGATACCACTTCAGAGAGCTAGGTGTTGTACATAATCAGGATGTAAACTTACATAGCTCTAGACTTAGTTTTAAGGAATTACTTGTGTATGCTGCTGACCCTGCTATGCACGCTGCTTCTGGTAATCTATTACTAGATAAACGCACTACGTGCTTTTCAGTAGCTGCACTTACTAACAATGTTGCTTTTCAAACTGTCAAACCCGGTAATTTTAACAAAGACTTCTATGACTTTGCTGTGTCTAAGGGTTTCTTTAAGGAAGGAAGTTCTGTTGAATTAAAACACTTCTTCTTTGCTCAGGATGGTAATGCTGCTATCAGCGATTATGACTACTATCGTTATAATCTACCAACAATGTGTGATATCAGACAACTACTATTTGTAGTTGAAGTTGTTGATAAGTACTTTGATTGTTACGATGGTGGCTGTATTAATGCTAACCAAGTCATCGTCAACAACCTAGACAAATCAGCTGGTTTTCCATTTAATAAATGGGGTAAGGCTAGACTTTATTATGATTCAATGAGTTATGAGGATCAAGATGCACTTTTCGCATATACAAAACGTAATGTCATCCCTACTATAACTCAAATGAATCTTAAGTATGCCATTAGTGCAAAGAATAGAGCTCGCACCGTAGCTGGTGTCTCTATCTGTAGTACTATGACCAATAGACAGTTTCATCAAAAATTATTGAAATCAATAGCCGCCACTAGAGGAGCTACTGTAGTAATTGGAACAAGCAAATTCTATGGTGGTTGGCACAACATGTTAAAAACTGTTTATAGTGATGTAGAAAACCCTCACCTTATGGGTTGGGATTATCCTAAATGTGATAGAGCCATGCCTAACATGCTTAGAATTATGGCCTCACTTGTTCTTGCTCGCAAACATACAACGTGTTGTAGCTTGTCACACCGTTTCTATAGATTAGCTAATGAGTGTGCTCAAGTATTGAGTGAAATGGTCATGTGTGGCGGTTCACTATATGTTAAACCAGGTGGAACCTCATCAGGAGATGCCACAACTGCTTATGCTAATAGTGTTTTTAACATTTGTCAAGCTGTCACGGCCAATGTTAATGCACTTTTATCTACTGATGGTAACAAAATTGCCGATAAGTATGTCCGCAATTTACAACACAGACTTTATGAGTGTCTCTATAGAAATAGAGATGTTGACACAGACTTTGTGAATGAGTTTTACGCATATTTGCGTAAACATTTCTCAATGATGATACTCTCTGACGATGCTGTTGTGTGTTTCAATAGCACTTATGCATCTCAAGGTCTAGTGGCTAGCATAAAGAACTTTAAGTCAGTTCTTTATTATCAAAACAATGTTTTTATGTCTGAAGCAAAATGTTGGACTGAGACTGACCTTACTAAAGGACCTCATGAATTTTGCTCTCAACATACAATGCTAGTTAAACAGGGTGATGATTATGTGTACCTTCCTTACCCAGATCCATCAAGAATCCTAGGGGCCGGCTGTTTTGTAGATGATATCGTAAAAACAGATGGTACACTTATGATTGAACGGTTCGTGTCTTTAGCTATAGATGCTTACCCACTTACTAAACATCCTAATCAGGAGTATGCTGATGTCTTTCATTTGTACTTACAATACATAAGAAAGCTACATGATGAGTTAACAGGACACATGTTAGACATGTATTCTGTTATGCTTACTAATGATAACACTTCAAGGTATTGGGAACCTGAGTTTTATGAGGCTATGTACACACCGCATACAGTCTTACAGGCTGTTGGGGCTTGTGTTCTTTGCAATTCACAGACTTCATTAAGATGTGGTGCTTGCATACGTAGACCATTCTTATGTTGTAAATGCTGTTACGACCATGTCATATCAACATCACATAAATTAGTCTTGTCTGTTAATCCGTATGTTTGCAATGCTCCAGGTTGTGATGTCACAGATGTGACTCAACTTTACTTAGGAGGTATGAGCTATTATTGTAAATCACATAAACCACCCATTAGTTTTCCATTGTGTGCTAATGGACAAGTTTTTGGTTTATATAAAAATACATGTGTTGGTAGCGATAATGTTACTGACTTTAATGCAATTGCAACATGTGACTGGACAAATGCTGGTGATTACATTTTAGCTAACACCTGTACTGAAAGACTCAAGCTTTTTGCAGCAGAAACGCTCAAAGCTACTGAGGAGACATTTAAACTGTCTTATGGTATTGCTACTGTACGTGAAGTGCTGTCTGACAGAGAATTACATCTTTCATGGGAAGTTGGTAAACCTAGACCACCACTTAACCGAAATTATGTCTTTACTGGTTATCGTGTAACTAAAAACAGTAAAGTACAAATAGGAGAGTACACCTTTGAAAAAGGTGACTATGGTGATGCTGTTGTTTACCGAGGTACAACAACTTACAAATTAAATGTTGGTGATTATTTTGTGCTGACATCACATACAGTAATGCCATTAAGTGCACCTACACTAGTGCCACAAGAGCACTATGTTAGAATTACTGGCTTATACCCAACACTCAATATCTCAGATGAGTTTTCTAGCAATGTTGCAAATTATCAAAAGGTTGGTATGCAAAAGTATTCTACACTCCAGGGACCACCTGGTACTGGTAAGAGTCATTTTGCTATTGGCCTAGCTCTCTACTACCCTTCTGCTCGCATAGTGTATACAGCTTGCTCTCATGCCGCTGTTGATGCACTATGTGAGAAGGCATTAAAATATTTGCCTATAGATAAATGTAGTAGAATTATACCTGCACGTGCTCGTGTAGAGTGTTTTGATAAATTCAAAGTGAATTCAACATTAGAACAGTATGTCTTTTGTACTGTAAATGCATTGCCTGAGACGACAGCAGATATAGTTGTCTTTGATGAAATTTCAATGGCCACAAATTATGATTTGAGTGTTGTCAATGCCAGATTACGTGCTAAGCACTATGTGTACATTGGCGACCCTGCTCAATTACCTGCACCACGCACATTGCTAACTAAGGGCACACTAGAACCAGAATATTTCAATTCAGTGTGTAGACTTATGAAAACTATAGGTCCAGACATGTTCCTCGGAACTTGTCGGCGTTGTCCTGCTGAAATTGTTGACACTGTGAGTGCTTTGGTTTATGATAATAAGCTTAAAGCACATAAAGACAAATCAGCTCAATGCTTTAAAATGTTTTATAAGGGTGTTATCACGCATGATGTTTCATCTGCAATTAACAGGCCACAAATAGGCGTGGTAAGAGAATTCCTTACACGTAACCCTGCTTGGAGAAAAGCTGTCTTTATTTCACCTTATAATTCACAGAATGCTGTAGCCTCAAAGATTTTGGGACTACCAACTCAAACTGTTGATTCATCACAGGGCTCAGAATATGACTATGTCATATTCACTCAAACCACTGAAACAGCTCACTCTTGTAATGTAAACAGATTTAATGTTGCTATTACCAGAGCAAAAGTAGGCATACTTTGCATAATGTCTGATAGAGACCTTTATGACAAGTTGCAATTTACAAGTCTTGAAATTCCACGTAGGAATGTGGCAACTTTACAAGCTGAAAATGTAACAGGACTCTTTAAAGATTGTAGTAAGGTAATCACTGGGTTACATCCTACACAGGCACCTACACACCTCAGTGTTGACACTAAATTCAAAACTGAAGGTTTATGTGTTGACATACCTGGCATACCTAAGGACATGACCTATAGAAGACTCATCTCTATGATGGGTTTTAAAATGAATTATCAAGTTAATGGTTACCCTAACATGTTTATCACCCGCGAAGAAGCTATAAGACATGTACGTGCATGGATTGGCTTCGATGTCGAGGGGTGTCATGCTACTAGAGAAGCTGTTGGTACCAATTTACCTTTACAGCTAGGTTTTTCTACAGGTGTTAACCTAGTTGCTGTACCTACAGGTTATGTTGATACACCTAATAATACAGATTTTTCCAGAGTTAGTGCTAAACCACCGCCTGGAGATCAATTTAAACACCTCATACCACTTATGTACAAAGGACTTCCTTGGAATGTAGTGCGTATAAAGATTGTACAAATGTTAAGTGACACACTTAAAAATCTCTCTGACAGAGTCGTATTTGTCTTATGGGCACATGGCTTTGAGTTGACATCTATGAAGTATTTTGTGAAAATAGGACCTGAGCGCACCTGTTGTCTATGTGATAGACGTGCCACATGCTTTTCCACTGCTTCAGACACTTATGCCTGTTGGCATCATTCTATTGGATTTGATTACGTCTATAATCCGTTTATGATTGATGTTCAACAATGGGGTTTTACAGGTAACCTACAAAGCAACCATGATCTGTATTGTCAAGTCCATGGTAATGCACATGTAGCTAGTTGTGATGCAATCATGACTAGGTGTCTAGCTGTCCACGAGTGCTTTGTTAAGCGTGTTGACTGGACTATTGAATATCCTATAATTGGTGATGAACTGAAGATTAATGCGGCTTGTAGAAAGGTTCAACACATGGTTGTTAAAGCTGCATTATTAGCAGACAAATTCCCAGTTCTTCACGACATTGGTAACCCTAAAGCTATTAAGTGTGTACCTCAAGCTGATGTAGAATGGAAGTTCTATGATGCACAGCCTTGTAGTGACAAAGCTTATAAAATAGAAGAATTATTCTATTCTTATGCCACACATTCTGACAAATTCACAGATGGTGTATGCCTATTTTGGAATTGCAATGTCGATAGATATCCTGCTAATTCCATTGTTTGTAGATTTGACACTAGAGTGCTATCTAACCTTAACTTGCCTGGTTGTGATGGTGGCAGTTTGTATGTAAATAAACATGCATTCCACACACCAGCTTTTGATAAAAGTGCTTTTGTTAATTTAAAACAATTACCATTTTTCTATTACTCTGACAGTCCATGTGAGTCTCATGGAAAACAAGTAGTGTCAGATATAGATTATGTACCACTAAAGTCTGCTACGTGTATAACACGTTGCAATTTAGGTGGTGCTGTCTGTAGACATCATGCTAATGAGTACAGATTGTATCTCGATGCTTATAACATGATGATCTCAGCTGGCTTTAGCTTGTGGGTTTACAAACAATTTGATACTTATAACCTCTGGAACACTTTTACAAGACTTCAGAGTTTAGAAAATGTGGCTTTTAATGTTGTAAATAAGGGACACTTTGATGGACAACAGGGTGAAGTACCAGTTTCTATCATTAATAACACTGTTTACACAAAAGTTGATGGTGTTGATGTAGAATTGTTTGAAAATAAAACAACATTACCTGTTAATGTAGCATTTGAGCTTTGGGCTAAGCGCAACATTAAACCAGTACCAGAGGTGAAAATACTCAATAATTTGGGTGTGGACATTGCTGCTAATACTGTGATCTGGGACTACAAAAGAGATGCTCCAGCACATATATCTACTATTGGTGTTTGTTCTATGACTGACATAGCCAAGAAACCAACTGAAACGATTTGTGCACCACTCACTGTCTTTTTTGATGGTAGAGTTGATGGTCAAGTAGACTTATTTAGAAATGCCCGTAATGGTGTTCTTATTACAGAAGGTAGTGTTAAAGGTTTACAACCATCTGTAGGTCCCAAACAAGCTAGTCTTAATGGAGTCACATTAATTGGAGAAGCCGTAAAAACACAGTTCAATTATTATAAGAAAGTTGATGGTGTTGTCCAACAATTACCTGAAACTTACTTTACTCAGAGTAGAAATTTACAAGAATTTAAACCCAGGAGTCAAATGGAAATTGATTTCTTAGAATTAGCTATGGATGAATTCATTGAACGGTATAAATTAGAAGGCTATGCCTTCGAACATATCGTTTATGGAGATTTTAGTCATAGTCAGTTAGGTGGTTTACATCTACTGATTGGACTAGCTAAACGTTTTAAGGAATCACCTTTTGAATTAGAAGATTTTATTCCTATGGACAGTACAGTTAAAAACTATTTCATAACAGATGCGCAAACAGGTTCATCTAAGTGTGTGTGTTCTGTTATTGATTTATTACTTGATGATTTTGTTGAAATAATAAAATCCCAAGATTTATCTGTAGTTTCTAAGGTTGTCAAAGTGACTATTGACTATACAGAAATTTCATTTATGCTTTGGTGTAAAGATGGCCATGTAGAAACATTTTACCCAAAATTACAATCTAGTCAAGCGTGGCAACCGGGTGTTGCTATGCCTAATCTTTACAAAATGCAAAGAATGCTATTAGAAAAGTGTGACCTTCAAAATTATGGTGATAGTGCAACATTACCTAAAGGCATAATGATGAATGTCGCAAAATATACTCAACTGTGTCAATATTTAAACACATTAACATTAGCTGTACCCTATAATATGAGAGTTATACATTTTGGTGCTGGTTCTGATAAAGGAGTTGCACCAGGTACAGCTGTTTTAAGACAGTGGTTGCCTACGGGTACGCTGCTTGTCGATTCAGATCTTAATGACTTTGTCTCTGATGCAGATTCAACTTTGATTGGTGATTGTGCAACTGTACATACAGCTAATAAATGGGATCTCATTATTAGTGATATGTACGACCCTAAGACTAAAAATGTTACAAAAGAAAATGACTCTAAAGAGGGTTTTTTCACTTACATTTGTGGGTTTATACAACAAAAGCTAGCTCTTGGAGGTTCCGTGGCTATAAAGATAACAGAACATTCTTGGAATGCTGATCTTTATAAGCTCATGGGACACTTCGCATGGTGGACAGCCTTTGTTACTAATGTGAATGCGTCATCATCTGAAGCATTTTTAATTGGATGTAATTATCTTGGCAAACCACGCGAACAAATAGATGGTTATGTCATGCATGCAAATTACATATTTTGGAGGAATACAAATCCAATTCAGTTGTCTTCCTATTCTTTATTTGACATGAGTAAATTTCCCCTTAAATTAAGGGGTACTGCTGTTATGTCTTTAAAAGAAGGTCAAATCAATGATATGATTTTATCTCTTCTTAGTAAAGGTAGACTTATAATTAGAGAAAACAACAGAGTTGTTATTTCTAGTGATGTTCTTGTTAACAACTAAACGAACAATGTTTGTTTTTCTTGTTTTATTGCCACTAGTCTCTAGTCAGTGTGTTAATCTTACAACCAGAACTCAATTACCCCCTGCATACACTAATTCTTTCACACGTGGTGTTTATTACCCTGACAAAGTTTTCAGATCCTCAGTTTTACATTCAACTCAGGACTTGTTCTTACCTTTCTTTTCCAATGTTACTTGGTTCCATGCTATACATGTCTCTGGGACCAATGGTACTAAGAGGTTTGATAACCCTGTCCTACCATTTAATGATGGTGTTTATTTTGCTTCCACTGAGAAGTCTAACATAATAAGAGGCTGGATTTTTGGTACTACTTTAGATTCGAAGACCCAGTCCCTACTTATTGTTAATAACGCTACTAATGTTGTTATTAAAGTCTGTGAATTTCAATTTTGTAATGATCCATTTTTGGGTGTTTATTACCACAAAAACAACAAAAGTTGGATGGAAAGTGAGTTCAGAGTTTATTCTAGTGCGAATAATTGCACTTTTGAATATGTCTCTCAGCCTTTTCTTATGGACCTTGAAGGAAAACAGGGTAATTTCAAAAATCTTAGGGAATTTGTGTTTAAGAATATTGATGGTTATTTTAAAATATATTCTAAGCACACGCCTATTAATTTAGTGCGTGATCTCCCTCAGGGTTTTTCGGCTTTAGAACCATTGGTAGATTTGCCAATAGGTATTAACATCACTAGGTTTCAAACTTTACTTGCTTTACATAGAAGTTATTTGACTCCTGGTGATTCTTCTTCAGGTTGGACAGCTGGTGCTGCAGCTTATTATGTGGGTTATCTTCAACCTAGGACTTTTCTATTAAAATATAATGAAAATGGAACCATTACAGATGCTGTAGACTGTGCACTTGACCCTCTCTCAGAAACAAAGTGTACGTTGAAATCCTTCACTGTAGAAAAAGGAATCTATCAAACTTCTAACTTTAGAGTCCAACCAACAGAATCTATTGTTAGATTTCCTAATATTACAAACTTGTGCCCTTTTGGTGAAGTTTTTAACGCCACCAGATTTGCATCTGTTTATGCTTGGAACAGGAAGAGAATCAGCAACTGTGTTGCTGATTATTCTGTCCTATATAATTCCGCATCATTTTCCACTTTTAAGTGTTATGGAGTGTCTCCTACTAAATTAAATGATCTCTGCTTTACTAATGTCTATGCAGATTCATTTGTAATTAGAGGTGATGAAGTCAGACAAATCGCTCCAGGGCAAACTGGAAAGATTGCTGATTATAATTATAAATTACCAGATGATTTTACAGGCTGCGTTATAGCTTGGAATTCTAACAATCTTGATTCTAAGGTTGGTGGTAATTATAATTACCTGTATAGATTGTTTAGGAAGTCTAATCTCAAACCTTTTGAGAGAGATATTTCAACTGAAATCTATCAGGCCGGTAGCACACCTTGTAATGGTGTTGAAGGTTTTAATTGTTACTTTCCTTTACAATCATATGGTTTCCAACCCACTAATGGTGTTGGTTACCAACCATACAGAGTAGTAGTACTTTCTTTTGAACTTCTACATGCACCAGCAACTGTTTGTGGACCTAAAAAGTCTACTAATTTGGTTAAAAACAAATGTGTCAATTTCAACTTCAATGGTTTAACAGGCACAGGTGTTCTTACTGAGTCTAACAAAAAGTTTCTGCCTTTCCAACAATTTGGCAGAGACATTGCTGACACTACTGATGCTGTCCGTGATCCACAGACACTTGAGATTCTTGACATTACACCATGTTCTTTTGGTGGTGTCAGTGTTATAACACCAGGAACAAATACTTCTAACCAGGTTGCTGTTCTTTATCAGGATGTTAACTGCACAGAAGTCCCTGTTGCTATTCATGCAGATCAACTTACTCCTACTTGGCGTGTTTATTCTACAGGTTCTAATGTTTTTCAAACACGTGCAGGCTGTTTAATAGGGGCTGAACATGTCAACAACTCATATGAGTGTGACATACCCATTGGTGCAGGTATATGCGCTAGTTATCAGACTCAGACTAATTCTCCTCGGCGGGCACGTAGTGTAGCTAGTCAATCCATCATTGCCTACACTATGTCACTTGGTGCAGAAAATTCAGTTGCTTACTCTAATAACTCTATTGCCATACCCACAAATTTTACTATTAGTGTTACCACAGAAATTCTACCAGTGTCTATGACCAAGACATCAGTAGATTGTACAATGTACATTTGTGGTGATTCAACTGAATGCAGCAATCTTTTGTTGCAATATGGCAGTTTTTGTACACAATTAAACCGTGCTTTAACTGGAATAGCTGTTGAACAAGACAAAAACACCCAAGAAGTTTTTGCACAAGTCAAACAAATTTACAAAACACCACCAATTAAAGATTTTGGTGGTTTTAATTTTTCACAAATATTACCAGATCCATCAAAACCAAGCAAGAGGTCATTTATTGAAGATCTACTTTTCAACAAAGTGACACTTGCAGATGCTGGCTTCATCAAACAATATGGTGATTGCCTTGGTGATATTGCTGCTAGAGACCTCATTTGTGCACAAAAGTTTAACGGCCTTACTGTTTTGCCACCTTTGCTCACAGATGAAATGATTGCTCAATACACTTCTGCACTGTTAGCGGGTACAATCACTTCTGGTTGGACCTTTGGTGCAGGTGCTGCATTACAAATACCATTTGCTATGCAAATGGCTTATAGGTTTAATGGTATTGGAGTTACACAGAATGTTCTCTATGAGAACCAAAAATTGATTGCCAACCAATTTAATAGTGCTATTGGCAAAATTCAAGACTCACTTTCTTCCACAGCAAGTGCACTTGGAAAACTTCAAGATGTGGTCAACCAAAATGCACAAGCTTTAAACACGCTTGTTAAACAACTTAGCTCCAATTTTGGTGCAATTTCAAGTGTTTTAAATGATATCCTTTCACGTCTTGACAAAGTTGAGGCTGAAGTGCAAATTGATAGGTTGATCACAGGCAGACTTCAAAGTTTGCAGACATATGTGACTCAACAATTAATTAGAGCTGCAGAAATCAGAGCTTCTGCTAATCTTGCTGCTACTAAAATGTCAGAGTGTGTACTTGGACAATCAAAAAGAGTTGATTTTTGTGGAAAGGGCTATCATCTTATGTCCTTCCCTCAGTCAGCACCTCATGGTGTAGTCTTCTTGCATGTGACTTATGTCCCTGCACAAGAAAAGAACTTCACAACTGCTCCTGCCATTTGTCATGATGGAAAAGCACACTTTCCTCGTGAAGGTGTCTTTGTTTCAAATGGCACACACTGGTTTGTAACACAAAGGAATTTTTATGAACCACAAATCATTACTACAGACAACACATTTGTGTCTGGTAACTGTGATGTTGTAATAGGAATTGTCAACAACACAGTTTATGATCCTTTGCAACCTGAATTAGACTCATTCAAGGAGGAGTTAGATAAATATTTTAAGAATCATACATCACCAGATGTTGATTTAGGTGACATCTCTGGCATTAATGCTTCAGTTGTAAACATTCAAAAAGAAATTGACCGCCTCAATGAGGTTGCCAAGAATTTAAATGAATCTCTCATCGATCTCCAAGAACTTGGAAAGTATGAGCAGTATATAAAATGGCCATGGTACATTTGGCTAGGTTTTATAGCTGGCTTGATTGCCATAGTAATGGTGACAATTATGCTTTGCTGTATGACCAGTTGCTGTAGTTGTCTCAAGGGCTGTTGTTCTTGTGGATCCTGCTGCAAATTTGATGAAGACGACTCTGAGCCAGTGCTCAAAGGAGTCAAATTACATTACACATAAACGAACTTATGGATTTGTTTATGAGAATCTTCACAATTGGAACTGTAACTTTGAAGCAAGGTGAAATCAAGGATGCTACTCCTTCAGATTTTGTTCGCGCTACTGCAACGATACCGATACAAGCCTCACTCCCTTTCGGATGGCTTATTGTTGGCGTTGCACTTCTTGCTGTTTTTCAGAGCGCTTCCAAAATCATAACCCTCAAAAAGAGATGGCAACTAGCACTCTCCAAGGGTGTTCACTTTGTTTGCAACTTGCTGTTGTTGTTTGTAACAGTTTACTCACACCTTTTGCTCGTTGCTGCTGGCCTTGAAGCCCCTTTTCTCTATCTTTATGCTTTAGTCTACTTCTTGCAGAGTATAAACTTTGTAAGAATAATAATGAGGCTTTGGCTTTGCTGGAAATGCCGTTCCAAAAACCCATTACTTTATGATGCCAACTATTTTCTTTGCTGGCATACTAATTGTTACGACTATTGTATACCTTACAATAGTGTAACTTCTTCAATTGTCATTACTTCAGGTGATGGCACAACAAGTCCTATTTCTGAACATGACTACCAGATTGGTGGTTATACTGAAAAATGGGAATCTGGAGTAAAAGACTGTGTTGTATTACACAGTTACTTCACTTCAGACTATTACCAGCTGTACTCAACTCAATTGAGTACAGACACTGGTGTTGAACATGTTACCTTCTTCATCTACAATAAAATTGTTGATGAGCCTGAAGAACATGTCCAAATTCACACAATCGACGGTTCATCCGGAGTTGTTAATCCAGTAATGGAACCAATTTATGATGAACCGACGACGACTACTAGCGTGCCTTTGTAAGCACAAGCTGATGAGTACGAACTTATGTACTCATTCGTTTCGGAAGAGACAGGTACGTTAATAGTTAATAGCGTACTTCTTTTTCTTGCTTTCGTGGTATTCTTGCTAGTTACACTAGCCATCCTTACTGCGCTTCGATTGTGTGCGTACTGCTGCAATATTGTTAACGTGAGTCTTGTAAAACCTTCTTTTTACGTTTACTCTCGTGTTAAAAATCTGAATTCTTCTAGAGTTCCTGATCTTCTGGTCTAAACGAACTAAATATTATATTAGTTTTTCTGTTTGGAACTTTAATTTTAGCCATGGCAGATTCCAACGGTACTATTACCGTTGAAGAGCTTAAAAAGCTCCTTGAACAATGGAACCTAGTAATAGGTTTCCTATTCCTTACATGGATTTGTCTTCTACAATTTGCCTATGCCAACAGGAATAGGTTTTTGTATATAATTAAGTTAATTTTCCTCTGGCTGTTATGGCCAGTAACTTTAGCTTGTTTTGTGCTTGCTGCTGTTTACAGAATAAATTGGATCACCGGTGGAATTGCTATCGCAATGGCTTGTCTTGTAGGCTTGATGTGGCTCAGCTACTTCATTGCTTCTTTCAGACTGTTTGCGCGTACGCGTTCCATGTGGTCATTCAATCCAGAAACTAACATTCTTCTCAACGTGCCACTCCATGGCACTATTCTGACCAGACCGCTTCTAGAAAGTGAACTCGTAATCGGAGCTGTGATCCTTCGTGGACATCTTCGTATTGCTGGACACCATCTAGGACGCTGTGACATCAAGGACCTGCCTAAAGAAATCACTGTTGCTACATCACGAACGCTTTCTTATTACAAATTGGGAGCTTCGCAGCGTGTAGCAGGTGACTCAGGTTTTGCTGCATACAGTCGCTACAGGATTGGCAACTATAAATTAAACACAGACCATTCCAGTAGCAGTGACAATATTGCTTTGCTTGTACAGTAAGTGACAACAGATGTTTCATCTCGTTGACTTTCAGGTTACTATAGCAGAGATATTACTAATTATTATGAGGACTTTTAAAGTTTCCATTTGGAATCTTGATTACATCATAAACCTCATAATTAAAAATTTATCTAAGTCACTAACTGAGAATAAATATTCTCAATTAGATGAAGAGCAACCAATGGAGATTGATTAAACGAACATGAAAATTATTCTTTTCTTGGCACTGATAACACTCGCTACTTGTGAGCTTTATCACTACCAAGAGTGTGTTAGAGGTACAACAGTACTTTTAAAAGAACCTTGCTCTTCTGGAACATACGAGGGCAATTCACCATTTCATCCTCTAGCTGATAACAAATTTGCACTGACTTGCTTTAGCACTCAATTTGCTTTTGCTTGTCCTGACGGCGTAAAACACGTCTATCAGTTACGTGCCAGATCAGTTTCACCTAAACTGTTCATCAGACAAGAGGAAGTTCAAGAACTTTACTCTCCAATTTTTCTTATTGTTGCGGCAATAGTGTTTATAACACTTTGCTTCACACTCAAAAGAAAGACAGAATGATTGAACTTTCATTAATTGACTTCTATTTGTGCTTTTTAGCCTTTCTGCTATTCCTTGTTTTAATTATGCTTATTATCTTTTGGTTCTCACTTGAACTGCAAGATCATAATGAAACTTGTCACGCCTAAACGAACATGAAATTTCTTGTTTTCTTAGGAATCATCACAACTGTAGCTGCATTTCACCAAGAATGTAGTTTACAGTCATGTACTCAACATCAACCATATGTAGTTGATGACCCGTGTCCTATTCACTTCTATTCTAAATGGTATATTAGAGTAGGAGCTAGAAAATCAGCACCTTTAATTGAATTGTGCGTGGATGAGGCTGGTTCTAAATCACCCATTCAGTACATCGATATCGGTAATTATACAGTTTCCTGTTTACCTTTTACAATTAATTGCCAGGAACCTAAATTGGGTAGTCTTGTAGTGCGTTGTTCGTTCTATGAAGACTTTTTAGAGTATCATGACGTTCGTGTTGTTTTAGATTTCATCTAAACGAACAAACTAAAATGTCTGATAATGGACCCCAAAATCAGCGAAATGCACCCCGCATTACGTTTGGTGGACCCTCAGATTCAACTGGCAGTAACCAGAATGGAGAACGCAGTGGGGCGCGATCAAAACAACGTCGGCCCCAAGGTTTACCCAATAATACTGCGTCTTGGTTCACCGCTCTCACTCAACATGGCAAGGAAGACCTTAAATTCCCTCGAGGACAAGGCGTTCCAATTAACACCAATAGCAGTCCAGATGACCAAATTGGCTACTACCGAAGAGCTACCAGACGAATTCGTGGTGGTGACGGTAAAATGAAAGATCTCAGTCCAAGATGGTATTTCTACTACCTAGGAACTGGGCCAGAAGCTGGACTTCCCTATGGTGCTAACAAAGACGGCATCATATGGGTTGCAACTGAGGGAGCCTTGAATACACCAAAAGATCACATTGGCACCCGCAATCCTGCTAACAATGCTGCAATCGTGCTACAACTTCCTCAAGGAACAACATTGCCAAAAGGCTTCTACGCAGAAGGGAGCAGAGGCGGCAGTCAAGCCTCTTCTCGTTCCTCATCACGTAGTCGCAACAGTTCAAGAAATTCAACTCCAGGCAGCAGTAGGGGAACTTCTCCTGCTAGAATGGCTGGCAATGGCGGTGATGCTGCTCTTGCTTTGCTGCTGCTTGACAGATTGAACCAGCTTGAGAGCAAAATGTCTGGTAAAGGCCAACAACAACAAGGCCAAACTGTCACTAAGAAATCTGCTGCTGAGGCTTCTAAGAAGCCTCGGCAAAAACGTACTGCCACTAAAGCATACAATGTAACACAAGCTTTCGGCAGACGTGGTCCAGAACAAACCCAAGGAAATTTTGGGGACCAGGAACTAATCAGACAAGGAACTGATTACAAACATTGGCCGCAAATTGCACAATTTGCCCCCAGCGCTTCAGCGTTCTTCGGAATGTCGCGCATTGGCATGGAAGTCACACCTTCGGGAACGTGGTTGACCTACACAGGTGCCATCAAATTGGATGACAAAGATCCAAATTTCAAAGATCAAGTCATTTTGCTGAATAAGCATATTGACGCATACAAAACATTCCCACCAACAGAGCCTAAAAAGGACAAAAAGAAGAAGGCTGATGAAACTCAAGCCTTACCGCAGAGACAGAAGAAACAGCAAACTGTGACTCTTCTTCCTGCTGCAGATTTGGATGATTTCTCCAAACAATTGCAACAATCCATGAGCAGTGCTGACTCAACTCAGGCCTAAACTCATGCAGACCACACAAGGCAGATGGGCTATATAAACGTTTTCGCTTTTCCGTTTACGATATATAGTCTACTCTTGTGCAGAATGAATTCTCGTAACTACATAGCACAAGTAGATGTAGTTAACTTTAATCTCACATAGCAATCTTTAATCAGTGTGTAACATTAGGGAGGACTTGAAAGAGCCACCACATTTTCACCGAGGCCACGCGGAGTACGATCGAGTGTACAGTGAACAATGCTAGGGAGAGCTGCCTATATGGAAGAGCCCTAATGTGTAAAATTAATTTTAGTAGTGCTATCCCCATGTGATTTTAATAGCTTCTTAGGAGAATGACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ }
+ ],
+ "genes": [
+ {
+ "name": "E",
+ "sequence": "MYSFVSEETGTLIVNSVLLFLAFVVFLLVTLAILTALRLCAYCCNIVNVSLVKPSFYVYSRVKNLNSSRVPDLLV*"
+ },
+ {
+ "name": "M",
+ "sequence": "MADSNGTITVEELKKLLEQWNLVIGFLFLTWICLLQFAYANRNRFLYIIKLIFLWLLWPVTLACFVLAAVYRINWITGGIAIAMACLVGLMWLSYFIASFRLFARTRSMWSFNPETNILLNVPLHGTILTRPLLESELVIGAVILRGHLRIAGHHLGRCDIKDLPKEITVATSRTLSYYKLGASQRVAGDSGFAAYSRYRIGNYKLNTDHSSSSDNIALLVQ*"
+ },
+ {
+ "name": "N",
+ "sequence": "MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQRRPQGLPNNTASWFTALTQHGKEDLKFPRGQGVPINTNSSPDDQIGYYRRATRRIRGGDGKMKDLSPRWYFYYLGTGPEAGLPYGANKDGIIWVATEGALNTPKDHIGTRNPANNAAIVLQLPQGTTLPKGFYAEGSRGGSQASSRSSSRSRNSSRNSTPGSSRGTSPARMAGNGGDAALALLLLDRLNQLESKMSGKGQQQQGQTVTKKSAAEASKKPRQKRTATKAYNVTQAFGRRGPEQTQGNFGDQELIRQGTDYKHWPQIAQFAPSASAFFGMSRIGMEVTPSGTWLTYTGAIKLDDKDPNFKDQVILLNKHIDAYKTFPPTEPKKDKKKKADETQALPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA*"
+ },
+ {
+ "name": "ORF1a",
+ "sequence": "MESLVPGFNEKTHVQLSLPVLQVRDVLVRGFGDSVEEVLSEARQHLKDGTCGLVEVEKGVLPQLEQPYVFIKRSDARTAPHGHVMVELVAELEGIQYGRSGETLGVLVPHVGEIPVAYRKVLLRKNGNKGAGGHSYGADLKSFDLGDELGTDPYEDFQENWNTKHSSGVTRELMRELNGGAYTRYVDNNFCGPDGYPLECIKDLLARAGKASCTLSEQLDFIDTKRGVYCCREHEHEIAWYTERSEKSYELQTPFEIKLAKKFDTFNGECPNFVFPLNSIIKTIQPRVEKKKLDGFMGRIRSVYPVASPNECNQMCLSTLMKCDHCGETSWQTGDFVKATCEFCGTENLTKEGATTCGYLPQNAVVKIYCPACHNSEVGPEHSLAEYHNESGLKTILRKGGRTIAFGGCVFSYVGCHNKCAYWVPRASANIGCNHTGVVGEGSEGLNDNLLEILQKEKVNINIVGDFKLNEEIAIILASFSASTSAFVETVKGLDYKAFKQIVESCGNFKVTKGKAKKGAWNIGEQKSILSPLYAFASEAARVVRSIFSRTLETAQNSVRVLQKAAITILDGISQYSLRLIDAMMFTSDLATNNLVVMAYITGGVVQLTSQWLTNIFGTVYEKLKPVLDWLEEKFKEGVEFLRDGWEIVKFISTCACEIVGGQIVTCAKEIKESVQTFFKLVNKFLALCADSIIIGGAKLKALNLGETFVTHSKGLYRKCVKSREETGLLMPLKAPKEIIFLEGETLPTEVLTEEVVLKTGDLQPLEQPTSEAVEAPLVGTPVCINGLMLLEIKDTEKYCALAPNMMVTNNTFTLKGGAPTKVTFGDDTVIEVQGYKSVNITFELDERIDKVLNEKCSAYTVELGTEVNEFACVVADAVIKTLQPVSELLTPLGIDLDEWSMATYYLFDESGEFKLASHMYCSFYPPDEDEEEGDCEEEEFEPSTQYEYGTEDDYQGKPLEFGATSAALQPEEEQEEDWLDDDSQQTVGQQDGSEDNQTTTIQTIVEVQPQLEMELTPVVQTIEVNSFSGYLKLTDNVYIKNADIVEEAKKVKPTVVVNAANVYLKHGGGVAGALNKATNNAMQVESDDYIATNGPLKVGGSCVLSGHNLAKHCLHVVGPNVNKGEDIQLLKSAYENFNQHEVLLAPLLSAGIFGADPIHSLRVCVDTVRTNVYLAVFDKNLYDKLVSSFLEMKSEKQVEQKIAEIPKEEVKPFITESKPSVEQRKQDDKKIKACVEEVTTTLEETKFLTENLLLYIDINGNLHPDSATLVSDIDITFLKKDAPYIVGDVVQEGVLTAVVIPTKKAGGTTEMLAKALRKVPTDNYITTYPGQGLNGYTVEEAKTVLKKCKSAFYILPSIISNEKQEILGTVSWNLREMLAHAEETRKLMPVCVETKAIVSTIQRKYKGIKIQEGVVDYGARFYFYTSKTTVASLINTLNDLNETLVTMPLGYVTHGLNLEEAARYMRSLKVPATVSVSSPDAVTAYNGYLTSSSKTPEEHFIETISLAGSYKDWSYSGQSTQLGIEFLKRGDKSVYYTSNPTTFHLDGEVITFDNLKTLLSLREVRTIKVFTTVDNINLHTQVVDMSMTYGQQFGPTYLDGADVTKIKPHNSHEGKTFYVLPNDDTLRVEAFEYYHTTDPSFLGRYMSALNHTKKWKYPQVNGLTSIKWADNNCYLATALLTLQQIELKFNPPALQDAYYRARAGEAANFCALILAYCNKTVGELGDVRETMSYLFQHANLDSCKRVLNVVCKTCGQQQTTLKGVEAVMYMGTLSYEQFKKGVQIPCTCGKQATKYLVQQESPFVMMSAPPAQYELKHGTFTCASEYTGNYQCGHYKHITSKETLYCIDGALLTKSSEYKGPITDVFYKENSYTTTIKPVTYKLDGVVCTEIDPKLDNYYKKDNSYFTEQPIDLVPNQPYPNASFDNFKFVCDNIKFADDLNQLTGYKKPASRELKVTFFPDLNGDVVAIDYKHYTPSFKKGAKLLHKPIVWHVNNATNKATYKPNTWCIRCLWSTKPVETSNSFDVLKSEDAQGMDNLACEDLKPVSEEVVENPTIQKDVLECNVKTTEVVGDIILKPANNSLKITEEVGHTDLMAAYVDNSSLTIKKPNELSRVLGLKTLATHGLAAVNSVPWDTIANYAKPFLNKVVSTTTNIVTRCLNRVCTNYMPYFFTLLLQLCTFTRSTNSRIKASMPTTIAKNTVKSVGKFCLEASFNYLKSPNFSKLINIIIWFLLLSVCLGSLIYSTAALGVLMSNLGMPSYCTGYREGYLNSTNVTIATYCTGSIPCSVCLSGLDSLDTYPSLETIQITISSFKWDLTAFGLVAEWFLAYILFTRFFYVLGLAAIMQLFFSYFAVHFISNSWLMWLIINLVQMAPISAMVRMYIFFASFYYVWKSYVHVVDGCNSSTCMMCYKRNRATRVECTTIVNGVRRSFYVYANGGKGFCKLHNWNCVNCDTFCAGSTFISDEVARDLSLQFKRPINPTDQSSYIVDSVTVKNGSIHLYFDKAGQKTYERHSLSHFVNLDNLRANNTKGSLPINVIVFDGKSKCEESSAKSASVYYSQLMCQPILLLDQALVSDVGDSAEVAVKMFDAYVNTFSSTFNVPMEKLKTLVATAEAELAKNVSLDNVLSTFISAARQGFVDSDVETKDVVECLKLSHQSDIEVTGDSCNNYMLTYNKVENMTPRDLGACIDCSARHINAQVAKSHNIALIWNVKDFMSLSEQLRKQIRSAAKKNNLPFKLTCATTRQVVNVVTTKIALKGGKIVNNWLKQLIKVTLVFLFVAAIFYLITPVHVMSKHTDFSSEIIGYKAIDGGVTRDIASTDTCFANKHADFDTWFSQRGGSYTNDKACPLIAAVITREVGFVVPGLPGTILRTTNGDFLHFLPRVFSAVGNICYTPSKLIEYTDFATSACVLAAECTIFKDASGKPVPYCYDTNVLEGSVAYESLRPDTRYVLMDGSIIQFPNTYLEGSVRVVTTFDSEYCRHGTCERSEAGVCVSTSGRWVLNNDYYRSLPGVFCGVDAVNLLTNMFTPLIQPIGALDISASIVAGGIVAIVVTCLAYYFMRFRRAFGEYSHVVAFNTLLFLMSFTVLCLTPVYSFLPGVYSVIYLYLTFYLTNDVSFLAHIQWMVMFTPLVPFWITIAYIICISTKHFYWFFSNYLKRRVVFNGVSFSTFEEAALCTFLLNKEMYLKLRSDVLLPLTQYNRYLALYNKYKYFSGAMDTTSYREAACCHLAKALNDFSNSGSDVLYQPPQTSITSAVLQSGFRKMAFPSGKVEGCMVQVTCGTTTLNGLWLDDVVYCPRHVICTSEDMLNPNYEDLLIRKSNHNFLVQAGNVQLRVIGHSMQNCVLKLKVDTANPKTPKYKFVRIQPGQTFSVLACYNGSPSGVYQCAMRPNFTIKGSFLNGSCGSVGFNIDYDCVSFCYMHHMELPTGVHAGTDLEGNFYGPFVDRQTAQAAGTDTTITVNVLAWLYAAVINGDRWFLNRFTTTLNDFNLVAMKYNYEPLTQDHVDILGPLSAQTGIAVLDMCASLKELLQNGMNGRTILGSALLEDEFTPFDVVRQCSGVTFQSAVKRTIKGTHHWLLLTILTSLLVLVQSTQWSLFFFLYENAFLPFAMGIIAMSAFAMMFVKHKHAFLCLFLLPSLATVAYFNMVYMPASWVMRIMTWLDMVDTSLSGFKLKDCVMYASAVVLLILMTARTVYDDGARRVWTLMNVLTLVYKVYYGNALDQAISMWALIISVTSNYSGVVTTVMFLARGIVFMCVEYCPIFFITGNTLQCIMLVYCFLGYFCTCYFGLFCLLNRYFRLTLGVYDYLVSTQEFRYMNSQGLLPPKNSIDAFKLNIKLLGVGGKPCIKVATVQSKMSDVKCTSVVLLSVLQQLRVESSSKLWAQCVQLHNDILLAKDTTEAFEKMVSLLSVLLSMQGAVDINKLCEEMLDNRATLQAIASEFSSLPSYAAFATAQEAYEQAVANGDSEVVLKKLKKSLNVAKSEFDRDAAMQRKLEKMADQAMTQMYKQARSEDKRAKVTSAMQTMLFTMLRKLDNDALNNIINNARDGCVPLNIIPLTTAAKLMVVIPDYNTYKNTCDGTTFTYASALWEIQQVVDADSKIVQLSEISMDNSPNLAWPLIVTALRANSAVKLQNNELSPVALRQMSCAAGTTQTACTDDNALAYYNTTKGGRFVLALLSDLQDLKWARFPKSDGTGTIYTELEPPCRFVTDTPKGPKVKYLYFIKGLNNLNRGMVLGSLAATVRLQAGNATEVPANSTVLSFCAFAVDAAKAYKDYLASGGQPITNCVKMLCTHTGTGQAITVTPEANMDQESFGGASCCLYCRCHIDHPNPKGFCDLKGKYVQIPTTCANDPVGFTLKNTVCTVCGMWKGYGCSCDQLREPMLQSADAQSFLN"
+ },
+ {
+ "name": "ORF1b",
+ "sequence": "RVCGVSAARLTPCGTGTSTDVVYRAFDIYNDKVAGFAKFLKTNCCRFQEKDEDDNLIDSYFVVKRHTFSNYQHEETIYNLLKDCPAVAKHDFFKFRIDGDMVPHISRQRLTKYTMADLVYALRHFDEGNCDTLKEILVTYNCCDDDYFNKKDWYDFVENPDILRVYANLGERVRQALLKTVQFCDAMRNAGIVGVLTLDNQDLNGNWYDFGDFIQTTPGSGVPVVDSYYSLLMPILTLTRALTAESHVDTDLTKPYIKWDLLKYDFTEERLKLFDRYFKYWDQTYHPNCVNCLDDRCILHCANFNVLFSTVFPPTSFGPLVRKIFVDGVPFVVSTGYHFRELGVVHNQDVNLHSSRLSFKELLVYAADPAMHAASGNLLLDKRTTCFSVAALTNNVAFQTVKPGNFNKDFYDFAVSKGFFKEGSSVELKHFFFAQDGNAAISDYDYYRYNLPTMCDIRQLLFVVEVVDKYFDCYDGGCINANQVIVNNLDKSAGFPFNKWGKARLYYDSMSYEDQDALFAYTKRNVIPTITQMNLKYAISAKNRARTVAGVSICSTMTNRQFHQKLLKSIAATRGATVVIGTSKFYGGWHNMLKTVYSDVENPHLMGWDYPKCDRAMPNMLRIMASLVLARKHTTCCSLSHRFYRLANECAQVLSEMVMCGGSLYVKPGGTSSGDATTAYANSVFNICQAVTANVNALLSTDGNKIADKYVRNLQHRLYECLYRNRDVDTDFVNEFYAYLRKHFSMMILSDDAVVCFNSTYASQGLVASIKNFKSVLYYQNNVFMSEAKCWTETDLTKGPHEFCSQHTMLVKQGDDYVYLPYPDPSRILGAGCFVDDIVKTDGTLMIERFVSLAIDAYPLTKHPNQEYADVFHLYLQYIRKLHDELTGHMLDMYSVMLTNDNTSRYWEPEFYEAMYTPHTVLQAVGACVLCNSQTSLRCGACIRRPFLCCKCCYDHVISTSHKLVLSVNPYVCNAPGCDVTDVTQLYLGGMSYYCKSHKPPISFPLCANGQVFGLYKNTCVGSDNVTDFNAIATCDWTNAGDYILANTCTERLKLFAAETLKATEETFKLSYGIATVREVLSDRELHLSWEVGKPRPPLNRNYVFTGYRVTKNSKVQIGEYTFEKGDYGDAVVYRGTTTYKLNVGDYFVLTSHTVMPLSAPTLVPQEHYVRITGLYPTLNISDEFSSNVANYQKVGMQKYSTLQGPPGTGKSHFAIGLALYYPSARIVYTACSHAAVDALCEKALKYLPIDKCSRIIPARARVECFDKFKVNSTLEQYVFCTVNALPETTADIVVFDEISMATNYDLSVVNARLRAKHYVYIGDPAQLPAPRTLLTKGTLEPEYFNSVCRLMKTIGPDMFLGTCRRCPAEIVDTVSALVYDNKLKAHKDKSAQCFKMFYKGVITHDVSSAINRPQIGVVREFLTRNPAWRKAVFISPYNSQNAVASKILGLPTQTVDSSQGSEYDYVIFTQTTETAHSCNVNRFNVAITRAKVGILCIMSDRDLYDKLQFTSLEIPRRNVATLQAENVTGLFKDCSKVITGLHPTQAPTHLSVDTKFKTEGLCVDIPGIPKDMTYRRLISMMGFKMNYQVNGYPNMFITREEAIRHVRAWIGFDVEGCHATREAVGTNLPLQLGFSTGVNLVAVPTGYVDTPNNTDFSRVSAKPPPGDQFKHLIPLMYKGLPWNVVRIKIVQMLSDTLKNLSDRVVFVLWAHGFELTSMKYFVKIGPERTCCLCDRRATCFSTASDTYACWHHSIGFDYVYNPFMIDVQQWGFTGNLQSNHDLYCQVHGNAHVASCDAIMTRCLAVHECFVKRVDWTIEYPIIGDELKINAACRKVQHMVVKAALLADKFPVLHDIGNPKAIKCVPQADVEWKFYDAQPCSDKAYKIEELFYSYATHSDKFTDGVCLFWNCNVDRYPANSIVCRFDTRVLSNLNLPGCDGGSLYVNKHAFHTPAFDKSAFVNLKQLPFFYYSDSPCESHGKQVVSDIDYVPLKSATCITRCNLGGAVCRHHANEYRLYLDAYNMMISAGFSLWVYKQFDTYNLWNTFTRLQSLENVAFNVVNKGHFDGQQGEVPVSIINNTVYTKVDGVDVELFENKTTLPVNVAFELWAKRNIKPVPEVKILNNLGVDIAANTVIWDYKRDAPAHISTIGVCSMTDIAKKPTETICAPLTVFFDGRVDGQVDLFRNARNGVLITEGSVKGLQPSVGPKQASLNGVTLIGEAVKTQFNYYKKVDGVVQQLPETYFTQSRNLQEFKPRSQMEIDFLELAMDEFIERYKLEGYAFEHIVYGDFSHSQLGGLHLLIGLAKRFKESPFELEDFIPMDSTVKNYFITDAQTGSSKCVCSVIDLLLDDFVEIIKSQDLSVVSKVVKVTIDYTEISFMLWCKDGHVETFYPKLQSSQAWQPGVAMPNLYKMQRMLLEKCDLQNYGDSATLPKGIMMNVAKYTQLCQYLNTLTLAVPYNMRVIHFGAGSDKGVAPGTAVLRQWLPTGTLLVDSDLNDFVSDADSTLIGDCATVHTANKWDLIISDMYDPKTKNVTKENDSKEGFFTYICGFIQQKLALGGSVAIKITEHSWNADLYKLMGHFAWWTAFVTNVNASSSEAFLIGCNYLGKPREQIDGYVMHANYIFWRNTNPIQLSSYSLFDMSKFPLKLRGTAVMSLKEGQINDMILSLLSKGRLIIRENNRVVISSDVLVNN*"
+ },
+ {
+ "name": "ORF3a",
+ "sequence": "MDLFMRIFTIGTVTLKQGEIKDATPSDFVRATATIPIQASLPFGWLIVGVALLAVFQSASKIITLKKRWQLALSKGVHFVCNLLLLFVTVYSHLLLVAAGLEAPFLYLYALVYFLQSINFVRIIMRLWLCWKCRSKNPLLYDANYFLCWHTNCYDYCIPYNSVTSSIVITSGDGTTSPISEHDYQIGGYTEKWESGVKDCVVLHSYFTSDYYQLYSTQLSTDTGVEHVTFFIYNKIVDEPEEHVQIHTIDGSSGVVNPVMEPIYDEPTTTTSVPL*"
+ },
+ {
+ "name": "ORF6",
+ "sequence": "MFHLVDFQVTIAEILLIIMRTFKVSIWNLDYIINLIIKNLSKSLTENKYSQLDEEQPMEID*"
+ },
+ {
+ "name": "ORF7a",
+ "sequence": "MKIILFLALITLATCELYHYQECVRGTTVLLKEPCSSGTYEGNSPFHPLADNKFALTCFSTQFAFACPDGVKHVYQLRARSVSPKLFIRQEEVQELYSPIFLIVAAIVFITLCFTLKRKTE*"
+ },
+ {
+ "name": "ORF7b",
+ "sequence": "MIELSLIDFYLCFLAFLLFLVLIMLIIFWFSLELQDHNETCHA*"
+ },
+ {
+ "name": "ORF8",
+ "sequence": "MKFLVFLGIITTVAAFHQECSLQSCTQHQPYVVDDPCPIHFYSKWYIRVGARKSAPLIELCVDEAGSKSPIQYIDIGNYTVSCLPFTINCQEPKLGSLVVRCSFYEDFLEYHDVRVVLDFI*"
+ },
+ {
+ "name": "ORF9b",
+ "sequence": "MDPKISEMHPALRLVDPQIQLAVTRMENAVGRDQNNVGPKVYPIILRLGSPLSLNMARKTLNSLEDKAFQLTPIAVQMTKLATTEELPDEFVVVTVK*"
+ },
+ {
+ "name": "S",
+ "sequence": "MFVFLVLLPLVSSQCVNLTTRTQLPPAYTNSFTRGVYYPDKVFRSSVLHSTQDLFLPFFSNVTWFHAIHVSGTNGTKRFDNPVLPFNDGVYFASTEKSNIIRGWIFGTTLDSKTQSLLIVNNATNVVIKVCEFQFCNDPFLGVYYHKNNKSWMESEFRVYSSANNCTFEYVSQPFLMDLEGKQGNFKNLREFVFKNIDGYFKIYSKHTPINLVRDLPQGFSALEPLVDLPIGINITRFQTLLALHRSYLTPGDSSSGWTAGAAAYYVGYLQPRTFLLKYNENGTITDAVDCALDPLSETKCTLKSFTVEKGIYQTSNFRVQPTESIVRFPNITNLCPFGEVFNATRFASVYAWNRKRISNCVADYSVLYNSASFSTFKCYGVSPTKLNDLCFTNVYADSFVIRGDEVRQIAPGQTGKIADYNYKLPDDFTGCVIAWNSNNLDSKVGGNYNYLYRLFRKSNLKPFERDISTEIYQAGSTPCNGVEGFNCYFPLQSYGFQPTNGVGYQPYRVVVLSFELLHAPATVCGPKKSTNLVKNKCVNFNFNGLTGTGVLTESNKKFLPFQQFGRDIADTTDAVRDPQTLEILDITPCSFGGVSVITPGTNTSNQVAVLYQDVNCTEVPVAIHADQLTPTWRVYSTGSNVFQTRAGCLIGAEHVNNSYECDIPIGAGICASYQTQTNSPRRARSVASQSIIAYTMSLGAENSVAYSNNSIAIPTNFTISVTTEILPVSMTKTSVDCTMYICGDSTECSNLLLQYGSFCTQLNRALTGIAVEQDKNTQEVFAQVKQIYKTPPIKDFGGFNFSQILPDPSKPSKRSFIEDLLFNKVTLADAGFIKQYGDCLGDIAARDLICAQKFNGLTVLPPLLTDEMIAQYTSALLAGTITSGWTFGAGAALQIPFAMQMAYRFNGIGVTQNVLYENQKLIANQFNSAIGKIQDSLSSTASALGKLQDVVNQNAQALNTLVKQLSSNFGAISSVLNDILSRLDKVEAEVQIDRLITGRLQSLQTYVTQQLIRAAEIRASANLAATKMSECVLGQSKRVDFCGKGYHLMSFPQSAPHGVVFLHVTYVPAQEKNFTTAPAICHDGKAHFPREGVFVSNGTHWFVTQRNFYEPQIITTDNTFVSGNCDVVIGIVNNTVYDPLQPELDSFKEELDKYFKNHTSPDVDLGDISGINASVVNIQKEIDRLNEVAKNLNESLIDLQELGKYEQYIKWPWYIWLGFIAGLIAIVMVTIMLCCMTSCCSCLKGCCSCGSCCKFDEDDSEPVLKGVKLHYT*"
+ }
+ ]
+}
diff --git a/lapis2/src/test/resources/config/testDatabaseConfig.yaml b/lapis2/src/test/resources/config/testDatabaseConfig.yaml
index 7cd74046..aa56983f 100644
--- a/lapis2/src/test/resources/config/testDatabaseConfig.yaml
+++ b/lapis2/src/test/resources/config/testDatabaseConfig.yaml
@@ -18,5 +18,4 @@ schema:
type: aaInsertion
features:
- name: sarsCoV2VariantQuery
- - name: isSingleSegmentedSequence
primaryKey: gisaid_epi_isl
diff --git a/siloLapisTests/test/aggregated.spec.ts b/siloLapisTests/test/aggregated.spec.ts
index 6491d0e1..4742e0eb 100644
--- a/siloLapisTests/test/aggregated.spec.ts
+++ b/siloLapisTests/test/aggregated.spec.ts
@@ -1,8 +1,8 @@
import { expect } from 'chai';
-import { lapisClient, basePath } from './common';
+import { basePath, lapisClient } from './common';
import fs from 'fs';
-import { AggregatedPostRequest } from './lapisClient/models/AggregatedPostRequest';
-import { AggregatedResponse } from './lapisClient/models/AggregatedResponse';
+import { AggregatedPostRequest } from './lapisClient';
+import { AggregatedResponse } from './lapisClient';
const queriesPath = __dirname + '/aggregatedQueries';
const aggregatedQueryFiles = fs.readdirSync(queriesPath);
diff --git a/siloLapisTests/test/alignedNucleotideSequence.spec.ts b/siloLapisTests/test/alignedNucleotideSequence.spec.ts
new file mode 100644
index 00000000..0ae9052b
--- /dev/null
+++ b/siloLapisTests/test/alignedNucleotideSequence.spec.ts
@@ -0,0 +1,112 @@
+import { expect } from 'chai';
+import { basePath, lapisSingleSegmentedSequenceController, sequenceData } from './common';
+
+describe('The /alignedNucleotideSequence endpoint', () => {
+ it('should return aligned nucleotide sequences for Switzerland', async () => {
+ const result = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: { country: 'Switzerland' },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3247294');
+ expect(sequences[0]).to.have.length(29903);
+ });
+
+ it('should order ascending by specified fields', async () => {
+ const result = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: { orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }] },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_1001493');
+ expect(sequences[0]).to.have.length(29903);
+ });
+
+ it('should order descending by specified fields', async () => {
+ const result = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: { orderBy: [{ field: 'gisaid_epi_isl', type: 'descending' }] },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_931279');
+ expect(sequences[0]).to.have.length(29903);
+ });
+
+ it('should apply limit and offset', async () => {
+ const resultWithLimit = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: {
+ orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }],
+ limit: 2,
+ },
+ });
+
+ const { primaryKeys: primaryKeysWithLimit, sequences: sequencesWithLimit } =
+ sequenceData(resultWithLimit);
+
+ expect(primaryKeysWithLimit).to.have.length(2);
+ expect(sequencesWithLimit).to.have.length(2);
+ expect(primaryKeysWithLimit[0]).to.equal('>EPI_ISL_1001493');
+ expect(sequencesWithLimit[0]).to.have.length(29903);
+
+ const resultWithLimitAndOffset =
+ await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: {
+ orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }],
+ limit: 2,
+ offset: 1,
+ },
+ });
+
+ const { primaryKeys: primaryKeysWithLimitAndOffset, sequences: sequencesWithLimitAndOffset } =
+ sequenceData(resultWithLimitAndOffset);
+
+ expect(primaryKeysWithLimitAndOffset).to.have.length(2);
+ expect(sequencesWithLimitAndOffset).to.have.length(2);
+ expect(primaryKeysWithLimitAndOffset[0]).to.equal(primaryKeysWithLimit[1]);
+ expect(sequencesWithLimitAndOffset[0]).to.equal(sequencesWithLimit[1]);
+ });
+
+ it('should correctly handle nucleotide insertion requests', async () => {
+ const result = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: {
+ nucleotideInsertions: ['ins_25701:CC?', 'ins_5959:?AT'],
+ },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(1);
+ expect(sequences).to.have.length(1);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3578231');
+ });
+
+ it('should correctly handle amino acid insertion requests', async () => {
+ const result = await lapisSingleSegmentedSequenceController.postAlignedNucleotideSequence({
+ sequenceRequest: {
+ aminoAcidInsertions: ['ins_S:143:T', 'ins_ORF1a:3602:F?P'],
+ },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(1);
+ expect(sequences).to.have.length(1);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3259931');
+ });
+
+ it('should return the lapis data version in the response', async () => {
+ const result = await fetch(basePath + '/alignedNucleotideSequences');
+
+ expect(result.status).equals(200);
+ expect(result.headers.get('lapis-data-version')).to.match(/\d{10}/);
+ });
+});
diff --git a/siloLapisTests/test/aminoAcidSequence.spec.ts b/siloLapisTests/test/aminoAcidSequence.spec.ts
new file mode 100644
index 00000000..2f2d0739
--- /dev/null
+++ b/siloLapisTests/test/aminoAcidSequence.spec.ts
@@ -0,0 +1,119 @@
+import { expect } from 'chai';
+import { basePath, lapisClient, sequenceData } from './common';
+
+describe('The /aminoAcidSequence endpoint', () => {
+ it('should return amino acid sequences for Switzerland', async () => {
+ const result = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: { country: 'Switzerland' },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3247294');
+ expect(sequences[0]).to.have.length(1274);
+ });
+
+ it('should order ascending by specified fields', async () => {
+ const result = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: { orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }] },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_1001493');
+ expect(sequences[0]).to.have.length(1274);
+ });
+
+ it('should order descending by specified fields', async () => {
+ const result = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: { orderBy: [{ field: 'gisaid_epi_isl', type: 'descending' }] },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(100);
+ expect(sequences).to.have.length(100);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_931279');
+ expect(sequences[0]).to.have.length(1274);
+ });
+
+ it('should apply limit and offset', async () => {
+ const resultWithLimit = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: {
+ orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }],
+ limit: 2,
+ },
+ });
+
+ const { primaryKeys: primaryKeysWithLimit, sequences: sequencesWithLimit } =
+ sequenceData(resultWithLimit);
+
+ expect(primaryKeysWithLimit).to.have.length(2);
+ expect(sequencesWithLimit).to.have.length(2);
+ expect(primaryKeysWithLimit[0]).to.equal('>EPI_ISL_1001493');
+ expect(sequencesWithLimit[0]).to.have.length(1274);
+
+ const resultWithLimitAndOffset = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: {
+ orderBy: [{ field: 'gisaid_epi_isl', type: 'ascending' }],
+ limit: 2,
+ offset: 1,
+ },
+ });
+
+ const { primaryKeys: primaryKeysWithLimitAndOffset, sequences: sequencesWithLimitAndOffset } =
+ sequenceData(resultWithLimitAndOffset);
+
+ expect(primaryKeysWithLimitAndOffset).to.have.length(2);
+ expect(sequencesWithLimitAndOffset).to.have.length(2);
+ expect(primaryKeysWithLimitAndOffset[0]).to.equal(primaryKeysWithLimit[1]);
+ expect(sequencesWithLimitAndOffset[0]).to.equal(sequencesWithLimit[1]);
+ });
+
+ it('should correctly handle nucleotide insertion requests', async () => {
+ const result = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: {
+ nucleotideInsertions: ['ins_25701:CC?', 'ins_5959:?AT'],
+ },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(1);
+ expect(sequences).to.have.length(1);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3578231');
+ expect(sequences[0]).to.have.length(1274);
+ });
+
+ it('should correctly handle amino acid insertion requests', async () => {
+ const result = await lapisClient.postAminoAcidSequence({
+ gene: 'S',
+ sequenceRequest: {
+ aminoAcidInsertions: ['ins_S:143:T', 'ins_ORF1a:3602:F?P'],
+ },
+ });
+
+ const { primaryKeys, sequences } = sequenceData(result);
+
+ expect(primaryKeys).to.have.length(1);
+ expect(sequences).to.have.length(1);
+ expect(primaryKeys[0]).to.equal('>EPI_ISL_3259931');
+ });
+
+ it('should return the lapis data version in the response', async () => {
+ const result = await fetch(basePath + '/aminoAcidSequences/S');
+
+ expect(result.status).equals(200);
+ expect(result.headers.get('lapis-data-version')).to.match(/\d{10}/);
+ });
+});
diff --git a/siloLapisTests/test/common.ts b/siloLapisTests/test/common.ts
index dd492b25..9e33583c 100644
--- a/siloLapisTests/test/common.ts
+++ b/siloLapisTests/test/common.ts
@@ -1,8 +1,13 @@
-import { Configuration, LapisControllerApi } from './lapisClient';
+import {
+ Configuration,
+ LapisControllerApi,
+ Middleware,
+ SingleSegmentedSequenceControllerApi,
+} from './lapisClient';
export const basePath = 'http://localhost:8080';
-export const lapisClient = new LapisControllerApi(new Configuration({ basePath })).withMiddleware({
+const middleware: Middleware = {
onError: errorContext => {
if (errorContext.response) {
console.log('Response status code: ', errorContext.response.status);
@@ -22,4 +27,21 @@ export const lapisClient = new LapisControllerApi(new Configuration({ basePath }
}
return Promise.resolve(responseContext.response);
},
-});
+};
+
+export const lapisClient = new LapisControllerApi(new Configuration({ basePath })).withMiddleware(middleware);
+
+export const lapisSingleSegmentedSequenceController = new SingleSegmentedSequenceControllerApi(
+ new Configuration({ basePath })
+).withMiddleware(middleware);
+
+export function sequenceData(serverResponse: string) {
+ const lines = serverResponse.split('\n');
+ const primaryKeys = lines.filter(line => line.startsWith('>'));
+ const sequences = lines.filter(line => !line.startsWith('>'));
+
+ return {
+ primaryKeys,
+ sequences,
+ };
+}
diff --git a/siloLapisTests/testData/testDatabaseConfig.yaml b/siloLapisTests/testData/testDatabaseConfig.yaml
index c9729d21..dd23ce92 100644
--- a/siloLapisTests/testData/testDatabaseConfig.yaml
+++ b/siloLapisTests/testData/testDatabaseConfig.yaml
@@ -27,7 +27,6 @@ schema:
type: aaInsertion
features:
- name: sarsCoV2VariantQuery
- - name: isSingleSegmentedSequence
primaryKey: gisaid_epi_isl
dateToSortBy: date
partitionBy: pango_lineage