diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml index f467d3c62..de5d3b9fd 100644 --- a/search-service/config/detekt/baseline.xml +++ b/search-service/config/detekt/baseline.xml @@ -5,6 +5,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() + ComplexCondition:EntityQueryService.kt$EntityQueryService$it && !inverse || !it && inverse 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`() diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/entity/sources/EntitySourceService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionService.kt similarity index 94% rename from search-service/src/main/kotlin/com/egm/stellio/search/entity/sources/EntitySourceService.kt rename to search-service/src/main/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionService.kt index b061681a7..b425d28f4 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/entity/sources/EntitySourceService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionService.kt @@ -1,4 +1,4 @@ -package com.egm.stellio.search.entity.sources +package com.egm.stellio.search.entity.compaction import arrow.core.Either import arrow.core.left @@ -27,16 +27,13 @@ import org.springframework.stereotype.Service import org.springframework.util.MultiValueMap import java.net.URI -// todo find better package name and file name @Service -class EntitySourceService( +class EntityCompactionService( private val entityQueryService: EntityQueryService, private val contextSourceRegistrationService: ContextSourceRegistrationService, private val linkedEntityService: LinkedEntityService ) : BaseHandler() { - // todo could also return Pair, List> - // + more consistent with getEntity and let combined ApiException with warnings (no case for now) - // - can't use .bind() easilly + suspend fun getEntitiesFromSources( sub: Sub?, contexts: List, 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 933e8bc44..4375be60f 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 @@ -5,8 +5,8 @@ import arrow.core.left import arrow.core.raise.either import arrow.core.right import com.egm.stellio.search.csr.model.addWarnings +import com.egm.stellio.search.entity.compaction.EntityCompactionService import com.egm.stellio.search.entity.service.EntityService -import com.egm.stellio.search.entity.sources.EntitySourceService import com.egm.stellio.search.entity.util.composeEntitiesQueryFromGet import com.egm.stellio.search.entity.util.validateMinimalQueryEntitiesParameters import com.egm.stellio.shared.config.ApplicationProperties @@ -63,7 +63,7 @@ import java.net.URI class EntityHandler( private val applicationProperties: ApplicationProperties, private val entityService: EntityService, - private val entitySourceService: EntitySourceService + private val entityCompactionService: EntityCompactionService ) : BaseHandler() { /** @@ -199,7 +199,7 @@ class EntityHandler( ).bind() .validateMinimalQueryEntitiesParameters().bind() - val (entities, count, warnings) = entitySourceService.getEntitiesFromSources( + val (entities, count, warnings) = entityCompactionService.getEntitiesFromSources( sub = sub.getOrNull(), contexts = contexts, entitiesQuery = entitiesQuery, @@ -248,7 +248,7 @@ class EntityHandler( contexts ).bind() - val (entityOrException, warnings) = entitySourceService.getEntityFromSources( + val (entityOrException, warnings) = entityCompactionService.getEntityFromSources( sub = sub.getOrNull(), contexts = contexts, entitiesQuery = entitiesQuery, diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionServiceTests.kt new file mode 100644 index 000000000..2f62109ff --- /dev/null +++ b/search-service/src/test/kotlin/com/egm/stellio/search/entity/compaction/EntityCompactionServiceTests.kt @@ -0,0 +1,511 @@ +// package com.egm.stellio.search.entity.compaction +// +// import com.egm.stellio.search.common.config.SearchProperties +// import com.egm.stellio.search.csr.service.ContextSourceRegistrationService +// import com.egm.stellio.search.entity.service.EntityQueryService +// import com.egm.stellio.search.entity.service.EntityService +// import com.egm.stellio.search.entity.service.LinkedEntityService +// import com.egm.stellio.shared.config.ApplicationProperties +// import com.egm.stellio.shared.util.toUri +// import com.ninjasquad.springmockk.MockkBean +// import io.mockk.coEvery +// import org.junit.jupiter.api.BeforeEach +// import org.springframework.beans.factory.annotation.Autowired +// import org.springframework.boot.context.properties.EnableConfigurationProperties +// import org.springframework.test.context.ActiveProfiles +// +// // DO NOT REVIEW WIP +// // DO NOT REVIEW WIP +// // DO NOT REVIEW WIP +// // DO NOT REVIEW WIP +// // DO NOT REVIEW WIP +// +// @ActiveProfiles("test") +// @EnableConfigurationProperties(ApplicationProperties::class, SearchProperties::class) +// class EntityCompactionServiceTests { +// +// @Autowired +// private lateinit var applicationProperties: ApplicationProperties +// +// @MockkBean +// private lateinit var entitySourceService: EntityCompactionService +// +// @MockkBean +// private lateinit var entityService: EntityService +// +// @MockkBean +// private lateinit var entityQueryService: EntityQueryService +// +// @MockkBean +// private lateinit var contextSourceRegistrationService: ContextSourceRegistrationService +// +// @MockkBean(relaxed = true) +// private lateinit var linkedEntityService: LinkedEntityService +// +// @BeforeEach +// fun mockCSR() { +// coEvery { +// contextSourceRegistrationService +// .getContextSourceRegistrations(any(), any(), any()) +// } returns listOf() +// } +// +// private val beehiveId = "urn:ngsi-ld:BeeHive:TESTC".toUri() +// private val fishNumberAttribute = "https://ontology.eglobalmark.com/aquac#fishNumber" +// private val fishSizeAttribute = "https://ontology.eglobalmark.com/aquac#fishSize" +// +// // // todo in entitySourceServiceTEst ? +// // @Test +// // fun `get entity by id should correctly serialize multi-attribute relationship having more than one instance`(){ +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/managedBy" to +// // listOf( +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", +// // NGSILD_RELATIONSHIP_OBJECT to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Beekeeper:1229" +// // ) +// // ), +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", +// // NGSILD_RELATIONSHIP_OBJECT to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Beekeeper:1230" +// // ), +// // NGSILD_DATASET_ID_PROPERTY to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Dataset:managedBy:0215" +// // ) +// // ) +// // ), +// // JSONLD_ID to "urn:ngsi-ld:Beehive:4567", +// // JSONLD_TYPE to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "id":"urn:ngsi-ld:Beehive:4567", +// // "type": "Beehive", +// // "managedBy":[ +// // { +// // "type": "Relationship", +// // "object":"urn:ngsi-ld:Beekeeper:1229" +// // }, +// // { +// // "type": "Relationship", +// // "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", +// // "object":"urn:ngsi-ld:Beekeeper:1230" +// // } +// // ], +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// +// // // todo put on EntitySource Test +// // @Test +// // fun `get entity by id should correctly filter the asked attributes`() = runTest { +// // val compactedEntity = """ +// // { +// // "id": "$beehiveId", +// // "type": "Beehive", +// // "attr1": { +// // "type": "Property", +// // "value": "some value 1" +// // }, +// // "attr2": { +// // "type": "Property", +// // "value": "some value 2" +// // }, +// // "@context" : [ +// // "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" +// // ] +// // } +// // """.trimIndent().deserializeAsMap() +// // +// // mockEntitySourceSuccess(compactedEntity) +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId?attrs=attr2") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody() +// // .jsonPath("$.attr1").doesNotExist() +// // .jsonPath("$.attr2").isNotEmpty +// // } +// // +// // // todo move to CompactedServiceTest +// // @Test +// // fun `get entity by id should return 404 if the entity has none of the requested attributes`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "@id" to beehiveId.toString(), +// // "@type" to listOf(BEEHIVE_TYPE) +// // ) +// // ).right() +// // +// // val expectedMessage = entityOrAttrsNotFoundMessage( +// // beehiveId.toString(), +// // setOf("https://uri.etsi.org/ngsi-ld/default-context/attr2") +// // ) +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId?attrs=attr2") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isNotFound +// // .expectBody().json( +// // """ +// // { +// // "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", +// // "title": "$expectedMessage", +// // "detail": "$DEFAULT_DETAIL" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTest and for dateTime +// // @Test +// // fun `get entity by id should correctly serialize properties of type DateTime `() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // NGSILD_CREATED_AT_PROPERTY to +// // mapOf( +// // "@type" to NGSILD_DATE_TIME_TYPE, +// // "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) +// // ), +// // "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( +// // "@type" to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to mapOf( +// // "@type" to NGSILD_DATE_TIME_TYPE, +// // "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) +// // ), +// // NGSILD_CREATED_AT_PROPERTY to +// // mapOf( +// // "@type" to NGSILD_DATE_TIME_TYPE, +// // "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) +// // ), +// // NGSILD_MODIFIED_AT_PROPERTY to +// // mapOf( +// // "@type" to NGSILD_DATE_TIME_TYPE, +// // "@value" to Instant.parse("2015-10-18T12:20:30.000001Z").atZone(ZoneOffset.UTC) +// // ) +// // ), +// // "@id" to beehiveId.toString(), +// // "@type" to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId?options=sysAttrs") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "createdAt":"2015-10-18T11:20:30.000001Z", +// // "testedAt":{ +// // "type": "Property", +// // "value":{ +// // "type": "DateTime", +// // "@value":"2015-10-18T11:20:30.000001Z" +// // }, +// // "createdAt":"2015-10-18T11:20:30.000001Z", +// // "modifiedAt":"2015-10-18T12:20:30.000001Z" +// // }, +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTest +// // @Test +// // fun `get entity by id should correctly serialize properties of type Date`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( +// // "@type" to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to mapOf( +// // "@type" to NGSILD_DATE_TYPE, +// // "@value" to LocalDate.of(2015, 10, 18) +// // ) +// // ), +// // "@id" to beehiveId.toString(), +// // "@type" to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "testedAt":{ +// // "type": "Property", +// // "value":{ +// // "type": "Date", +// // "@value":"2015-10-18" +// // } +// // }, +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTEst +// // @Test +// // fun `get entity by id should correctly serialize properties of type Time`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( +// // "@type" to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to mapOf( +// // "@type" to NGSILD_TIME_TYPE, +// // "@value" to LocalTime.of(11, 20, 30) +// // ) +// // ), +// // "@id" to "urn:ngsi-ld:Beehive:4567", +// // "@type" to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "testedAt":{ +// // "type": "Property", +// // "value":{ +// // "type": "Time", +// // "@value":"11:20:30" +// // } +// // }, +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTEst ? +// // @Test +// // fun `get entity by id should correctly serialize multi-attribute property having one instance`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/name" to +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to "ruche", +// // NGSILD_DATASET_ID_PROPERTY to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Property:french-name" +// // ) +// // ), +// // JSONLD_ID to "urn:ngsi-ld:Beehive:4567", +// // JSONLD_TYPE to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "id":"urn:ngsi-ld:Beehive:4567", +// // "type": "Beehive", +// // "name":{"type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche"}, +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTEst ? +// // @Test +// // fun `get entity by id should correctly serialize multi-attribute property having more than one instance`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/name" to +// // listOf( +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to "beehive", +// // NGSILD_DATASET_ID_PROPERTY to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Property:english-name" +// // ) +// // ), +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", +// // NGSILD_PROPERTY_VALUE to "ruche", +// // NGSILD_DATASET_ID_PROPERTY to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Property:french-name" +// // ) +// // ) +// // ), +// // JSONLD_ID to "urn:ngsi-ld:Beehive:4567", +// // JSONLD_TYPE to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "id":"urn:ngsi-ld:Beehive:4567", +// // "type": "Beehive", +// // "name":[ +// // { +// // "type": "Property","datasetId":"urn:ngsi-ld:Property:english-name","value":"beehive" +// // }, +// // { +// // "type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche" +// // } +// // ], +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// // +// // // todo in entitySourceServiceTEst ? +// // @Test +// // fun `get entity by id should correctly serialize multi-attribute relationship having one instance`() { +// // coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( +// // mapOf( +// // "https://uri.etsi.org/ngsi-ld/default-context/managedBy" to +// // mapOf( +// // JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", +// // NGSILD_RELATIONSHIP_OBJECT to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Beekeeper:1230" +// // ), +// // NGSILD_DATASET_ID_PROPERTY to mapOf( +// // JSONLD_ID to "urn:ngsi-ld:Dataset:managedBy:0215" +// // ) +// // ), +// // JSONLD_ID to "urn:ngsi-ld:Beehive:4567", +// // JSONLD_TYPE to listOf("Beehive") +// // ) +// // ).right() +// // +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/$beehiveId") +// // .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) +// // .exchange() +// // .expectStatus().isOk +// // .expectBody().json( +// // """ +// // { +// // "id":"urn:ngsi-ld:Beehive:4567", +// // "type": "Beehive", +// // "managedBy": { +// // "type": "Relationship", +// // "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", +// // "object":"urn:ngsi-ld:Beekeeper:1230" +// // }, +// // "@context": "${applicationProperties.contexts.core}" +// // } +// // """.trimIndent() +// // ) +// // } +// +// // DO NOT REVIEW WIP +// // +// // @Test +// // fun `get entity by id should return the warnings sent by the CSRs and update the CSRs statuses`() { +// // val csr = gimmeRawCSR() +// // coEvery { +// // entityQueryService.queryEntity("urn:ngsi-ld:BeeHive:TEST".toUri(), sub.getOrNull()) +// // } returns ResourceNotFoundException("no entity").left() +// // +// // coEvery { +// // contextSourceRegistrationService +// // .getContextSourceRegistrations(any(), any(), any()) +// // } returns listOf(csr, csr) +// // +// // mockkObject(ContextSourceCaller) { +// // coEvery { +// // ContextSourceCaller.retrieveContextSourceEntity(any(), any(), any(), any()) +// // } returns MiscellaneousWarning( +// // "message with\nline\nbreaks", +// // csr +// // ).left() andThen +// // MiscellaneousWarning("message", csr).left() +// // +// // coEvery { contextSourceRegistrationService.updateContextSourceStatus(any(), any()) } returns Unit +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TEST") +// // .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) +// // .exchange() +// // .expectStatus().isNotFound +// // .expectHeader().valueEquals( +// // NGSILDWarning.HEADER_NAME, +// // "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", +// // "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" +// // ) +// // +// // coVerify(exactly = 2) { contextSourceRegistrationService.updateContextSourceStatus(any(), false) } +// // } +// // } +// // +// // +// // @Test +// // fun `get entities should return the warnings sent by the CSRs and update the CSRs statuses`() { +// // val csr = gimmeRawCSR() +// // +// // coEvery { +// // entityQueryService.queryEntities(any(), sub.getOrNull()) +// // } returns (emptyList() to 0).right() +// // +// // coEvery { +// // contextSourceRegistrationService +// // .getContextSourceRegistrations(any(), any(), any()) +// // } returns listOf(csr, csr) +// // +// // mockkObject(ContextSourceCaller) { +// // coEvery { +// // ContextSourceCaller.queryContextSourceEntities(any(), any(), any()) +// // } returns MiscellaneousWarning( +// // "message with\nline\nbreaks", +// // csr +// // ).left() andThen +// // MiscellaneousWarning("message", csr).left() +// // +// // coEvery { contextSourceRegistrationService.updateContextSourceStatus(any(), any()) } returns Unit +// // webClient.get() +// // .uri("/ngsi-ld/v1/entities?type=$BEEHIVE_COMPACT_TYPE&count=true") +// // .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) +// // .exchange() +// // .expectStatus().isOk +// // .expectHeader().valueEquals( +// // NGSILDWarning.HEADER_NAME, +// // "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", +// // "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" +// // ).expectHeader().valueEquals(RESULTS_COUNT_HEADER, "0",) +// // +// // coVerify(exactly = 2) { contextSourceRegistrationService.updateContextSourceStatus(any(), false) } +// // } +// // } +// } 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 3b89c8169..7167e00e9 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 @@ -6,69 +6,54 @@ import com.egm.stellio.search.common.config.SearchProperties import com.egm.stellio.search.csr.CsrUtils.gimmeRawCSR import com.egm.stellio.search.csr.model.MiscellaneousWarning import com.egm.stellio.search.csr.model.NGSILDWarning -import com.egm.stellio.search.csr.service.ContextSourceCaller import com.egm.stellio.search.csr.service.ContextSourceRegistrationService -import com.egm.stellio.search.entity.model.EntitiesQueryFromGet +import com.egm.stellio.search.entity.compaction.EntityCompactionService import com.egm.stellio.search.entity.model.NotUpdatedDetails import com.egm.stellio.search.entity.model.UpdateResult -import com.egm.stellio.search.entity.service.EntityQueryService +import com.egm.stellio.search.entity.model.UpdatedDetails import com.egm.stellio.search.entity.service.EntityService -import com.egm.stellio.search.entity.service.LinkedEntityService import com.egm.stellio.shared.config.ApplicationProperties import com.egm.stellio.shared.model.AccessDeniedException import com.egm.stellio.shared.model.AlreadyExistsException import com.egm.stellio.shared.model.BadRequestDataException import com.egm.stellio.shared.model.CompactedEntity import com.egm.stellio.shared.model.DEFAULT_DETAIL -import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.InternalErrorException import com.egm.stellio.shared.model.NgsiLdEntity import com.egm.stellio.shared.model.ResourceNotFoundException -import com.egm.stellio.shared.queryparameter.PaginationQuery import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXT -import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS import com.egm.stellio.shared.util.APIC_HEADER_LINK import com.egm.stellio.shared.util.AQUAC_HEADER_LINK import com.egm.stellio.shared.util.BEEHIVE_COMPACT_TYPE -import com.egm.stellio.shared.util.BEEHIVE_TYPE import com.egm.stellio.shared.util.INCOMING_COMPACT_PROPERTY import com.egm.stellio.shared.util.INCOMING_PROPERTY import com.egm.stellio.shared.util.JSON_LD_MEDIA_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID_TERM +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_OBJECT import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_CREATED_AT_PROPERTY -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_PROPERTY -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATE_TIME_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATE_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECT -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_TIME_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_CREATED_AT_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TERM +import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap import com.egm.stellio.shared.util.MOCK_USER_SUB import com.egm.stellio.shared.util.RESULTS_COUNT_HEADER -import com.egm.stellio.shared.util.Sub import com.egm.stellio.shared.util.TEMPERATURE_COMPACT_PROPERTY import com.egm.stellio.shared.util.TEMPERATURE_PROPERTY import com.egm.stellio.shared.util.buildContextLinkHeader import com.egm.stellio.shared.util.entityNotFoundMessage -import com.egm.stellio.shared.util.entityOrAttrsNotFoundMessage -import com.egm.stellio.shared.util.expandJsonLdEntity import com.egm.stellio.shared.util.loadSampleData +import com.egm.stellio.shared.util.ngsiLdDateTime import com.egm.stellio.shared.util.sub import com.egm.stellio.shared.util.toUri import com.ninjasquad.springmockk.MockkBean import io.mockk.called import io.mockk.coEvery import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockkClass -import io.mockk.mockkObject -import io.mockk.slot import io.mockk.verify -import kotlinx.coroutines.test.runTest import org.hamcrest.core.Is import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach @@ -87,8 +72,6 @@ import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.reactive.server.WebTestClient import java.lang.reflect.UndeclaredThrowableException import java.time.Instant -import java.time.LocalDate -import java.time.LocalTime import java.time.ZoneOffset import java.time.ZonedDateTime @@ -104,17 +87,14 @@ class EntityHandlerTests { private lateinit var applicationProperties: ApplicationProperties @MockkBean - private lateinit var entityService: EntityService + private lateinit var entitySourceService: EntityCompactionService @MockkBean - private lateinit var entityQueryService: EntityQueryService + private lateinit var entityService: EntityService @MockkBean private lateinit var contextSourceRegistrationService: ContextSourceRegistrationService - @MockkBean(relaxed = true) - private lateinit var linkedEntityService: LinkedEntityService - @BeforeAll fun configureWebClientDefaults() { webClient = webClient.mutate() @@ -349,22 +329,29 @@ class EntityHandlerTests { ) } - fun initializeRetrieveEntityMocks() { - val compactedEntity = slot() - - coEvery { - linkedEntityService.processLinkedEntities(capture(compactedEntity), any(), any()) - } answers { - listOf(compactedEntity.captured).right() - } + private fun mockEntitySourceSuccess(entity: CompactedEntity) { + coEvery { entitySourceService.getEntityFromSources(MOCK_USER_SUB, any(), any(), any(), any(), any()) } returns + (listOf(entity).right() to emptyList()) } @Test fun `get entity by id should return 200 when entity exists`() { - initializeRetrieveEntityMocks() - val returnedExpandedEntity = mockkClass(ExpandedEntity::class, relaxed = true) - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns returnedExpandedEntity.right() - every { returnedExpandedEntity.checkContainsAnyOf(any()) } returns Unit.right() + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "languageProperty": { + "type": "LanguageProperty", + "languageMap": { + "fr": "Grand Place", + "nl": "Grote Markt", + "@none": "Big Place" + } + } + } + """.trimIndent() + .deserializeAsMap() + mockEntitySourceSuccess(compactedEntity) webClient.get() .uri("/ngsi-ld/v1/entities/$beehiveId") @@ -375,18 +362,13 @@ class EntityHandlerTests { @Test fun `get entity by id should correctly serialize temporal properties`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( + mockEntitySourceSuccess( mapOf( - NGSILD_CREATED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") + NGSILD_CREATED_AT_TERM to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC), + "id" to beehiveId.toString(), + "type" to listOf("Beehive") ) - ).right() + ) webClient.get() .uri("/ngsi-ld/v1/entities/$beehiveId?options=sysAttrs") @@ -397,67 +379,28 @@ class EntityHandlerTests { """ { "createdAt": "2015-10-18T11:20:30.000001Z", - "@context": "${applicationProperties.contexts.core}" } """.trimIndent() ) } - @Test - fun `get entity by id should correctly filter the asked attributes`() = runTest { - initializeRetrieveEntityMocks() - val entity = """ - { - "id": "$beehiveId", - "type": "Beehive", - "attr1": { - "type": "Property", - "value": "some value 1" - }, - "attr2": { - "type": "Property", - "value": "some value 2" - }, - "@context" : [ - "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" - ] - } - """.trimIndent() - val expandedEntity = expandJsonLdEntity(entity) - - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns expandedEntity.right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId?attrs=attr2") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody() - .jsonPath("$.attr1").doesNotExist() - .jsonPath("$.attr2").isNotEmpty - } - @Test fun `get entity by id should correctly return the simplified representation of an entity`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( + mockEntitySourceSuccess( mapOf( - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive"), - "https://uri.etsi.org/ngsi-ld/default-context/prop1" to mapOf( - JSONLD_TYPE to NGSILD_PROPERTY_TYPE.uri, - NGSILD_PROPERTY_VALUE to mapOf( - JSONLD_VALUE to "some value" - ) + "id" to beehiveId.toString(), + "type" to "Beehive", + "prop1" to mapOf( + JSONLD_TYPE_TERM to NGSILD_PROPERTY_TERM, + JSONLD_VALUE_TERM to "some value" ), - "https://uri.etsi.org/ngsi-ld/default-context/rel1" to mapOf( - JSONLD_TYPE to NGSILD_RELATIONSHIP_TYPE.uri, - NGSILD_RELATIONSHIP_OBJECT to mapOf( - JSONLD_ID to "urn:ngsi-ld:Entity:1234" - ) - ) + "rel1" to mapOf( + JSONLD_TYPE_TERM to NGSILD_RELATIONSHIP_TERM, + JSONLD_OBJECT to "urn:ngsi-ld:Entity:1234" + ), + "@context" to applicationProperties.contexts.core ) - ).right() + ) webClient.get() .uri("/ngsi-ld/v1/entities/$beehiveId?options=keyValues") @@ -478,342 +421,47 @@ class EntityHandlerTests { ) } - @Test - fun `get entity by id should return 404 if the entity has none of the requested attributes`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "@id" to beehiveId.toString(), - "@type" to listOf(BEEHIVE_TYPE) - ) - ).right() - - val expectedMessage = entityOrAttrsNotFoundMessage( - beehiveId.toString(), - setOf("https://uri.etsi.org/ngsi-ld/default-context/attr2") - ) - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId?attrs=attr2") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isNotFound - .expectBody().json( - """ - { - "type": "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", - "title": "$expectedMessage", - "detail": "$DEFAULT_DETAIL" - } - """.trimIndent() - ) - } - @Test fun `get entity by id should not include temporal properties if optional query param sysAttrs is not present`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( + mockEntitySourceSuccess( mapOf( - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") + "id" to beehiveId.toString(), + "type" to BEEHIVE_COMPACT_TYPE, + "createdAt" to ngsiLdDateTime(), + "modifiedAt" to ngsiLdDateTime() ) - ).right() + ) webClient.get() .uri("/ngsi-ld/v1/entities/$beehiveId") .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isOk - .expectBody().json("""{"@context":"${applicationProperties.contexts.core}"}""") + .expectBody().json("""{"type":"$BEEHIVE_COMPACT_TYPE"}""") .jsonPath("$.createdAt").doesNotExist() .jsonPath("$.modifiedAt").doesNotExist() } - @Test - fun `get entity by id should correctly serialize properties of type DateTime and display sysAttrs asked`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - NGSILD_CREATED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( - "@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - NGSILD_CREATED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - NGSILD_MODIFIED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T12:20:30.000001Z").atZone(ZoneOffset.UTC) - ) - ), - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId?options=sysAttrs") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "createdAt":"2015-10-18T11:20:30.000001Z", - "testedAt":{ - "type": "Property", - "value":{ - "type": "DateTime", - "@value":"2015-10-18T11:20:30.000001Z" - }, - "createdAt":"2015-10-18T11:20:30.000001Z", - "modifiedAt":"2015-10-18T12:20:30.000001Z" - }, - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - - @Test - fun `get entity by id should correctly serialize properties of type Date`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( - "@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_DATE_TYPE, - "@value" to LocalDate.of(2015, 10, 18) - ) - ), - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "testedAt":{ - "type": "Property", - "value":{ - "type": "Date", - "@value":"2015-10-18" - } - }, - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - - @Test - fun `get entity by id should correctly serialize properties of type Time`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( - "@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_TIME_TYPE, - "@value" to LocalTime.of(11, 20, 30) - ) - ), - "@id" to "urn:ngsi-ld:Beehive:4567", - "@type" to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "testedAt":{ - "type": "Property", - "value":{ - "type": "Time", - "@value":"11:20:30" - } - }, - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - - @Test - fun `get entity by id should correctly serialize multi-attribute property having one instance`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/name" to - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to "ruche", - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Property:french-name" - ) - ), - JSONLD_ID to "urn:ngsi-ld:Beehive:4567", - JSONLD_TYPE to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "id":"urn:ngsi-ld:Beehive:4567", - "type": "Beehive", - "name":{"type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche"}, - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - - @Test - fun `get entity by id should correctly serialize multi-attribute property having more than one instance`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/name" to - listOf( - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to "beehive", - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Property:english-name" - ) - ), - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to "ruche", - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Property:french-name" - ) - ) - ), - JSONLD_ID to "urn:ngsi-ld:Beehive:4567", - JSONLD_TYPE to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "id":"urn:ngsi-ld:Beehive:4567", - "type": "Beehive", - "name":[ - { - "type": "Property","datasetId":"urn:ngsi-ld:Property:english-name","value":"beehive" - }, - { - "type": "Property","datasetId":"urn:ngsi-ld:Property:french-name","value":"ruche" - } - ], - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - - @Test - fun `get entity by id should correctly serialize multi-attribute relationship having one instance`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/managedBy" to - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", - NGSILD_RELATIONSHIP_OBJECT to mapOf( - JSONLD_ID to "urn:ngsi-ld:Beekeeper:1230" - ), - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Dataset:managedBy:0215" - ) - ), - JSONLD_ID to "urn:ngsi-ld:Beehive:4567", - JSONLD_TYPE to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( - """ - { - "id":"urn:ngsi-ld:Beehive:4567", - "type": "Beehive", - "managedBy": { - "type": "Relationship", - "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", - "object":"urn:ngsi-ld:Beekeeper:1230" - }, - "@context": "${applicationProperties.contexts.core}" - } - """.trimIndent() - ) - } - @Test fun `get entity by id should include createdAt & modifiedAt if query param sysAttrs is present`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( + mockEntitySourceSuccess( mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/managedBy" to + "managedBy" to mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", - NGSILD_RELATIONSHIP_OBJECT to mapOf( - JSONLD_ID to "urn:ngsi-ld:Beekeeper:1230" + JSONLD_TYPE_TERM to "Relationship", + JSONLD_OBJECT to "urn:ngsi-ld:Beekeeper:1230", + NGSILD_DATASET_ID_TERM to mapOf( + JSONLD_ID_TERM to "urn:ngsi-ld:Dataset:managedBy:0215" ), - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Dataset:managedBy:0215" - ), - NGSILD_CREATED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - NGSILD_MODIFIED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T12:20:30.000001Z").atZone(ZoneOffset.UTC) - ) + NGSILD_CREATED_AT_TERM to + Instant.parse("2015-10-18T13:20:30.000001Z").atZone(ZoneOffset.UTC), + NGSILD_MODIFIED_AT_TERM to + Instant.parse("2015-10-18T14:20:30.000001Z").atZone(ZoneOffset.UTC) ), - JSONLD_ID to "urn:ngsi-ld:Beehive:4567", - JSONLD_TYPE to listOf("Beehive") + JSONLD_ID_TERM to "urn:ngsi-ld:Beehive:4567", + JSONLD_TYPE_TERM to "Beehive", + NGSILD_CREATED_AT_TERM to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC), + NGSILD_MODIFIED_AT_TERM to Instant.parse("2015-10-18T12:20:30.000001Z").atZone(ZoneOffset.UTC) ) ).right() @@ -823,71 +471,26 @@ class EntityHandlerTests { .exchange() .expectStatus().isOk .expectBody() - .jsonPath("$..createdAt").isEqualTo("2015-10-18T11:20:30.000001Z") - .jsonPath("$..modifiedAt").isEqualTo("2015-10-18T12:20:30.000001Z") - } - - @Test - fun `get entity by id should correctly serialize multi-attribute relationship having more than one instance`() { - initializeRetrieveEntityMocks() - coEvery { entityQueryService.queryEntity(any(), MOCK_USER_SUB) } returns ExpandedEntity( - mapOf( - "https://uri.etsi.org/ngsi-ld/default-context/managedBy" to - listOf( - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", - NGSILD_RELATIONSHIP_OBJECT to mapOf( - JSONLD_ID to "urn:ngsi-ld:Beekeeper:1229" - ) - ), - mapOf( - JSONLD_TYPE to "https://uri.etsi.org/ngsi-ld/Relationship", - NGSILD_RELATIONSHIP_OBJECT to mapOf( - JSONLD_ID to "urn:ngsi-ld:Beekeeper:1230" - ), - NGSILD_DATASET_ID_PROPERTY to mapOf( - JSONLD_ID to "urn:ngsi-ld:Dataset:managedBy:0215" - ) - ) - ), - JSONLD_ID to "urn:ngsi-ld:Beehive:4567", - JSONLD_TYPE to listOf("Beehive") - ) - ).right() - - webClient.get() - .uri("/ngsi-ld/v1/entities/$beehiveId") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .exchange() - .expectStatus().isOk - .expectBody().json( + .json( """ - { - "id":"urn:ngsi-ld:Beehive:4567", - "type": "Beehive", - "managedBy":[ - { - "type": "Relationship", - "object":"urn:ngsi-ld:Beekeeper:1229" - }, - { - "type": "Relationship", - "datasetId":"urn:ngsi-ld:Dataset:managedBy:0215", - "object":"urn:ngsi-ld:Beekeeper:1230" - } - ], - "@context": "${applicationProperties.contexts.core}" + { + createdAt: "2015-10-18T11:20:30.000001Z", + modifiedAt: "2015-10-18T12:20:30.000001Z", + managedBy: { + createdAt: "2015-10-18T13:20:30.000001Z", + modifiedAt: "2015-10-18T14:20:30.000001Z" + } } """.trimIndent() ) } @Test - fun `get entity by id should return 404 when entity does not exist`() { - initializeRetrieveEntityMocks() + fun `get entity by id should return the received error`() { coEvery { - entityQueryService.queryEntity(any(), MOCK_USER_SUB) - } returns ResourceNotFoundException(entityNotFoundMessage("urn:ngsi-ld:BeeHive:TEST")).left() + entitySourceService.getEntityFromSources(MOCK_USER_SUB, any(), any(), any(), any(), any()) + } returns + (ResourceNotFoundException(entityNotFoundMessage("urn:ngsi-ld:BeeHive:TEST")).left() to emptyList()) webClient.get() .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TEST") @@ -906,85 +509,79 @@ class EntityHandlerTests { } @Test - fun `get entity by id should return 403 if user is not authorized to read an entity`() { - initializeRetrieveEntityMocks() + fun `get entity by id should return the warnings for success and errors`() { + val csr = gimmeRawCSR() coEvery { - entityQueryService.queryEntity("urn:ngsi-ld:BeeHive:TEST".toUri(), sub.getOrNull()) - } returns AccessDeniedException("User forbidden read access to entity urn:ngsi-ld:BeeHive:TEST").left() + entitySourceService.getEntityFromSources( + MOCK_USER_SUB, + any(), + any(), + any(), + any(), + any() + ) + } returns + ( + ResourceNotFoundException("no entity").left() to listOf( + MiscellaneousWarning( + "message with\nline\nbreaks", + csr + ), + MiscellaneousWarning("message", csr) + ) + ) andThen ( + emptyList().right() to listOf( + MiscellaneousWarning( + "message with\nline\nbreaks", + csr + ), + MiscellaneousWarning("message", csr) + ) + ) webClient.get() .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TEST") .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) .exchange() - .expectStatus().isForbidden - .expectBody().json( - """ - { - "type": "https://uri.etsi.org/ngsi-ld/errors/AccessDenied", - "title": "User forbidden read access to entity urn:ngsi-ld:BeeHive:TEST", - "detail": "$DEFAULT_DETAIL" - } - """.trimIndent() + .expectStatus().isNotFound + .expectHeader().valueEquals( + NGSILDWarning.HEADER_NAME, + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" ) - } - @Test - fun `get entity by id should return the warnings sent by the CSRs and update the CSRs statuses`() { - val csr = gimmeRawCSR() - coEvery { - entityQueryService.queryEntity("urn:ngsi-ld:BeeHive:TEST".toUri(), sub.getOrNull()) - } returns ResourceNotFoundException("no entity").left() - - coEvery { - contextSourceRegistrationService - .getContextSourceRegistrations(any(), any(), any()) - } returns listOf(csr, csr) - - mockkObject(ContextSourceCaller) { - coEvery { - ContextSourceCaller.retrieveContextSourceEntity(any(), any(), any(), any()) - } returns MiscellaneousWarning( - "message with\nline\nbreaks", - csr - ).left() andThen - MiscellaneousWarning("message", csr).left() - - coEvery { contextSourceRegistrationService.updateContextSourceStatus(any(), any()) } returns Unit - webClient.get() - .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TEST") - .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) - .exchange() - .expectStatus().isNotFound - .expectHeader().valueEquals( - NGSILDWarning.HEADER_NAME, - "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", - "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" - ) - - coVerify(exactly = 2) { contextSourceRegistrationService.updateContextSourceStatus(any(), false) } - } + webClient.get() + .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TEST") + .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals( + NGSILDWarning.HEADER_NAME, + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" + ) } - fun initializeQueryEntitiesMocks() { - val compactedEntities = slot>() - + fun mockGetEntitiesFromSourceSuccess( + entities: List, + count: Int = 0, + warnings: List = emptyList() + ) { coEvery { - linkedEntityService.processLinkedEntities(capture(compactedEntities), any(), any()) - } answers { - compactedEntities.captured.right() - } + entitySourceService.getEntitiesFromSources(any(), any(), any(), any(), any()) + } returns Triple(entities, count, warnings).right() } @Test fun `get entities by type should not include temporal properties if query param sysAttrs is not present`() { - initializeQueryEntitiesMocks() - coEvery { entityQueryService.queryEntities(any(), any()) } returns Pair( + mockGetEntitiesFromSourceSuccess( listOf( - ExpandedEntity( - mapOf( - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") - ) + mapOf( + "id" to beehiveId.toString(), + "type" to "Beehive", + NGSILD_CREATED_AT_TERM to ngsiLdDateTime(), + NGSILD_MODIFIED_AT_TERM to ngsiLdDateTime(), + "@context" to applicationProperties.contexts.core ) ), 1 @@ -1011,29 +608,15 @@ class EntityHandlerTests { } @Test - fun `get entities by type should include temporal properties if optional query param sysAttrs is present`() { - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities( - EntitiesQueryFromGet( - typeSelection = "https://uri.etsi.org/ngsi-ld/default-context/Beehive", - paginationQuery = PaginationQuery(offset = 0, limit = 30), - contexts = listOf(applicationProperties.contexts.core) - ), - any() - ) - } returns Pair( + fun `get entities by type should include temporal properties if query param sysAttrs is present`() { + mockGetEntitiesFromSourceSuccess( listOf( - ExpandedEntity( - mapOf( - NGSILD_CREATED_AT_PROPERTY to - mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) - ), - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") - ) + mapOf( + "id" to beehiveId.toString(), + "type" to "Beehive", + NGSILD_CREATED_AT_TERM to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC), + NGSILD_MODIFIED_AT_TERM to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC), + "@context" to applicationProperties.contexts.core ) ), 1 @@ -1051,6 +634,7 @@ class EntityHandlerTests { "id": "$beehiveId", "type": "Beehive", "createdAt":"2015-10-18T11:20:30.000001Z", + "modifiedAt":"2015-10-18T11:20:30.000001Z", "@context": "${applicationProperties.contexts.core}" } ] @@ -1060,15 +644,16 @@ class EntityHandlerTests { @Test fun `get entities should return 200 with prev and next link header if exists`() { - initializeQueryEntitiesMocks() - coEvery { entityQueryService.queryEntities(any(), any()) } returns Pair( + mockGetEntitiesFromSourceSuccess( listOf( - ExpandedEntity( - mapOf("@id" to "urn:ngsi-ld:Beehive:TESTC", "@type" to listOf("Beehive")) + mapOf( + "id" to "urn:ngsi-ld:Beehive:TESTC", + "type" to "Beehive", + "@context" to applicationProperties.contexts.core ) ), 3 - ).right() + ) webClient.get() .uri( @@ -1101,10 +686,7 @@ class EntityHandlerTests { @Test fun `get entities should return 200 and empty response if requested offset does not exists`() { - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities(any(), any()) - } returns Pair(emptyList(), 0).right() + mockGetEntitiesFromSourceSuccess(emptyList()) webClient.get() .uri("/ngsi-ld/v1/entities/?type=Beehive&limit=1&offset=9") @@ -1149,24 +731,12 @@ class EntityHandlerTests { @Test fun `get entities with id and type should return 200`() { - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities( - EntitiesQueryFromGet( - ids = setOf(beehiveId), - typeSelection = BEEHIVE_TYPE, - paginationQuery = PaginationQuery(offset = 0, limit = 30), - contexts = APIC_COMPOUND_CONTEXTS - ), - any() - ) - } returns Pair( + mockGetEntitiesFromSourceSuccess( listOf( - ExpandedEntity( - mapOf( - "@id" to beehiveId.toString(), - "@type" to listOf("Beehive") - ) + mapOf( + "id" to beehiveId.toString(), + "type" to "Beehive", + "@context" to APIC_COMPOUND_CONTEXT ) ), 1 @@ -1193,11 +763,7 @@ class EntityHandlerTests { @Test fun `get entities should return 200 and the number of results`() { - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities(any(), any()) - } returns Pair(emptyList(), 3).right() - + mockGetEntitiesFromSourceSuccess(emptyList(), 3) webClient.get() .uri("/ngsi-ld/v1/entities/?type=Beehive&limit=0&offset=1&count=true") .exchange() @@ -1225,10 +791,7 @@ class EntityHandlerTests { @Test fun `get entities should allow a query not including a type request parameter`() { - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities(any(), any()) - } returns Pair(emptyList(), 0).right() + mockGetEntitiesFromSourceSuccess(emptyList()) webClient.get() .uri("/ngsi-ld/v1/entities/?attrs=myProp") @@ -1256,42 +819,31 @@ class EntityHandlerTests { } @Test - fun `get entities should return the warnings sent by the CSRs and update the CSRs statuses`() { + fun `get entities should return the received warnings`() { val csr = gimmeRawCSR() - initializeQueryEntitiesMocks() - coEvery { - entityQueryService.queryEntities(any(), sub.getOrNull()) - } returns (emptyList() to 0).right() + mockGetEntitiesFromSourceSuccess( + emptyList(), + warnings = listOf( + MiscellaneousWarning( + "message with\nline\nbreaks", + csr + ), + MiscellaneousWarning("message", csr) + ) + ) - coEvery { - contextSourceRegistrationService - .getContextSourceRegistrations(any(), any(), any()) - } returns listOf(csr, csr) - - mockkObject(ContextSourceCaller) { - coEvery { - ContextSourceCaller.queryContextSourceEntities(any(), any(), any()) - } returns MiscellaneousWarning( - "message with\nline\nbreaks", - csr - ).left() andThen - MiscellaneousWarning("message", csr).left() - - coEvery { contextSourceRegistrationService.updateContextSourceStatus(any(), any()) } returns Unit - webClient.get() - .uri("/ngsi-ld/v1/entities?type=$BEEHIVE_COMPACT_TYPE&count=true") - .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) - .exchange() - .expectStatus().isOk - .expectHeader().valueEquals( - NGSILDWarning.HEADER_NAME, - "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", - "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" - ).expectHeader().valueEquals(RESULTS_COUNT_HEADER, "0") - - coVerify(exactly = 2) { contextSourceRegistrationService.updateContextSourceStatus(any(), false) } - } + coEvery { contextSourceRegistrationService.updateContextSourceStatus(any(), any()) } returns Unit + webClient.get() + .uri("/ngsi-ld/v1/entities?type=$BEEHIVE_COMPACT_TYPE&count=true") + .header(HttpHeaders.LINK, AQUAC_HEADER_LINK) + .exchange() + .expectStatus().isOk + .expectHeader().valueEquals( + NGSILDWarning.HEADER_NAME, + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message with line breaks\"", + "199 urn:ngsi-ld:ContextSourceRegistration:test \"message\"" + ).expectHeader().valueEquals(RESULTS_COUNT_HEADER, "0") } @Test @@ -1503,7 +1055,8 @@ class EntityHandlerTests { @Test fun `append entity attribute should return a 207 if types or attributes could not be appended`() { - val jsonLdFile = ClassPathResource("/ngsild/aquac/fragments/BreedingService_newInvalidTypeAndAttribute.json") + val jsonLdFile = + ClassPathResource("/ngsild/aquac/fragments/BreedingService_newInvalidTypeAndAttribute.json") val entityId = "urn:ngsi-ld:BreedingService:0214".toUri() coEvery { diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt index 58681c694..83d4468d8 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt @@ -98,26 +98,6 @@ object JsonLdUtils { val JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS = setOf(JSONLD_TYPE, NGSILD_SCOPE_PROPERTY) - // List of members that are part of a core entity base definition (i.e., without attributes) - val JSONLD_EXPANDED_ENTITY_CORE_MEMBERS = - setOf( - JSONLD_ID, - JSONLD_TYPE, - JSONLD_CONTEXT, - NGSILD_SCOPE_PROPERTY, - NGSILD_CREATED_AT_PROPERTY, - NGSILD_MODIFIED_AT_PROPERTY - ) - val JSONLD_COMPACTED_ENTITY_CORE_MEMBERS = - setOf( - JSONLD_ID_TERM, - JSONLD_TYPE_TERM, - JSONLD_CONTEXT, - NGSILD_SCOPE_TERM, - NGSILD_CREATED_AT_TERM, - NGSILD_MODIFIED_AT_TERM - ) - const val NGSILD_CREATED_AT_TERM = "createdAt" const val NGSILD_MODIFIED_AT_TERM = "modifiedAt" val NGSILD_SYSATTRS_TERMS = setOf(NGSILD_CREATED_AT_TERM, NGSILD_MODIFIED_AT_TERM, NGSILD_DELETED_AT_TERM) @@ -148,9 +128,31 @@ object JsonLdUtils { const val NGSILD_CSR_TERM = "ContextSourceRegistration" const val NGSILD_DATE_TIME_TYPE = "https://uri.etsi.org/ngsi-ld/DateTime" + const val NGSILD_DATE_TIME_TERM = "DateTime" + const val NGSILD_DATE_TYPE = "https://uri.etsi.org/ngsi-ld/Date" const val NGSILD_TIME_TYPE = "https://uri.etsi.org/ngsi-ld/Time" + // List of members that are part of a core entity base definition (i.e., without attributes) + val JSONLD_EXPANDED_ENTITY_CORE_MEMBERS = + setOf( + JSONLD_ID, + JSONLD_TYPE, + JSONLD_CONTEXT, + NGSILD_SCOPE_PROPERTY, + NGSILD_CREATED_AT_PROPERTY, + NGSILD_MODIFIED_AT_PROPERTY + ) + val JSONLD_COMPACTED_ENTITY_CORE_MEMBERS = + setOf( + JSONLD_ID_TERM, + JSONLD_TYPE_TERM, + JSONLD_CONTEXT, + NGSILD_SCOPE_TERM, + NGSILD_CREATED_AT_TERM, + NGSILD_MODIFIED_AT_TERM + ) + val logger: Logger = LoggerFactory.getLogger(javaClass) private const val CONTEXT_CACHE_CAPACITY = 128