From 14454c8dd81c252b94fdd0afc994017cd47b42c9 Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Mon, 10 Feb 2025 15:43:45 +0100 Subject: [PATCH 1/3] fix: add filter to query csr (+ query csr tests) --- search-service/config/detekt/baseline.xml | 12 ++++-- .../stellio/search/csr/model/CSRFilters.kt | 21 ++++++++++ .../ContextSourceRegistrationService.kt | 17 +++++--- .../web/ContextSourceRegistrationHandler.kt | 25 ++++++----- .../search/entity/model/EntitiesQuery.kt | 9 ++-- .../search/entity/util/EntitiesQueryUtils.kt | 8 ++-- .../search/entity/web/EntityHandler.kt | 2 +- .../ContextSourceRegistrationServiceTests.kt | 17 ++++++++ .../ContextSourceRegistrationHandlerTests.kt | 42 +++++++++++++++++++ .../search/entity/web/EntityHandlerTests.kt | 2 +- .../temporal/util/TemporalQueryUtilsTests.kt | 5 ++- .../web/TemporalEntityHandlerTests.kt | 2 +- 12 files changed, 133 insertions(+), 29 deletions(-) diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml index 9a599bb9b..354131c82 100644 --- a/search-service/config/detekt/baseline.xml +++ b/search-service/config/detekt/baseline.xml @@ -4,7 +4,9 @@ ClassNaming:V0_29_JsonLd_migrationTests.kt$V0_29_JsonLd_migrationTests ClassNaming:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration : BaseJavaMigration - ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && typeSelection.isNullOrEmpty() && attrs.isEmpty() + ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && + typeSelection.isNullOrEmpty() && attrs.isEmpty() && local != true + Filename:V0_29__JsonLd_migration.kt$db.migration.V0_29__JsonLd_migration.kt LongMethod:AttributeInstanceService.kt$AttributeInstanceService$@Transactional suspend fun create(attributeInstance: AttributeInstance): Either<APIException, Unit> LongMethod:EnabledAuthorizationServiceTests.kt$EnabledAuthorizationServiceTests$@Test fun `it should return serialized access control entities with other rigths if user is owner`() @@ -19,7 +21,11 @@ LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? ) LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null ) LongParameterList:BusinessObjectsFactory.kt$( attributeUuid: UUID, timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT, measuredValue: Double? = Random.nextDouble(), value: String? = null, time: ZonedDateTime = ngsiLdDateTime(), sub: Sub? = null ) - LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val contexts: List<String> ) + LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val + paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, + open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val local: Boolean?, open val + contexts: List<String> ) + LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, mergedAt: ZonedDateTime, observedAt: ZonedDateTime?, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, ngsiLdAttribute: NgsiLdAttribute, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) @@ -27,8 +33,6 @@ LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, disallowOverwrite: Boolean, createdAt: ZonedDateTime, sub: Sub? ) LongParameterList:TemporalEntityHandler.kt$TemporalEntityHandler$( @RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: URI, @PathVariable attrId: String, @PathVariable instanceId: URI, @RequestBody requestBody: Mono<String>, @AllowedParameters(notImplemented = [QP.LOCAL, QP.VIA]) @RequestParam queryParams: MultiValueMap<String, String> ) LongParameterList:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$( entityId: URI, attributeName: ExpandedTerm, datasetId: URI?, attributePayload: ExpandedAttributeInstance, ngsiLdAttributeInstance: NgsiLdAttributeInstance, defaultCreatedAt: ZonedDateTime ) - MaxLineLength:DistributedEntityProvisionServiceTests.kt$DistributedEntityProvisionServiceTests$fun - MaximumLineLength:DistributedEntityProvisionServiceTests.kt$DistributedEntityProvisionServiceTests$ NestedBlockDepth:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$override fun migrate(context: Context) SwallowedException:TemporalQueryUtils.kt$e: IllegalArgumentException diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt index 0993bdfba..2d4261ba8 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt @@ -1,6 +1,14 @@ package com.egm.stellio.search.csr.model +import arrow.core.Either +import arrow.core.raise.either +import com.egm.stellio.shared.model.APIException import com.egm.stellio.shared.model.EntityTypeSelection +import com.egm.stellio.shared.queryparameter.QueryParameter +import com.egm.stellio.shared.util.expandTypeSelection +import com.egm.stellio.shared.util.toListOfUri +import com.egm.stellio.shared.util.validateIdPattern +import org.springframework.util.MultiValueMap import java.net.URI open class CSRFilters( // we should use a combination of EntitiesQuery TemporalQuery (when we implement all operations) @@ -33,4 +41,17 @@ open class CSRFilters( // we should use a combination of EntitiesQuery TemporalQ idPattern = idPattern, operations = operations ) + + companion object { + fun fromQueryParameter( + queryParams: MultiValueMap, + contexts: List + ): Either = either { + val ids = queryParams.getFirst(QueryParameter.ID.key)?.split(",").orEmpty().toListOfUri().toSet() + val typeSelection = expandTypeSelection(queryParams.getFirst(QueryParameter.TYPE.key), contexts) + val idPattern = validateIdPattern(queryParams.getFirst(QueryParameter.ID_PATTERN.key)).bind() + + CSRFilters(ids = ids, typeSelection = typeSelection, idPattern = idPattern) + } + } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationService.kt index 4903e9d6f..eecf096a3 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationService.kt @@ -215,15 +215,22 @@ class ContextSourceRegistrationService( .allToMappedList { rowToContextSourceRegistration(it) } } - suspend fun getContextSourceRegistrationsCount(sub: Option): Either { + suspend fun getContextSourceRegistrationsCount( + filters: CSRFilters = CSRFilters(), + ): Either { + val filterQuery = buildWhereStatement(filters) + val selectStatement = """ - SELECT count(*) - FROM context_source_registration - WHERE sub = :sub + SELECT count(distinct csr.id) + FROM context_source_registration as csr + LEFT JOIN jsonb_to_recordset(information) + as information(entities jsonb, propertyNames text[], relationshipNames text[]) on true + LEFT JOIN jsonb_to_recordset(entities) + as entity_info(id text, idPattern text, type text[]) on true + WHERE $filterQuery """.trimIndent() return databaseClient.sql(selectStatement) - .bind("sub", sub.toStringValue()) .oneToResult { toInt(it["count"]) } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt index 4fdd6f56b..d9e6e6b4c 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt @@ -6,6 +6,7 @@ import arrow.core.flatMap import arrow.core.left import arrow.core.raise.either import arrow.core.right +import com.egm.stellio.search.csr.model.CSRFilters import com.egm.stellio.search.csr.model.ContextSourceRegistration.Companion.deserialize import com.egm.stellio.search.csr.model.ContextSourceRegistration.Companion.unauthorizedMessage import com.egm.stellio.search.csr.model.serialize @@ -59,7 +60,7 @@ class ContextSourceRegistrationHandler( * Implements 6.8.3.1 - Create ContextSourceRegistration */ @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun create( + suspend fun createContextSourceRegistration( @RequestHeader httpHeaders: HttpHeaders, @RequestBody requestBody: Mono ): ResponseEntity<*> = either { @@ -83,12 +84,15 @@ class ContextSourceRegistrationHandler( * Implements 6.8.3.2 - Query ContextSourceRegistrations */ @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun get( + suspend fun queryContextSourceRegistrations( @RequestHeader httpHeaders: HttpHeaders, @AllowedParameters( - implemented = [QP.OPTIONS, QP.COUNT, QP.OFFSET, QP.LIMIT], + implemented = [ + QP.OPTIONS, QP.COUNT, QP.OFFSET, QP.LIMIT, + QP.ID, QP.TYPE, QP.ID_PATTERN + ], notImplemented = [ - QP.ID, QP.TYPE, QP.ID_PATTERN, QP.ATTRS, QP.Q, QP.CSF, + QP.ATTRS, QP.Q, QP.CSF, QP.GEOMETRY, QP.GEOREL, QP.COORDINATES, QP.GEOPROPERTY, QP.TIMEPROPERTY, QP.TIMEREL, QP.TIMEAT, QP.ENDTIMEAT, QP.GEOMETRY_PROPERTY, QP.LANG, QP.SCOPEQ, @@ -98,7 +102,7 @@ class ContextSourceRegistrationHandler( ): ResponseEntity<*> = either { val contexts = getContextFromLinkHeaderOrDefault(httpHeaders, applicationProperties.contexts.core).bind() val mediaType = getApplicableMediaType(httpHeaders).bind() - val sub = getSubFromSecurityContext() + val csrFilters = CSRFilters.fromQueryParameter(queryParams, contexts).bind() val includeSysAttrs = queryParams.getOrDefault(QueryParameter.OPTIONS.key, emptyList()) .contains(OptionsValue.SYS_ATTRS.value) @@ -108,11 +112,12 @@ class ContextSourceRegistrationHandler( applicationProperties.pagination.limitMax ).bind() val contextSourceRegistrations = contextSourceRegistrationService.getContextSourceRegistrations( - limit = paginationQuery.limit, - offset = paginationQuery.offset, + csrFilters, + paginationQuery.limit, + paginationQuery.offset, ).serialize(contexts, mediaType, includeSysAttrs) val contextSourceRegistrationsCount = contextSourceRegistrationService.getContextSourceRegistrationsCount( - sub + csrFilters ).bind() buildQueryResponse( @@ -133,7 +138,7 @@ class ContextSourceRegistrationHandler( * Implements 6.9.3.1 - Retrieve ContextSourceRegistration */ @GetMapping("/{contextSourceRegistrationId}", produces = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun getByURI( + suspend fun retrieveContextSourceRegistration( @RequestHeader httpHeaders: HttpHeaders, @PathVariable contextSourceRegistrationId: URI, @AllowedParameters(implemented = [QP.OPTIONS]) @@ -159,7 +164,7 @@ class ContextSourceRegistrationHandler( * Implements 6.9.3.3 - Delete ContextSourceRegistration */ @DeleteMapping("/{contextSourceRegistrationId}") - suspend fun delete( + suspend fun deleteContextSourceRegistration( @PathVariable contextSourceRegistrationId: URI, @AllowedParameters // no query parameter is defined in the specification @RequestParam queryParams: MultiValueMap diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt index 61ecb6a54..e6593799e 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt @@ -16,6 +16,7 @@ sealed class EntitiesQuery( open val datasetId: Set, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, + open val local: Boolean?, open val contexts: List ) @@ -30,8 +31,9 @@ data class EntitiesQueryFromGet( override val datasetId: Set = emptySet(), override val geoQuery: GeoQuery? = null, override val linkedEntityQuery: LinkedEntityQuery? = null, - override val contexts: List -) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, contexts) + override val contexts: List, + override val local: Boolean? = null, +) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, local, contexts) data class EntitiesQueryFromPost( val entitySelectors: List? = null, @@ -42,5 +44,6 @@ data class EntitiesQueryFromPost( override val datasetId: Set = emptySet(), override val geoQuery: GeoQuery? = null, override val linkedEntityQuery: LinkedEntityQuery? = null, + override val local: Boolean? = null, override val contexts: List -) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, contexts) +) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, local, contexts) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt index 0fcba5de6..6e056f75e 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt @@ -52,7 +52,7 @@ fun composeEntitiesQueryFromGet( queryParams.getFirst(QueryParameter.JOIN_LEVEL.key), queryParams.getFirst(QueryParameter.CONTAINED_BY.key) ).bind() - + val local = queryParams.getFirst(QueryParameter.LOCAL.key)?.toBoolean() EntitiesQueryFromGet( ids = ids, typeSelection = typeSelection, @@ -64,6 +64,7 @@ fun composeEntitiesQueryFromGet( datasetId = datasetId, geoQuery = geoQuery, linkedEntityQuery = linkedEntityQuery, + local = local, contexts = contexts ) } @@ -73,10 +74,11 @@ fun EntitiesQueryFromGet.validateMinimalQueryEntitiesParameters(): Either() this@validateMinimalQueryEntitiesParameters diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt index 4c2b8426a..59d3d202e 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/web/EntityHandler.kt @@ -231,7 +231,7 @@ class EntityHandler( } val (warnings, entities, count) = - if (queryParams.getFirst(QP.LOCAL.key)?.toBoolean() != true) { + if (entitiesQuery.local != true) { val (queryWarnings, remoteEntitiesWithCSR, remoteCounts) = distributedEntityConsumptionService.distributeQueryEntitiesOperation( entitiesQuery, diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationServiceTests.kt index 9f99f0a4d..c048238c4 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/csr/service/ContextSourceRegistrationServiceTests.kt @@ -324,6 +324,23 @@ class ContextSourceRegistrationServiceTests : WithTimescaleContainer, WithKafkaC assertTrue(notMatchingCsr.isEmpty()) } + @Test + fun `count should apply the filter`() = runTest { + val contextSourceRegistration = + loadAndDeserializeContextSourceRegistration("csr/contextSourceRegistration_minimal_entities.json") + contextSourceRegistrationService.create(contextSourceRegistration, mockUserSub).shouldSucceed() + + val count = contextSourceRegistrationService.getContextSourceRegistrationsCount( + CSRFilters(idPattern = ".*") + ) + assertEquals(1, count.getOrNull()) + + val countEmpty = contextSourceRegistrationService.getContextSourceRegistrationsCount( + CSRFilters(idPattern = "INVALID") + ) + assertEquals(0, countEmpty.getOrNull()) + } + @Test fun `delete an existing CSR should succeed`() = runTest { val contextSourceRegistration = diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandlerTests.kt index 52a1f565f..0166332c8 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandlerTests.kt @@ -11,6 +11,7 @@ import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.AQUAC_HEADER_LINK import com.egm.stellio.shared.util.JSON_LD_MEDIA_TYPE import com.egm.stellio.shared.util.MOCK_USER_SUB +import com.egm.stellio.shared.util.RESULTS_COUNT_HEADER import com.egm.stellio.shared.util.toUri import com.ninjasquad.springmockk.MockkBean import io.mockk.coEvery @@ -108,6 +109,47 @@ class ContextSourceRegistrationHandlerTests { coVerify { contextSourceRegistrationService.getById(contextSourceRegistration.id) } } + @Test + fun `query CSR should return 200 when it exists`() = runTest { + val contextSourceRegistration = ContextSourceRegistration(id = id, endpoint = endpoint) + + coEvery { contextSourceRegistrationService.isCreatorOf(any(), any()) } returns true.right() + coEvery { + contextSourceRegistrationService.getContextSourceRegistrations(any(), any(), any()) + } returns listOf(contextSourceRegistration) + + coEvery { contextSourceRegistrationService.getContextSourceRegistrationsCount(any()) } returns 1.right() + + webClient.get() + .uri("$csrUri?id=$id") + .exchange() + .expectStatus().isOk + .expectBody() + + coVerify { contextSourceRegistrationService.getContextSourceRegistrations(any(), any()) } + } + + @Test + fun `query CSR should return the count if it was asked`() = runTest { + val contextSourceRegistration = ContextSourceRegistration(id = id, endpoint = endpoint) + + coEvery { contextSourceRegistrationService.isCreatorOf(any(), any()) } returns true.right() + coEvery { + contextSourceRegistrationService.getContextSourceRegistrations(any(), any(), any()) + } returns listOf(contextSourceRegistration) + + coEvery { contextSourceRegistrationService.getContextSourceRegistrationsCount(any()) } returns 1.right() + + webClient.get() + .uri("$csrUri?id=$id&count=true") + .exchange() + .expectStatus().isOk + .expectHeader().exists(RESULTS_COUNT_HEADER) + .expectBody() + + coVerify { contextSourceRegistrationService.getContextSourceRegistrations(any(), any()) } + } + @Test fun `delete CSR should return the errors from the service`() = runTest { coEvery { contextSourceRegistrationService.isCreatorOf(any(), any()) } returns true.right() diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt index 07b8ddb51..a66c8a83b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/web/EntityHandlerTests.kt @@ -1390,7 +1390,7 @@ class EntityHandlerTests { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query", + "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query unless local is true", "detail": "$DEFAULT_DETAIL" } """.trimIndent() diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/util/TemporalQueryUtilsTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/util/TemporalQueryUtilsTests.kt index 7008d1897..f4e9b511e 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/util/TemporalQueryUtilsTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/util/TemporalQueryUtilsTests.kt @@ -51,7 +51,10 @@ class TemporalQueryUtilsTests { true ).shouldFail { assertInstanceOf(BadRequestDataException::class.java, it) - assertEquals("One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query", it.message) + assertEquals( + "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query unless local is true", + it.message + ) } } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt index a4d811227..188c670b7 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/temporal/web/TemporalEntityHandlerTests.kt @@ -1475,7 +1475,7 @@ class TemporalEntityHandlerTests : TemporalEntityHandlerTestCommon() { """ { "type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData", - "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query", + "title": "One of 'type', 'attrs', 'q', 'geoQ' must be provided in the query unless local is true", "detail": "$DEFAULT_DETAIL" } """.trimIndent() From 9d7a2b45d17145a5d20d16f42d85e242e03f1f38 Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Wed, 12 Feb 2025 12:11:54 +0100 Subject: [PATCH 2/3] fix: detekt --- search-service/config/detekt/baseline.xml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml index 354131c82..6dea7ba08 100644 --- a/search-service/config/detekt/baseline.xml +++ b/search-service/config/detekt/baseline.xml @@ -4,9 +4,7 @@ ClassNaming:V0_29_JsonLd_migrationTests.kt$V0_29_JsonLd_migrationTests ClassNaming:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration : BaseJavaMigration - ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && - typeSelection.isNullOrEmpty() && attrs.isEmpty() && local != true - + ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && typeSelection.isNullOrEmpty() && attrs.isEmpty() && local != true Filename:V0_29__JsonLd_migration.kt$db.migration.V0_29__JsonLd_migration.kt LongMethod:AttributeInstanceService.kt$AttributeInstanceService$@Transactional suspend fun create(attributeInstance: AttributeInstance): Either<APIException, Unit> LongMethod:EnabledAuthorizationServiceTests.kt$EnabledAuthorizationServiceTests$@Test fun `it should return serialized access control entities with other rigths if user is owner`() @@ -21,11 +19,7 @@ LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? ) LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null ) LongParameterList:BusinessObjectsFactory.kt$( attributeUuid: UUID, timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT, measuredValue: Double? = Random.nextDouble(), value: String? = null, time: ZonedDateTime = ngsiLdDateTime(), sub: Sub? = null ) - LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val - paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, - open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val local: Boolean?, open val - contexts: List<String> ) - + LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val local: Boolean?, open val contexts: List<String> ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, mergedAt: ZonedDateTime, observedAt: ZonedDateTime?, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, ngsiLdAttribute: NgsiLdAttribute, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) From fcc7d4523d01702c9e71a829bb9d6583c561cae9 Mon Sep 17 00:00:00 2001 From: Thomas BOUSSELIN Date: Thu, 13 Feb 2025 14:34:53 +0100 Subject: [PATCH 3/3] fix: PR comments --- search-service/config/detekt/baseline.xml | 4 ++-- .../egm/stellio/search/csr/model/CSRFilters.kt | 16 ++++++++-------- .../csr/web/ContextSourceRegistrationHandler.kt | 10 +++++----- .../stellio/search/entity/model/EntitiesQuery.kt | 6 +++--- .../search/entity/util/EntitiesQueryUtils.kt | 5 +++-- .../web/ContextSourceRegistrationHandlerTests.kt | 3 +-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml index 6dea7ba08..ae30c7a05 100644 --- a/search-service/config/detekt/baseline.xml +++ b/search-service/config/detekt/baseline.xml @@ -4,7 +4,7 @@ ClassNaming:V0_29_JsonLd_migrationTests.kt$V0_29_JsonLd_migrationTests ClassNaming:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration : BaseJavaMigration - ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && typeSelection.isNullOrEmpty() && attrs.isEmpty() && local != true + ComplexCondition:EntitiesQueryUtils.kt$geoQuery == null && q.isNullOrEmpty() && typeSelection.isNullOrEmpty() && attrs.isEmpty() && !local Filename:V0_29__JsonLd_migration.kt$db.migration.V0_29__JsonLd_migration.kt LongMethod:AttributeInstanceService.kt$AttributeInstanceService$@Transactional suspend fun create(attributeInstance: AttributeInstance): Either<APIException, Unit> LongMethod:EnabledAuthorizationServiceTests.kt$EnabledAuthorizationServiceTests$@Test fun `it should return serialized access control entities with other rigths if user is owner`() @@ -19,7 +19,7 @@ LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? ) LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( attributeUuid: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null ) LongParameterList:BusinessObjectsFactory.kt$( attributeUuid: UUID, timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT, measuredValue: Double? = Random.nextDouble(), value: String? = null, time: ZonedDateTime = ngsiLdDateTime(), sub: Sub? = null ) - LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val local: Boolean?, open val contexts: List<String> ) + LongParameterList:EntitiesQuery.kt$EntitiesQuery$( open val q: String?, open val scopeQ: String?, open val paginationQuery: PaginationQuery, open val attrs: Set<ExpandedTerm>, open val datasetId: Set<String>, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, open val local: Boolean = false, open val contexts: List<String> ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, mergedAt: ZonedDateTime, observedAt: ZonedDateTime?, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( attribute: Attribute, ngsiLdAttribute: NgsiLdAttribute, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:EntityAttributeService.kt$EntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt index 2d4261ba8..09b9b9914 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/model/CSRFilters.kt @@ -24,9 +24,9 @@ open class CSRFilters( // we should use a combination of EntitiesQuery TemporalQ operations: List? ) : this( - ids = ids, - typeSelection = typeSelection, - idPattern = idPattern, + ids, + typeSelection, + idPattern, csf = operations?.joinToString("|") { "${ContextSourceRegistration::operations.name}==${it.key}" } ) @@ -36,14 +36,14 @@ open class CSRFilters( // we should use a combination of EntitiesQuery TemporalQ idPattern: String? = null, operations: List? = null ) : this( - ids = ids, - typeSelection = types.joinToString("|"), - idPattern = idPattern, + ids, + types.joinToString("|"), + idPattern, operations = operations ) companion object { - fun fromQueryParameter( + fun fromQueryParameters( queryParams: MultiValueMap, contexts: List ): Either = either { @@ -51,7 +51,7 @@ open class CSRFilters( // we should use a combination of EntitiesQuery TemporalQ val typeSelection = expandTypeSelection(queryParams.getFirst(QueryParameter.TYPE.key), contexts) val idPattern = validateIdPattern(queryParams.getFirst(QueryParameter.ID_PATTERN.key)).bind() - CSRFilters(ids = ids, typeSelection = typeSelection, idPattern = idPattern) + CSRFilters(ids, typeSelection, idPattern) } } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt index d9e6e6b4c..1728e1b2f 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/csr/web/ContextSourceRegistrationHandler.kt @@ -60,7 +60,7 @@ class ContextSourceRegistrationHandler( * Implements 6.8.3.1 - Create ContextSourceRegistration */ @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun createContextSourceRegistration( + suspend fun create( @RequestHeader httpHeaders: HttpHeaders, @RequestBody requestBody: Mono ): ResponseEntity<*> = either { @@ -84,7 +84,7 @@ class ContextSourceRegistrationHandler( * Implements 6.8.3.2 - Query ContextSourceRegistrations */ @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun queryContextSourceRegistrations( + suspend fun query( @RequestHeader httpHeaders: HttpHeaders, @AllowedParameters( implemented = [ @@ -102,7 +102,7 @@ class ContextSourceRegistrationHandler( ): ResponseEntity<*> = either { val contexts = getContextFromLinkHeaderOrDefault(httpHeaders, applicationProperties.contexts.core).bind() val mediaType = getApplicableMediaType(httpHeaders).bind() - val csrFilters = CSRFilters.fromQueryParameter(queryParams, contexts).bind() + val csrFilters = CSRFilters.fromQueryParameters(queryParams, contexts).bind() val includeSysAttrs = queryParams.getOrDefault(QueryParameter.OPTIONS.key, emptyList()) .contains(OptionsValue.SYS_ATTRS.value) @@ -138,7 +138,7 @@ class ContextSourceRegistrationHandler( * Implements 6.9.3.1 - Retrieve ContextSourceRegistration */ @GetMapping("/{contextSourceRegistrationId}", produces = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - suspend fun retrieveContextSourceRegistration( + suspend fun retrieve( @RequestHeader httpHeaders: HttpHeaders, @PathVariable contextSourceRegistrationId: URI, @AllowedParameters(implemented = [QP.OPTIONS]) @@ -164,7 +164,7 @@ class ContextSourceRegistrationHandler( * Implements 6.9.3.3 - Delete ContextSourceRegistration */ @DeleteMapping("/{contextSourceRegistrationId}") - suspend fun deleteContextSourceRegistration( + suspend fun delete( @PathVariable contextSourceRegistrationId: URI, @AllowedParameters // no query parameter is defined in the specification @RequestParam queryParams: MultiValueMap diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt index e6593799e..d3f32de79 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/model/EntitiesQuery.kt @@ -16,7 +16,7 @@ sealed class EntitiesQuery( open val datasetId: Set, open val geoQuery: GeoQuery?, open val linkedEntityQuery: LinkedEntityQuery?, - open val local: Boolean?, + open val local: Boolean = false, open val contexts: List ) @@ -32,7 +32,7 @@ data class EntitiesQueryFromGet( override val geoQuery: GeoQuery? = null, override val linkedEntityQuery: LinkedEntityQuery? = null, override val contexts: List, - override val local: Boolean? = null, + override val local: Boolean = false, ) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, local, contexts) data class EntitiesQueryFromPost( @@ -44,6 +44,6 @@ data class EntitiesQueryFromPost( override val datasetId: Set = emptySet(), override val geoQuery: GeoQuery? = null, override val linkedEntityQuery: LinkedEntityQuery? = null, - override val local: Boolean? = null, + override val local: Boolean = false, override val contexts: List ) : EntitiesQuery(q, scopeQ, paginationQuery, attrs, datasetId, geoQuery, linkedEntityQuery, local, contexts) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt index 6e056f75e..1ddc11853 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/util/EntitiesQueryUtils.kt @@ -52,7 +52,8 @@ fun composeEntitiesQueryFromGet( queryParams.getFirst(QueryParameter.JOIN_LEVEL.key), queryParams.getFirst(QueryParameter.CONTAINED_BY.key) ).bind() - val local = queryParams.getFirst(QueryParameter.LOCAL.key)?.toBoolean() + val local = queryParams.getFirst(QueryParameter.LOCAL.key)?.toBoolean() ?: false + EntitiesQueryFromGet( ids = ids, typeSelection = typeSelection, @@ -75,7 +76,7 @@ fun EntitiesQueryFromGet.validateMinimalQueryEntitiesParameters(): Either