diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 212ac25cb..774cd05f8 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -16,10 +16,10 @@ jobs: with: distribution: 'temurin' java-version: '21' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - name: Assemble project - uses: gradle/gradle-build-action@v2 - with: - arguments: assemble + run: ./gradlew assemble - name: Upload jars artifacts uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8da0f1101..b8dd8f45a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,12 +29,8 @@ jobs: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 - name: Build env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index df2061db0..6a1a33d35 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,7 +12,7 @@ jobs: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' # Beta Release - uses: cla-assistant/github-action@v2.3.1 + uses: cla-assistant/github-action@v2.3.2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the below token should have repo scope and must be manually added by you in the repository's secret diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index b22c88cd0..c759bf2c0 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -13,6 +13,6 @@ jobs: pull-requests: write # for TimonVS/pr-labeler-action to add labels in PR runs-on: ubuntu-latest steps: - - uses: TimonVS/pr-labeler-action@v4.1.1 + - uses: TimonVS/pr-labeler-action@v5.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml index 58e777757..2123aaf27 100644 --- a/.github/workflows/release-drafter.yml +++ b/.github/workflows/release-drafter.yml @@ -12,6 +12,6 @@ jobs: runs-on: ubuntu-latest steps: # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 + - uses: release-drafter/release-drafter@v6 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/api-gateway/build.gradle.kts b/api-gateway/build.gradle.kts index c15910165..5c3a1d244 100644 --- a/api-gateway/build.gradle.kts +++ b/api-gateway/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5") } springBoot { diff --git a/build.gradle.kts b/build.gradle.kts index 347bc2bd8..162340232 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,13 +18,13 @@ plugins { `kotlin-dsl` // only apply the plugin in the subprojects requiring it because it expects a Spring Boot app // and the shared lib is obviously not one - id("org.springframework.boot") version "3.2.1" apply false + id("org.springframework.boot") version "3.2.4" apply false id("io.spring.dependency-management") version "1.1.4" apply false - id("org.graalvm.buildtools.native") version "0.9.28" - kotlin("jvm") version "1.9.22" apply false - kotlin("plugin.spring") version "1.9.22" apply false - id("com.google.cloud.tools.jib") version "3.4.0" apply false - id("io.gitlab.arturbosch.detekt") version "1.23.4" apply false + id("org.graalvm.buildtools.native") version "0.10.1" + kotlin("jvm") version "1.9.23" apply false + kotlin("plugin.spring") version "1.9.23" apply false + id("com.google.cloud.tools.jib") version "3.4.1" apply false + id("io.gitlab.arturbosch.detekt") version "1.23.5" apply false id("org.sonarqube") version "4.4.1.3373" jacoco } @@ -64,10 +64,10 @@ subprojects { implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("com.apicatalog:titanium-json-ld:1.3.3") + implementation("com.apicatalog:titanium-json-ld:1.4.0") implementation("org.glassfish:jakarta.json:2.0.1") - implementation("io.arrow-kt:arrow-fx-coroutines:1.2.1") + implementation("io.arrow-kt:arrow-fx-coroutines:1.2.3") implementation("org.locationtech.jts.io:jts-io-common:1.19.0") @@ -104,7 +104,7 @@ subprojects { configurations.matching { it.name == "detekt" }.all { resolutionStrategy.eachDependency { if (requested.group == "org.jetbrains.kotlin") { - useVersion("1.9.21") + useVersion("1.9.22") } } } diff --git a/search-service/build.gradle.kts b/search-service/build.gradle.kts index b62d9400f..c36f7f389 100644 --- a/search-service/build.gradle.kts +++ b/search-service/build.gradle.kts @@ -20,10 +20,10 @@ dependencies { // implementation (and not runtime) because we are using the native jsonb encoding provided by PG implementation("org.postgresql:r2dbc-postgresql") implementation("com.github.stellio-hub:json-merge:0.1.0") - implementation("org.json:json:20231013") + implementation("org.json:json:20240303") implementation(project(":shared")) - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5") developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/search-service/config/detekt/baseline.xml b/search-service/config/detekt/baseline.xml index b10aa1709..124c05513 100644 --- a/search-service/config/detekt/baseline.xml +++ b/search-service/config/detekt/baseline.xml @@ -17,9 +17,7 @@ LongMethod:V0_29__JsonLd_migration.kt$V0_29__JsonLd_migration$override fun migrate(context: Context) LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( temporalEntityAttribute: UUID, instanceId: URI = generateRandomInstanceId(), timeAndProperty: Pair<ZonedDateTime, TemporalProperty>, value: Triple<String?, Double?, WKTCoordinates?>, payload: ExpandedAttributeInstance, sub: String? ) LongParameterList:AttributeInstance.kt$AttributeInstance.Companion$( temporalEntityAttribute: UUID, instanceId: URI = generateRandomInstanceId(), timeProperty: TemporalProperty? = TemporalProperty.OBSERVED_AT, modifiedAt: ZonedDateTime? = null, attributeMetadata: AttributeMetadata, payload: ExpandedAttributeInstance, time: ZonedDateTime, sub: String? = null ) - LongParameterList:EntityEventService.kt$EntityEventService$( sub: String?, entityId: URI, attributeName: ExpandedTerm, datasetId: URI? = null, deleteAll: Boolean, contexts: List<String> ) - LongParameterList:EntityEventService.kt$EntityEventService$( sub: String?, entityId: URI, jsonLdAttributes: Map<String, Any>, updateResult: UpdateResult, overwrite: Boolean, contexts: List<String> ) - LongParameterList:EntityEventService.kt$EntityEventService$( updatedDetails: UpdatedDetails, sub: String?, tenantName: String, entityId: URI, entityTypesAndPayload: Pair<List<ExpandedTerm>, String>, serializedAttribute: Pair<ExpandedTerm, String>, overwrite: Boolean, contexts: List<String> ) + LongParameterList:EntityEventService.kt$EntityEventService$( updatedDetails: UpdatedDetails, sub: String?, tenantName: String, entityId: URI, entityTypesAndPayload: Pair<List<ExpandedTerm>, String>, serializedAttribute: Pair<ExpandedTerm, String>, overwrite: Boolean ) LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityId: URI, attributeName: ExpandedTerm, attributeMetadata: AttributeMetadata, createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? ) LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, createdAt: ZonedDateTime, observedAt: ZonedDateTime?, sub: Sub? ) LongParameterList:TemporalEntityAttributeService.kt$TemporalEntityAttributeService$( entityUri: URI, ngsiLdAttributes: List<NgsiLdAttribute>, expandedAttributes: ExpandedAttributes, disallowOverwrite: Boolean, createdAt: ZonedDateTime, sub: Sub? ) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt b/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt index dde5c44b3..f04afeeaa 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/listener/ObservationEventListener.kt @@ -78,8 +78,7 @@ class ObservationEventListener( entityEventService.publishEntityCreateEvent( observationEvent.sub, observationEvent.entityId, - expandJsonLdTerms(observationEvent.entityTypes, observationEvent.contexts), - observationEvent.contexts + expandJsonLdTerms(observationEvent.entityTypes, observationEvent.contexts) ) } }.writeContextAndSubscribe(tenantName, observationEvent) @@ -115,8 +114,7 @@ class ObservationEventListener( observationEvent.entityId, expandedAttribute.toExpandedAttributes(), it, - false, - observationEvent.contexts + false ) } } @@ -153,8 +151,7 @@ class ObservationEventListener( observationEvent.entityId, expandedAttribute.toExpandedAttributes(), it, - observationEvent.overwrite, - observationEvent.contexts + observationEvent.overwrite ) } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt index 65ea363d8..afdfe71fc 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/AttributeInstance.kt @@ -1,12 +1,12 @@ package com.egm.stellio.search.model +import com.egm.stellio.search.util.toJson import com.egm.stellio.shared.model.ExpandedAttributeInstance import com.egm.stellio.shared.model.WKTCoordinates import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_INSTANCE_ID_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedPropertyValue import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedTemporalValue -import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.egm.stellio.shared.util.toUri import io.r2dbc.postgresql.codec.Json import java.net.URI @@ -66,8 +66,6 @@ data class AttributeInstance private constructor( sub = sub ) - private fun ExpandedAttributeInstance.toJson(): Json = Json.of(serializeObject(this)) - private fun ExpandedAttributeInstance.composePayload( instanceId: URI, modifiedAt: ZonedDateTime? = null diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt index 3a182299e..5f8d986d7 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityPayload.kt @@ -21,9 +21,6 @@ data class EntityPayload( val scopes: List? = null, val createdAt: ZonedDateTime, val modifiedAt: ZonedDateTime? = null, - // creation time contexts - // FIXME only stored because needed to compact types at deletion time... - val contexts: List, val payload: Json, val specificAccessPolicy: SpecificAccessPolicy? = null ) { diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt index 16ceefd97..7655065e2 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/EntityTypeInfo.kt @@ -21,7 +21,8 @@ data class AttributeInfo( enum class AttributeType(val key: String) { Property("Property"), Relationship("Relationship"), - GeoProperty("GeoProperty"); + GeoProperty("GeoProperty"), + JsonProperty("JsonProperty"); companion object { fun forKey(key: String): AttributeType = diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt index 189aec73b..badf90d3a 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalEntityAttribute.kt @@ -2,7 +2,12 @@ package com.egm.stellio.search.model import com.egm.stellio.shared.model.ExpandedTerm import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUES +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUES import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUES +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TYPE import io.r2dbc.postgresql.codec.Json import org.springframework.data.annotation.Id @@ -32,19 +37,33 @@ data class TemporalEntityAttribute( DATETIME, DATE, TIME, - URI + URI, + JSON } enum class AttributeType { Property, Relationship, - GeoProperty; + GeoProperty, + JsonProperty; fun toExpandedName(): String = when (this) { Property -> NGSILD_PROPERTY_TYPE.uri Relationship -> NGSILD_RELATIONSHIP_TYPE.uri GeoProperty -> NGSILD_GEOPROPERTY_TYPE.uri + JsonProperty -> NGSILD_JSONPROPERTY_TYPE.uri + } + + /** + * Returns the key of the member for the simplified representation of the attribute, as defined in 4.5.9 + */ + fun toSimpliedRepresentationKey(): String = + when (this) { + Property -> NGSILD_PROPERTY_VALUES + Relationship -> NGSILD_RELATIONSHIP_OBJECTS + GeoProperty -> NGSILD_GEOPROPERTY_VALUES + JsonProperty -> NGSILD_JSONPROPERTY_VALUES } } } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt index a12267577..e84f688a5 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityEventService.kt @@ -38,8 +38,7 @@ class EntityEventService( suspend fun publishEntityCreateEvent( sub: String?, entityId: URI, - entityTypes: List, - contexts: List + entityTypes: List ): Job { val tenantName = getTenantFromContext() val entity = getSerializedEntity(entityId) @@ -47,7 +46,7 @@ class EntityEventService( logger.debug("Sending create event for entity {} in tenant {}", entityId, tenantName) entity.onRight { publishEntityEvent( - EntityCreateEvent(sub, tenantName, entityId, entityTypes, it.second, contexts) + EntityCreateEvent(sub, tenantName, entityId, entityTypes, it.second, emptyList()) ) }.logEntityEvent(EventsType.ENTITY_CREATE, entityId, tenantName) } @@ -56,8 +55,7 @@ class EntityEventService( suspend fun publishEntityReplaceEvent( sub: String?, entityId: URI, - entityTypes: List, - contexts: List + entityTypes: List ): Job { val tenantName = getTenantFromContext() val entity = getSerializedEntity(entityId) @@ -65,7 +63,7 @@ class EntityEventService( logger.debug("Sending replace event for entity {} in tenant {}", entityId, tenantName) entity.onRight { publishEntityEvent( - EntityReplaceEvent(sub, tenantName, entityId, entityTypes, it.second, contexts) + EntityReplaceEvent(sub, tenantName, entityId, entityTypes, it.second, emptyList()) ) }.logEntityEvent(EventsType.ENTITY_REPLACE, entityId, tenantName) } @@ -73,8 +71,7 @@ class EntityEventService( suspend fun publishEntityDeleteEvent( sub: String?, - entityPayload: EntityPayload, - contexts: List + entityPayload: EntityPayload ): Job { val tenantName = getTenantFromContext() return coroutineScope.launch { @@ -86,7 +83,7 @@ class EntityEventService( entityPayload.entityId, entityPayload.types, entityPayload.payload.asString(), - contexts + emptyList() ) ) } @@ -97,8 +94,7 @@ class EntityEventService( entityId: URI, jsonLdAttributes: Map, updateResult: UpdateResult, - overwrite: Boolean, - contexts: List + overwrite: Boolean ): Job { val tenantName = getTenantFromContext() val entity = getSerializedEntity(entityId) @@ -116,8 +112,7 @@ class EntityEventService( entityId, it, serializedAttribute, - overwrite, - contexts + overwrite ) } }.logAttributeEvent("Attribute Change", entityId, tenantName) @@ -131,8 +126,7 @@ class EntityEventService( entityId: URI, entityTypesAndPayload: Pair, String>, serializedAttribute: Pair, - overwrite: Boolean, - contexts: List + overwrite: Boolean ) { when (updatedDetails.updateOperationResult) { UpdateOperationResult.APPENDED -> @@ -147,7 +141,7 @@ class EntityEventService( overwrite, serializedAttribute.second, entityTypesAndPayload.second, - contexts + emptyList() ) ) @@ -162,7 +156,7 @@ class EntityEventService( updatedDetails.datasetId, serializedAttribute.second, entityTypesAndPayload.second, - contexts + emptyList() ) ) @@ -177,7 +171,7 @@ class EntityEventService( updatedDetails.datasetId, serializedAttribute.second, entityTypesAndPayload.second, - contexts + emptyList() ) ) @@ -194,8 +188,7 @@ class EntityEventService( entityId: URI, attributeName: ExpandedTerm, datasetId: URI? = null, - deleteAll: Boolean, - contexts: List + deleteAll: Boolean ): Job { val tenantName = getTenantFromContext() val entity = getSerializedEntity(entityId) @@ -216,7 +209,7 @@ class EntityEventService( it.first, attributeName, it.second, - contexts + emptyList() ) ) else @@ -229,7 +222,7 @@ class EntityEventService( attributeName, datasetId, it.second, - contexts + emptyList() ) ) }.logAttributeEvent("Attribute Delete", entityId, tenantName) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt index 9187d4b40..baedc1590 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityOperationService.kt @@ -202,7 +202,7 @@ class EntityOperationService( sub: Sub? ): Either = either { val (jsonLdEntity, ngsiLdEntity) = entity - temporalEntityAttributeService.deleteTemporalAttributesOfEntity(ngsiLdEntity.id) + temporalEntityAttributeService.deleteTemporalAttributesOfEntity(ngsiLdEntity.id).bind() val updateResult = entityPayloadService.appendAttributes( ngsiLdEntity.id, jsonLdEntity.getAttributes(), diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt index a9b3e1cda..2179d79b3 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityPayloadService.kt @@ -64,8 +64,8 @@ class EntityPayloadService( val specificAccessPolicy = ngsiLdEntity.getSpecificAccessPolicy()?.bind() databaseClient.sql( """ - INSERT INTO entity_payload (entity_id, types, scopes, created_at, payload, contexts, specific_access_policy) - VALUES (:entity_id, :types, :scopes, :created_at, :payload, :contexts, :specific_access_policy) + INSERT INTO entity_payload (entity_id, types, scopes, created_at, payload, specific_access_policy) + VALUES (:entity_id, :types, :scopes, :created_at, :payload, :specific_access_policy) """.trimIndent() ) .bind("entity_id", ngsiLdEntity.id) @@ -73,7 +73,6 @@ class EntityPayloadService( .bind("scopes", ngsiLdEntity.scopes?.toTypedArray()) .bind("created_at", createdAt) .bind("payload", Json.of(serializeObject(expandedEntity.populateCreationTimeDate(createdAt).members))) - .bind("contexts", ngsiLdEntity.contexts.toTypedArray()) .bind("specific_access_policy", specificAccessPolicy?.toString()) .execute() .map { @@ -155,8 +154,7 @@ class EntityPayloadService( scopes = :scopes, modified_at = :modified_at, payload = :payload, - specific_access_policy = :specific_access_policy, - contexts = :contexts + specific_access_policy = :specific_access_policy WHERE entity_id = :entity_id """.trimIndent() ) @@ -165,7 +163,6 @@ class EntityPayloadService( .bind("scopes", ngsiLdEntity.scopes?.toTypedArray()) .bind("modified_at", replacedAt) .bind("payload", Json.of(serializedPayload)) - .bind("contexts", expandedEntity.contexts.toTypedArray()) .bind("specific_access_policy", specificAccessPolicy?.toString()) .execute() .map { @@ -210,7 +207,6 @@ class EntityPayloadService( scopes = toOptionalList(row["scopes"]), createdAt = toZonedDateTime(row["created_at"]), modifiedAt = toOptionalZonedDateTime(row["modified_at"]), - contexts = toList(row["contexts"]), payload = toJson(row["payload"]), specificAccessPolicy = toOptionalEnum(row["specific_access_policy"]) ) @@ -421,58 +417,46 @@ class EntityPayloadService( newTypes: List, modifiedAt: ZonedDateTime, allowEmptyListOfTypes: Boolean = true - ): Either = - either { - val entityPayload = retrieve(entityId).bind() - val currentTypes = entityPayload.types - // when dealing with an entity update, list of types can be empty if no change of type is requested - if (currentTypes.sorted() == newTypes.sorted() || newTypes.isEmpty() && allowEmptyListOfTypes) - return@either UpdateResult(emptyList(), emptyList()) - if (!newTypes.containsAll(currentTypes)) { - val removedTypes = currentTypes.minus(newTypes) - return@either updateResultFromDetailedResult( + ): Either = either { + val entityPayload = retrieve(entityId).bind() + val currentTypes = entityPayload.types + // when dealing with an entity update, list of types can be empty if no change of type is requested + if (currentTypes.sorted() == newTypes.sorted() || newTypes.isEmpty() && allowEmptyListOfTypes) + return@either UpdateResult(emptyList(), emptyList()) + + val updatedTypes = currentTypes.union(newTypes) + val updatedPayload = entityPayload.payload.deserializeExpandedPayload() + .mapValues { + if (it.key == JSONLD_TYPE) + updatedTypes + else it + } + + databaseClient.sql( + """ + UPDATE entity_payload + SET types = :types, + modified_at = :modified_at, + payload = :payload + WHERE entity_id = :entity_id + """.trimIndent() + ) + .bind("entity_id", entityId) + .bind("modified_at", modifiedAt) + .bind("types", updatedTypes.toTypedArray()) + .bind("payload", Json.of(serializeObject(updatedPayload))) + .execute() + .map { + updateResultFromDetailedResult( listOf( UpdateAttributeResult( attributeName = JSONLD_TYPE, - updateOperationResult = UpdateOperationResult.FAILED, - errorMessage = "A type cannot be removed from an entity: $removedTypes have been removed" + updateOperationResult = UpdateOperationResult.APPENDED ) ) ) - } - - val updatedPayload = entityPayload.payload.deserializeExpandedPayload() - .mapValues { - if (it.key == JSONLD_TYPE) - newTypes - else it - } - - databaseClient.sql( - """ - UPDATE entity_payload - SET types = :types, - modified_at = :modified_at, - payload = :payload - WHERE entity_id = :entity_id - """.trimIndent() - ) - .bind("entity_id", entityId) - .bind("types", newTypes.toTypedArray()) - .bind("modified_at", modifiedAt) - .bind("payload", Json.of(serializeObject(updatedPayload))) - .execute() - .map { - updateResultFromDetailedResult( - listOf( - UpdateAttributeResult( - attributeName = JSONLD_TYPE, - updateOperationResult = UpdateOperationResult.APPENDED - ) - ) - ) - }.bind() - } + }.bind() + } @Transactional suspend fun appendAttributes( @@ -483,7 +467,7 @@ class EntityPayloadService( ): Either = either { val (coreAttrs, otherAttrs) = expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) } - val createdAt = ZonedDateTime.now(ZoneOffset.UTC) + val createdAt = ngsiLdDateTime() val operationType = if (disallowOverwrite) APPEND_ATTRIBUTES @@ -512,50 +496,48 @@ class EntityPayloadService( entityUri: URI, expandedAttributes: ExpandedAttributes, sub: Sub? - ): Either = - either { - val (coreAttrs, otherAttrs) = - expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) } - val createdAt = ZonedDateTime.now(ZoneOffset.UTC) + ): Either = either { + val (coreAttrs, otherAttrs) = + expandedAttributes.toList().partition { JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS.contains(it.first) } + val createdAt = ngsiLdDateTime() - val coreUpdateResult = updateCoreAttributes(entityUri, coreAttrs, createdAt, UPDATE_ATTRIBUTES).bind() - val attrsUpdateResult = temporalEntityAttributeService.updateEntityAttributes( - entityUri, - otherAttrs.toMap().toNgsiLdAttributes().bind(), - expandedAttributes, - createdAt, - sub - ).bind() - - val updateResult = coreUpdateResult.mergeWith(attrsUpdateResult) - // update modifiedAt in entity if at least one attribute has been added - if (updateResult.hasSuccessfulUpdate()) { - val teas = temporalEntityAttributeService.getForEntity(entityUri, emptySet()) - updateState(entityUri, createdAt, teas).bind() - } - updateResult + val coreUpdateResult = updateCoreAttributes(entityUri, coreAttrs, createdAt, UPDATE_ATTRIBUTES).bind() + val attrsUpdateResult = temporalEntityAttributeService.updateEntityAttributes( + entityUri, + otherAttrs.toMap().toNgsiLdAttributes().bind(), + expandedAttributes, + createdAt, + sub + ).bind() + + val updateResult = coreUpdateResult.mergeWith(attrsUpdateResult) + // update modifiedAt in entity if at least one attribute has been added + if (updateResult.hasSuccessfulUpdate()) { + val teas = temporalEntityAttributeService.getForEntity(entityUri, emptySet()) + updateState(entityUri, createdAt, teas).bind() } + updateResult + } @Transactional suspend fun partialUpdateAttribute( entityId: URI, expandedAttribute: ExpandedAttribute, sub: Sub? - ): Either = - either { - val modifiedAt = ZonedDateTime.now(ZoneOffset.UTC) - val updateResult = temporalEntityAttributeService.partialUpdateEntityAttribute( - entityId, - expandedAttribute, - modifiedAt, - sub - ).bind() - if (updateResult.isSuccessful()) { - val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet()) - updateState(entityId, modifiedAt, teas).bind() - } - updateResult + ): Either = either { + val modifiedAt = ngsiLdDateTime() + val updateResult = temporalEntityAttributeService.partialUpdateEntityAttribute( + entityId, + expandedAttribute, + modifiedAt, + sub + ).bind() + if (updateResult.isSuccessful()) { + val teas = temporalEntityAttributeService.getForEntity(entityId, emptySet()) + updateState(entityId, modifiedAt, teas).bind() } + updateResult + } @Transactional suspend fun upsertAttributes( diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt index b8369eda1..40b6c1092 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityTypeService.kt @@ -58,11 +58,11 @@ class EntityTypeService( val result = databaseClient.sql( """ WITH entities AS ( - SELECT entity_id + SELECT entity_id FROM entity_payload WHERE :type_name = any (types) ) - SELECT attribute_name, attribute_type, count(distinct(entity_id)) as count_entity + SELECT attribute_name, attribute_type, (select count(entity_id) from entities) as entity_count FROM temporal_entity_attribute WHERE entity_id IN (SELECT entity_id FROM entities) GROUP BY attribute_name, attribute_type @@ -77,7 +77,7 @@ class EntityTypeService( return EntityTypeInfo( id = toUri(typeName), typeName = compactTerm(typeName, contexts), - entityCount = toInt(result.first()["count_entity"]), + entityCount = toInt(result.first()["entity_count"]), attributeDetails = result.map { AttributeInfo( id = toUri(it["attribute_name"]), diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt index 2e3559ea9..33b490858 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeService.kt @@ -13,6 +13,7 @@ import com.egm.stellio.shared.model.* import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.AttributeType import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECT @@ -212,41 +213,40 @@ class TemporalEntityAttributeService( createdAt: ZonedDateTime, attributePayload: ExpandedAttributeInstance, sub: Sub? - ): Either = - either { - logger.debug( - "Replacing attribute {} ({}) in entity {}", - ngsiLdAttribute.name, - attributeMetadata.datasetId, - temporalEntityAttribute.entityId - ) - updateOnReplace( - temporalEntityAttribute.id, - attributeMetadata, - createdAt, - serializeObject(attributePayload) - ).bind() + ): Either = either { + logger.debug( + "Replacing attribute {} ({}) in entity {}", + ngsiLdAttribute.name, + attributeMetadata.datasetId, + temporalEntityAttribute.entityId + ) + updateOnReplace( + temporalEntityAttribute.id, + attributeMetadata, + createdAt, + serializeObject(attributePayload) + ).bind() + + val attributeInstance = AttributeInstance( + temporalEntityAttribute = temporalEntityAttribute.id, + timeProperty = AttributeInstance.TemporalProperty.MODIFIED_AT, + time = createdAt, + attributeMetadata = attributeMetadata, + payload = attributePayload, + sub = sub + ) + attributeInstanceService.create(attributeInstance).bind() - val attributeInstance = AttributeInstance( + if (attributeMetadata.observedAt != null) { + val attributeObservedAtInstance = AttributeInstance( temporalEntityAttribute = temporalEntityAttribute.id, - timeProperty = AttributeInstance.TemporalProperty.MODIFIED_AT, - time = createdAt, + time = attributeMetadata.observedAt, attributeMetadata = attributeMetadata, - payload = attributePayload, - sub = sub + payload = attributePayload ) - attributeInstanceService.create(attributeInstance).bind() - - if (attributeMetadata.observedAt != null) { - val attributeObservedAtInstance = AttributeInstance( - temporalEntityAttribute = temporalEntityAttribute.id, - time = attributeMetadata.observedAt, - attributeMetadata = attributeMetadata, - payload = attributePayload - ) - attributeInstanceService.create(attributeObservedAtInstance).bind() - } + attributeInstanceService.create(attributeObservedAtInstance).bind() } + } @Transactional suspend fun mergeAttribute( @@ -857,6 +857,12 @@ class TemporalEntityAttributeService( null, WKTCoordinates(attributePayload.getPropertyValue()!! as String) ) + TemporalEntityAttribute.AttributeType.JsonProperty -> + Triple( + serializeObject(attributePayload.getMemberValue(NGSILD_JSONPROPERTY_VALUE)!!), + null, + null + ) } suspend fun mergeAttributePayload( @@ -865,11 +871,21 @@ class TemporalEntityAttributeService( ): Pair { val jsonSourceObject = JSONObject(tea.payload.asString()) val jsonUpdateObject = JSONObject(expandedAttributeInstance) + // if the attribute is a JsonProperty, preserve its JSON value to avoid it being merged + // (the whole JSON value shall be replaced) + val preservedJsonValue = if (tea.attributeType == TemporalEntityAttribute.AttributeType.JsonProperty) + expandedAttributeInstance[NGSILD_JSONPROPERTY_VALUE] + else null val jsonMerger = JsonMerger( arrayMergeMode = JsonMerger.ArrayMergeMode.REPLACE_ARRAY, objectMergeMode = JsonMerger.ObjectMergeMode.MERGE_OBJECT ) val jsonTargetObject = jsonMerger.merge(jsonSourceObject, jsonUpdateObject) + .let { + if (preservedJsonValue != null) + it.put(NGSILD_JSONPROPERTY_VALUE, preservedJsonValue) + else it + } val updatedAttributeInstance = jsonTargetObject.toMap() as ExpandedAttributeInstance return Pair(jsonTargetObject, updatedAttributeInstance) } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt index a7252aa04..20a3cc80e 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/AttributeInstanceUtils.kt @@ -59,6 +59,12 @@ fun NgsiLdAttributeInstance.toTemporalAttributeMetadata(): Either + Triple( + TemporalEntityAttribute.AttributeType.JsonProperty, + AttributeValueType.JSON, + Triple(serializeObject(this.json), null, null) + ) } if (attributeValue == Triple(null, null, null)) { logger.warn("Unable to get a value from attribute: $this") @@ -85,6 +91,7 @@ fun guessAttributeValueType( guessPropertyValueType(expandedAttributeInstance.getPropertyValue()!!).first TemporalEntityAttribute.AttributeType.Relationship -> AttributeValueType.URI TemporalEntityAttribute.AttributeType.GeoProperty -> AttributeValueType.GEOMETRY + TemporalEntityAttribute.AttributeType.JsonProperty -> AttributeValueType.JSON } fun guessPropertyValueType( diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt index fbe6e4f56..c930c9597 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBAggregationUtils.kt @@ -17,6 +17,7 @@ fun aggrMethodToSqlAggregate( AttributeValueType.TIME -> sqlAggregateForTime(aggregate) AttributeValueType.URI -> sqlAggregateForURI(aggregate) AttributeValueType.GEOMETRY -> "null" + AttributeValueType.JSON -> "null" } fun sqlAggregationForJsonString(aggregate: TemporalQuery.Aggregate): String = when (aggregate) { diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt index fd44e28fa..d81b48770 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/DBConversionUtils.kt @@ -1,7 +1,9 @@ package com.egm.stellio.search.util +import com.egm.stellio.shared.model.ExpandedAttributeInstance import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap import com.egm.stellio.shared.util.JsonUtils.deserializeExpandedPayload +import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.egm.stellio.shared.util.toUri import io.r2dbc.postgresql.codec.Json import java.net.URI @@ -29,3 +31,5 @@ fun toInt(entry: Any?): Int = (entry as Long).toInt() fun Json.deserializeExpandedPayload(): Map> = this.asString().deserializeExpandedPayload() fun Json.deserializeAsMap(): Map = this.asString().deserializeAsMap() + +fun ExpandedAttributeInstance.toJson(): Json = Json.of(serializeObject(this)) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt index f606d6ac6..c64e3980f 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalEntityBuilder.kt @@ -5,20 +5,20 @@ import com.egm.stellio.search.scope.TemporalScopeBuilder import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.ExpandedTerm import com.egm.stellio.shared.util.AuthContextModel.AUTH_PROP_SUB +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_JSON 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.JSONLD_VALUE_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TYPE -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUES +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PREFIX -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUES -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_OBJECTS import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedPropertyValue import com.egm.stellio.shared.util.JsonLdUtils.buildExpandedTemporalValue import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedPropertyValue import com.egm.stellio.shared.util.JsonLdUtils.buildNonReifiedTemporalValue -import com.egm.stellio.shared.util.JsonUtils +import com.egm.stellio.shared.util.JsonUtils.deserializeListOfObjects +import com.egm.stellio.shared.util.JsonUtils.deserializeObject import com.egm.stellio.shared.util.wktToGeoJson typealias SimplifiedTemporalAttribute = Map @@ -74,7 +74,7 @@ object TemporalEntityBuilder { mergeFullTemporalAttributesOnAttributeName(attributeAndResultsMap) .mapValues { (_, attributeInstanceResults) -> attributeInstanceResults.map { attributeInstanceResult -> - JsonUtils.deserializeObject(attributeInstanceResult.payload) + deserializeObject(attributeInstanceResult.payload) .let { instancePayload -> injectSub(temporalEntitiesQuery, attributeInstanceResult, instancePayload) }.let { instancePayload -> @@ -121,19 +121,33 @@ object TemporalEntityBuilder { it.key.datasetId?.let { datasetId -> attributeInstance[NGSILD_DATASET_ID_PROPERTY] = buildNonReifiedPropertyValue(datasetId.toString()) } - val valuesKey = - when (it.key.attributeType) { - TemporalEntityAttribute.AttributeType.Property -> NGSILD_PROPERTY_VALUES - TemporalEntityAttribute.AttributeType.Relationship -> NGSILD_RELATIONSHIP_OBJECTS - TemporalEntityAttribute.AttributeType.GeoProperty -> NGSILD_GEOPROPERTY_VALUES - } + val valuesKey = it.key.attributeType.toSimpliedRepresentationKey() attributeInstance[valuesKey] = buildExpandedTemporalValue(it.value) { attributeInstanceResult -> attributeInstanceResult as SimplifiedAttributeInstanceResult - listOf( - mapOf(JSONLD_VALUE to attributeInstanceResult.value), - mapOf(JSONLD_VALUE to attributeInstanceResult.time) - ) + if (it.key.attributeType == TemporalEntityAttribute.AttributeType.JsonProperty) { + // flaky way to know if the serialized value is a JSON object or an array of JSON objects + val deserializedJsonValue: Any = + if ((attributeInstanceResult.value as String).startsWith("[")) + deserializeListOfObjects(attributeInstanceResult.value) + else deserializeObject(attributeInstanceResult.value) + listOf( + mapOf( + NGSILD_JSONPROPERTY_VALUE to listOf( + mapOf( + JSONLD_TYPE to JSONLD_JSON, + JSONLD_VALUE to deserializedJsonValue + ) + ) + ), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } else { + listOf( + mapOf(JSONLD_VALUE to attributeInstanceResult.value), + mapOf(JSONLD_VALUE to attributeInstanceResult.time) + ) + } } attributeInstance.toMap() } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt index 161a45669..610538e05 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityHandler.kt @@ -67,8 +67,7 @@ class EntityHandler( entityEventService.publishEntityCreateEvent( sub.getOrNull(), ngsiLdEntity.id, - ngsiLdEntity.types, - contexts + ngsiLdEntity.types ) ResponseEntity.status(HttpStatus.CREATED) @@ -115,8 +114,7 @@ class EntityHandler( entityId, expandedAttributes, updateResult, - true, - contexts + true ) } @@ -165,8 +163,7 @@ class EntityHandler( entityEventService.publishEntityReplaceEvent( sub.getOrNull(), ngsiLdEntity.id, - ngsiLdEntity.types, - contexts + ngsiLdEntity.types ) ResponseEntity.status(HttpStatus.NO_CONTENT).build() @@ -277,7 +274,7 @@ class EntityHandler( entityPayloadService.deleteEntity(entityId).bind() authorizationService.removeRightsOnEntity(entityId).bind() - entityEventService.publishEntityDeleteEvent(sub.getOrNull(), entity, entity.contexts) + entityEventService.publishEntityDeleteEvent(sub.getOrNull(), entity) ResponseEntity.status(HttpStatus.NO_CONTENT).build() }.fold( @@ -319,8 +316,7 @@ class EntityHandler( entityId, expandedAttributes, updateResult, - true, - contexts + true ) } @@ -369,8 +365,7 @@ class EntityHandler( entityId, expandedAttributes, updateResult, - true, - contexts + true ) } @@ -426,8 +421,7 @@ class EntityHandler( entityId, expandedAttribute.toExpandedAttributes(), it, - false, - contexts + false ) ResponseEntity.status(HttpStatus.NO_CONTENT).build().right() @@ -473,8 +467,7 @@ class EntityHandler( entityId, expandedAttrId, datasetId, - deleteAll, - contexts + deleteAll ) ResponseEntity.status(HttpStatus.NO_CONTENT).build() }.fold( @@ -514,8 +507,7 @@ class EntityHandler( entityId, expandedAttribute.toExpandedAttributes(), it, - false, - contexts + false ) ResponseEntity.status(HttpStatus.NO_CONTENT).build().right() diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt index 896c5f75d..4a145e974 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt @@ -244,8 +244,7 @@ class EntityOperationHandler( val entity = entitiesBeforeDelete.find { it.entityId == uri }!! entityEventService.publishEntityDeleteEvent( sub.getOrNull(), - entity, - entity.contexts + entity ) } @@ -354,8 +353,7 @@ class EntityOperationHandler( entityEventService.publishEntityCreateEvent( sub.getOrNull(), ngsiLdEntity.id, - ngsiLdEntity.types, - ngsiLdEntity.contexts + ngsiLdEntity.types ) } batchOperationResult.errors.addAll(createOperationResult.errors) @@ -372,8 +370,7 @@ class EntityOperationHandler( entityEventService.publishEntityReplaceEvent( sub, it.entityId(), - it.second.types, - it.second.contexts + it.second.types ) } @@ -383,7 +380,7 @@ class EntityOperationHandler( jsonLdNgsiLdEntities: List ) { updateBatchOperationResult.success.forEach { - val (jsonLdEntity, ngsiLdEntity) = jsonLdNgsiLdEntities.find { jsonLdNgsiLdEntity -> + val (jsonLdEntity, _) = jsonLdNgsiLdEntities.find { jsonLdNgsiLdEntity -> jsonLdNgsiLdEntity.entityId() == it.entityId }!! entityEventService.publishAttributeChangeEvents( @@ -391,8 +388,7 @@ class EntityOperationHandler( it.entityId, jsonLdEntity.members, it.updateResult!!, - true, - ngsiLdEntity.contexts + true ) } } diff --git a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt index bd1f1d7dc..abca87bd7 100644 --- a/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt +++ b/search-service/src/main/kotlin/db/migration/V0_29__JsonLd_migration.kt @@ -207,8 +207,9 @@ class V0_29__JsonLd_migration : BaseJavaMigration() { is Either.Left -> logger.warn("Unable to process attribute $attributeName ($datasetId) from entity $entityId") is Either.Right -> { - val createdAt = ngsiLdAttributeInstance.createdAt ?: defaultCreatedAt - val modifiedAt = ngsiLdAttributeInstance.modifiedAt + val createdAt = + attributePayload.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) ?: defaultCreatedAt + val modifiedAt = attributePayload.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY) val atributeType = temporalAttributesMetadata.value.type val attributeValueType = temporalAttributesMetadata.value.valueType val serializedAttributePayload = serializeObject(attributePayload) @@ -259,14 +260,16 @@ class V0_29__JsonLd_migration : BaseJavaMigration() { ngsiLdAttributeInstance: NgsiLdAttributeInstance, defaultCreatedAt: ZonedDateTime ) { - val createdAt = ngsiLdAttributeInstance.createdAt ?: defaultCreatedAt - val modifiedAt = ngsiLdAttributeInstance.modifiedAt + val createdAt = + attributePayload.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) ?: defaultCreatedAt + val modifiedAt = attributePayload.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY) val serializedAttributePayload = serializeObject(attributePayload) val valueType = when (ngsiLdAttributeInstance) { is NgsiLdPropertyInstance -> guessPropertyValueType(ngsiLdAttributeInstance).first is NgsiLdRelationshipInstance -> TemporalEntityAttribute.AttributeValueType.URI is NgsiLdGeoPropertyInstance -> TemporalEntityAttribute.AttributeValueType.GEOMETRY + is NgsiLdJsonPropertyInstance -> TemporalEntityAttribute.AttributeValueType.OBJECT } jdbcTemplate.execute( diff --git a/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql b/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql new file mode 100644 index 000000000..fb2d78a20 --- /dev/null +++ b/search-service/src/main/resources/db/migration/V0_39__remove_contexts_column_from_entity_payload.sql @@ -0,0 +1,2 @@ +alter table entity_payload + drop column contexts; diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt index e7d289095..ff5c18f1b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/listener/ObservationEventListenerTests.kt @@ -9,7 +9,10 @@ import com.egm.stellio.search.service.EntityEventService import com.egm.stellio.search.service.EntityPayloadService import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.NgsiLdEntity -import com.egm.stellio.shared.util.* +import com.egm.stellio.shared.util.BEEHIVE_TYPE +import com.egm.stellio.shared.util.TEMPERATURE_PROPERTY +import com.egm.stellio.shared.util.loadSampleData +import com.egm.stellio.shared.util.toUri import com.ninjasquad.springmockk.MockkBean import io.mockk.* import kotlinx.coroutines.Job @@ -43,7 +46,7 @@ class ObservationEventListenerTests { coEvery { entityPayloadService.createEntity(any(), any(), any()) } returns Unit.right() - coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job() observationEventListener.dispatchObservationMessage(observationEvent) @@ -59,8 +62,7 @@ class ObservationEventListenerTests { entityEventService.publishEntityCreateEvent( eq("0123456789-1234-5678-987654321"), eq(expectedEntityId), - eq(listOf(BEEHIVE_TYPE)), - eq(APIC_COMPOUND_CONTEXTS) + eq(listOf(BEEHIVE_TYPE)) ) } } @@ -83,7 +85,7 @@ class ObservationEventListenerTests { ).right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any()) } returns Job() observationEventListener.dispatchObservationMessage(observationEvent) @@ -106,8 +108,7 @@ class ObservationEventListenerTests { it.updated[0].datasetId == expectedTemperatureDatasetId && it.updated[0].updateOperationResult == UpdateOperationResult.UPDATED }, - eq(false), - eq(APIC_COMPOUND_CONTEXTS) + eq(false) ) } } @@ -147,7 +148,7 @@ class ObservationEventListenerTests { val mockedExpandedEntity = mockkClass(ExpandedEntity::class, relaxed = true) every { mockedExpandedEntity.types } returns listOf(BEEHIVE_TYPE) coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any()) } returns Job() observationEventListener.dispatchObservationMessage(observationEvent) @@ -173,8 +174,7 @@ class ObservationEventListenerTests { it.updated[0].attributeName == TEMPERATURE_PROPERTY && it.updated[0].datasetId == expectedTemperatureDatasetId }, - eq(true), - eq(APIC_COMPOUND_CONTEXTS) + eq(true) ) } } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt index 7ecf93b3f..89d30032a 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/model/EntityModelTests.kt @@ -1,7 +1,10 @@ package com.egm.stellio.search.model import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD -import com.egm.stellio.shared.util.* +import com.egm.stellio.shared.util.AuthContextModel +import com.egm.stellio.shared.util.BEEHIVE_TYPE +import com.egm.stellio.shared.util.JsonLdUtils +import com.egm.stellio.shared.util.toUri import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -17,8 +20,7 @@ class EntityModelTests { types = listOf(BEEHIVE_TYPE), createdAt = now, modifiedAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = NGSILD_TEST_CORE_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ) @Test diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt index c5467fd66..03492fdb7 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/scope/TemporalScopeBuilderTests.kt @@ -1,12 +1,14 @@ package com.egm.stellio.search.scope import com.egm.stellio.search.model.AttributeInstance.TemporalProperty -import com.egm.stellio.search.model.EntityPayload import com.egm.stellio.search.model.TemporalEntitiesQuery import com.egm.stellio.search.model.TemporalQuery -import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.buildDefaultQueryParams -import com.egm.stellio.shared.util.* +import com.egm.stellio.search.support.gimmeEntityPayload +import com.egm.stellio.shared.util.JsonUtils +import com.egm.stellio.shared.util.assertJsonPayloadsAreEqual +import com.egm.stellio.shared.util.loadSampleData +import com.egm.stellio.shared.util.toUri import org.junit.jupiter.api.Test import org.springframework.test.context.ActiveProfiles import java.time.Instant @@ -16,13 +18,11 @@ import java.time.ZonedDateTime @ActiveProfiles("test") class TemporalScopeBuilderTests { - private val now = ngsiLdDateTime() - private val entityId = "urn:ngsi-ld:Beehive:1234".toUri() @Test fun `it should build an aggregated temporal representation of scopes`() { - val entityPayload = gimmeEntityPayload() + val entityPayload = gimmeEntityPayload(entityId) val scopeInstances = listOf( AggregatedScopeInstanceResult( entityId = entityId, @@ -86,7 +86,7 @@ class TemporalScopeBuilderTests { @Test fun `it should build a temporal values representation of scopes`() { - val entityPayload = gimmeEntityPayload() + val entityPayload = gimmeEntityPayload(entityId) val scopeInstances = listOf( SimplifiedScopeInstanceResult( entityId = entityId, @@ -124,7 +124,7 @@ class TemporalScopeBuilderTests { @Test fun `it should build a full representation of scopes`() { - val entityPayload = gimmeEntityPayload() + val entityPayload = gimmeEntityPayload(entityId) val scopeInstances = listOf( FullScopeInstanceResult( entityId = entityId, @@ -161,13 +161,4 @@ class TemporalScopeBuilderTests { JsonUtils.serializeObject(scopeHistory) ) } - - private fun gimmeEntityPayload(): EntityPayload = - EntityPayload( - entityId = entityId, - types = listOf(BEEHIVE_TYPE), - createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS - ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt index 3510dce8e..6e2f1842b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AggregatedQueryServiceTests.kt @@ -71,7 +71,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on JSON Number values`(aggrMethod: String, expectedValue: String) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(measuredValue = i.toDouble()) attributeInstanceService.create(attributeInstance) } @@ -100,7 +100,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on JSON String values`(aggrMethod: String, expectedValue: String?) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.STRING) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(measuredValue = null, value = "a$i") attributeInstanceService.create(attributeInstance) } @@ -129,7 +129,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on JSON Object values`(aggrMethod: String, expectedValue: String?) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.OBJECT) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = """ @@ -165,7 +165,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on JSON Array values`(aggrMethod: String, expectedValue: String?) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.ARRAY) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = """ @@ -199,7 +199,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on JSON Boolean values`(aggrMethod: String, expectedValue: String?) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.BOOLEAN) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = if (i % 2 == 0) "true" else "false" @@ -232,7 +232,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.DATETIME) val baseDateTime = ZonedDateTime.parse("2023-03-05T00:01:01Z") (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = baseDateTime.plusHours(i.toLong()).toString() @@ -265,7 +265,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.DATE) val baseDateTime = LocalDate.parse("2023-03-05") (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = baseDateTime.plusDays(i.toLong()).toString() @@ -298,7 +298,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.TIME) val baseDateTime = OffsetTime.parse("00:00:01Z") (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = baseDateTime.plusHours(i.toLong()).toString() @@ -330,7 +330,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should correctly aggregate on URI values`(aggrMethod: String, expectedValue: String?) = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.URI) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy( measuredValue = null, value = "urn:ngsi-ld:Entity:$i" @@ -352,7 +352,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should aggregate on the whole time range if no aggrPeriodDuration is given`() = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER) (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(measuredValue = i.toDouble()) attributeInstanceService.create(attributeInstance) } @@ -389,7 +389,7 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.NUMBER) val startTimestamp = ZonedDateTime.parse("2023-12-28T12:00:00Z") (1..10).forEach { i -> - val attributeInstance = gimmeAttributeInstance(teaUuid) + val attributeInstance = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(time = startTimestamp.plusDays(i.toLong())) attributeInstanceService.create(attributeInstance) } @@ -411,10 +411,10 @@ class AggregatedQueryServiceTests : WithTimescaleContainer, WithKafkaContainer { fun `it should handle aggregates for an attribute having different types of values in history`() = runTest { val temporalEntityAttribute = createTemporalEntityAttribute(TemporalEntityAttribute.AttributeValueType.ARRAY) (1..10).forEach { i -> - val attributeInstanceWithArrayValue = gimmeAttributeInstance(teaUuid) + val attributeInstanceWithArrayValue = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(measuredValue = null, value = "[ $i ]") attributeInstanceService.create(attributeInstanceWithArrayValue) - val attributeInstanceWithStringValue = gimmeAttributeInstance(teaUuid) + val attributeInstanceWithStringValue = gimmeNumericPropertyAttributeInstance(teaUuid) .copy(measuredValue = null, value = "$i") attributeInstanceService.create(attributeInstanceWithStringValue) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt index ef46b42c5..fb09f23c5 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeInstanceServiceTests.kt @@ -11,6 +11,7 @@ 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_DATE_TIME_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_INSTANCE_ID_PROPERTY +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_VALUE @@ -55,6 +56,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer private lateinit var incomingTemporalEntityAttribute: TemporalEntityAttribute private lateinit var outgoingTemporalEntityAttribute: TemporalEntityAttribute + private lateinit var jsonTemporalEntityAttribute: TemporalEntityAttribute val entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri() @@ -83,6 +85,18 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer runBlocking { temporalEntityAttributeService.create(outgoingTemporalEntityAttribute) } + + jsonTemporalEntityAttribute = TemporalEntityAttribute( + entityId = entityId, + attributeName = LUMINOSITY_JSONPROPERTY, + attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON, + createdAt = now, + payload = SAMPLE_JSON_PROPERTY_PAYLOAD + ) + + runBlocking { + temporalEntityAttributeService.create(jsonTemporalEntityAttribute) + } } @AfterEach @@ -100,7 +114,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should retrieve a full instance if temporalValues are not asked for`() = runTest { - val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy( + val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy( time = now, measuredValue = 12.4 ) @@ -122,7 +136,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should retrieve an instance having the corresponding time property value`() = runTest { - val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy( + val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy( timeProperty = AttributeInstance.TemporalProperty.CREATED_AT, time = now, measuredValue = 12.4 @@ -145,7 +159,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should retrieve an instance with audit info if time property is not observedAt`() = runTest { - val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy( + val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy( timeProperty = AttributeInstance.TemporalProperty.CREATED_AT, time = now, measuredValue = 12.4, @@ -170,7 +184,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should not retrieve an instance not having the corresponding time property value`() = runTest { - val observation = gimmeAttributeInstance(incomingTemporalEntityAttribute.id).copy( + val observation = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id).copy( timeProperty = AttributeInstance.TemporalProperty.CREATED_AT, time = now, measuredValue = 12.4 @@ -194,7 +208,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should retrieve all full instances if temporalValues are not asked for`() = runTest { (1..10).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)) } val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( @@ -213,7 +227,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should retrieve all instances when no timerel and time parameters are provided`() = runTest { (1..10).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)) } attributeInstanceService.search(gimmeTemporalEntitiesQuery(TemporalQuery()), incomingTemporalEntityAttribute) @@ -274,7 +288,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer fun `it should set the start time to the oldest value if asking for no timerel`() = runTest { (1..9).forEachIndexed { index, _ -> val attributeInstance = - gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) .copy( measuredValue = index.toDouble(), time = ZonedDateTime.parse("2022-07-0${index + 1}T00:00:00Z") @@ -301,7 +315,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should only return the last n instances asked in the temporal query`() = runTest { (1..10).forEach { _ -> - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) .copy(measuredValue = 1.0) attributeInstanceService.create(attributeInstance) } @@ -333,10 +347,10 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer temporalEntityAttributeService.create(temporalEntityAttribute2) (1..10).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)) } (1..5).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(temporalEntityAttribute2.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(temporalEntityAttribute2.id)) } val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( @@ -358,7 +372,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should not retrieve any instance if temporal entity does not match`() = runTest { (1..10).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)) } val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( @@ -379,7 +393,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should not retrieve any instance if there is no value in the time interval`() = runTest { (1..10).forEach { _ -> - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create(gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id)) } val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( @@ -397,7 +411,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should update an existing attribute instance with same observation date`() = runTest { - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) attributeInstanceService.create(attributeInstance) @@ -541,8 +555,8 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer } @Test - fun `it should modify attribute instance`() = runTest { - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + fun `it should modify attribute instance for a property`() = runTest { + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) attributeInstanceService.create(attributeInstance) val instanceTemporalFragment = @@ -577,9 +591,48 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer } } + @Test + fun `it should modify attribute instance for a JSON property`() = runTest { + val attributeInstance = gimmeJsonPropertyAttributeInstance(jsonTemporalEntityAttribute.id) + attributeInstanceService.create(attributeInstance) + + val instanceTemporalFragment = + loadSampleData("fragments/temporal_instance_json_fragment.jsonld") + val attributeInstancePayload = + mapOf(LUMINOSITY_COMPACT_JSONPROPERTY to instanceTemporalFragment.deserializeAsMap()) + val jsonLdAttribute = JsonLdUtils.expandJsonLdFragment( + attributeInstancePayload, + APIC_COMPOUND_CONTEXTS + ) as ExpandedAttributes + + val temporalEntitiesQuery = gimmeTemporalEntitiesQuery( + TemporalQuery( + timerel = TemporalQuery.Timerel.AFTER, + timeAt = ZonedDateTime.parse("1970-01-01T00:00:00Z") + ) + ) + + attributeInstanceService.modifyAttributeInstance( + entityId, + LUMINOSITY_JSONPROPERTY, + attributeInstance.instanceId, + jsonLdAttribute.entries.first().value + ).shouldSucceed() + + attributeInstanceService.search(temporalEntitiesQuery, jsonTemporalEntityAttribute) + .shouldSucceedWith { + (it as List).single { result -> + result.time == ZonedDateTime.parse("2023-03-13T12:33:06Z") && + result.payload.deserializeAsMap().containsKey(NGSILD_MODIFIED_AT_PROPERTY) && + result.payload.deserializeAsMap().containsKey(NGSILD_INSTANCE_ID_PROPERTY) && + result.payload.deserializeAsMap().containsKey(NGSILD_JSONPROPERTY_VALUE) + } + } + } + @Test fun `it should delete attribute instance`() = runTest { - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) attributeInstanceService.create(attributeInstance).shouldSucceed() attributeInstanceService.deleteInstance( @@ -597,7 +650,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should not delete attribute instance if attribute name is not found`() = runTest { - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) attributeInstanceService.create(attributeInstance).shouldSucceed() attributeInstanceService.deleteInstance( @@ -618,7 +671,7 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer @Test fun `it should not delete attribute instance if instanceID is not found`() = runTest { - val attributeInstance = gimmeAttributeInstance(incomingTemporalEntityAttribute.id) + val attributeInstance = gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) val instanceId = "urn:ngsi-ld:Instance:notFound".toUri() attributeInstanceService.create(attributeInstance).shouldSucceed() @@ -642,10 +695,12 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer fun `it should delete all instances of an entity`() = runTest { (1..10).forEachIndexed { index, _ -> if (index % 2 == 0) - attributeInstanceService.create(gimmeAttributeInstance(incomingTemporalEntityAttribute.id)) + attributeInstanceService.create( + gimmeNumericPropertyAttributeInstance(incomingTemporalEntityAttribute.id) + ) else attributeInstanceService.create( - gimmeAttributeInstance( + gimmeNumericPropertyAttributeInstance( teaUuid = incomingTemporalEntityAttribute.id, timeProperty = AttributeInstance.TemporalProperty.CREATED_AT ) @@ -680,11 +735,11 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer (1..10).forEachIndexed { index, _ -> if (index % 2 == 0) attributeInstanceService.create( - gimmeAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id) + gimmeNumericPropertyAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id) ) else attributeInstanceService.create( - gimmeAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id) + gimmeNumericPropertyAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id) ) } @@ -707,11 +762,11 @@ class AttributeInstanceServiceTests : WithTimescaleContainer, WithKafkaContainer (1..10).forEachIndexed { index, _ -> if (index % 2 == 0) attributeInstanceService.create( - gimmeAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id) + gimmeNumericPropertyAttributeInstance(teaUuid = incomingTemporalEntityAttribute.id) ) else attributeInstanceService.create( - gimmeAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id) + gimmeNumericPropertyAttributeInstance(teaUuid = outgoingTemporalEntityAttribute.id) ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt index 703e83ddd..324692660 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/AttributeServiceTests.kt @@ -6,6 +6,7 @@ import com.egm.stellio.search.model.AttributeType import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.WithKafkaContainer import com.egm.stellio.search.support.WithTimescaleContainer +import com.egm.stellio.search.support.gimmeEntityPayload import com.egm.stellio.search.util.execute import com.egm.stellio.search.util.toUri import com.egm.stellio.shared.model.APIException @@ -43,9 +44,9 @@ class AttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { private val now = Instant.now().atZone(ZoneOffset.UTC) - private val entityPayload1 = newEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE)) - private val entityPayload2 = newEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE)) - private val entityPayload3 = newEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE)) + private val entityPayload1 = gimmeEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE)) + private val entityPayload2 = gimmeEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE)) + private val entityPayload3 = gimmeEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE)) private val temporalEntityAttribute1 = newTemporalEntityAttribute( "urn:ngsi-ld:BeeHive:TESTA", INCOMING_PROPERTY, @@ -229,17 +230,4 @@ class AttributeServiceTests : WithTimescaleContainer, WithKafkaContainer { .bind("types", entityPayload.types.toTypedArray()) .execute() } - - private fun newEntityPayload( - id: String, - types: List, - contexts: List = APIC_COMPOUND_CONTEXTS - ): EntityPayload = - EntityPayload( - entityId = toUri(id), - types = types, - createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = contexts - ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt index 37e24a183..851f242ef 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityEventServiceTests.kt @@ -7,10 +7,12 @@ import com.egm.stellio.search.model.UpdateResult import com.egm.stellio.search.model.UpdatedDetails import com.egm.stellio.search.support.EMPTY_PAYLOAD import com.egm.stellio.shared.model.* -import com.egm.stellio.shared.util.* +import com.egm.stellio.shared.util.AQUAC_COMPOUND_CONTEXT import com.egm.stellio.shared.util.JsonLdUtils.expandAttribute import com.egm.stellio.shared.util.JsonLdUtils.expandAttributes import com.egm.stellio.shared.util.JsonUtils.serializeObject +import com.egm.stellio.shared.util.matchContent +import com.egm.stellio.shared.util.toUri import com.egm.stellio.shared.web.DEFAULT_TENANT_NAME import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.SpykBean @@ -87,8 +89,7 @@ class EntityEventServiceTests { entityEventService.publishEntityCreateEvent( null, breedingServiceUri, - listOf(breedingServiceType), - listOf(AQUAC_COMPOUND_CONTEXT) + listOf(breedingServiceType) ).join() coVerify { entityEventService.getSerializedEntity(eq(breedingServiceUri)) } @@ -105,8 +106,7 @@ class EntityEventServiceTests { entityEventService.publishEntityReplaceEvent( null, breedingServiceUri, - listOf(breedingServiceType), - listOf(AQUAC_COMPOUND_CONTEXT) + listOf(breedingServiceType) ).join() coVerify { entityEventService.getSerializedEntity(eq(breedingServiceUri)) } @@ -120,11 +120,7 @@ class EntityEventServiceTests { } every { kafkaTemplate.send(any(), any(), any()) } returns CompletableFuture() - entityEventService.publishEntityDeleteEvent( - null, - entityPayload, - listOf(AQUAC_COMPOUND_CONTEXT) - ).join() + entityEventService.publishEntityDeleteEvent(null, entityPayload).join() verify { kafkaTemplate.send("cim.entity._CatchAll", breedingServiceUri.toString(), any()) } } @@ -145,8 +141,7 @@ class EntityEventServiceTests { listOf(UpdatedDetails(fishNumberProperty, null, UpdateOperationResult.APPENDED)), emptyList() ), - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -159,7 +154,7 @@ class EntityEventServiceTests { it.attributeName == fishNumberProperty && it.datasetId == null && it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } ) } @@ -181,8 +176,7 @@ class EntityEventServiceTests { listOf(UpdatedDetails(fishNumberProperty, null, UpdateOperationResult.REPLACED)), emptyList() ), - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -195,7 +189,7 @@ class EntityEventServiceTests { it.attributeName == fishNumberProperty && it.datasetId == null && it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } ) } @@ -229,8 +223,7 @@ class EntityEventServiceTests { breedingServiceUri, jsonLdAttributes, appendResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -246,7 +239,7 @@ class EntityEventServiceTests { it.operationPayload.matchContent( serializedAttributePayload(jsonLdAttributes) ) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } is AttributeReplaceEvent -> @@ -258,7 +251,7 @@ class EntityEventServiceTests { it.operationPayload.matchContent( serializedAttributePayload(jsonLdAttributes, fishNameProperty) ) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } else -> false @@ -295,8 +288,7 @@ class EntityEventServiceTests { breedingServiceUri, jsonLdAttributes, updateResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -314,7 +306,7 @@ class EntityEventServiceTests { serializedAttributePayload(jsonLdAttributes, fishNameProperty) ) ) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } } ) @@ -355,8 +347,7 @@ class EntityEventServiceTests { breedingServiceUri, jsonLdAttributes, updateResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -376,7 +367,7 @@ class EntityEventServiceTests { serializedAttributePayload(jsonLdAttributes, fishNameProperty, 1) ) ) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } } ) @@ -404,8 +395,7 @@ class EntityEventServiceTests { breedingServiceUri, expandedAttribute.toExpandedAttributes(), UpdateResult(updatedDetails, emptyList()), - false, - listOf(AQUAC_COMPOUND_CONTEXT) + false ).join() verify { @@ -417,7 +407,7 @@ class EntityEventServiceTests { it.attributeName == fishNameProperty && it.datasetId == fishName1DatasetUri && it.operationPayload.matchContent(serializedAttributePayload(expandedAttribute)) && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } ) } @@ -436,8 +426,7 @@ class EntityEventServiceTests { breedingServiceUri, fishNameProperty, null, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ).join() verify { @@ -448,7 +437,7 @@ class EntityEventServiceTests { it.entityId == breedingServiceUri && it.entityTypes == listOf(breedingServiceType) && it.attributeName == fishNameProperty && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } } ) @@ -467,8 +456,7 @@ class EntityEventServiceTests { breedingServiceUri, fishNameProperty, fishName1DatasetUri, - false, - listOf(AQUAC_COMPOUND_CONTEXT) + false ).join() verify { @@ -480,7 +468,7 @@ class EntityEventServiceTests { it.entityTypes == listOf(breedingServiceType) && it.attributeName == fishNameProperty && it.datasetId == fishName1DatasetUri && - it.contexts == listOf(AQUAC_COMPOUND_CONTEXT) + it.contexts.isEmpty() } } ) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt index 2bc9952fc..e424251a5 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityOperationServiceTests.kt @@ -271,6 +271,9 @@ class EntityOperationServiceTests { @Test fun `it should ask to replace entities`() = runTest { + coEvery { + temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any()) + } returns Unit.right() coEvery { entityPayloadService.appendAttributes(any(), any(), any(), any()) } returns EMPTY_UPDATE_RESULT.right() @@ -302,6 +305,9 @@ class EntityOperationServiceTests { @Test fun `it should count as error an replace which raises a BadRequestDataException`() = runTest { + coEvery { + temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any()) + } returns Unit.right() coEvery { entityPayloadService.appendAttributes(firstEntityURI, any(), any(), any()) } returns EMPTY_UPDATE_RESULT.right() @@ -326,6 +332,9 @@ class EntityOperationServiceTests { @Test fun `it should count as error not replaced entities in entities`() = runTest { + coEvery { + temporalEntityAttributeService.deleteTemporalAttributesOfEntity(any()) + } returns Unit.right() coEvery { entityPayloadService.appendAttributes(firstEntityURI, any(), any(), any()) } returns EMPTY_UPDATE_RESULT.right() diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt index b8b2a055e..0264dc0bd 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityPayloadServiceTests.kt @@ -157,7 +157,8 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer { entityPayloadService.retrieve(beehiveTestCId) .shouldSucceedWith { - assertEquals(APIC_COMPOUND_CONTEXTS, it.contexts) + assertEquals("urn:ngsi-ld:BeeHive:TESTC".toUri(), it.entityId) + assertEquals(listOf(BEEHIVE_TYPE), it.types) } coVerify { @@ -437,7 +438,6 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer { .hasFieldOrPropertyWithValue("types", listOf(BEEHIVE_TYPE)) .hasFieldOrPropertyWithValue("createdAt", now) .hasFieldOrPropertyWithValue("modifiedAt", null) - .hasFieldOrPropertyWithValue("contexts", listOf(NGSILD_TEST_CORE_CONTEXT)) .hasFieldOrPropertyWithValue("specificAccessPolicy", null) } } @@ -576,7 +576,7 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer { } @Test - fun `it should not add a type if existing types are not in the list of types to add`() = runTest { + fun `it should add a type to an entity even if existing types are not in the list of types to add`() = runTest { loadMinimalEntity(entity01Uri, setOf(BEEHIVE_TYPE)) .sampleDataToNgsiLdEntity() .map { @@ -586,16 +586,15 @@ class EntityPayloadServiceTests : WithTimescaleContainer, WithKafkaContainer { now ) } - entityPayloadService.updateTypes(entity01Uri, listOf(APIARY_TYPE), ngsiLdDateTime(), false) + .shouldSucceed() + + entityPayloadService.retrieve(entity01Uri) .shouldSucceedWith { - assertFalse(it.isSuccessful()) - assertEquals(1, it.notUpdated.size) - val notUpdatedDetails = it.notUpdated[0] - assertEquals(JSONLD_TYPE, notUpdatedDetails.attributeName) + assertEquals(listOf(BEEHIVE_TYPE, APIARY_TYPE), it.types) assertEquals( - "A type cannot be removed from an entity: [$BEEHIVE_TYPE] have been removed", - notUpdatedDetails.reason + listOf(BEEHIVE_TYPE, APIARY_TYPE), + it.payload.asString().deserializeExpandedPayload()[JSONLD_TYPE] ) } } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt index fea6cb905..77fcdf6d0 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityTypeServiceTests.kt @@ -6,6 +6,7 @@ import com.egm.stellio.search.model.AttributeType import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.WithKafkaContainer import com.egm.stellio.search.support.WithTimescaleContainer +import com.egm.stellio.search.support.gimmeEntityPayload import com.egm.stellio.search.util.execute import com.egm.stellio.search.util.toUri import com.egm.stellio.shared.model.APIException @@ -15,9 +16,9 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_TERM import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest +import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -43,33 +44,39 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { private val now = Instant.now().atZone(ZoneOffset.UTC) - private val entityPayload1 = newEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE)) - private val entityPayload2 = newEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE)) - private val entityPayload3 = newEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE)) - private val temporalEntityAttribute1 = newTemporalEntityAttribute( + private val entityPayload1 = gimmeEntityPayload("urn:ngsi-ld:BeeHive:TESTA", listOf(BEEHIVE_TYPE, SENSOR_TYPE)) + private val entityPayload2 = gimmeEntityPayload("urn:ngsi-ld:Sensor:TESTB", listOf(SENSOR_TYPE)) + private val entityPayload3 = gimmeEntityPayload("urn:ngsi-ld:Apiary:TESTC", listOf(APIARY_TYPE)) + private val incomingProperty = newTemporalEntityAttribute( "urn:ngsi-ld:BeeHive:TESTA", INCOMING_PROPERTY, TemporalEntityAttribute.AttributeType.Property, TemporalEntityAttribute.AttributeValueType.NUMBER ) - private val temporalEntityAttribute2 = newTemporalEntityAttribute( + private val managedByRelationship = newTemporalEntityAttribute( "urn:ngsi-ld:BeeHive:TESTA", MANAGED_BY_RELATIONSHIP, TemporalEntityAttribute.AttributeType.Relationship, TemporalEntityAttribute.AttributeValueType.STRING ) - private val temporalEntityAttribute3 = newTemporalEntityAttribute( + private val locationGeoProperty = newTemporalEntityAttribute( "urn:ngsi-ld:Apiary:TESTC", NGSILD_LOCATION_PROPERTY, TemporalEntityAttribute.AttributeType.GeoProperty, TemporalEntityAttribute.AttributeValueType.GEOMETRY ) - private val temporalEntityAttribute4 = newTemporalEntityAttribute( + private val outgoingProperty = newTemporalEntityAttribute( "urn:ngsi-ld:Sensor:TESTB", OUTGOING_PROPERTY, TemporalEntityAttribute.AttributeType.Property, TemporalEntityAttribute.AttributeValueType.GEOMETRY ) + private val luminosityJsonProperty = newTemporalEntityAttribute( + "urn:ngsi-ld:Sensor:TESTB", + LUMINOSITY_JSONPROPERTY, + TemporalEntityAttribute.AttributeType.JsonProperty, + TemporalEntityAttribute.AttributeValueType.OBJECT + ) @AfterEach fun clearPreviousTemporalEntityAttributesAndObservations() { @@ -86,36 +93,35 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { createEntityPayload(entityPayload1) createEntityPayload(entityPayload2) createEntityPayload(entityPayload3) - createTemporalEntityAttribute(temporalEntityAttribute1) - createTemporalEntityAttribute(temporalEntityAttribute2) - createTemporalEntityAttribute(temporalEntityAttribute3) - createTemporalEntityAttribute(temporalEntityAttribute4) + createTemporalEntityAttribute(incomingProperty) + createTemporalEntityAttribute(managedByRelationship) + createTemporalEntityAttribute(locationGeoProperty) + createTemporalEntityAttribute(outgoingProperty) + createTemporalEntityAttribute(luminosityJsonProperty) } @Test - fun `it should return an EntityTypeList`() = runTest { + fun `it should return a list of all known entity types`() = runTest { val entityTypes = entityTypeService.getEntityTypeList(APIC_COMPOUND_CONTEXTS) - assertTrue( - entityTypes.typeList == listOf(APIARY_COMPACT_TYPE, BEEHIVE_COMPACT_TYPE, SENSOR_COMPACT_TYPE) - ) + assertEquals(listOf(APIARY_COMPACT_TYPE, BEEHIVE_COMPACT_TYPE, SENSOR_COMPACT_TYPE), entityTypes.typeList) } @Test - fun `it should return an empty list of types if no entity was found`() = runTest { + fun `it should return an empty list of types if no entity exists`() = runTest { clearPreviousTemporalEntityAttributesAndObservations() val entityTypes = entityTypeService.getEntityTypeList(listOf(AQUAC_COMPOUND_CONTEXT)) - assert(entityTypes.typeList.isEmpty()) + assertThat(entityTypes.typeList).isEmpty() } @Test - fun `it should return a list of EntityType`() = runTest { + fun `it should return all known entity types with details`() = runTest { val entityTypes = entityTypeService.getEntityTypes(APIC_COMPOUND_CONTEXTS) - assertTrue(entityTypes.size == 3) - assertTrue( - entityTypes.containsAll( + assertEquals(3, entityTypes.size) + assertThat(entityTypes) + .containsAll( listOf( EntityType( id = toUri(APIARY_TYPE), @@ -132,59 +138,67 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { typeName = SENSOR_COMPACT_TYPE, attributeNames = listOf( INCOMING_COMPACT_PROPERTY, + LUMINOSITY_COMPACT_JSONPROPERTY, MANAGED_BY_COMPACT_RELATIONSHIP, OUTGOING_COMPACT_PROPERTY ) ) ) ) - ) } @Test - fun `it should return an empty list of EntityTypes if no entity was found`() = runTest { + fun `it should return an empty list of detailed entity types if no entity exists`() = runTest { clearPreviousTemporalEntityAttributesAndObservations() val entityTypes = entityTypeService.getEntityTypes(listOf(AQUAC_COMPOUND_CONTEXT)) - assert(entityTypes.isEmpty()) + assertThat(entityTypes).isEmpty() } @Test - fun `it should return an EntityTypeInfo for a specific type`() = runTest { - val entityTypeInfo = entityTypeService.getEntityTypeInfoByType( - BEEHIVE_TYPE, - APIC_COMPOUND_CONTEXTS - ) + fun `it should return entity type info for a specific type`() = runTest { + val entityTypeInfo = entityTypeService.getEntityTypeInfoByType(SENSOR_TYPE, APIC_COMPOUND_CONTEXTS) entityTypeInfo.shouldSucceedWith { - EntityTypeInfo( - id = toUri(BEEHIVE_TYPE), - typeName = BEEHIVE_COMPACT_TYPE, - entityCount = 1, - attributeDetails = listOf( - AttributeInfo( - id = toUri(INCOMING_PROPERTY), - attributeName = INCOMING_COMPACT_PROPERTY, - attributeTypes = listOf(AttributeType.Property) - ), - AttributeInfo( - id = toUri(MANAGED_BY_RELATIONSHIP), - attributeName = MANAGED_BY_COMPACT_RELATIONSHIP, - attributeTypes = listOf(AttributeType.Relationship) + assertEquals( + EntityTypeInfo( + id = toUri(SENSOR_TYPE), + typeName = SENSOR_COMPACT_TYPE, + entityCount = 2, + attributeDetails = listOf( + AttributeInfo( + id = toUri(INCOMING_PROPERTY), + attributeName = INCOMING_COMPACT_PROPERTY, + attributeTypes = listOf(AttributeType.Property) + ), + AttributeInfo( + id = toUri(LUMINOSITY_JSONPROPERTY), + attributeName = LUMINOSITY_COMPACT_JSONPROPERTY, + attributeTypes = listOf(AttributeType.JsonProperty) + ), + AttributeInfo( + id = toUri(MANAGED_BY_RELATIONSHIP), + attributeName = MANAGED_BY_COMPACT_RELATIONSHIP, + attributeTypes = listOf(AttributeType.Relationship) + ), + AttributeInfo( + id = toUri(OUTGOING_PROPERTY), + attributeName = OUTGOING_COMPACT_PROPERTY, + attributeTypes = listOf(AttributeType.Property) + ) ) - ) + ), + it ) } } @Test - fun `it should error when type doesn't exist`() = runTest { - val entityTypeInfo = - entityTypeService.getEntityTypeInfoByType(TEMPERATURE_PROPERTY, APIC_COMPOUND_CONTEXTS) - - entityTypeInfo.shouldFail { - assertEquals(ResourceNotFoundException(typeNotFoundMessage(TEMPERATURE_PROPERTY)), it) - } + fun `it should return an error when entity type doesn't exist`() = runTest { + entityTypeService.getEntityTypeInfoByType(TEMPERATURE_PROPERTY, APIC_COMPOUND_CONTEXTS) + .shouldFail { + assertEquals(ResourceNotFoundException(typeNotFoundMessage(TEMPERATURE_PROPERTY)), it) + } } private fun createTemporalEntityAttribute( @@ -237,17 +251,4 @@ class EntityTypeServiceTests : WithTimescaleContainer, WithKafkaContainer { .bind("types", entityPayload.types.toTypedArray()) .execute() } - - private fun newEntityPayload( - id: String, - types: List, - contexts: List = APIC_COMPOUND_CONTEXTS - ): EntityPayload = - EntityPayload( - entityId = toUri(id), - types = types, - createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = contexts - ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt index 05eb66203..fa53a9398 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/QueryServiceTests.kt @@ -8,6 +8,7 @@ import com.egm.stellio.search.scope.ScopeService import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.EMPTY_PAYLOAD import com.egm.stellio.search.support.buildDefaultQueryParams +import com.egm.stellio.search.support.gimmeEntityPayload import com.egm.stellio.shared.model.PaginationQuery import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.* @@ -16,7 +17,6 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_CREATED_AT_TERM import com.ninjasquad.springmockk.MockkBean import io.mockk.coEvery import io.mockk.coVerify -import io.r2dbc.postgresql.codec.Json import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat @@ -413,12 +413,9 @@ class QueryServiceTests { }) } - private fun gimmeEntityPayload(): EntityPayload = - EntityPayload( + private fun gimmeEntityPayload() = + gimmeEntityPayload( entityId = entityUri, - types = listOf(BEEHIVE_TYPE), - createdAt = now, - contexts = APIC_COMPOUND_CONTEXTS, - payload = Json.of(loadSampleData("beehive_expanded.jsonld")) + payload = loadSampleData("beehive_expanded.jsonld") ) } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt index b65534660..bda5d024e 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/TemporalEntityAttributeServiceTests.kt @@ -5,6 +5,7 @@ import com.egm.stellio.search.model.* import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.WithKafkaContainer import com.egm.stellio.search.support.WithTimescaleContainer +import com.egm.stellio.search.util.toJson import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.model.toNgsiLdAttribute import com.egm.stellio.shared.model.toNgsiLdAttributes @@ -56,7 +57,6 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon entityId = beehiveTestCId, types = listOf(BEEHIVE_TYPE), createdAt = Instant.now().atZone(UTC), - contexts = APIC_COMPOUND_CONTEXTS, payload = EMPTY_JSON_PAYLOAD ) ).block() @@ -66,7 +66,6 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon entityId = beehiveTestDId, types = listOf(BEEHIVE_TYPE), createdAt = Instant.now().atZone(UTC), - contexts = APIC_COMPOUND_CONTEXTS, payload = EMPTY_JSON_PAYLOAD ) ).block() @@ -282,6 +281,65 @@ class TemporalEntityAttributeServiceTests : WithTimescaleContainer, WithKafkaCon } } + @Test + fun `it should merge an attribute payload for a JsonProperty`() = runTest { + val initialJsonValue = """ + { + "incoming": { + "type": "JsonProperty", + "json": { "id": 1, "b": null, "c": 12.4 }, + "observedAt": "2022-12-24T14:01:22.066Z", + "subAttribute": { + "type": "Property", + "value": "subAttribute" + } + } + } + """.trimIndent() + val temporalEntityAttribute = TemporalEntityAttribute( + entityId = beehiveTestCId, + attributeName = INCOMING_PROPERTY, + attributeType = TemporalEntityAttribute.AttributeType.JsonProperty, + attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON, + createdAt = ngsiLdDateTime(), + payload = expandAttribute(initialJsonValue, APIC_COMPOUND_CONTEXTS).second[0].toJson() + ) + + val newJsonValue = """ + { + "incoming": { + "type": "JsonProperty", + "json": { "id": 2, "b": "something" }, + "observedAt": "2023-12-24T14:01:22.066Z" + } + } + """.trimIndent() + val newJsonExpandedAttribute = expandAttribute(newJsonValue, APIC_COMPOUND_CONTEXTS).second[0] + val (_, expandedAttributeInstance) = temporalEntityAttributeService.mergeAttributePayload( + temporalEntityAttribute, + newJsonExpandedAttribute + ) + + val expectedJsonValue = """ + { + "incoming": { + "type": "JsonProperty", + "json": { "id": 2, "b": "something" }, + "observedAt": "2023-12-24T14:01:22.066Z", + "subAttribute": { + "type": "Property", + "value": "subAttribute" + } + } + } + """.trimIndent() + val expectedJsonExpandedAttribute = expandAttribute(expectedJsonValue, APIC_COMPOUND_CONTEXTS).second[0] + assertJsonPayloadsAreEqual( + serializeObject(expectedJsonExpandedAttribute), + serializeObject(expandedAttributeInstance) + ) + } + @Test fun `it should merge an entity attribute`() = runTest { val rawEntity = loadSampleData() diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt index 9c4cc2090..80097011a 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/BusinessObjectsFactory.kt @@ -1,16 +1,36 @@ package com.egm.stellio.search.support import com.egm.stellio.search.model.* +import com.egm.stellio.shared.model.ExpandedTerm import com.egm.stellio.shared.model.PaginationQuery import com.egm.stellio.shared.model.addNonReifiedTemporalProperty import com.egm.stellio.shared.model.getSingleEntry -import com.egm.stellio.shared.util.APIC_COMPOUND_CONTEXTS -import com.egm.stellio.shared.util.JsonLdUtils -import com.egm.stellio.shared.util.ngsiLdDateTime +import com.egm.stellio.shared.util.* +import io.r2dbc.postgresql.codec.Json +import java.net.URI import java.util.UUID import kotlin.random.Random -fun gimmeAttributeInstance( +fun gimmeEntityPayload( + entityId: String, + types: List = listOf(BEEHIVE_TYPE), + payload: String = EMPTY_PAYLOAD +): EntityPayload = + gimmeEntityPayload(entityId.toUri(), types, payload) + +fun gimmeEntityPayload( + entityId: URI, + types: List = listOf(BEEHIVE_TYPE), + payload: String = EMPTY_PAYLOAD +): EntityPayload = + EntityPayload( + entityId = entityId, + types = types, + createdAt = ngsiLdDateTime(), + payload = Json.of(payload) + ) + +fun gimmeNumericPropertyAttributeInstance( teaUuid: UUID, timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT ): AttributeInstance { @@ -36,6 +56,32 @@ fun gimmeAttributeInstance( ) } +fun gimmeJsonPropertyAttributeInstance( + teaUuid: UUID, + timeProperty: AttributeInstance.TemporalProperty = AttributeInstance.TemporalProperty.OBSERVED_AT +): AttributeInstance { + val attributeMetadata = AttributeMetadata( + measuredValue = null, + value = SAMPLE_JSON_PROPERTY_PAYLOAD.asString(), + geoValue = null, + valueType = TemporalEntityAttribute.AttributeValueType.JSON, + datasetId = null, + type = TemporalEntityAttribute.AttributeType.JsonProperty, + observedAt = ngsiLdDateTime() + ) + val payload = JsonLdUtils.buildExpandedPropertyValue(attributeMetadata.value!!) + .addNonReifiedTemporalProperty(JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY, attributeMetadata.observedAt!!) + .getSingleEntry() + + return AttributeInstance( + temporalEntityAttribute = teaUuid, + time = attributeMetadata.observedAt!!, + attributeMetadata = attributeMetadata, + timeProperty = timeProperty, + payload = payload + ) +} + fun gimmeTemporalEntitiesQuery( temporalQuery: TemporalQuery, withTemporalValues: Boolean = false, diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt index 518b59801..7c31f6346 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/support/TestUtils.kt @@ -50,3 +50,8 @@ suspend fun buildSapAttribute(specificAccessPolicy: AuthContextModel.SpecificAcc const val EMPTY_PAYLOAD = "{}" val EMPTY_JSON_PAYLOAD = Json.of(EMPTY_PAYLOAD) +val SAMPLE_JSON_PROPERTY_PAYLOAD = Json.of( + """ + { "id": "123", "stringValue": "value", "nullValue": null } + """.trimIndent() +) diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt index a44cb9e32..8329bc3dc 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/AttributeInstanceUtilsTests.kt @@ -134,6 +134,22 @@ class AttributeInstanceUtilsTests { ) } + @Test + fun `it should guess the value type of a JSON property`() = runTest { + val expandedJsonProperty = expandAttribute( + "jsonProperty", + mapOf( + "type" to "JsonProperty", + "json" to mapOf("key" to "value") + ), + NGSILD_TEST_CORE_CONTEXTS + ) + assertEquals( + TemporalEntityAttribute.AttributeValueType.JSON, + guessAttributeValueType(TemporalEntityAttribute.AttributeType.JsonProperty, expandedJsonProperty.second[0]) + ) + } + @Test fun `it should guess the value type of a relationship`() = runTest { val expandedGeoRelationship = expandAttribute( diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt index cb709ed23..8dca9b1bc 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntitiesParameterizedSource.kt @@ -3,7 +3,10 @@ package com.egm.stellio.search.util import com.egm.stellio.search.model.* import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD import com.egm.stellio.search.support.buildAttributeInstancePayload -import com.egm.stellio.shared.util.* +import com.egm.stellio.shared.util.BEEHIVE_TYPE +import com.egm.stellio.shared.util.JsonLdUtils +import com.egm.stellio.shared.util.loadSampleData +import com.egm.stellio.shared.util.toUri import org.junit.jupiter.params.provider.Arguments import java.time.Instant import java.time.ZoneOffset @@ -24,8 +27,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( @@ -49,8 +51,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( @@ -78,8 +79,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( @@ -110,8 +110,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( @@ -146,8 +145,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( @@ -185,8 +183,7 @@ class TemporalEntitiesParameterizedSource { entityId = "urn:ngsi-ld:BeeHive:TESTD".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ), emptyList(), mapOf( diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt index 8c7651b4e..f03433213 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityBuilderTests.kt @@ -37,8 +37,7 @@ class TemporalEntityBuilderTests { entityId = "urn:ngsi-ld:Beehive:1234".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ) val temporalEntity = TemporalEntityBuilder.buildTemporalEntity( EntityTemporalResult(entityPayload, emptyList(), attributeAndResultsMap), @@ -71,8 +70,7 @@ class TemporalEntityBuilderTests { entityId = "urn:ngsi-ld:BeeHive:TESTC".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ) val temporalEntity = TemporalEntityBuilder.buildTemporalEntity( @@ -177,8 +175,7 @@ class TemporalEntityBuilderTests { entityId = "urn:ngsi-ld:Beehive:1234".toUri(), types = listOf(BEEHIVE_TYPE), createdAt = now, - payload = EMPTY_JSON_PAYLOAD, - contexts = APIC_COMPOUND_CONTEXTS + payload = EMPTY_JSON_PAYLOAD ) val temporalEntity = TemporalEntityBuilder.buildTemporalEntity( diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt index d617e2fd3..3ec633e69 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/TemporalEntityParameterizedSource.kt @@ -5,6 +5,7 @@ import com.egm.stellio.search.scope.FullScopeInstanceResult import com.egm.stellio.search.scope.ScopeInstanceResult import com.egm.stellio.search.scope.SimplifiedScopeInstanceResult import com.egm.stellio.search.support.EMPTY_JSON_PAYLOAD +import com.egm.stellio.search.support.SAMPLE_JSON_PROPERTY_PAYLOAD import com.egm.stellio.search.support.buildAttributeInstancePayload import com.egm.stellio.shared.util.JsonLdUtils import com.egm.stellio.shared.util.loadSampleData @@ -539,6 +540,41 @@ class TemporalEntityParameterizedSource { loadSampleData("expectations/beehive_scope_multi_instances.jsonld") ) + private val beehiveJsonPropertyTemporalValues = + Arguments.arguments( + emptyList(), + mapOf( + TemporalEntityAttribute( + entityId = entityId, + attributeName = "https://ontology.eglobalmark.com/apic#luminosity", + attributeType = TemporalEntityAttribute.AttributeType.JsonProperty, + attributeValueType = TemporalEntityAttribute.AttributeValueType.JSON, + datasetId = null, + createdAt = now, + payload = SAMPLE_JSON_PROPERTY_PAYLOAD + ) to + listOf( + SimplifiedAttributeInstanceResult( + temporalEntityAttribute = UUID.randomUUID(), + value = """ + { "id": "123", "stringValue": "value", "nullValue": null } + """, + time = ZonedDateTime.parse("2020-03-25T08:29:17.965206Z") + ), + SimplifiedAttributeInstanceResult( + temporalEntityAttribute = UUID.randomUUID(), + value = """ + { "id": "456", "stringValue": "anotherValue" } + """, + time = ZonedDateTime.parse("2020-03-25T08:33:17.965206Z") + ) + ) + ), + true, + false, + loadSampleData("expectations/beehive_json_property_temporal_values.jsonld") + ) + @JvmStatic fun rawResultsProvider(): Stream { return Stream.of( @@ -554,7 +590,8 @@ class TemporalEntityParameterizedSource { beehivePropertyMultiInstancesWithoutDatasetIdStringValuesTemporalValues, beehiveRelationshipMultiInstancesTemporalValues, beehiveScopeMultiInstancesTemporalValues, - beehiveScopeMultiInstances + beehiveScopeMultiInstances, + beehiveJsonPropertyTemporalValues ) } } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt index 989ef4144..05cc710cb 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityHandlerTests.kt @@ -100,7 +100,7 @@ class EntityHandlerTests { entityPayloadService.createEntity(any(), any(), any()) } returns Unit.right() coEvery { authorizationService.createAdminRight(any(), any()) } returns Unit.right() - coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job() webClient.post() .uri("/ngsi-ld/v1/entities") @@ -125,8 +125,7 @@ class EntityHandlerTests { entityEventService.publishEntityCreateEvent( eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"), eq(breedingServiceId), - eq(listOf(breedingServiceType)), - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + eq(listOf(breedingServiceType)) ) } } @@ -1153,7 +1152,7 @@ class EntityHandlerTests { coEvery { entityPayloadService.replaceEntity(any(), any(), any(), any()) } returns Unit.right() - coEvery { entityEventService.publishEntityReplaceEvent(any(), any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityReplaceEvent(any(), any(), any()) } returns Job() webClient.put() .uri("/ngsi-ld/v1/entities/$breedingServiceId") @@ -1177,8 +1176,7 @@ class EntityHandlerTests { entityEventService.publishEntityReplaceEvent( eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"), eq(breedingServiceId), - eq(listOf(breedingServiceType)), - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + eq(listOf(breedingServiceType)) ) } } @@ -1274,7 +1272,7 @@ class EntityHandlerTests { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() } @@ -1322,8 +1320,7 @@ class EntityHandlerTests { eq(entityId), any(), appendResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ) } } @@ -1379,8 +1376,7 @@ class EntityHandlerTests { eq(entityId), any(), appendResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ) } } @@ -1422,8 +1418,7 @@ class EntityHandlerTests { eq(entityId), any(), appendTypeResult, - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ) } } @@ -1471,8 +1466,7 @@ class EntityHandlerTests { eq(entityId), any(), appendTypeResult.mergeWith(appendResult), - true, - listOf(AQUAC_COMPOUND_CONTEXT) + true ) } } @@ -1599,7 +1593,7 @@ class EntityHandlerTests { entityPayloadService.partialUpdateAttribute(any(), any(), any()) } returns updateResult.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any()) } returns Job() webClient.patch() @@ -1621,8 +1615,7 @@ class EntityHandlerTests { eq(entityId), any(), eq(updateResult), - eq(false), - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + eq(false) ) } } @@ -1744,7 +1737,7 @@ class EntityHandlerTests { entityPayloadService.mergeEntity(any(), any(), any(), any()) } returns updateResult.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.patch() @@ -1766,8 +1759,7 @@ class EntityHandlerTests { eq(entityId), any(), eq(updateResult), - true, - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + true ) } } @@ -1798,7 +1790,7 @@ class EntityHandlerTests { entityPayloadService.mergeEntity(any(), any(), any(), any()) } returns updateResult.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.patch() @@ -1825,8 +1817,7 @@ class EntityHandlerTests { eq(entityId), any(), eq(updateResult), - true, - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + true ) } } @@ -1951,7 +1942,7 @@ class EntityHandlerTests { entityPayloadService.updateAttributes(any(), any(), any()) } returns updateResult.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.patch() @@ -1973,8 +1964,7 @@ class EntityHandlerTests { eq(entityId), any(), eq(updateResult), - true, - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + true ) } } @@ -1997,7 +1987,7 @@ class EntityHandlerTests { notUpdated = arrayListOf(notUpdatedAttribute) ).right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.patch() @@ -2024,7 +2014,7 @@ class EntityHandlerTests { notUpdated = listOf(NotUpdatedDetails("type", "A type cannot be removed")) ).right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.patch() @@ -2065,7 +2055,7 @@ class EntityHandlerTests { { "type": "https://uri.etsi.org/ngsi-ld/errors/LdContextNotAvailable", "title": "A remote JSON-LD @context referenced in a request cannot be retrieved by the NGSI-LD Broker and expansion or compaction cannot be performed", - "detail": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED].])" + "detail": "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There wa a problem encountered loading a remote context [https://easyglobalmarket.com/contexts/diat.jsonld]])" } """.trimIndent() ) @@ -2135,11 +2125,10 @@ class EntityHandlerTests { coEvery { entityPayloadService.checkEntityExistence(beehiveId) } returns Unit.right() coEvery { entityPayloadService.retrieve(any()) } returns entity.right() every { entity.types } returns listOf(BEEHIVE_TYPE) - every { entity.contexts } returns APIC_COMPOUND_CONTEXTS coEvery { authorizationService.userCanAdminEntity(beehiveId, sub) } returns Unit.right() coEvery { entityPayloadService.deleteEntity(any()) } returns Unit.right() coEvery { authorizationService.removeRightsOnEntity(any()) } returns Unit.right() - coEvery { entityEventService.publishEntityDeleteEvent(any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityDeleteEvent(any(), any()) } returns Job() webClient.delete() .uri("/ngsi-ld/v1/entities/$beehiveId") @@ -2157,8 +2146,7 @@ class EntityHandlerTests { coVerify { entityEventService.publishEntityDeleteEvent( eq("60AAEBA3-C0C7-42B6-8CB0-0D30857F210E"), - eq(entity), - eq(APIC_COMPOUND_CONTEXTS) + eq(entity) ) } } @@ -2242,7 +2230,7 @@ class EntityHandlerTests { coEvery { entityPayloadService.getTypes(any()) } returns listOf(BEEHIVE_TYPE).right() coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right() coEvery { - entityEventService.publishAttributeDeleteEvent(any(), any(), any(), any(), any(), any()) + entityEventService.publishAttributeDeleteEvent(any(), any(), any(), any(), any()) } returns Job() } @@ -2275,8 +2263,7 @@ class EntityHandlerTests { eq(beehiveId), eq(TEMPERATURE_PROPERTY), isNull(), - eq(false), - eq(APIC_COMPOUND_CONTEXTS) + eq(false) ) } } @@ -2310,8 +2297,7 @@ class EntityHandlerTests { eq(beehiveId), eq(TEMPERATURE_PROPERTY), isNull(), - eq(true), - eq(APIC_COMPOUND_CONTEXTS) + eq(true) ) } } @@ -2345,8 +2331,7 @@ class EntityHandlerTests { eq(beehiveId), eq(TEMPERATURE_PROPERTY), eq(datasetId.toUri()), - eq(false), - eq(APIC_COMPOUND_CONTEXTS) + eq(false) ) } } @@ -2450,7 +2435,7 @@ class EntityHandlerTests { coEvery { entityPayloadService.checkEntityExistence(any()) } returns Unit.right() coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right() coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any(), any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), any()) } returns Job() } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt index d07ae2e0b..01492e202 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/web/EntityOperationHandlerTests.kt @@ -91,29 +91,29 @@ class EntityOperationHandlerTests { mockedTemperatureSensorEntity = mockkClass(NgsiLdEntity::class) { every { id } returns temperatureSensorUri every { types } returns listOf(SENSOR_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } mockedTemperatureSensorExpandedEntity = mockkClass(ExpandedEntity::class) { every { id } returns temperatureSensorUri.toString() every { members } returns emptyMap() + every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } mockedDissolvedOxygenSensorEntity = mockkClass(NgsiLdEntity::class) { every { id } returns dissolvedOxygenSensorUri every { types } returns listOf(SENSOR_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } mockedDissolvedOxygenSensorExpandedEntity = mockkClass(ExpandedEntity::class) { every { id } returns dissolvedOxygenSensorUri.toString() every { members } returns emptyMap() + every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } mockedDeviceEntity = mockkClass(NgsiLdEntity::class) { every { id } returns deviceUri every { types } returns listOf(DEVICE_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } mockedDeviceExpandedEntity = mockkClass(ExpandedEntity::class) { every { id } returns deviceUri.toString() every { members } returns emptyMap() + every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } } @@ -129,8 +129,6 @@ class EntityOperationHandlerTests { private val batchDeleteEndpoint = "/ngsi-ld/v1/entityOperations/delete" private val queryEntitiesEndpoint = "/ngsi-ld/v1/entityOperations/query" - private val hcmrContext = listOf(AQUAC_COMPOUND_CONTEXT) - @Test fun `update batch entity should return a 204 if JSON-LD payload is correct`() = runTest { val jsonLdFile = ClassPathResource("/ngsild/hcmr/HCMR_test_file.json") @@ -331,8 +329,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityCreateEvent( any(), capture(capturedEntitiesIds), - capture(capturedEntityTypes), - any() + capture(capturedEntityTypes) ) } returns Job() @@ -349,7 +346,7 @@ class EntityOperationHandlerTests { coVerify { authorizationService.createAdminRights(allEntitiesUris, sub) } coVerify(timeout = 1000, exactly = 3) { - entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) + entityEventService.publishEntityCreateEvent(any(), any(), any()) } capturedEntitiesIds.forEach { assertTrue(it in allEntitiesUris) } assertTrue(capturedEntityTypes.captured[0] in listOf(SENSOR_TYPE, DEVICE_TYPE)) @@ -389,8 +386,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityCreateEvent( any(), capture(capturedEntitiesIds), - capture(capturedEntityTypes), - any() + capture(capturedEntityTypes) ) } returns Job() @@ -418,7 +414,7 @@ class EntityOperationHandlerTests { coVerify { authorizationService.createAdminRights(createdEntitiesIds, sub) } coVerify(timeout = 1000, exactly = 2) { - entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) + entityEventService.publishEntityCreateEvent(any(), any(), any()) } capturedEntitiesIds.forEach { assertTrue(it in createdEntitiesIds) } assertTrue(capturedEntityTypes.captured[0] in listOf(SENSOR_TYPE, DEVICE_TYPE)) @@ -456,8 +452,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityCreateEvent( any(), capture(capturedEntitiesIds), - capture(capturedEntityTypes), - any() + capture(capturedEntityTypes) ) } returns Job() @@ -572,12 +567,12 @@ class EntityOperationHandlerTests { coEvery { authorizationService.userCanCreateEntities(any()) } returns Unit.right() coEvery { entityOperationService.create(any(), any()) } returns createdBatchResult coEvery { authorizationService.createAdminRights(any(), eq(sub)) } returns Unit.right() - coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job() coEvery { authorizationService.userCanUpdateEntity(any(), sub) } returns Unit.right() coEvery { entityOperationService.update(any(), any(), any()) } returns updatedBatchResult coEvery { - entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true, any()) + entityEventService.publishAttributeChangeEvents(any(), any(), any(), any(), true) } returns Job() webClient.post() @@ -593,8 +588,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityCreateEvent( eq(sub.value), match { it in createdEntitiesIds }, - eq(listOf(SENSOR_TYPE)), - eq(hcmrContext) + eq(listOf(SENSOR_TYPE)) ) } coVerify(timeout = 1000, exactly = 2) { @@ -603,8 +597,7 @@ class EntityOperationHandlerTests { match { it in updatedEntitiesIds }, any(), match { it in updatedBatchResult.success.map { it.updateResult } }, - true, - hcmrContext + true ) } } @@ -695,7 +688,7 @@ class EntityOperationHandlerTests { ) coVerify { authorizationService.createAdminRights(listOf(deviceUri), sub) } - coVerify(exactly = 1) { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } + coVerify(exactly = 1) { entityEventService.publishEntityCreateEvent(any(), any(), any()) } } @Test @@ -719,7 +712,7 @@ class EntityOperationHandlerTests { entitiesIds.map { BatchEntitySuccess(it) }.toMutableList(), arrayListOf() ) - coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityCreateEvent(any(), any(), any()) } returns Job() webClient.post() .uri(batchUpsertEndpoint) @@ -734,8 +727,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityReplaceEvent( eq(sub.value), match { it in entitiesIds }, - eq(listOf(SENSOR_TYPE)), - eq(hcmrContext) + eq(listOf(SENSOR_TYPE)) ) } } @@ -833,8 +825,7 @@ class EntityOperationHandlerTests { entityEventService.publishEntityReplaceEvent( eq(sub.value), eq(temperatureSensorUri), - eq(listOf(SENSOR_TYPE)), - eq(hcmrContext) + eq(listOf(SENSOR_TYPE)) ) } } @@ -885,20 +876,17 @@ class EntityOperationHandlerTests { mockkClass(EntityPayload::class, relaxed = true) { every { entityId } returns dissolvedOxygenSensorUri every { types } returns listOf(SENSOR_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) }, mockkClass(EntityPayload::class, relaxed = true) { every { entityId } returns temperatureSensorUri every { types } returns listOf(SENSOR_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) }, mockkClass(EntityPayload::class, relaxed = true) { every { entityId } returns deviceUri every { types } returns listOf(DEVICE_TYPE) - every { contexts } returns listOf(AQUAC_COMPOUND_CONTEXT) } ) - coEvery { entityEventService.publishEntityDeleteEvent(any(), any(), any()) } returns Job() + coEvery { entityEventService.publishEntityDeleteEvent(any(), any()) } returns Job() webClient.post() .uri(batchDeleteEndpoint) @@ -909,8 +897,7 @@ class EntityOperationHandlerTests { coVerify(timeout = 1000, exactly = 3) { entityEventService.publishEntityDeleteEvent( eq(sub.value), - any(), - eq(listOf(AQUAC_COMPOUND_CONTEXT)) + any() ) } } diff --git a/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld b/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld new file mode 100644 index 000000000..cb2cd5634 --- /dev/null +++ b/search-service/src/test/resources/ngsild/expectations/beehive_json_property_temporal_values.jsonld @@ -0,0 +1,56 @@ +{ + "@id": "urn:ngsi-ld:BeeHive:TESTC", + "@type": [ + "https://ontology.eglobalmark.com/apic#BeeHive" + ], + "https://ontology.eglobalmark.com/apic#luminosity": [ + { + "@type": [ + "https://uri.etsi.org/ngsi-ld/JsonProperty" + ], + "https://uri.etsi.org/ngsi-ld/jsons": [ + { + "@list": [ + { + "@list": [ + { + "https://uri.etsi.org/ngsi-ld/hasJSON": [ + { + "@type": "@json", + "@value": { + "id": "123", + "stringValue": "value", + "nullValue": null + } + } + ] + }, + { + "@value": "2020-03-25T08:29:17.965206Z" + } + ] + }, + { + "@list": [ + { + "https://uri.etsi.org/ngsi-ld/hasJSON": [ + { + "@type": "@json", + "@value": { + "id": "456", + "stringValue": "anotherValue" + } + } + ] + }, + { + "@value": "2020-03-25T08:33:17.965206Z" + } + ] + } + ] + } + ] + } + ] +} diff --git a/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld b/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld new file mode 100644 index 000000000..ed94b972f --- /dev/null +++ b/search-service/src/test/resources/ngsild/fragments/temporal_instance_json_fragment.jsonld @@ -0,0 +1,8 @@ +{ + "type": "JsonProperty", + "json": { + "key": "value", + "type": "unknown" + }, + "observedAt": "2023-03-13T12:33:06Z" +} diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 112b58960..62070e515 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -26,7 +26,7 @@ dependencies { testFixturesImplementation("org.springframework.security:spring-security-oauth2-jose") testFixturesImplementation("org.springframework.security:spring-security-test") testFixturesImplementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") - testFixturesImplementation("io.arrow-kt:arrow-fx-coroutines:1.2.1") + testFixturesImplementation("io.arrow-kt:arrow-fx-coroutines:1.2.3") testFixturesImplementation("org.springframework.boot:spring-boot-starter-test") { // to ensure we are using mocks and spies from springmockk lib instead exclude(module = "mockito-core") @@ -34,7 +34,7 @@ dependencies { testFixturesImplementation("org.mock-server:mockserver-netty-no-dependencies:5.15.0") testFixturesImplementation("org.mock-server:mockserver-client-java-no-dependencies:5.15.0") - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5") testFixturesApi("org.testcontainers:testcontainers") testFixturesApi("org.testcontainers:junit-jupiter") diff --git a/shared/config/detekt/baseline.xml b/shared/config/detekt/baseline.xml index 4f8418074..c3deb4468 100644 --- a/shared/config/detekt/baseline.xml +++ b/shared/config/detekt/baseline.xml @@ -3,14 +3,10 @@ CyclomaticComplexMethod:ExceptionHandler.kt$ExceptionHandler$@ExceptionHandler fun transformErrorResponse(throwable: Throwable): ResponseEntity<ProblemDetail> - LongMethod:ExpandedEntityTests.kt$ExpandedEntityTests$@Test fun `it should return simplified GeoJSON entities`() + LongMethod:CompactedEntityTests.kt$CompactedEntityTests$@Test fun `it should return the GeoJSON representation of simplified entities`() LongMethod:QueryUtils.kt$private fun transformQQueryToSqlJsonPath( mainAttributePath: List<ExpandedTerm>, trailingAttributePath: List<ExpandedTerm>, operator: String, value: String ) LongParameterList:ApiResponses.kt$( body: String, count: Int, resourceUrl: String, paginationQuery: PaginationQuery, requestParams: MultiValueMap<String, String>, mediaType: MediaType, contexts: List<String> ) LongParameterList:ApiResponses.kt$( entities: Any, count: Int, resourceUrl: String, paginationQuery: PaginationQuery, requestParams: MultiValueMap<String, String>, mediaType: MediaType, contexts: List<String> ) - LongParameterList:NgsiLdEntity.kt$NgsiLdEntity$( val id: URI, val types: List<ExpandedTerm>, val scopes: List<String>?, val relationships: List<NgsiLdRelationship>, val properties: List<NgsiLdProperty>, val geoProperties: List<NgsiLdGeoProperty>, val contexts: List<String> ) - LongParameterList:NgsiLdEntity.kt$NgsiLdGeoPropertyInstance$( val coordinates: WKTCoordinates, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> ) - LongParameterList:NgsiLdEntity.kt$NgsiLdPropertyInstance$( val value: Any, val unitCode: String?, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> ) - LongParameterList:NgsiLdEntity.kt$NgsiLdRelationshipInstance$( val objectId: URI, createdAt: ZonedDateTime?, modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, properties: List<NgsiLdProperty>, relationships: List<NgsiLdRelationship> ) SpreadOperator:EntityEvent.kt$EntityEvent$( *[ JsonSubTypes.Type(value = EntityCreateEvent::class), JsonSubTypes.Type(value = EntityReplaceEvent::class), JsonSubTypes.Type(value = EntityDeleteEvent::class), JsonSubTypes.Type(value = AttributeAppendEvent::class), JsonSubTypes.Type(value = AttributeReplaceEvent::class), JsonSubTypes.Type(value = AttributeUpdateEvent::class), JsonSubTypes.Type(value = AttributeDeleteEvent::class), JsonSubTypes.Type(value = AttributeDeleteAllInstancesEvent::class) ] ) SwallowedException:JsonLdUtils.kt$JsonLdUtils$e: JsonLdError TooManyFunctions:JsonLdUtils.kt$JsonLdUtils diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt index cbbcc998f..781701e73 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt @@ -3,12 +3,13 @@ package com.egm.stellio.shared.model import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_CONTEXT import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID_TERM +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_JSON_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_OBJECT 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_GEOPROPERTY_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.JsonLdUtils.NGSILD_DATASET_ID_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NONE_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SYSATTRS_TERMS typealias CompactedEntity = Map @@ -21,23 +22,36 @@ private fun simplifyRepresentation(value: Any): Any = // an attribute with a single instance is Map<*, *> -> simplifyValue(value as Map) // an attribute with multiple instances - is List<*> -> value.map { - when (it) { - is Map<*, *> -> simplifyValue(it as Map) + is List<*> -> { + when (value.first()) { + is Map<*, *> -> simplifyMultiInstanceAttribute(value as List>) // we keep @context value as it is (List) - else -> it + else -> value } } // keep id, type and other non-reified properties as they are (typically string or list) else -> value } -private fun simplifyValue(value: Map): Any = - when (value[JSONLD_TYPE_TERM]) { - NGSILD_PROPERTY_TERM, NGSILD_GEOPROPERTY_TERM -> value.getOrDefault(JSONLD_VALUE_TERM, value) - NGSILD_RELATIONSHIP_TERM -> value.getOrDefault(JSONLD_OBJECT, value) - else -> value +private fun simplifyMultiInstanceAttribute(value: List>): Map> { + val datasetIds = value.map { + val datasetId = (it[NGSILD_DATASET_ID_TERM] as? String) ?: NGSILD_NONE_TERM + val datasetValue: Any = simplifyValue(it) + Pair(datasetId, datasetValue) + } + return mapOf(NGSILD_DATASET_TERM to datasetIds.toMap()) +} + +private fun simplifyValue(value: Map): Any { + val attributeCompactedType = AttributeCompactedType.forKey(value[JSONLD_TYPE_TERM] as String)!! + return when (attributeCompactedType) { + AttributeCompactedType.PROPERTY, AttributeCompactedType.GEOPROPERTY -> { + value.getOrDefault(JSONLD_VALUE_TERM, value) + } + AttributeCompactedType.JSONPROPERTY -> mapOf(JSONLD_JSON_TERM to value.getOrDefault(JSONLD_JSON_TERM, value)) + AttributeCompactedType.RELATIONSHIP -> value.getOrDefault(JSONLD_OBJECT, value) } +} fun CompactedEntity.toGeoJson(geometryProperty: String): Map { val geometryAttributeContent = this[geometryProperty] as? Map @@ -117,3 +131,15 @@ fun List.toFinalRepresentation( ) } else it } + +enum class AttributeCompactedType(val key: String) { + PROPERTY("Property"), + RELATIONSHIP("Relationship"), + GEOPROPERTY("GeoProperty"), + JSONPROPERTY("JsonProperty"); + + companion object { + fun forKey(key: String): AttributeCompactedType? = + entries.find { it.key == key } + } +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt index 05543e2ec..16669b8fe 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdEntity.kt @@ -1,7 +1,6 @@ package com.egm.stellio.shared.model import arrow.core.Either -import arrow.core.flatten import arrow.core.left import arrow.core.raise.either import arrow.core.raise.ensure @@ -11,11 +10,12 @@ import arrow.fx.coroutines.parMap import com.egm.stellio.shared.util.AttributeType import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID 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_GEOPROPERTY_TYPE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_VALUE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_TYPE +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_VALUE import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_MODIFIED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TYPE @@ -24,7 +24,6 @@ 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_SCOPE_PROPERTY import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_UNIT_CODE_PROPERTY -import com.egm.stellio.shared.util.ngsiLdDateTime import com.egm.stellio.shared.util.toUri import java.net.URI import java.time.ZonedDateTime @@ -33,15 +32,11 @@ class NgsiLdEntity private constructor( val id: URI, val types: List, val scopes: List?, - val relationships: List, - val properties: List, - val geoProperties: List, - val contexts: List + val attributes: List ) { companion object { suspend fun create( - parsedKeys: Map, - contexts: List + parsedKeys: Map ): Either = either { ensure(parsedKeys.containsKey(JSONLD_ID)) { BadRequestDataException("The provided NGSI-LD entity does not contain an id property") @@ -55,34 +50,32 @@ class NgsiLdEntity private constructor( val scopes = (parsedKeys as Map>).getScopes() - val attributes = getNonCoreAttributes(parsedKeys, NGSILD_ENTITY_CORE_MEMBERS) - val relationships = getAttributesOfType(attributes, NGSILD_RELATIONSHIP_TYPE).bind() - val properties = getAttributesOfType(attributes, NGSILD_PROPERTY_TYPE).bind() - val geoProperties = getAttributesOfType(attributes, NGSILD_GEOPROPERTY_TYPE).bind() - ensure(attributes.size == relationships.size + properties.size + geoProperties.size) { - val attributesWithUnknownTypes = - attributes.keys.minus(setOf(relationships, properties, geoProperties).flatten().map { it.name }) - BadRequestDataException("Entity has attribute(s) with an unknown type: $attributesWithUnknownTypes") - } + val rawAttributes = getNonCoreMembers(parsedKeys, NGSILD_ENTITY_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() - NgsiLdEntity(id, types, scopes, relationships, properties, geoProperties, contexts) + NgsiLdEntity(id, types, scopes, attributes) } } - val attributes: List = properties.plus(relationships).plus(geoProperties) + inline fun getAttributesOfType(): List = attributes.filterIsInstance() + + val properties = getAttributesOfType() + val relationships = getAttributesOfType() + val geoProperties = getAttributesOfType() + val jsonProperties = getAttributesOfType() } -sealed class NgsiLdAttribute(val name: String) { +sealed class NgsiLdAttribute(val name: ExpandedTerm) { abstract fun getAttributeInstances(): List } class NgsiLdProperty private constructor( - name: String, + name: ExpandedTerm, val instances: List ) : NgsiLdAttribute(name) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, instances: ExpandedAttributeInstances ): Either = either { checkInstancesAreOfSameType(name, instances, NGSILD_PROPERTY_TYPE).bind() @@ -102,12 +95,12 @@ class NgsiLdProperty private constructor( } class NgsiLdRelationship private constructor( - name: String, + name: ExpandedTerm, val instances: List ) : NgsiLdAttribute(name) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, instances: ExpandedAttributeInstances ): Either = either { checkInstancesAreOfSameType(name, instances, NGSILD_RELATIONSHIP_TYPE).bind() @@ -127,12 +120,12 @@ class NgsiLdRelationship private constructor( } class NgsiLdGeoProperty private constructor( - name: String, + name: ExpandedTerm, val instances: List ) : NgsiLdAttribute(name) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, instances: ExpandedAttributeInstances ): Either = either { checkInstancesAreOfSameType(name, instances, NGSILD_GEOPROPERTY_TYPE).bind() @@ -151,28 +144,47 @@ class NgsiLdGeoProperty private constructor( override fun getAttributeInstances(): List = instances } +class NgsiLdJsonProperty private constructor( + name: ExpandedTerm, + val instances: List +) : NgsiLdAttribute(name) { + companion object { + suspend fun create( + name: ExpandedTerm, + instances: ExpandedAttributeInstances + ): Either = either { + checkInstancesAreOfSameType(name, instances, NGSILD_JSONPROPERTY_TYPE).bind() + + val ngsiLdJsonPropertyInstances = instances.parMap { instance -> + NgsiLdJsonPropertyInstance.create(name, instance).bind() + } + + checkAttributeDefaultInstance(name, ngsiLdJsonPropertyInstances).bind() + checkAttributeDuplicateDatasetId(name, ngsiLdJsonPropertyInstances).bind() + + NgsiLdJsonProperty(name, ngsiLdJsonPropertyInstances) + } + } + + override fun getAttributeInstances(): List = instances +} + sealed class NgsiLdAttributeInstance( - val createdAt: ZonedDateTime? = ngsiLdDateTime(), - val modifiedAt: ZonedDateTime?, val observedAt: ZonedDateTime?, val datasetId: URI?, - val properties: List, - val relationships: List + val attributes: List ) class NgsiLdPropertyInstance private constructor( val value: Any, val unitCode: String?, - createdAt: ZonedDateTime?, - modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, - properties: List, - relationships: List -) : NgsiLdAttributeInstance(createdAt, modifiedAt, observedAt, datasetId, properties, relationships) { + attributes: List +) : NgsiLdAttributeInstance(observedAt, datasetId, attributes) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, values: ExpandedAttributeInstance ): Either = either { val value = values.getPropertyValue() @@ -181,27 +193,20 @@ class NgsiLdPropertyInstance private constructor( } val unitCode = values.getMemberValueAsString(NGSILD_UNIT_CODE_PROPERTY) - val createdAt = values.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) - val modifiedAt = values.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY) val observedAt = values.getMemberValueAsDateTime(NGSILD_OBSERVED_AT_PROPERTY) val datasetId = values.getDatasetId() - val attributes = getNonCoreAttributes(values, NGSILD_PROPERTIES_CORE_MEMBERS) - val relationships = getAttributesOfType(attributes, NGSILD_RELATIONSHIP_TYPE).bind() - val properties = getAttributesOfType(attributes, NGSILD_PROPERTY_TYPE).bind() - ensure(attributes.size == relationships.size + properties.size) { - BadRequestDataException("Property $name has unknown attributes types: $attributes") - } + checkAttributeHasNoForbiddenMembers(name, values, NGSILD_PROPERTIES_FORBIDDEN_MEMBERS).bind() + + val rawAttributes = getNonCoreMembers(values, NGSILD_PROPERTIES_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() NgsiLdPropertyInstance( value, unitCode, - createdAt, - modifiedAt, observedAt, datasetId, - properties, - relationships + attributes ) } } @@ -211,39 +216,29 @@ class NgsiLdPropertyInstance private constructor( class NgsiLdRelationshipInstance private constructor( val objectId: URI, - createdAt: ZonedDateTime?, - modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, - properties: List, - relationships: List -) : NgsiLdAttributeInstance(createdAt, modifiedAt, observedAt, datasetId, properties, relationships) { + attributes: List +) : NgsiLdAttributeInstance(observedAt, datasetId, attributes) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, values: ExpandedAttributeInstance ): Either = either { val objectId = values.getRelationshipObject(name).bind() - val createdAt = values.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) - val modifiedAt = values.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY) val observedAt = values.getMemberValueAsDateTime(NGSILD_OBSERVED_AT_PROPERTY) val datasetId = values.getDatasetId() - val attributes = getNonCoreAttributes(values, NGSILD_RELATIONSHIPS_CORE_MEMBERS) - val relationships = getAttributesOfType(attributes, NGSILD_RELATIONSHIP_TYPE).bind() - val properties = getAttributesOfType(attributes, NGSILD_PROPERTY_TYPE).bind() - ensure(attributes.size == relationships.size + properties.size) { - BadRequestDataException("Relationship $name has unknown attributes: $attributes") - } + checkAttributeHasNoForbiddenMembers(name, values, NGSILD_RELATIONSHIPS_FORBIDDEN_MEMBERS).bind() + + val rawAttributes = getNonCoreMembers(values, NGSILD_RELATIONSHIPS_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() NgsiLdRelationshipInstance( objectId, - createdAt, - modifiedAt, observedAt, datasetId, - properties, - relationships + attributes ) } } @@ -253,39 +248,32 @@ class NgsiLdRelationshipInstance private constructor( class NgsiLdGeoPropertyInstance( val coordinates: WKTCoordinates, - createdAt: ZonedDateTime?, - modifiedAt: ZonedDateTime?, observedAt: ZonedDateTime?, datasetId: URI?, - properties: List, - relationships: List -) : NgsiLdAttributeInstance(createdAt, modifiedAt, observedAt, datasetId, properties, relationships) { + attributes: List +) : NgsiLdAttributeInstance(observedAt, datasetId, attributes) { companion object { suspend fun create( - name: String, + name: ExpandedTerm, values: ExpandedAttributeInstance ): Either = either { - val createdAt = values.getMemberValueAsDateTime(NGSILD_CREATED_AT_PROPERTY) - val modifiedAt = values.getMemberValueAsDateTime(NGSILD_MODIFIED_AT_PROPERTY) + val wktValue = values.getMemberValue(NGSILD_GEOPROPERTY_VALUE) as? String + ensureNotNull(wktValue) { + BadRequestDataException("GeoProperty $name has an instance without a value") + } val observedAt = values.getMemberValueAsDateTime(NGSILD_OBSERVED_AT_PROPERTY) val datasetId = values.getDatasetId() - val wktValue = (values[NGSILD_GEOPROPERTY_VALUE]!![0] as Map)[JSONLD_VALUE] as String - val attributes = getNonCoreAttributes(values, NGSILD_GEOPROPERTIES_CORE_MEMBERS) - val relationships = getAttributesOfType(attributes, NGSILD_RELATIONSHIP_TYPE).bind() - val properties = getAttributesOfType(attributes, NGSILD_PROPERTY_TYPE).bind() - ensure(attributes.size == relationships.size + properties.size) { - BadRequestDataException("Geoproperty $name has unknown attributes: $attributes") - } + checkAttributeHasNoForbiddenMembers(name, values, NGSILD_GEOPROPERTIES_FORBIDDEN_MEMBERS).bind() + + val rawAttributes = getNonCoreMembers(values, NGSILD_GEOPROPERTIES_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() NgsiLdGeoPropertyInstance( WKTCoordinates(wktValue), - createdAt, - modifiedAt, observedAt, datasetId, - properties, - relationships + attributes ) } } @@ -293,40 +281,79 @@ class NgsiLdGeoPropertyInstance( override fun toString(): String = "NgsiLdGeoPropertyInstance(coordinates=$coordinates)" } +class NgsiLdJsonPropertyInstance private constructor( + val json: Any, + observedAt: ZonedDateTime?, + datasetId: URI?, + attributes: List +) : NgsiLdAttributeInstance(observedAt, datasetId, attributes) { + companion object { + suspend fun create( + name: ExpandedTerm, + values: ExpandedAttributeInstance + ): Either = either { + val json = values.getMemberValue(NGSILD_JSONPROPERTY_VALUE) + ensureNotNull(json) { + BadRequestDataException("Property $name has an instance without a json member") + } + ensure(json is Map<*, *> || (json is List<*> && json.all { it is Map<*, *> })) { + BadRequestDataException( + "Property $name has a json member that is not a JSON object, nor an array of JSON objects" + ) + } + + val observedAt = values.getMemberValueAsDateTime(NGSILD_OBSERVED_AT_PROPERTY) + val datasetId = values.getDatasetId() + + checkAttributeHasNoForbiddenMembers(name, values, NGSILD_JSONPROPERTIES_FORBIDDEN_MEMBERS).bind() + + val rawAttributes = getNonCoreMembers(values, NGSILD_JSONPROPERTIES_CORE_MEMBERS) + val attributes = parseAttributes(rawAttributes).bind() + + NgsiLdJsonPropertyInstance( + json, + observedAt, + datasetId, + attributes + ) + } + } + + override fun toString(): String = "NgsiLdJsonPropertyInstance(json=$json)" +} + @JvmInline value class WKTCoordinates(val value: String) /** * Given an entity's attribute, returns whether it is of the given attribute type - * (i.e. property, geo property or relationship) + * (i.e. property, geo property, json property or relationship) */ fun isAttributeOfType(attributeInstance: ExpandedAttributeInstance, type: AttributeType): Boolean = attributeInstance.containsKey(JSONLD_TYPE) && attributeInstance[JSONLD_TYPE] is List<*> && attributeInstance.getOrElse(JSONLD_TYPE) { emptyList() }[0] == type.uri -private suspend inline fun getAttributesOfType( - attributes: Map, - type: AttributeType -): Either> = either { +private suspend fun parseAttributes( + attributes: Map +): Either> = attributes - .mapValues { - castAttributeValue(it.value) - }.filter { - // only check the first entry, multi-attribute consistency is later checked by each attribute - isAttributeOfType(it.value[0], type) - }.entries + .mapValues { castAttributeValue(it.value) } + .toList() .map { - when (type) { - NGSILD_PROPERTY_TYPE -> NgsiLdProperty.create(it.key, it.value).bind() as T - NGSILD_RELATIONSHIP_TYPE -> NgsiLdRelationship.create(it.key, it.value).bind() as T - NGSILD_GEOPROPERTY_TYPE -> NgsiLdGeoProperty.create(it.key, it.value).bind() as T - else -> BadRequestDataException("Unrecognized type: $type").left().bind() + val attributeType = (it.second[0][JSONLD_TYPE] as? List)?.get(0) + when (attributeType) { + NGSILD_PROPERTY_TYPE.uri -> NgsiLdProperty.create(it.first, it.second) + NGSILD_RELATIONSHIP_TYPE.uri -> NgsiLdRelationship.create(it.first, it.second) + NGSILD_GEOPROPERTY_TYPE.uri -> NgsiLdGeoProperty.create(it.first, it.second) + NGSILD_JSONPROPERTY_TYPE.uri -> NgsiLdJsonProperty.create(it.first, it.second) + else -> BadRequestDataException("Attribute ${it.first} has an unknown type: $attributeType").left() } + }.let { l -> + either { l.bindAll() } } -} -private fun getNonCoreAttributes(parsedKeys: Map, keysToFilter: List): Map = +private fun getNonCoreMembers(parsedKeys: Map, keysToFilter: List): Map = parsedKeys.filterKeys { !keysToFilter.contains(it) } @@ -337,7 +364,7 @@ fun checkInstancesAreOfSameType( type: AttributeType ): Either = either { ensure(values.all { isAttributeOfType(it, type) }) { - BadRequestDataException("Attribute $name instances must have the same type") + BadRequestDataException("Attribute $name can't have instances with different types") } } @@ -353,13 +380,26 @@ fun checkAttributeDefaultInstance( fun checkAttributeDuplicateDatasetId( name: String, instances: List -): Either { +): Either = either { val datasetIds = instances.map { it.datasetId } - return if (datasetIds.toSet().count() != datasetIds.count()) - BadRequestDataException("Attribute $name can't have more than one instance with the same datasetId").left() - else Unit.right() + ensure(datasetIds.toSet().count() == datasetIds.count()) { + BadRequestDataException("Attribute $name can't have more than one instance with the same datasetId") + } +} + +fun checkAttributeHasNoForbiddenMembers( + name: ExpandedTerm, + instance: ExpandedAttributeInstance, + forbiddenMembers: List +): Either = either { + forbiddenMembers.find { + instance.getMemberValue(it) != null + }.let { + if (it != null) BadRequestDataException("Attribute $name has an instance with a forbidden member: $it").left() + else Unit.right() + } } suspend fun ExpandedAttributes.toNgsiLdAttributes(): Either> = either { @@ -381,11 +421,13 @@ suspend fun ExpandedAttributeInstances.toNgsiLdAttribute( NgsiLdRelationship.create(attributeName, this) isAttributeOfType(this[0], NGSILD_GEOPROPERTY_TYPE) -> NgsiLdGeoProperty.create(attributeName, this) + isAttributeOfType(this[0], NGSILD_JSONPROPERTY_TYPE) -> + NgsiLdJsonProperty.create(attributeName, this) else -> BadRequestDataException("Unrecognized type for $attributeName").left() } suspend fun ExpandedEntity.toNgsiLdEntity(): Either = - NgsiLdEntity.create(this.members, this.contexts) + NgsiLdEntity.create(this.members) fun List.flatOnInstances(): List> = this.flatMap { ngsiLdAttribute -> @@ -413,10 +455,37 @@ val NGSILD_PROPERTIES_CORE_MEMBERS = listOf( NGSILD_UNIT_CODE_PROPERTY ).plus(NGSILD_ATTRIBUTES_CORE_MEMBERS) +val NGSILD_PROPERTIES_FORBIDDEN_MEMBERS = listOf( + NGSILD_RELATIONSHIP_OBJECT, + NGSILD_JSONPROPERTY_VALUE, +) + val NGSILD_RELATIONSHIPS_CORE_MEMBERS = listOf( NGSILD_RELATIONSHIP_OBJECT ).plus(NGSILD_ATTRIBUTES_CORE_MEMBERS) +val NGSILD_RELATIONSHIPS_FORBIDDEN_MEMBERS = listOf( + NGSILD_PROPERTY_VALUE, + NGSILD_JSONPROPERTY_VALUE, + NGSILD_UNIT_CODE_PROPERTY +) + val NGSILD_GEOPROPERTIES_CORE_MEMBERS = listOf( NGSILD_GEOPROPERTY_VALUE ).plus(NGSILD_ATTRIBUTES_CORE_MEMBERS) + +val NGSILD_GEOPROPERTIES_FORBIDDEN_MEMBERS = listOf( + NGSILD_RELATIONSHIP_OBJECT, + NGSILD_JSONPROPERTY_VALUE, + NGSILD_UNIT_CODE_PROPERTY +) + +val NGSILD_JSONPROPERTIES_CORE_MEMBERS = listOf( + NGSILD_JSONPROPERTY_VALUE +).plus(NGSILD_ATTRIBUTES_CORE_MEMBERS) + +val NGSILD_JSONPROPERTIES_FORBIDDEN_MEMBERS = listOf( + NGSILD_RELATIONSHIP_OBJECT, + NGSILD_PROPERTY_VALUE, + NGSILD_UNIT_CODE_PROPERTY +) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/GeoQueryUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/GeoQueryUtils.kt index 183ff2fd7..c6aec1b4c 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/GeoQueryUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/GeoQueryUtils.kt @@ -115,8 +115,8 @@ fun buildGeoQuery(geoQuery: GeoQuery, target: ExpandedEntity? = null): String { if (georelQuery.first == GEOREL_NEAR_DISTANCE_MODIFIER) """ public.ST_Distance( - 'SRID=4326;${geoQuery.wktCoordinates.value}'::geography, - ('SRID=4326;' || $targetWKTCoordinates)::geography, + cast('SRID=4326;${geoQuery.wktCoordinates.value}' as public.geography), + cast('SRID=4326;' || $targetWKTCoordinates as public.geography), false ) ${georelQuery.second} ${georelQuery.third} """.trimIndent() 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 c548ba70c..b4a9ca95a 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 @@ -26,8 +26,7 @@ value class AttributeType(val uri: String) object JsonLdUtils { - const val NGSILD_CORE_CONTEXT = "https://easy-global-market.github.io/ngsild-api-data-models/" + - "shared-jsonld-contexts/ngsi-ld-core-context-v1.8.jsonld" + const val NGSILD_CORE_CONTEXT = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.8.jsonld" val NGSILD_CORE_CONTEXTS = listOf(NGSILD_CORE_CONTEXT) const val NGSILD_PREFIX = "https://uri.etsi.org/ngsi-ld/" @@ -42,6 +41,10 @@ object JsonLdUtils { const val NGSILD_RELATIONSHIP_TERM = "Relationship" val NGSILD_RELATIONSHIP_TYPE = AttributeType("https://uri.etsi.org/ngsi-ld/Relationship") const val NGSILD_RELATIONSHIP_OBJECT = "https://uri.etsi.org/ngsi-ld/hasObject" + const val NGSILD_JSONPROPERTY_TERM = "JsonProperty" + val NGSILD_JSONPROPERTY_TYPE = AttributeType("https://uri.etsi.org/ngsi-ld/JsonProperty") + const val NGSILD_JSONPROPERTY_VALUE = "https://uri.etsi.org/ngsi-ld/hasJSON" + const val NGSILD_JSONPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/jsons" const val NGSILD_PROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasValues" const val NGSILD_GEOPROPERTY_VALUES = "https://uri.etsi.org/ngsi-ld/hasValues" @@ -56,10 +59,13 @@ object JsonLdUtils { const val JSONLD_VALUE = "@value" const val JSONLD_OBJECT = "object" const val JSONLD_LIST = "@list" + const val JSONLD_JSON_TERM = "json" + const val JSONLD_JSON = "@json" const val JSONLD_CONTEXT = "@context" const val NGSILD_SCOPE_TERM = "scope" const val NGSILD_SCOPE_PROPERTY = "https://uri.etsi.org/ngsi-ld/$NGSILD_SCOPE_TERM" - + const val NGSILD_NONE_TERM = "@none" + const val NGSILD_DATASET_TERM = "dataset" 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) @@ -85,6 +91,8 @@ object JsonLdUtils { val NGSILD_GEO_PROPERTIES_TERMS = setOf(NGSILD_LOCATION_TERM, NGSILD_OBSERVATION_SPACE_TERM, NGSILD_OPERATION_SPACE_TERM) const val NGSILD_DATASET_ID_PROPERTY = "https://uri.etsi.org/ngsi-ld/datasetId" + const val NGSILD_DATASET_ID_TERM = "datasetId" + const val NGSILD_INSTANCE_ID_PROPERTY = "https://uri.etsi.org/ngsi-ld/instanceId" const val NGSILD_SUBSCRIPTION_TERM = "Subscription" diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonUtils.kt index ce46ee852..93ac64a7e 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonUtils.kt @@ -1,6 +1,7 @@ package com.egm.stellio.shared.util import com.egm.stellio.shared.model.InvalidRequestException +import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_JSON_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.core.JsonProcessingException @@ -9,13 +10,11 @@ import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider -import com.fasterxml.jackson.module.kotlin.convertValue import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlin.reflect.KClass val mapper: ObjectMapper = jacksonObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) .findAndRegisterModules() .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) @@ -87,9 +86,6 @@ object JsonUtils { return mapperWithMixin.writer(filterProvider).writeValueAsString(input) } - fun convertToMap(input: Any): Map = - mapper.convertValue(input) - fun Map.getAllKeys(): Set = this.entries.fold(emptySet()) { acc, entry -> // what is inside the value of a property is not a key @@ -114,9 +110,11 @@ object JsonUtils { fun Map.getAllValues(): Set = this.entries.fold(emptySet()) { acc, entry -> - val values = when (entry.value) { - is Map<*, *> -> (entry.value as Map).getAllValues() - is List<*> -> + val values = when { + entry.value is Map<*, *> && + entry.key in listOf(JSONLD_VALUE_TERM, JSONLD_JSON_TERM) -> setOf(entry.value) + entry.value is Map<*, *> -> (entry.value as Map).getAllValues() + entry.value is List<*> -> (entry.value as List).map { when (it) { is Map<*, *> -> (it as Map).getAllValues() diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/QueryUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/QueryUtils.kt index 35f1b41c8..9f4ce3790 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/QueryUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/QueryUtils.kt @@ -82,13 +82,13 @@ fun String.escapeRegexpPattern(): String = if (this.matches(innerRegexPattern.toRegex())) { this.replace(innerRegexPattern.toRegex()) { matchResult -> matchResult.value - .replace("(?i)", "##?i//") + .replace("(?i)", "##?i§§") } } else this fun String.unescapeRegexPattern(): String = this.replace("##", "(") - .replace("//", ")") + .replace("§§", ")") fun buildTypeQuery(rawQuery: String, target: List? = null): String = rawQuery.replace(typeSelectionRegex) { matchResult -> diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt new file mode 100644 index 000000000..245bfebd4 --- /dev/null +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt @@ -0,0 +1,753 @@ +package com.egm.stellio.shared.model + +import com.egm.stellio.shared.util.JsonLdUtils +import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap +import com.egm.stellio.shared.util.JsonUtils.serializeObject +import com.egm.stellio.shared.util.assertJsonPayloadsAreEqual +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.springframework.http.MediaType + +class CompactedEntityTests { + + private val normalizedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04Z", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 24.30623, + 60.07966 + ] + } + }, + "@context": [ + "https://example.org/ngsi-ld/latest/commonTerms.jsonld", + "https://example.org/ngsi-ld/latest/vehicle.jsonld", + "https://example.org/ngsi-ld/latest/parking.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" + ] + } + """.trimIndent() + + private val simplifiedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "brandName": "Mercedes", + "isParked": "urn:ngsi-ld:OffStreetParking:Downtown1", + "location": { + "type": "Point", + "coordinates": [ + 24.30623, + 60.07966 + ] + }, + "@context": [ + "https://example.org/ngsi-ld/latest/commonTerms.jsonld", + "https://example.org/ngsi-ld/latest/vehicle.jsonld", + "https://example.org/ngsi-ld/latest/parking.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" + ] + } + """.trimIndent() + + private val normalizedMultiAttributeEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "speed": [ + { + "type": "Property", + "datasetId": "urn:ngsi-ld:Dataset:01", + "value": 10 + }, + { + "type": "Property", + "datasetId": "urn:ngsi-ld:Dataset:02", + "value": 11 + } + ], + "hasOwner": [ + { + "type": "Relationship", + "datasetId": "urn:ngsi-ld:Dataset:01", + "object": "urn:ngsi-ld:Person:John" + }, + { + "type": "Relationship", + "datasetId": "urn:ngsi-ld:Dataset:02", + "object": "urn:ngsi-ld:Person:Jane" + } + ] + } + """.trimIndent() + + private val simplifiedMultiAttributeEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "speed": { + "dataset": { + "urn:ngsi-ld:Dataset:01": 10, + "urn:ngsi-ld:Dataset:02": 11 + } + }, + "hasOwner": { + "dataset": { + "urn:ngsi-ld:Dataset:01": "urn:ngsi-ld:Person:John", + "urn:ngsi-ld:Dataset:02": "urn:ngsi-ld:Person:Jane" + } + } + } + """.trimIndent() + + @Test + fun `it should simplify a compacted entity`() { + val normalizedMap = normalizedEntity.deserializeAsMap() + val simplifiedMap = simplifiedEntity.deserializeAsMap() + + val resultMap = normalizedMap.toKeyValues() + + assertEquals(simplifiedMap, resultMap) + } + + @Test + fun `it should simplify a compacted entity with multi-attributes`() { + val normalizedMap = normalizedMultiAttributeEntity.deserializeAsMap() + val simplifiedMap = simplifiedMultiAttributeEntity.deserializeAsMap() + + val resultMap = normalizedMap.toKeyValues() + + assertEquals(simplifiedMap, resultMap) + } + + @Test + fun `it should return the simplified representation of a JsonProperty having an object as value`() { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "jsonProperty": { + "type": "JsonProperty", + "json": { + "anId": "id", + "aNullValue": null, + "anArray": [1, 2] + } + } + } + """.trimIndent() + .deserializeAsMap() + + val simplifiedRepresentation = compactedEntity.toKeyValues() + + val expectedSimplifiedRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "jsonProperty": { + "json": { + "anId": "id", + "aNullValue": null, + "anArray": [1, 2] + } + } + } + """.trimIndent() + assertJsonPayloadsAreEqual(expectedSimplifiedRepresentation, serializeObject(simplifiedRepresentation)) + } + + @Test + fun `it should return the simplified representation of a JsonProperty having an array as value`() { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "jsonProperty": { + "type": "JsonProperty", + "json": [ + { "anId": "id" }, + { "anotherId": "anotherId" } + ] + } + } + """.trimIndent() + .deserializeAsMap() + + val simplifiedRepresentation = compactedEntity.toKeyValues() + + val expectedSimplifiedRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "jsonProperty": { + "json": [ + { "anId": "id" }, + { "anotherId": "anotherId" } + ] + } + } + """.trimIndent() + assertJsonPayloadsAreEqual(expectedSimplifiedRepresentation, serializeObject(simplifiedRepresentation)) + } + + @Test + fun `it should return a simplified entity with sysAttrs`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "createdAt": "2023-11-25T08:00:00Z", + "modifiedAt": "2023-11-25T09:00:00Z", + "brandName": { + "type": "Property", + "value": "Mercedes" + } + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "createdAt": "2023-11-25T08:00:00Z", + "modifiedAt": "2023-11-25T09:00:00Z", + "brandName": "Mercedes" + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.JSON, + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = true + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity without sysAttrs`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "createdAt": "2023-11-25T08:00:00Z", + "modifiedAt": "2023-11-25T09:00:00Z", + "brandName": { + "type": "Property", + "value": "Mercedes" + } + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "brandName": "Mercedes" + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity with a multi-attribute Property`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "speed": [ + { + "type": "Property", + "value": 55, + "datasetId": "urn:ngsi-ld:Property:speedometerA4567-speed" + }, + { + "type": "Property", + "value": 54.5, + "datasetId": "urn:ngsi-ld:Property:gpsBxyz123-speed" + }] + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "speed": { + "dataset": { + "urn:ngsi-ld:Property:speedometerA4567-speed": 55, + "urn:ngsi-ld:Property:gpsBxyz123-speed": 54.5 + } + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity with a multi-attribute Relationship`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "hasOwner": [ + { + "type": "Relationship", + "datasetId": "urn:ngsi-ld:Dataset:01", + "object": "urn:ngsi-ld:Person:John" + }, + { + "type": "Relationship", + "datasetId": "urn:ngsi-ld:Dataset:02", + "object": "urn:ngsi-ld:Person:Jane" + } + ] + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "hasOwner": { + "dataset": { + "urn:ngsi-ld:Dataset:01": "urn:ngsi-ld:Person:John", + "urn:ngsi-ld:Dataset:02": "urn:ngsi-ld:Person:Jane" + } + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity with a multi-attribute JsonProperty`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "speed": [ + { + "type": "JsonProperty", + "json": { + "anId": "id" + }, + "datasetId": "urn:ngsi-ld:JsonProperty:1" + }, + { + "type": "JsonProperty", + "json": { + "anArray": [1, 2] + }, + "datasetId": "urn:ngsi-ld:JsonProperty:2" + }] + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "speed": { + "dataset": { + "urn:ngsi-ld:JsonProperty:1": { + "json": { + "anId": "id" + } + }, + "urn:ngsi-ld:JsonProperty:2": { + "json": { + "anArray": [1, 2] + } + } + } + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity with a multi-attribute GeoProperty`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "location": [ + { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "datasetId": "urn:ngsi-ld:GeoProperty:1" + }, + { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ 25.30623, 60.08066 ] + }, + "datasetId": "urn:ngsi-ld:GeoProperty:2" + }] + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "location": + { + "dataset": { + "urn:ngsi-ld:GeoProperty:1": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "urn:ngsi-ld:GeoProperty:2": { + "type": "Point", + "coordinates": [ 25.30623, 60.08066 ] + } + } + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return a simplified entity with a multi-Attribute having a default instance`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "speed": [ + { + "type": "Property", + "value": 10 + }, + { + "type": "Property", + "datasetId": "urn:ngsi-ld:Dataset:01", + "value": 11 + } + ], + "hasOwner": [ + { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:John" + }, + { + "type": "Relationship", + "datasetId": "urn:ngsi-ld:Dataset:01", + "object": "urn:ngsi-ld:Person:Jane" + } + ] + } + """.trimIndent().deserializeAsMap() + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "speed": { + "dataset": { + "@none": 10, + "urn:ngsi-ld:Dataset:01": 11 + } + }, + "hasOwner": { + "dataset": { + "@none": "urn:ngsi-ld:Person:John", + "urn:ngsi-ld:Dataset:01": "urn:ngsi-ld:Person:Jane" + } + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return the GeoJSON representation of a normalized entity on location attribute`() { + val inputEntity = normalizedEntity.deserializeAsMap() + + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "properties": { + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + }, + "isParked": { + "type": "Relationship", + "object": "urn:ngsi-ld:OffStreetParking:Downtown1", + "observedAt": "2017-07-29T12:00:04Z", + "providedBy": { + "type": "Relationship", + "object": "urn:ngsi-ld:Person:Bob" + } + }, + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ + 24.30623, + 60.07966 + ] + } + }, + "@context": [ + "https://example.org/ngsi-ld/latest/commonTerms.jsonld", + "https://example.org/ngsi-ld/latest/vehicle.jsonld", + "https://example.org/ngsi-ld/latest/parking.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" + ] + } + } + """.trimIndent().deserializeAsMap() + + val actualEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.GEO_JSON, + AttributeRepresentation.NORMALIZED, + includeSysAttrs = false, + geometryProperty = JsonLdUtils.NGSILD_LOCATION_TERM + ) + ) + + assertEquals(expectedEntity, actualEntity) + } + + @Test + fun `it should return the GeoJSON representation of a simplified entity on location attribute`() { + val inputEntity = normalizedEntity.deserializeAsMap() + + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "properties": { + "type": "Vehicle", + "brandName": "Mercedes", + "isParked": "urn:ngsi-ld:OffStreetParking:Downtown1", + "location": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "@context": [ + "https://example.org/ngsi-ld/latest/commonTerms.jsonld", + "https://example.org/ngsi-ld/latest/vehicle.jsonld", + "https://example.org/ngsi-ld/latest/parking.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" + ] + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.GEO_JSON, + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false, + geometryProperty = JsonLdUtils.NGSILD_LOCATION_TERM + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return the GeoJSON representation of an entity with a null geometry if it does not exist`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "brandName": { + "type": "Property", + "value": "Mercedes" + } + } + """.trimIndent().deserializeAsMap() + + val expectedEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Feature", + "geometry": null, + "properties": { + "type": "Vehicle", + "brandName": "Mercedes" + } + } + """.trimIndent().deserializeAsMap() + + val simplifiedEntity = inputEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.GEO_JSON, + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false, + geometryProperty = JsonLdUtils.NGSILD_LOCATION_TERM + ) + ) + + assertEquals(expectedEntity, simplifiedEntity) + } + + @Test + fun `it should return the GeoJSON representation of simplified entities`() { + val inputEntity = + """ + { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Vehicle", + "location": { + "type": "GeoProperty", + "value": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + } + } + } + """.trimIndent().deserializeAsMap() + + val expectedEntities = + """ + { + "type": "FeatureCollection", + "features": [{ + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "properties": { + "type": "Vehicle", + "location": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + } + } + }, { + "id": "urn:ngsi-ld:Vehicle:A4567", + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + }, + "properties": { + "type": "Vehicle", + "location": { + "type": "Point", + "coordinates": [ 24.30623, 60.07966 ] + } + } + }] + } + """.trimIndent().deserializeAsMap() + + val actualEntities = listOf(inputEntity, inputEntity).toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.GEO_JSON, + AttributeRepresentation.SIMPLIFIED, + includeSysAttrs = false, + JsonLdUtils.NGSILD_LOCATION_TERM + ) + ) + + assertEquals(expectedEntities, actualEntities) + } +} diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/ExpandedEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/ExpandedEntityTests.kt index 1c70d3cc8..e935f63af 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/model/ExpandedEntityTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/ExpandedEntityTests.kt @@ -3,119 +3,12 @@ package com.egm.stellio.shared.model import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_ID import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_CREATED_AT_PROPERTY -import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LOCATION_TERM -import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap import kotlinx.coroutines.test.runTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail -import org.springframework.http.MediaType - class ExpandedEntityTests { - - private val normalizedJson = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "brandName": { - "type": "Property", - "value": "Mercedes" - }, - "isParked": { - "type": "Relationship", - "object": "urn:ngsi-ld:OffStreetParking:Downtown1", - "observedAt": "2017-07-29T12:00:04Z", - "providedBy": { - "type": "Relationship", - "object": "urn:ngsi-ld:Person:Bob" - } - }, - "location": { - "type": "GeoProperty", - "value": { - "type": "Point", - "coordinates": [ - 24.30623, - 60.07966 - ] - } - }, - "@context": [ - "https://example.org/ngsi-ld/latest/commonTerms.jsonld", - "https://example.org/ngsi-ld/latest/vehicle.jsonld", - "https://example.org/ngsi-ld/latest/parking.jsonld", - "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" - ] - } - """.trimIndent() - - private val simplifiedJson = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "brandName": "Mercedes", - "isParked": "urn:ngsi-ld:OffStreetParking:Downtown1", - "location": { - "type": "Point", - "coordinates": [ - 24.30623, - 60.07966 - ] - }, - "@context": [ - "https://example.org/ngsi-ld/latest/commonTerms.jsonld", - "https://example.org/ngsi-ld/latest/vehicle.jsonld", - "https://example.org/ngsi-ld/latest/parking.jsonld", - "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" - ] - } - """.trimIndent() - - private val normalizedMultiAttributeJson = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "speed": [ - { - "type": "Property", - "datasetId": "urn:ngsi-ld:Dataset:01", - "value": 10 - }, - { - "type": "Property", - "datasetId": "urn:ngsi-ld:Dataset:02", - "value": 11 - } - ], - "hasOwner": [ - { - "type": "Relationship", - "datasetId": "urn:ngsi-ld:Dataset:01", - "object": "urn:ngsi-ld:Person:John" - }, - { - "type": "Relationship", - "datasetId": "urn:ngsi-ld:Dataset:02", - "object": "urn:ngsi-ld:Person:Jane" - } - ] - } - """.trimIndent() - - private val simplifiedMultiAttributeJson = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "speed": [ 10, 11 ], - "hasOwner": [ "urn:ngsi-ld:Person:John", "urn:ngsi-ld:Person:Jane" ] - } - """.trimIndent() - @Test fun `it should find an expanded attribute contained in the entity`() { val expandedEntity = ExpandedEntity( @@ -207,303 +100,4 @@ class ExpandedEntityTests { assertThat(nameAttributeInstances).hasSize(1) assertThat(nameAttributeInstances[0]).containsKey(NGSILD_CREATED_AT_PROPERTY) } - - @Test - fun `it should simplify a compacted entity`() { - val normalizedMap = normalizedJson.deserializeAsMap() - val simplifiedMap = simplifiedJson.deserializeAsMap() - - val resultMap = normalizedMap.toKeyValues() - - assertEquals(simplifiedMap, resultMap) - } - - @Test - fun `it should simplify a compacted entity with multi-attributes`() { - val normalizedMap = normalizedMultiAttributeJson.deserializeAsMap() - val simplifiedMap = simplifiedMultiAttributeJson.deserializeAsMap() - - val resultMap = normalizedMap.toKeyValues() - - assertEquals(simplifiedMap, resultMap) - } - - @Test - fun `it should return a simplified entity with sysAttrs`() { - val inputEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "createdAt": "2023-11-25T08:00:00Z", - "modifiedAt": "2023-11-25T09:00:00Z", - "brandName": { - "type": "Property", - "value": "Mercedes" - } - } - """.trimIndent().deserializeAsMap() - val expectedEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "createdAt": "2023-11-25T08:00:00Z", - "modifiedAt": "2023-11-25T09:00:00Z", - "brandName": "Mercedes" - } - """.trimIndent().deserializeAsMap() - - val simplifiedEntity = inputEntity.toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.JSON, - AttributeRepresentation.SIMPLIFIED, - includeSysAttrs = true - ) - ) - - assertEquals(expectedEntity, simplifiedEntity) - } - - @Test - fun `it should return a simplified entity without sysAttrs`() { - val inputEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "createdAt": "2023-11-25T08:00:00Z", - "modifiedAt": "2023-11-25T09:00:00Z", - "brandName": { - "type": "Property", - "value": "Mercedes" - } - } - """.trimIndent().deserializeAsMap() - val expectedEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "brandName": "Mercedes" - } - """.trimIndent().deserializeAsMap() - - val simplifiedEntity = inputEntity.toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.forMediaType(MediaType.APPLICATION_JSON), - AttributeRepresentation.SIMPLIFIED, - includeSysAttrs = false - ) - ) - - assertEquals(expectedEntity, simplifiedEntity) - } - - @Test - fun `it should return a normalized GeoJSON entity on location attribute`() { - val inputEntity = normalizedJson.deserializeAsMap() - - val expectedEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - }, - "properties": { - "type": "Vehicle", - "brandName": { - "type": "Property", - "value": "Mercedes" - }, - "isParked": { - "type": "Relationship", - "object": "urn:ngsi-ld:OffStreetParking:Downtown1", - "observedAt": "2017-07-29T12:00:04Z", - "providedBy": { - "type": "Relationship", - "object": "urn:ngsi-ld:Person:Bob" - } - }, - "location": { - "type": "GeoProperty", - "value": { - "type": "Point", - "coordinates": [ - 24.30623, - 60.07966 - ] - } - }, - "@context": [ - "https://example.org/ngsi-ld/latest/commonTerms.jsonld", - "https://example.org/ngsi-ld/latest/vehicle.jsonld", - "https://example.org/ngsi-ld/latest/parking.jsonld", - "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" - ] - } - } - """.trimIndent().deserializeAsMap() - - val actualEntity = inputEntity.toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.GEO_JSON, - AttributeRepresentation.NORMALIZED, - includeSysAttrs = false, - geometryProperty = NGSILD_LOCATION_TERM - ) - ) - - assertEquals(expectedEntity, actualEntity) - } - - @Test - fun `it should return a simplified GeoJSON entity on location attribute`() { - val inputEntity = normalizedJson.deserializeAsMap() - - val expectedEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - }, - "properties": { - "type": "Vehicle", - "brandName": "Mercedes", - "isParked": "urn:ngsi-ld:OffStreetParking:Downtown1", - "location": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - }, - "@context": [ - "https://example.org/ngsi-ld/latest/commonTerms.jsonld", - "https://example.org/ngsi-ld/latest/vehicle.jsonld", - "https://example.org/ngsi-ld/latest/parking.jsonld", - "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-v1.7.jsonld" - ] - } - } - """.trimIndent().deserializeAsMap() - - val simplifiedEntity = inputEntity.toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.GEO_JSON, - AttributeRepresentation.SIMPLIFIED, - includeSysAttrs = false, - geometryProperty = NGSILD_LOCATION_TERM - ) - ) - - assertEquals(expectedEntity, simplifiedEntity) - } - - @Test - fun `it should return a GeoJSON entity with a null geometry if the GeoProperty does not exist`() { - val inputEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "brandName": { - "type": "Property", - "value": "Mercedes" - } - } - """.trimIndent().deserializeAsMap() - - val expectedEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Feature", - "geometry": null, - "properties": { - "type": "Vehicle", - "brandName": "Mercedes" - } - } - """.trimIndent().deserializeAsMap() - - val simplifiedEntity = inputEntity.toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.GEO_JSON, - AttributeRepresentation.SIMPLIFIED, - includeSysAttrs = false, - geometryProperty = NGSILD_LOCATION_TERM - ) - ) - - assertEquals(expectedEntity, simplifiedEntity) - } - - @Test - fun `it should return simplified GeoJSON entities`() { - val inputEntity = - """ - { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Vehicle", - "location": { - "type": "GeoProperty", - "value": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - } - } - } - """.trimIndent().deserializeAsMap() - - val expectedEntities = - """ - { - "type": "FeatureCollection", - "features": [{ - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - }, - "properties": { - "type": "Vehicle", - "location": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - } - } - }, { - "id": "urn:ngsi-ld:Vehicle:A4567", - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - }, - "properties": { - "type": "Vehicle", - "location": { - "type": "Point", - "coordinates": [ 24.30623, 60.07966 ] - } - } - }] - } - """.trimIndent().deserializeAsMap() - - val actualEntities = listOf(inputEntity, inputEntity).toFinalRepresentation( - NgsiLdDataRepresentation( - EntityRepresentation.GEO_JSON, - AttributeRepresentation.SIMPLIFIED, - includeSysAttrs = false, - NGSILD_LOCATION_TERM - ) - ) - - assertEquals(expectedEntities, actualEntities) - } } diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt index f418c672f..62f401bb0 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/NgsiLdEntityTests.kt @@ -83,7 +83,8 @@ class NgsiLdEntityTests { expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity().shouldFail { assertInstanceOf(BadRequestDataException::class.java, it) assertEquals( - "Entity has attribute(s) with an unknown type: [${NGSILD_DEFAULT_VOCAB}deviceState]", + "Attribute ${NGSILD_DEFAULT_VOCAB}deviceState has an unknown type: " + + "https://uri.etsi.org/ngsi-ld/default-context/UnknownProperty", it.message ) } @@ -112,8 +113,6 @@ class NgsiLdEntityTests { assertEquals(1, ngsiLdProperty.instances.size) val ngsiLdPropertyInstance = ngsiLdProperty.instances[0] assertEquals("Open", ngsiLdPropertyInstance.value) - assertNull(ngsiLdPropertyInstance.createdAt) - assertNull(ngsiLdPropertyInstance.modifiedAt) } @Test @@ -185,31 +184,7 @@ class NgsiLdEntityTests { } @Test - fun `it should parse an entity with a property having createdAt and modifiedAt information`() = runTest { - val rawEntity = - """ - { - "id": "urn:ngsi-ld:Device:01234", - "type": "Device", - "deviceState": { - "type": "Property", - "value": "Open", - "createdAt": "2022-01-19T00:00:00Z", - "modifiedAt": "2022-01-29T00:00:00Z" - } - } - """.trimIndent() - - val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() - .shouldSucceedAndResult() - - val ngsiLdPropertyInstance = ngsiLdEntity.properties[0].instances[0] - assertEquals(ZonedDateTime.parse("2022-01-19T00:00:00Z"), ngsiLdPropertyInstance.createdAt) - assertEquals(ZonedDateTime.parse("2022-01-29T00:00:00Z"), ngsiLdPropertyInstance.modifiedAt) - } - - @Test - fun `it should not parse an entity without a property without a value`() = runTest { + fun `it should not parse an entity with a property without a value`() = runTest { val rawEntity = """ { @@ -282,7 +257,7 @@ class NgsiLdEntityTests { expandAttributes(rawProperty, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdAttributes().shouldFail { assertInstanceOf(BadRequestDataException::class.java, it) assertEquals( - "Attribute ${NGSILD_DEFAULT_VOCAB}deviceState instances must have the same type", + "Attribute ${NGSILD_DEFAULT_VOCAB}deviceState can't have instances with different types", it.message ) } @@ -408,8 +383,6 @@ class NgsiLdEntityTests { assertEquals(1, ngsiLdRelationship.instances.size) val ngsiLdRelationshipInstance = ngsiLdRelationship.instances[0] assertEquals("urn:ngsi-ld:DeviceModel:09876".toUri(), ngsiLdRelationshipInstance.objectId) - assertNull(ngsiLdRelationshipInstance.createdAt) - assertNull(ngsiLdRelationshipInstance.modifiedAt) } @Test @@ -527,7 +500,7 @@ class NgsiLdEntityTests { } @Test - fun `it should not parse a relationship with different type instances`() = runTest { + fun `it should not parse an attribute with different type instances`() = runTest { val rawRelationship = """ { @@ -553,7 +526,7 @@ class NgsiLdEntityTests { expandAttributes(rawRelationship, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdAttributes().shouldFail { assertInstanceOf(BadRequestDataException::class.java, it) assertEquals( - "Attribute ${NGSILD_DEFAULT_VOCAB}refDeviceModel instances must have the same type", + "Attribute ${NGSILD_DEFAULT_VOCAB}refDeviceModel can't have instances with different types", it.message ) } @@ -591,8 +564,6 @@ class NgsiLdEntityTests { assertEquals(1, location?.instances?.size) val locationInstance = location?.instances?.get(0) assertEquals("POLYGON ((100 0, 101 0, 101 1, 100 1, 100 0))", locationInstance?.coordinates?.value) - assertNull(locationInstance?.createdAt) - assertNull(locationInstance?.modifiedAt) } @Test @@ -676,4 +647,215 @@ class NgsiLdEntityTests { val locationInstance = location?.instances?.get(0) assertEquals("POINT (24.30623 60.07966)", locationInstance?.coordinates?.value) } + + @Test + fun `it should parse an entity with a JsonProperty having a JSON object as a value`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "json": { + "address": "Parc des Princes", + "city": "Paris" + } + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val jsonProperty = ngsiLdEntity.jsonProperties.first() + assertNotNull(jsonProperty) + assertEquals("${NGSILD_DEFAULT_VOCAB}jsonProperty", jsonProperty.name) + assertEquals(1, jsonProperty.instances.size) + val jsonPropertyInstance = jsonProperty.instances[0] + assertEquals( + mapOf( + "address" to "Parc des Princes", + "city" to "Paris" + ), + jsonPropertyInstance.json + ) + } + + @Test + fun `it should parse an entity with a JsonProperty having an array of JSON objects as a value`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "json": [ + { "key1": "value1" }, + { "key2": "value2" } + ] + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val jsonProperty = ngsiLdEntity.jsonProperties.first() + assertNotNull(jsonProperty) + assertEquals("${NGSILD_DEFAULT_VOCAB}jsonProperty", jsonProperty.name) + assertEquals(1, jsonProperty.instances.size) + val jsonPropertyInstance = jsonProperty.instances[0] + assertEquals( + listOf( + mapOf("key1" to "value1"), + mapOf("key2" to "value2") + ), + jsonPropertyInstance.json + ) + } + + @Test + fun `it should parse an entity with a multi-attribute JsonProperty`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": [{ + "type": "JsonProperty", + "json": { + "address": "Parc des Princes", + "city": "Paris" + }, + "datasetId": "urn:ngsi-ld:dataset:Parc-des-Princes" + }, { + "type": "JsonProperty", + "json": { + "address": "Stade de la Beaujoire", + "city": "Nantes" + }, + "datasetId": "urn:ngsi-ld:dataset:Stade-de-la-Beaujoire" + }] + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val jsonProperty = ngsiLdEntity.jsonProperties.first() + assertEquals(2, jsonProperty.instances.size) + } + + @Test + fun `it should parse an entity with a JsonProperty having JSON-LD reserved names as keys`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "json": { + "id": "Parc-des-Princes", + "type": "Stadium" + } + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val jsonPropertyInstance = ngsiLdEntity.jsonProperties.first().instances[0] + assertEquals( + mapOf( + "id" to "Parc-des-Princes", + "type" to "Stadium" + ), + jsonPropertyInstance.json + ) + } + + @Test + fun `it should parse an entity with a JsonProperty having a null value in its JSON map`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "json": { + "aNotNullMember": "notNull", + "aNullMember": null + } + } + } + """.trimIndent() + + val ngsiLdEntity = expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldSucceedAndResult() + + val jsonPropertyInstance = ngsiLdEntity.jsonProperties.first().instances[0] + assertEquals( + mapOf( + "aNotNullMember" to "notNull", + "aNullMember" to null + ), + jsonPropertyInstance.json + ) + } + + @Test + fun `it should not parse an entity with a JsonProperty without a json member`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "value": { + "id": "Parc-des-Princes" + } + } + } + """.trimIndent() + + expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldFail { + assertInstanceOf(BadRequestDataException::class.java, it) + assertEquals( + "Property ${NGSILD_DEFAULT_VOCAB}jsonProperty has an instance without a json member", + it.message + ) + } + } + + @Test + fun `it should not parse an entity with a JsonProperty having a forbidden JSON type as value`() = runTest { + val rawEntity = + """ + { + "id":"urn:ngsi-ld:Device:01234", + "type":"Device", + "jsonProperty": { + "type": "JsonProperty", + "json": "Parc-des-Princes" + } + } + """.trimIndent() + + expandJsonLdEntity(rawEntity, NGSILD_TEST_CORE_CONTEXTS).toNgsiLdEntity() + .shouldFail { + assertInstanceOf(BadRequestDataException::class.java, it) + assertEquals( + "Property ${NGSILD_DEFAULT_VOCAB}jsonProperty has a json member that is not a JSON object, " + + "nor an array of JSON objects", + it.message + ) + } + } } diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/GeoQueryUtilsTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/GeoQueryUtilsTests.kt index 02c2ad055..2ea162b52 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/GeoQueryUtilsTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/GeoQueryUtilsTests.kt @@ -168,8 +168,8 @@ class GeoQueryUtilsTests { assertEqualsIgnoringNoise( """ public.ST_Distance( - 'SRID=4326;POINT(60.124.6)'::geography, - ('SRID=4326;' || (select jsonb_path_query_first('{"@id":"urn:ngsi-ld:Entity:01","@type":["https://uri.etsi.org/ngsi-ld/default-context/Entity"],"https://uri.etsi.org/ngsi-ld/location":[{"@type":["https://uri.etsi.org/ngsi-ld/GeoProperty"],"https://uri.etsi.org/ngsi-ld/hasValue":[{"@value":"POINT(60.0796624.30623)"}]}]}','$."https://uri.etsi.org/ngsi-ld/location"."https://uri.etsi.org/ngsi-ld/hasValue"[0]')->>'@value'))::geography, + cast('SRID=4326;POINT(60.124.6)' as public.geography), + cast('SRID=4326;' || (select jsonb_path_query_first('{"@id":"urn:ngsi-ld:Entity:01","@type":["https://uri.etsi.org/ngsi-ld/default-context/Entity"],"https://uri.etsi.org/ngsi-ld/location":[{"@type":["https://uri.etsi.org/ngsi-ld/GeoProperty"],"https://uri.etsi.org/ngsi-ld/hasValue":[{"@value":"POINT(60.0796624.30623)"}]}]}','$."https://uri.etsi.org/ngsi-ld/location"."https://uri.etsi.org/ngsi-ld/hasValue"[0]')->>'@value') as public.geography), false ) <= 2000 """, @@ -192,8 +192,8 @@ class GeoQueryUtilsTests { assertEqualsIgnoringNoise( """ public.ST_Distance( - 'SRID=4326;POINT(60.124.6)'::geography, - ('SRID=4326;' || (select jsonb_path_query_first('{"@id":"urn:ngsi-ld:Entity:01","@type":["https://uri.etsi.org/ngsi-ld/default-context/Entity"],"https://uri.etsi.org/ngsi-ld/location":[{"@type":["https://uri.etsi.org/ngsi-ld/GeoProperty"],"https://uri.etsi.org/ngsi-ld/hasValue":[{"@value":"POINT(60.3062330.07966)"}]}]}','$."https://uri.etsi.org/ngsi-ld/location"."https://uri.etsi.org/ngsi-ld/hasValue"[0]')->>'@value'))::geography, + cast('SRID=4326;POINT(60.124.6)' as public.geography), + cast('SRID=4326;' || (select jsonb_path_query_first('{"@id":"urn:ngsi-ld:Entity:01","@type":["https://uri.etsi.org/ngsi-ld/default-context/Entity"],"https://uri.etsi.org/ngsi-ld/location":[{"@type":["https://uri.etsi.org/ngsi-ld/GeoProperty"],"https://uri.etsi.org/ngsi-ld/hasValue":[{"@value":"POINT(60.3062330.07966)"}]}]}','$."https://uri.etsi.org/ngsi-ld/location"."https://uri.etsi.org/ngsi-ld/hasValue"[0]')->>'@value') as public.geography), false ) >= 15 """, diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/JsonUtilsTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/JsonUtilsTests.kt index f72e8632b..97c90dfd8 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/JsonUtilsTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/JsonUtilsTests.kt @@ -122,7 +122,7 @@ class JsonUtilsTests { entityId, listOf(BEEHIVE_TYPE), serializeObject(expandJsonLdFragment(entityPayload, APIC_COMPOUND_CONTEXTS)), - APIC_COMPOUND_CONTEXTS + emptyList() ) ) assertJsonPayloadsAreEqual(loadSampleData("events/entity/entityCreateEvent.json"), event) @@ -137,7 +137,7 @@ class JsonUtilsTests { entityId, listOf(BEEHIVE_TYPE), serializeObject(expandJsonLdFragment(entityPayload, APIC_COMPOUND_CONTEXTS)), - APIC_COMPOUND_CONTEXTS + emptyList() ) ) assertJsonPayloadsAreEqual(loadSampleData("events/entity/entityDeleteEvent.json"), event) diff --git a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt index 506cb2e57..14f1b8e86 100644 --- a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt +++ b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/util/JsonLdContextUtils.kt @@ -33,6 +33,8 @@ const val OUTGOING_COMPACT_PROPERTY = "outgoing" const val OUTGOING_PROPERTY = "https://ontology.eglobalmark.com/apic#$OUTGOING_COMPACT_PROPERTY" const val TEMPERATURE_COMPACT_PROPERTY = "temperature" const val TEMPERATURE_PROPERTY = "https://ontology.eglobalmark.com/apic#$TEMPERATURE_COMPACT_PROPERTY" +const val LUMINOSITY_COMPACT_JSONPROPERTY = "luminosity" +const val LUMINOSITY_JSONPROPERTY = "https://ontology.eglobalmark.com/apic#$LUMINOSITY_COMPACT_JSONPROPERTY" const val MANAGED_BY_COMPACT_RELATIONSHIP = "managedBy" const val MANAGED_BY_RELATIONSHIP = "https://ontology.eglobalmark.com/egm#$MANAGED_BY_COMPACT_RELATIONSHIP" diff --git a/shared/src/testFixtures/resources/jsonld-contexts/ngsi-ld-core-context-v1.8.jsonld b/shared/src/testFixtures/resources/jsonld-contexts/ngsi-ld-core-context-v1.8.jsonld index 6a8cb2788..ce3392b4f 100644 --- a/shared/src/testFixtures/resources/jsonld-contexts/ngsi-ld-core-context-v1.8.jsonld +++ b/shared/src/testFixtures/resources/jsonld-contexts/ngsi-ld-core-context-v1.8.jsonld @@ -1,361 +1,378 @@ { - "@context": { - "@vocab": "https://uri.etsi.org/ngsi-ld/default-context/", - "Attribute": "ngsi-ld:Attribute", - "AttributeList": "ngsi-ld:AttributeList", - "ContextSourceIdentity": "ngsi-ld:ContextSourceIdentity", - "ContextSourceNotification": "ngsi-ld:ContextSourceNotification", - "ContextSourceRegistration": "ngsi-ld:ContextSourceRegistration", - "Date": "ngsi-ld:Date", - "DateTime": "ngsi-ld:DateTime", - "EntityType": "ngsi-ld:EntityType", - "EntityTypeInfo": "ngsi-ld:EntityTypeInfo", - "EntityTypeList": "ngsi-ld:EntityTypeList", - "Feature": "geojson:Feature", - "FeatureCollection": "geojson:FeatureCollection", - "GeoProperty": "ngsi-ld:GeoProperty", - "GeometryCollection": "geojson:GeometryCollection", - "JsonProperty": "ngsi-ld:JsonProperty", - "LanguageProperty": "ngsi-ld:LanguageProperty", - "LineString": "geojson:LineString", - "ListProperty": "ngsi-ld:ListProperty", - "ListRelationship": "ngsi-ld:ListRelationship", - "MultiLineString": "geojson:MultiLineString", - "MultiPoint": "geojson:MultiPoint", - "MultiPolygon": "geojson:MultiPolygon", - "Notification": "ngsi-ld:Notification", - "Point": "geojson:Point", - "Polygon": "geojson:Polygon", - "Property": "ngsi-ld:Property", - "Relationship": "ngsi-ld:Relationship", - "Subscription": "ngsi-ld:Subscription", - "TemporalProperty": "ngsi-ld:TemporalProperty", - "Time": "ngsi-ld:Time", - "VocabProperty": "ngsi-ld:VocabProperty", - "accept": "ngsi-ld:accept", - "attributeCount": "attributeCount", - "attributeDetails": "attributeDetails", - "attributeList": { - "@id": "ngsi-ld:attributeList", - "@type": "@vocab" - }, - "attributeName": { - "@id": "ngsi-ld:attributeName", - "@type": "@vocab" - }, - "attributeNames": { - "@id": "ngsi-ld:attributeNames", - "@type": "@vocab" - }, - "attributeTypes": { - "@id": "ngsi-ld:attributeTypes", - "@type": "@vocab" - }, - "attributes": { - "@id": "ngsi-ld:attributes", - "@type": "@vocab" - }, - "attrs": "ngsi-ld:attrs", - "avg": { - "@container": "@list", - "@id": "ngsi-ld:avg" - }, - "bbox": { - "@container": "@list", - "@id": "geojson:bbox" - }, - "cacheDuration": "ngsi-ld:cacheDuration", - "contextSourceInfo": "ngsi-ld:contextSourceInfo", - "cooldown": "ngsi-ld:cooldown", - "coordinates": { - "@container": "@list", - "@id": "geojson:coordinates" - }, - "createdAt": { - "@id": "ngsi-ld:createdAt", - "@type": "DateTime" - }, - "csf": "ngsi-ld:csf", - "data": "ngsi-ld:data", - "dataset": { - "@container": "@index", - "@id": "ngsi-ld:hasDataset" - }, - "datasetId": { - "@id": "ngsi-ld:datasetId", - "@type": "@id" - }, - "deletedAt": { - "@id": "ngsi-ld:deletedAt", - "@type": "DateTime" - }, - "description": "http://purl.org/dc/terms/description", - "detail": "ngsi-ld:detail", - "distinctCount": { - "@container": "@list", - "@id": "ngsi-ld:distinctCount" - }, - "endAt": { - "@id": "ngsi-ld:endAt", - "@type": "DateTime" - }, - "endTimeAt": { - "@id": "ngsi-ld:endTimeAt", - "@type": "DateTime" - }, - "endpoint": "ngsi-ld:endpoint", - "entities": "ngsi-ld:entities", - "entity": "ngsi-ld:entity", - "entityCount": "ngsi-ld:entityCount", - "entityId": { - "@id": "ngsi-ld:entityId", - "@type": "@id" - }, - "entityList": { - "@container": "@list", - "@id": "ngsi-ld:entityList" - }, - "error": "ngsi-ld:error", - "errors": "ngsi-ld:errors", - "expiresAt": { - "@id": "ngsi-ld:expiresAt", - "@type": "DateTime" - }, - "features": { - "@container": "@set", - "@id": "geojson:features" - }, - "format": "ngsi-ld:format", - "geoQ": "ngsi-ld:geoQ", - "geojson": "https://purl.org/geojson/vocab#", - "geometry": "geojson:geometry", - "geoproperty": "ngsi-ld:geoproperty", - "georel": "ngsi-ld:georel", - "id": "@id", - "idPattern": "ngsi-ld:idPattern", - "information": "ngsi-ld:information", - "instanceId": { - "@id": "ngsi-ld:instanceId", - "@type": "@id" - }, - "isActive": "ngsi-ld:isActive", - "json": { - "@id": "ngsi-ld:hasJSON", - "@type": "@json" - }, - "jsons": { - "@container": "@list", - "@id": "ngsi-ld:jsons" - }, - "key": "ngsi-ld:hasKey", - "lang": "ngsi-ld:lang", - "languageMap": { - "@container": "@language", - "@id": "ngsi-ld:hasLanguageMap" - }, - "languageMaps": { - "@container": "@list", - "@id": "ngsi-ld:hasLanguageMaps" - }, - "lastFailure": { - "@id": "ngsi-ld:lastFailure", - "@type": "DateTime" - }, - "lastNotification": { - "@id": "ngsi-ld:lastNotification", - "@type": "DateTime" - }, - "lastSuccess": { - "@id": "ngsi-ld:lastSuccess", - "@type": "DateTime" - }, - "localOnly": "ngsi-ld:localOnly", - "location": "ngsi-ld:location", - "management": "ngsi-ld:management", - "managementInterval": "ngsi-ld:managementInterval", - "max": { - "@container": "@list", - "@id": "ngsi-ld:max" - }, - "min": { - "@container": "@list", - "@id": "ngsi-ld:min" - }, - "mode": "ngsi-ld:mode", - "modifiedAt": { - "@id": "ngsi-ld:modifiedAt", - "@type": "DateTime" - }, - "ngsi-ld": "https://uri.etsi.org/ngsi-ld/", - "notUpdated": "ngsi-ld:notUpdated", - "notification": "ngsi-ld:notification", - "notificationTrigger": "ngsi-ld:notificationTrigger", - "notifiedAt": { - "@id": "ngsi-ld:notifiedAt", - "@type": "DateTime" - }, - "notifierInfo": "ngsi-ld:notifierInfo", - "object": { - "@id": "ngsi-ld:hasObject", - "@type": "@id" - }, - "objectList": { - "@container": "@list", - "@id": "ngsi-ld:hasObjectList" - }, - "objectType": { - "@id": "ngsi-ld:hasObjectType", - "@type": "@vocab" - }, - "objects": { - "@container": "@list", - "@id": "ngsi-ld:hasObjects" - }, - "objectsLists": { - "@container": "@list", - "@id": "ngsi-ld:hasObjectsLists" - }, - "observationInterval": "ngsi-ld:observationInterval", - "observationSpace": "ngsi-ld:observationSpace", - "observedAt": { - "@id": "ngsi-ld:observedAt", - "@type": "DateTime" - }, - "operationSpace": "ngsi-ld:operationSpace", - "operations": "ngsi-ld:operations", - "previousJson": { - "@id": "ngsi-ld:hasPreviousJson", - "@type": "@json" - }, - "previousLanguageMap": { - "@container": "@language", - "@id": "ngsi-ld:hasPreviousLanguageMap" - }, - "previousObject": { - "@id": "ngsi-ld:hasPreviousObject", - "@type": "@id" - }, - "previousObjectList": { - "@container": "@list", - "@id": "ngsi-ld:hasPreviousObjectList" - }, - "previousValue": "ngsi-ld:hasPreviousValue", - "previousValueList": { - "@container": "@list", - "@id": "ngsi-ld:hasPreviousValueList" - }, - "previousVocab": { - "@id": "ngsi-ld:hasPreviousVocab", - "@type": "@vocab" - }, - "properties": "geojson:properties", - "propertyNames": { - "@id": "ngsi-ld:propertyNames", - "@type": "@vocab" - }, - "q": "ngsi-ld:q", - "reason": "ngsi-ld:reason", - "receiverInfo": "ngsi-ld:receiverInfo", - "refreshRate": "ngsi-ld:refreshRate", - "registrationId": "ngsi-ld:registrationId", - "registrationName": "ngsi-ld:registrationName", - "relationshipNames": { - "@id": "ngsi-ld:relationshipNames", - "@type": "@vocab" - }, - "scope": "ngsi-ld:scope", - "scopeQ": "ngsi-ld:scopeQ", - "showChanges": "ngsi-ld:showChanges", - "startAt": { - "@id": "ngsi-ld:startAt", - "@type": "DateTime" - }, - "status": "ngsi-ld:status", - "stddev": { - "@container": "@list", - "@id": "ngsi-ld:stddev" - }, - "subscriptionId": { - "@id": "ngsi-ld:subscriptionId", - "@type": "@id" - }, - "subscriptionName": "ngsi-ld:subscriptionName", - "success": { - "@id": "ngsi-ld:success", - "@type": "@id" - }, - "sum": { - "@container": "@list", - "@id": "ngsi-ld:sum" - }, - "sumsq": { - "@container": "@list", - "@id": "ngsi-ld:sumsq" - }, - "sysAttrs": "ngsi-ld:sysAttrs", - "temporalQ": "ngsi-ld:temporalQ", - "tenant": { - "@id": "ngsi-ld:tenant", - "@type": "@id" - }, - "throttling": "ngsi-ld:throttling", - "timeAt": { - "@id": "ngsi-ld:timeAt", - "@type": "DateTime" - }, - "timeInterval": "ngsi-ld:timeInterval", - "timeout": "ngsi-ld:timeout", - "timeproperty": "ngsi-ld:timeproperty", - "timerel": "ngsi-ld:timerel", - "timesFailed": "ngsi-ld:timesFailed", - "timesSent": "ngsi-ld:timesSent", - "title": "http://purl.org/dc/terms/title", - "totalCount": { - "@container": "@list", - "@id": "ngsi-ld:totalCount" - }, - "triggerReason": "ngsi-ld:triggerReason", - "type": "@type", - "typeList": { - "@id": "ngsi-ld:typeList", - "@type": "@vocab" - }, - "typeName": { - "@id": "ngsi-ld:typeName", - "@type": "@vocab" - }, - "typeNames": { - "@id": "ngsi-ld:typeNames", - "@type": "@vocab" - }, - "unchanged": "ngsi-ld:unchanged", - "unitCode": "ngsi-ld:unitCode", - "updated": "ngsi-ld:updated", - "uri": "ngsi-ld:uri", - "value": "ngsi-ld:hasValue", - "valueList": { - "@container": "@list", - "@id": "ngsi-ld:hasValueList" - }, - "valueLists": { - "@container": "@list", - "@id": "ngsi-ld:hasValueLists" - }, - "values": { - "@container": "@list", - "@id": "ngsi-ld:hasValues" - }, - "vocab": { - "@id": "ngsi-ld:hasVocab", - "@type": "@vocab" - }, - "vocabs": { - "@container": "@list", - "@id": "ngsi-ld:hasVocabs" - }, - "watchedAttributes": { - "@id": "ngsi-ld:watchedAttributes", - "@type": "@vocab" - } - } + "@context": { + "@version": 1.1, + "@protected": true, + "ngsi-ld": "https://uri.etsi.org/ngsi-ld/", + "geojson": "https://purl.org/geojson/vocab#", + "id": "@id", + "type": "@type", + "Attribute": "ngsi-ld:Attribute", + "AttributeList": "ngsi-ld:AttributeList", + "ContextSourceIdentity": "ngsi-ld:ContextSourceIdentity", + "ContextSourceNotification": "ngsi-ld:ContextSourceNotification", + "ContextSourceRegistration": "ngsi-ld:ContextSourceRegistration", + "Date": "ngsi-ld:Date", + "DateTime": "ngsi-ld:DateTime", + "EntityType": "ngsi-ld:EntityType", + "EntityTypeInfo": "ngsi-ld:EntityTypeInfo", + "EntityTypeList": "ngsi-ld:EntityTypeList", + "Feature": "geojson:Feature", + "FeatureCollection": "geojson:FeatureCollection", + "GeoProperty": "ngsi-ld:GeoProperty", + "GeometryCollection": "geojson:GeometryCollection", + "JsonProperty": "ngsi-ld:JsonProperty", + "LanguageProperty": "ngsi-ld:LanguageProperty", + "LineString": "geojson:LineString", + "ListProperty": "ngsi-ld:ListProperty", + "ListRelationship": "ngsi-ld:ListRelationship", + "MultiLineString": "geojson:MultiLineString", + "MultiPoint": "geojson:MultiPoint", + "MultiPolygon": "geojson:MultiPolygon", + "Notification": "ngsi-ld:Notification", + "Point": "geojson:Point", + "Polygon": "geojson:Polygon", + "Property": "ngsi-ld:Property", + "Relationship": "ngsi-ld:Relationship", + "Subscription": "ngsi-ld:Subscription", + "TemporalProperty": "ngsi-ld:TemporalProperty", + "Time": "ngsi-ld:Time", + "VocabProperty": "ngsi-ld:VocabProperty", + "accept": "ngsi-ld:accept", + "attributeCount": "attributeCount", + "attributeDetails": "attributeDetails", + "attributeList": { + "@id": "ngsi-ld:attributeList", + "@type": "@vocab" + }, + "attributeName": { + "@id": "ngsi-ld:attributeName", + "@type": "@vocab" + }, + "attributeNames": { + "@id": "ngsi-ld:attributeNames", + "@type": "@vocab" + }, + "attributeTypes": { + "@id": "ngsi-ld:attributeTypes", + "@type": "@vocab" + }, + "attributes": { + "@id": "ngsi-ld:attributes", + "@type": "@vocab" + }, + "attrs": "ngsi-ld:attrs", + "avg": { + "@id": "ngsi-ld:avg", + "@container": "@list" + }, + "bbox": { + "@container": "@list", + "@id": "geojson:bbox" + }, + "cacheDuration": "ngsi-ld:cacheDuration", + "containedBy": "ngsi-ld:isContainedBy", + "contextSourceAlias": "ngsi-ld:contextSourceAlias", + "contextSourceExtras": { + "@id": "ngsi-ld:contextSourceExtras", + "@type": "@json" + }, + "contextSourceInfo": "ngsi-ld:contextSourceInfo", + "contextSourceTimeAt": { + "@id": "ngsi-ld:contextSourceTimeAt", + "@type": "DateTime" + }, + "contextSourceUptime": "ngsi-ld:contextSourceUptime", + "cooldown": "ngsi-ld:cooldown", + "coordinates": { + "@container": "@list", + "@id": "geojson:coordinates" + }, + "createdAt": { + "@id": "ngsi-ld:createdAt", + "@type": "DateTime" + }, + "csf": "ngsi-ld:csf", + "data": "ngsi-ld:data", + "dataset": { + "@id": "ngsi-ld:hasDataset", + "@container": "@index" + }, + "datasetId": { + "@id": "ngsi-ld:datasetId", + "@type": "@id" + }, + "deletedAt": { + "@id": "ngsi-ld:deletedAt", + "@type": "DateTime" + }, + "description": "http://purl.org/dc/terms/description", + "detail": "ngsi-ld:detail", + "distinctCount": { + "@id": "ngsi-ld:distinctCount", + "@container": "@list" + }, + "endAt": { + "@id": "ngsi-ld:endAt", + "@type": "DateTime" + }, + "endTimeAt": { + "@id": "ngsi-ld:endTimeAt", + "@type": "DateTime" + }, + "endpoint": "ngsi-ld:endpoint", + "entities": "ngsi-ld:entities", + "entity": "ngsi-ld:entity", + "entityCount": "ngsi-ld:entityCount", + "entityId": { + "@id": "ngsi-ld:entityId", + "@type": "@id" + }, + "entityList": { + "@id": "ngsi-ld:entityList", + "@container": "@list" + }, + "entityMap": "ngsi-ld:hasEntityMap", + "error": "ngsi-ld:error", + "errors": "ngsi-ld:errors", + "expiresAt": { + "@id": "ngsi-ld:expiresAt", + "@type": "DateTime" + }, + "features": { + "@container": "@set", + "@id": "geojson:features" + }, + "format": "ngsi-ld:format", + "geoQ": "ngsi-ld:geoQ", + "geometry": "geojson:geometry", + "geoproperty": "ngsi-ld:geoproperty", + "georel": "ngsi-ld:georel", + "idPattern": "ngsi-ld:idPattern", + "information": "ngsi-ld:information", + "instanceId": { + "@id": "ngsi-ld:instanceId", + "@type": "@id" + }, + "isActive": "ngsi-ld:isActive", + "join": "ngsi-ld:join", + "joinLevel": "ngsi-ld:hasJoinLevel", + "json": { + "@id": "ngsi-ld:hasJSON", + "@type": "@json" + }, + "jsons": { + "@id": "ngsi-ld:jsons", + "@container": "@list" + }, + "key": "ngsi-ld:hasKey", + "lang": "ngsi-ld:lang", + "languageMap": { + "@id": "ngsi-ld:hasLanguageMap", + "@container": "@language" + }, + "languageMaps": { + "@id": "ngsi-ld:hasLanguageMaps", + "@container": "@list" + }, + "lastFailure": { + "@id": "ngsi-ld:lastFailure", + "@type": "DateTime" + }, + "lastNotification": { + "@id": "ngsi-ld:lastNotification", + "@type": "DateTime" + }, + "lastSuccess": { + "@id": "ngsi-ld:lastSuccess", + "@type": "DateTime" + }, + "linkedMaps": "ngsi-ld:linkedMaps", + "localOnly": "ngsi-ld:localOnly", + "location": "ngsi-ld:location", + "management": "ngsi-ld:management", + "managementInterval": "ngsi-ld:managementInterval", + "max": { + "@id": "ngsi-ld:max", + "@container": "@list" + }, + "min": { + "@id": "ngsi-ld:min", + "@container": "@list" + }, + "mode": "ngsi-ld:mode", + "modifiedAt": { + "@id": "ngsi-ld:modifiedAt", + "@type": "DateTime" + }, + "notification": "ngsi-ld:notification", + "notificationTrigger": "ngsi-ld:notificationTrigger", + "notifiedAt": { + "@id": "ngsi-ld:notifiedAt", + "@type": "DateTime" + }, + "notifierInfo": "ngsi-ld:notifierInfo", + "notUpdated": "ngsi-ld:notUpdated", + "object": { + "@id": "ngsi-ld:hasObject", + "@type": "@id" + }, + "objectList": { + "@id": "ngsi-ld:hasObjectList", + "@container": "@list" + }, + "objects": { + "@id": "ngsi-ld:hasObjects", + "@container": "@list" + }, + "objectsLists": { + "@id": "ngsi-ld:hasObjectsLists", + "@container": "@list" + }, + "objectType": { + "@id": "ngsi-ld:hasObjectType", + "@type": "@vocab" + }, + "observationInterval": "ngsi-ld:observationInterval", + "observationSpace": "ngsi-ld:observationSpace", + "observedAt": { + "@id": "ngsi-ld:observedAt", + "@type": "DateTime" + }, + "operationSpace": "ngsi-ld:operationSpace", + "operations": "ngsi-ld:operations", + "previousJson": { + "@id": "ngsi-ld:hasPreviousJson", + "@type": "@json" + }, + "previousLanguageMap": { + "@id": "ngsi-ld:hasPreviousLanguageMap", + "@container": "@language" + }, + "previousObject": { + "@id": "ngsi-ld:hasPreviousObject", + "@type": "@id" + }, + "previousObjectList": { + "@id": "ngsi-ld:hasPreviousObjectList", + "@container": "@list" + }, + "previousValue": "ngsi-ld:hasPreviousValue", + "previousValueList": { + "@id": "ngsi-ld:hasPreviousValueList", + "@container": "@list" + }, + "previousVocab": { + "@id": "ngsi-ld:hasPreviousVocab", + "@type": "@vocab" + }, + "properties": "geojson:properties", + "propertyNames": { + "@id": "ngsi-ld:propertyNames", + "@type": "@vocab" + }, + "q": "ngsi-ld:q", + "reason": "ngsi-ld:reason", + "receiverInfo": "ngsi-ld:receiverInfo", + "refreshRate": "ngsi-ld:refreshRate", + "registrationId": "ngsi-ld:registrationId", + "registrationName": "ngsi-ld:registrationName", + "relationshipNames": { + "@id": "ngsi-ld:relationshipNames", + "@type": "@vocab" + }, + "scope": "ngsi-ld:scope", + "scopeQ": "ngsi-ld:scopeQ", + "showChanges": "ngsi-ld:showChanges", + "startAt": { + "@id": "ngsi-ld:startAt", + "@type": "DateTime" + }, + "status": "ngsi-ld:status", + "stddev": { + "@id": "ngsi-ld:stddev", + "@container": "@list" + }, + "subscriptionId": { + "@id": "ngsi-ld:subscriptionId", + "@type": "@id" + }, + "subscriptionName": "ngsi-ld:subscriptionName", + "success": { + "@id": "ngsi-ld:success", + "@type": "@id" + }, + "sum": { + "@id": "ngsi-ld:sum", + "@container": "@list" + }, + "sumsq": { + "@id": "ngsi-ld:sumsq", + "@container": "@list" + }, + "sysAttrs": "ngsi-ld:sysAttrs", + "temporalQ": "ngsi-ld:temporalQ", + "tenant": { + "@id": "ngsi-ld:tenant", + "@type": "@id" + }, + "throttling": "ngsi-ld:throttling", + "timeAt": { + "@id": "ngsi-ld:timeAt", + "@type": "DateTime" + }, + "timeInterval": "ngsi-ld:timeInterval", + "timeout": "ngsi-ld:timeout", + "timeproperty": "ngsi-ld:timeproperty", + "timerel": "ngsi-ld:timerel", + "timesFailed": "ngsi-ld:timesFailed", + "timesSent": "ngsi-ld:timesSent", + "title": "http://purl.org/dc/terms/title", + "totalCount": { + "@id": "ngsi-ld:totalCount", + "@container": "@list" + }, + "triggerReason": "ngsi-ld:triggerReason", + "typeList": { + "@id": "ngsi-ld:typeList", + "@type": "@vocab" + }, + "typeName": { + "@id": "ngsi-ld:typeName", + "@type": "@vocab" + }, + "typeNames": { + "@id": "ngsi-ld:typeNames", + "@type": "@vocab" + }, + "unchanged": "ngsi-ld:unchanged", + "unitCode": "ngsi-ld:unitCode", + "updated": "ngsi-ld:updated", + "uri": "ngsi-ld:uri", + "value": "ngsi-ld:hasValue", + "valueList": { + "@id": "ngsi-ld:hasValueList", + "@container": "@list" + }, + "valueLists": { + "@id": "ngsi-ld:hasValueLists", + "@container": "@list" + }, + "values": { + "@id": "ngsi-ld:hasValues", + "@container": "@list" + }, + "vocab": { + "@id": "ngsi-ld:hasVocab", + "@type": "@vocab" + }, + "vocabs": { + "@id": "ngsi-ld:hasVocabs", + "@container": "@list" + }, + "watchedAttributes": { + "@id": "ngsi-ld:watchedAttributes", + "@type": "@vocab" + }, + "@vocab": "https://uri.etsi.org/ngsi-ld/default-context/" + } } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropDatasetIdEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropDatasetIdEvent.json index 3595f92cb..3e65b879d 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropDatasetIdEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropDatasetIdEvent.json @@ -7,6 +7,6 @@ "overwrite" : true, "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":30}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-10-26T21:32:52.98601Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]},{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.239954Z\"}],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":30}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.357024Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_APPEND" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropEvent.json index 1bb55c897..691eee8e2 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendNumericPropEvent.json @@ -6,6 +6,6 @@ "overwrite" : true, "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.98601Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.939064Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.066037Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_APPEND" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendRelEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendRelEvent.json index 2d947cacc..e87da5e5c 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendRelEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendRelEvent.json @@ -6,6 +6,6 @@ "overwrite" : true, "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:05:39.532687Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.524818Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_APPEND" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTextPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTextPropEvent.json index 461baccd3..725e74884 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTextPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTextPropEvent.json @@ -6,6 +6,6 @@ "overwrite" : true, "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"BeeHiveSophia\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.455870Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.448205Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.094923Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.045041Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:39:39.923329Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.389815Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.417938Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://schema.org/name\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:40:29.239195Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"BeeHiveSophia\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.179446Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:40:29.316765Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_APPEND" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTypeEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTypeEvent.json index 52b8dc6eb..025d59ed4 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTypeEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeAppendTypeEvent.json @@ -6,6 +6,6 @@ "overwrite" : true, "operationPayload" : "[\"https://ontology.eglobalmark.com/apic#BeeHive\",\"https://ontology.eglobalmark.com/apic#Apiary\"]", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\",\"https://ontology.eglobalmark.com/apic#Apiary\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.524818Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_APPEND" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteAllInstancesEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteAllInstancesEvent.json index b5f43ece8..f74eabc89 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteAllInstancesEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteAllInstancesEvent.json @@ -4,6 +4,6 @@ "entityTypes" : [ "https://ontology.eglobalmark.com/apic#BeeHive" ], "attributeName" : "https://ontology.eglobalmark.com/apic#temperature", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:10:16.963869Z\"}]}],\"https://schema.org/name\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:14:03.758379Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"Beehive - Biot\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:14:03.812339Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_DELETE_ALL_INSTANCES" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteEvent.json index 78797861a..3bd992fe1 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeDeleteEvent.json @@ -4,6 +4,6 @@ "entityTypes" : [ "https://ontology.eglobalmark.com/apic#BeeHive" ], "attributeName" : "https://schema.org/name", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.455870Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.448205Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.094923Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.045041Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:39:39.923329Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.389815Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.417938Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.179446Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:40:29.316765Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_DELETE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceNumericPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceNumericPropEvent.json index dd944cf46..fcc11ca83 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceNumericPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceNumericPropEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://ontology.eglobalmark.com/apic#temperature", "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":44}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-11-26T22:35:52.98601Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.455870Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.448205Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.094923Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.045041Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":44}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-11-26T22:35:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.389815Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.417938Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.179446Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.218595Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_REPLACE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceRelEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceRelEvent.json index 740ca5f33..bbe01672b 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceRelEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceRelEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://ontology.eglobalmark.com/egm#managedBy", "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.741832Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_REPLACE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceTextPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceTextPropEvent.json index 236d912cb..39e61ca55 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceTextPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeReplaceTextPropEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://schema.org/name", "operationPayload" : "{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"Beehive - Biot\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]},{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.239954Z\"}],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:07:38.495067Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:10:16.963869Z\"}]}],\"https://schema.org/name\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:14:03.758379Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"Beehive - Biot\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:14:03.812339Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_REPLACE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropDatasetIdEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropDatasetIdEvent.json index db9e185cd..8fc7f069c 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropDatasetIdEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropDatasetIdEvent.json @@ -6,6 +6,6 @@ "datasetId" : "urn:ngsi-ld:Dataset:WeatherApi", "operationPayload" : "{\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.98601Z\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]},{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.239954Z\"}],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:07:38.495067Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:07:38.782299Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_UPDATE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropEvent.json index 94741fe6a..33406f720 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateNumericPropEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://ontology.eglobalmark.com/apic#temperature", "operationPayload" : "{\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.98601Z\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.455870Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.448205Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.094923Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:38:17.045041Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:39:39.923329Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.389815Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.417938Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:36:59.179446Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-12T08:39:39.974642Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_UPDATE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateRelEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateRelEvent.json index 171a1ac5d..211095a2d 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateRelEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateRelEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://ontology.eglobalmark.com/egm#managedBy", "operationPayload" : "{\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]},{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.239954Z\"}],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:07:38.495067Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:10:16.963869Z\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:10:17.318630Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_UPDATE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateTextPropEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateTextPropEvent.json index 4a4008e20..fc5dd3a12 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateTextPropEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/attributeUpdateTextPropEvent.json @@ -5,6 +5,6 @@ "attributeName" : "https://schema.org/name", "operationPayload" : "{\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"Beehive - Valbonne\"}]}", "updatedEntity" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.011609Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.982113Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#luminosity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:57.017715Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:56.987108Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":120}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-02T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"LUX\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.063606Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:50.056169Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]},{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:05:02.239954Z\"}],\"https://uri.etsi.org/ngsi-ld/datasetId\":[{\"@id\":\"urn:ngsi-ld:Dataset:WeatherApi\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":2}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:07:38.495067Z\"}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2021-12-08T10:20:30.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.793453Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#createdBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:07:08.429628Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Craftman:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:00:45.114035Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:02\"}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:10:16.963869Z\"}]}],\"https://schema.org/name\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:11:55.143625Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"Beehive - Valbonne\"}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:12:42.361048Z\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T14:12:42.408699Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ATTRIBUTE_UPDATE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/entityCreateEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/entityCreateEvent.json index 4e7ced377..90de7c3e2 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/entityCreateEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/entityCreateEvent.json @@ -4,6 +4,6 @@ "entityId" : "urn:ngsi-ld:BeeHive:01", "entityTypes" : [ "https://ontology.eglobalmark.com/apic#BeeHive" ], "operationPayload": "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.455870Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.448205Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@value\":\"2019-10-26T21:32:52.986010Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.473904Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.465937Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@value\":\"2019-10-26T21:32:52.986010Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.389815Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.417938Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.179446Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@value\":\"2022-02-12T08:36:59.218595Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ENTITY_CREATE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/entityDeleteEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/entityDeleteEvent.json index 4ca7c4b0c..ed92fc60c 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/entityDeleteEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/entityDeleteEvent.json @@ -1,8 +1,9 @@ { + "sub": null, "tenantName": "urn:ngsi-ld:tenant:default", "entityId" : "urn:ngsi-ld:BeeHive:01", "entityTypes" : [ "https://ontology.eglobalmark.com/apic#BeeHive" ], "deletedEntity":"{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.455870Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.448205Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@value\":\"2019-10-26T21:32:52.986010Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.473904Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.465937Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@value\":\"2019-10-26T21:32:52.986010Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#belongs\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.389815Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Apiary:01\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.417938Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@value\":\"2022-02-12T08:36:59.179446Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@value\":\"2022-02-12T08:36:59.218595Z\",\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ENTITY_DELETE" } diff --git a/shared/src/testFixtures/resources/ngsild/events/entity/entityReplaceEvent.json b/shared/src/testFixtures/resources/ngsild/events/entity/entityReplaceEvent.json index 29cb8383f..84f188351 100644 --- a/shared/src/testFixtures/resources/ngsild/events/entity/entityReplaceEvent.json +++ b/shared/src/testFixtures/resources/ngsild/events/entity/entityReplaceEvent.json @@ -3,6 +3,6 @@ "entityId" : "urn:ngsi-ld:BeeHive:01", "entityTypes" : [ "https://ontology.eglobalmark.com/apic#BeeHive" ], "operationPayload" : "{\"@id\":\"urn:ngsi-ld:BeeHive:01\",\"@type\":[\"https://ontology.eglobalmark.com/apic#BeeHive\"],\"https://ontology.eglobalmark.com/apic#humidity\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.031664Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:02\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.018709Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":60}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"P1\"}]}],\"https://ontology.eglobalmark.com/apic#temperature\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Property\"],\"https://ontology.eglobalmark.com/egm#observedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.083398Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Sensor:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.067964Z\"}],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":22.2}],\"https://uri.etsi.org/ngsi-ld/observedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2019-10-26T21:32:52.986010Z\"}],\"https://uri.etsi.org/ngsi-ld/unitCode\":[{\"@value\":\"CEL\"}]}],\"https://ontology.eglobalmark.com/egm#managedBy\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/Relationship\"],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.109131Z\"}],\"https://uri.etsi.org/ngsi-ld/hasObject\":[{\"@id\":\"urn:ngsi-ld:Beekeeper:01\"}]}],\"https://uri.etsi.org/ngsi-ld/createdAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-13T08:02:49.359316Z\"}],\"https://uri.etsi.org/ngsi-ld/location\":[{\"@type\":[\"https://uri.etsi.org/ngsi-ld/GeoProperty\"],\"https://uri.etsi.org/ngsi-ld/hasValue\":[{\"@value\":\"POINT (24.30623 60.07966)\"}]}],\"https://uri.etsi.org/ngsi-ld/modifiedAt\":[{\"@type\":\"https://uri.etsi.org/ngsi-ld/DateTime\",\"@value\":\"2022-02-14T07:27:15.141078Z\"}]}", - "contexts" : [ "http://localhost:8093/jsonld-contexts/apic-compound.jsonld" ], + "contexts" : [], "operationType" : "ENTITY_REPLACE" } diff --git a/subscription-service/build.gradle.kts b/subscription-service/build.gradle.kts index 6052bf089..ff8e5a18f 100644 --- a/subscription-service/build.gradle.kts +++ b/subscription-service/build.gradle.kts @@ -18,10 +18,10 @@ dependencies { implementation("org.springframework:spring-jdbc") implementation("org.flywaydb:flyway-core") implementation("org.postgresql:r2dbc-postgresql") - implementation("com.jayway.jsonpath:json-path:2.8.0") + implementation("com.jayway.jsonpath:json-path:2.9.0") implementation(project(":shared")) - detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4") + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.5") developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/NotificationParams.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/NotificationParams.kt index 39eea3b2f..2cb0852b9 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/NotificationParams.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/NotificationParams.kt @@ -15,7 +15,9 @@ data class NotificationParams( val timesSent: Int = 0, val lastNotification: ZonedDateTime? = null, val lastFailure: ZonedDateTime? = null, - val lastSuccess: ZonedDateTime? = null + val lastSuccess: ZonedDateTime? = null, + @JsonInclude(value = JsonInclude.Include.NON_DEFAULT) + val sysAttrs: Boolean = false ) { enum class FormatType(val format: String) { @JsonProperty("keyValues") diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Subscription.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Subscription.kt index 04ce928d9..8d7454140 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Subscription.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Subscription.kt @@ -7,10 +7,10 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_CONTEXT import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SYSATTRS_TERMS import com.egm.stellio.shared.util.JsonLdUtils.compactTerm import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdTerm -import com.egm.stellio.shared.util.JsonUtils.convertToMap -import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.egm.stellio.subscription.model.NotificationTrigger.ATTRIBUTE_CREATED import com.egm.stellio.subscription.model.NotificationTrigger.ATTRIBUTE_UPDATED +import com.egm.stellio.subscription.utils.ParsingUtils.convertToMap +import com.egm.stellio.subscription.utils.ParsingUtils.serializeSubscription import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty import org.springframework.data.annotation.Id @@ -49,7 +49,8 @@ data class Subscription( // - used to compact entities in notifications // - used when needed to serve contexts in JSON notifications @JsonProperty(value = JSONLD_CONTEXT) - val contexts: List + val contexts: List, + val throttling: Int? = null ) { @Transient @@ -95,9 +96,6 @@ data class Subscription( watchedAttributes = this.watchedAttributes?.map { compactTerm(it, contexts) } ) - fun compact(context: String): Subscription = - compact(listOf(context)) - fun serialize( contexts: List, mediaType: MediaType = JSON_LD_MEDIA_TYPE, @@ -105,7 +103,7 @@ data class Subscription( ): String = convertToMap(this.compact(contexts)) .toFinalRepresentation(mediaType, includeSysAttrs) - .let { serializeObject(it) } + .let { serializeSubscription(it) } fun serialize( context: String, @@ -185,7 +183,7 @@ fun List.serialize( convertToMap(it.compact(contexts)) .toFinalRepresentation(mediaType, includeSysAttrs) }.let { - serializeObject(it) + serializeSubscription(it) } fun List.mergeEntitySelectorsOnSubscriptions() = diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationService.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationService.kt index 393ebf2a1..9d07ba653 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationService.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationService.kt @@ -47,7 +47,11 @@ class NotificationService( ExpandedEntity(filteredEntity, it.contexts), it.contexts ).toFinalRepresentation( - NgsiLdDataRepresentation(entityRepresentation, attributeRepresentation, true) + NgsiLdDataRepresentation( + entityRepresentation, + attributeRepresentation, + it.notification.sysAttrs + ) ) callSubscriber(it, compactedEntity) } diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionService.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionService.kt index a2f92c482..e64e12c9c 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionService.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionService.kt @@ -1,7 +1,10 @@ package com.egm.stellio.subscription.service -import arrow.core.* +import arrow.core.Either +import arrow.core.Option +import arrow.core.left import arrow.core.raise.either +import arrow.core.right import com.egm.stellio.shared.model.* import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM @@ -10,8 +13,6 @@ import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SUBSCRIPTION_TERM import com.egm.stellio.shared.util.JsonLdUtils.expandJsonLdTerm import com.egm.stellio.subscription.config.SubscriptionProperties import com.egm.stellio.subscription.model.* -import com.egm.stellio.subscription.model.GeoQ -import com.egm.stellio.subscription.model.Subscription import com.egm.stellio.subscription.utils.* import com.egm.stellio.subscription.utils.ParsingUtils.endpointInfoMapToString import com.egm.stellio.subscription.utils.ParsingUtils.endpointInfoToString @@ -19,10 +20,11 @@ import com.egm.stellio.subscription.utils.ParsingUtils.parseEndpointInfo import com.egm.stellio.subscription.utils.ParsingUtils.parseEntitySelector import com.egm.stellio.subscription.utils.ParsingUtils.toSqlColumnName import com.egm.stellio.subscription.utils.ParsingUtils.toSqlValue +import com.egm.stellio.subscription.web.invalidSubscriptionAttributeMessage +import com.egm.stellio.subscription.web.unsupportedSubscriptionAttributeMessage import io.r2dbc.postgresql.codec.Json import kotlinx.coroutines.reactive.awaitFirst import org.locationtech.jts.geom.Geometry -import org.slf4j.LoggerFactory import org.springframework.data.r2dbc.core.R2dbcEntityTemplate import org.springframework.data.relational.core.query.Criteria.where import org.springframework.data.relational.core.query.Query.query @@ -44,13 +46,12 @@ class SubscriptionService( private val r2dbcEntityTemplate: R2dbcEntityTemplate ) { - private val logger = LoggerFactory.getLogger(javaClass) - suspend fun validateNewSubscription(subscription: Subscription): Either = either { checkTypeIsSubscription(subscription).bind() checkIdIsValid(subscription).bind() checkEntitiesOrWatchedAttributes(subscription).bind() checkTimeIntervalGreaterThanZero(subscription).bind() + checkThrottlingGreaterThanZero(subscription).bind() checkSubscriptionValidity(subscription).bind() checkExpiresAtInTheFuture(subscription).bind() checkIdPatternIsValid(subscription).bind() @@ -73,17 +74,31 @@ class SubscriptionService( else Unit.right() private fun checkSubscriptionValidity(subscription: Subscription): Either = - if (subscription.watchedAttributes != null && subscription.timeInterval != null) - BadRequestDataException( - "You can't use 'timeInterval' in conjunction with 'watchedAttributes'" - ).left() - else Unit.right() + when { + subscription.watchedAttributes != null && subscription.timeInterval != null -> { + BadRequestDataException( + "You can't use 'timeInterval' in conjunction with 'watchedAttributes'" + ).left() + } + subscription.timeInterval != null && subscription.throttling != null -> { + BadRequestDataException( + "You can't use 'timeInterval' in conjunction with 'throttling'" + ).left() + } + else -> + Unit.right() + } private fun checkTimeIntervalGreaterThanZero(subscription: Subscription): Either = if (subscription.timeInterval != null && subscription.timeInterval < 1) BadRequestDataException("The value of 'timeInterval' must be greater than zero (int)").left() else Unit.right() + private fun checkThrottlingGreaterThanZero(subscription: Subscription): Either = + if (subscription.throttling != null && subscription.throttling < 1) + BadRequestDataException("The value of 'throttling' must be greater than zero (int)").left() + else Unit.right() + private fun checkExpiresAtInTheFuture(subscription: Subscription): Either = if (subscription.expiresAt != null && subscription.expiresAt.isBefore(ngsiLdDateTime())) BadRequestDataException("'expiresAt' must be in the future").left() @@ -134,11 +149,11 @@ class SubscriptionService( INSERT INTO subscription(id, type, subscription_name, created_at, description, watched_attributes, notification_trigger, time_interval, q, scope_q, notif_attributes, notif_format, endpoint_uri, endpoint_accept, endpoint_receiver_info, endpoint_notifier_info, times_sent, is_active, - expires_at, sub, contexts) + expires_at, sub, contexts, throttling, sys_attrs) VALUES(:id, :type, :subscription_name, :created_at, :description, :watched_attributes, :notification_trigger, :time_interval, :q, :scope_q, :notif_attributes, :notif_format, :endpoint_uri, :endpoint_accept, :endpoint_receiver_info, :endpoint_notifier_info, :times_sent, :is_active, - :expires_at, :sub, :contexts) + :expires_at, :sub, :contexts, :throttling, :sys_attrs) """.trimIndent() databaseClient.sql(insertStatement) @@ -163,6 +178,8 @@ class SubscriptionService( .bind("expires_at", subscription.expiresAt) .bind("sub", sub.toStringValue()) .bind("contexts", subscription.contexts.toTypedArray()) + .bind("throttling", subscription.throttling) + .bind("sys_attrs", subscription.notification.sysAttrs) .execute().bind() geoQuery?.let { @@ -233,7 +250,7 @@ class SubscriptionService( notif_format, endpoint_uri, endpoint_accept, endpoint_receiver_info, endpoint_notifier_info, status, times_sent, is_active, last_notification, last_failure, last_success, entity_selector.id as entity_id, id_pattern, entity_selector.type_selection as type_selection, georel, geometry, coordinates, - pgis_geometry, geoproperty, scope_q, expires_at, contexts + pgis_geometry, geoproperty, scope_q, expires_at, contexts, throttling, sys_attrs FROM subscription LEFT JOIN entity_selector ON entity_selector.subscription_id = :id LEFT JOIN geometry_query ON geometry_query.subscription_id = :id @@ -337,22 +354,21 @@ class SubscriptionService( "q", "scopeQ", "isActive", - "modifiedAt" + "modifiedAt", + "throttling" ).contains(it.key) -> { val columnName = it.key.toSqlColumnName() val value = it.value.toSqlValue(it.key) updateSubscriptionAttribute(subscriptionId, columnName, value).bind() } - listOf("csf", "throttling", "temporalQ").contains(it.key) -> { - logger.warn("Subscription $subscriptionId has unsupported attribute: ${it.key}") - NotImplementedException("Subscription $subscriptionId has unsupported attribute: ${it.key}") + listOf("csf", "temporalQ").contains(it.key) -> { + NotImplementedException(unsupportedSubscriptionAttributeMessage(subscriptionId, it.key)) .left().bind() } else -> { - logger.warn("Subscription $subscriptionId has invalid attribute: ${it.key}") - BadRequestDataException("Subscription $subscriptionId has invalid attribute: ${it.key}") + BadRequestDataException(invalidSubscriptionAttributeMessage(subscriptionId, it.key)) .left().bind() } } @@ -416,6 +432,7 @@ class SubscriptionService( } listOf(Pair("notif_attributes", attributes)) } + "format" -> { val format = if (attribute.value == "keyValues") @@ -424,6 +441,7 @@ class SubscriptionService( NotificationParams.FormatType.NORMALIZED.name listOf(Pair("notif_format", format)) } + "endpoint" -> { val endpoint = attribute.value as Map val accept = @@ -441,6 +459,7 @@ class SubscriptionService( Pair("endpoint_notifier_info", Json.of(endpointInfoMapToString(endpointNotifierInfo))) ) } + else -> throw BadRequestDataException("Could not update attribute ${attribute.key}") } } @@ -479,7 +498,7 @@ class SubscriptionService( notif_format, endpoint_uri, endpoint_accept, endpoint_receiver_info, endpoint_notifier_info, status, times_sent, is_active, last_notification, last_failure, last_success, entity_selector.id as entity_id, id_pattern, entity_selector.type_selection as type_selection, georel, geometry, coordinates, - pgis_geometry, geoproperty, scope_q, expires_at, contexts + pgis_geometry, geoproperty, scope_q, expires_at, contexts, throttling, sys_attrs FROM subscription LEFT JOIN entity_selector ON entity_selector.subscription_id = subscription.id LEFT JOIN geometry_query ON geometry_query.subscription_id = subscription.id @@ -521,13 +540,16 @@ class SubscriptionService( entity_selector.id as entity_id, entity_selector.id_pattern as id_pattern, entity_selector.type_selection as type_selection, georel, geometry, coordinates, pgis_geometry, geoproperty, scope_q, notif_attributes, notif_format, endpoint_uri, endpoint_accept, times_sent, - endpoint_receiver_info, endpoint_notifier_info, contexts + endpoint_receiver_info, endpoint_notifier_info, contexts, throttling, sys_attrs FROM subscription LEFT JOIN entity_selector on subscription.id = entity_selector.subscription_id LEFT JOIN geometry_query on subscription.id = geometry_query.subscription_id WHERE is_active AND ( expires_at is null OR expires_at >= :date ) AND time_interval IS NULL + AND ( throttling IS NULL + OR (last_notification + throttling * INTERVAL '1 second') < :date + OR last_notification IS NULL) AND ( string_to_array(watched_attributes, ',') && string_to_array(:updatedAttributes, ',') OR watched_attributes IS NULL) AND CASE @@ -538,7 +560,7 @@ class SubscriptionService( """.trimIndent() return databaseClient.sql(selectStatement) .bind("updatedAttributes", updatedAttributes.joinToString(separator = ",")) - .bind("date", Instant.now().atZone(ZoneOffset.UTC)) + .bind("date", ngsiLdDateTime()) .allToMappedList { rowToMinimalMatchSubscription(it) } .mergeEntitySelectorsOnSubscriptions() } @@ -666,10 +688,12 @@ class SubscriptionService( timesSent = row["times_sent"] as Int, lastNotification = toNullableZonedDateTime(row["last_notification"]), lastFailure = toNullableZonedDateTime(row["last_failure"]), - lastSuccess = toNullableZonedDateTime(row["last_success"]) + lastSuccess = toNullableZonedDateTime(row["last_success"]), + sysAttrs = row["sys_attrs"] as Boolean ), isActive = toBoolean(row["is_active"]), - contexts = toList(row["contexts"]) + contexts = toList(row["contexts"]), + throttling = toNullableInt(row["throttling"]) ) } @@ -696,9 +720,11 @@ class SubscriptionService( timesSent = row["times_sent"] as Int, lastNotification = null, lastFailure = null, - lastSuccess = null + lastSuccess = null, + sysAttrs = row["sys_attrs"] as Boolean ), - contexts = toList(row["contexts"]) + contexts = toList(row["contexts"]), + throttling = toNullableInt(row["throttling"]) ) } @@ -734,7 +760,7 @@ class SubscriptionService( scope_q, notif_attributes, notif_format, endpoint_uri, endpoint_accept, endpoint_receiver_info, endpoint_notifier_info, status, times_sent, last_notification, last_failure, last_success, is_active, entity_selector.id as entity_id, id_pattern, entity_selector.type_selection as type_selection, georel, - geometry, coordinates, pgis_geometry, geoproperty, contexts + geometry, coordinates, pgis_geometry, geoproperty, contexts, throttling, sys_attrs FROM subscription LEFT JOIN entity_selector ON entity_selector.subscription_id = subscription.id LEFT JOIN geometry_query ON geometry_query.subscription_id = subscription.id diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/ParsingUtils.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/ParsingUtils.kt index ede239240..baa16b0bd 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/ParsingUtils.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/ParsingUtils.kt @@ -10,12 +10,24 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_CONTEXT import com.egm.stellio.shared.util.JsonUtils.deserializeAs import com.egm.stellio.shared.util.JsonUtils.serializeObject import com.egm.stellio.shared.util.expandTypeSelection -import com.egm.stellio.shared.util.mapper import com.egm.stellio.subscription.model.EndpointInfo import com.egm.stellio.subscription.model.Subscription +import com.fasterxml.jackson.annotation.JsonInclude +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.databind.SerializationFeature +import com.fasterxml.jackson.module.kotlin.convertValue +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper object ParsingUtils { + private val mapper: ObjectMapper = + jacksonObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .findAndRegisterModules() + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + fun parseSubscription(input: Map, contexts: List): Either = runCatching { deserializeAs(serializeObject(input.plus(JSONLD_CONTEXT to contexts))) @@ -25,6 +37,12 @@ object ParsingUtils { { it.toAPIException("Failed to parse subscription").left() } ) + fun convertToMap(input: Any): Map = + mapper.convertValue(input) + + fun serializeSubscription(input: Any): String = + mapper.writeValueAsString(input) + fun parseEntitySelector(input: Map, contexts: List): EntitySelector { val entitySelector = mapper.convertValue(input, EntitySelector::class.java) return entitySelector.copy( diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/APIResponses.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/APIResponses.kt index 26ec9a88b..bb593b6c1 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/APIResponses.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/APIResponses.kt @@ -6,3 +6,8 @@ fun subscriptionNotFoundMessage(subscriptionId: URI) = "Could not find a subscri fun subscriptionAlreadyExistsMessage(subscriptionId: URI) = "A subscription with id $subscriptionId already exists" fun subscriptionUnauthorizedMessage(subscriptionId: URI) = "User is not authorized to access subscription $subscriptionId" +fun unsupportedSubscriptionAttributeMessage(subscriptionId: URI, key: String) = + "Subscription $subscriptionId has unsupported attribute: $key" + +fun invalidSubscriptionAttributeMessage(subscriptionId: URI, key: String) = + "Subscription $subscriptionId has invalid attribute: $key" diff --git a/subscription-service/src/main/resources/db/migration/V0_26__add_throttling_to_subscription.sql b/subscription-service/src/main/resources/db/migration/V0_26__add_throttling_to_subscription.sql new file mode 100644 index 000000000..90052808f --- /dev/null +++ b/subscription-service/src/main/resources/db/migration/V0_26__add_throttling_to_subscription.sql @@ -0,0 +1,2 @@ +ALTER TABLE subscription + ADD throttling integer; \ No newline at end of file diff --git a/subscription-service/src/main/resources/db/migration/V0_27__add_sysAttrs_column.sql b/subscription-service/src/main/resources/db/migration/V0_27__add_sysAttrs_column.sql new file mode 100644 index 000000000..84b56c769 --- /dev/null +++ b/subscription-service/src/main/resources/db/migration/V0_27__add_sysAttrs_column.sql @@ -0,0 +1,5 @@ +ALTER TABLE subscription + ADD sys_attrs boolean; + +UPDATE subscription + SET sys_attrs = false; diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/NotificationServiceTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/NotificationServiceTests.kt index 4e2965f03..8ea143423 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/NotificationServiceTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/NotificationServiceTests.kt @@ -1,5 +1,6 @@ package com.egm.stellio.subscription.service +import arrow.core.filterIsInstance import arrow.core.right import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_COMPACTED_ENTITY_CORE_MEMBERS @@ -21,8 +22,8 @@ import io.mockk.confirmVerified import kotlinx.coroutines.reactor.awaitSingle import kotlinx.coroutines.reactor.mono import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -69,6 +70,23 @@ class NotificationServiceTests { } """.trimIndent() + private val entityWithSysAttrs = + """ + { + "id":"$apiaryId", + "type":"Apiary", + "createdAt": "2024-02-13T18:15:00Z", + "modifiedAt": "2024-02-13T18:16:00Z", + "name": { + "type":"Property", + "value":"ApiarySophia", + "createdAt": "2024-02-13T18:15:00Z", + "modifiedAt": "2024-02-13T18:16:00Z" + }, + "@context":[ "$APIC_COMPOUND_CONTEXT" ] + } + """.trimIndent() + @Test fun `it should notify the subscriber and update the subscription`() = runTest { val subscription = gimmeRawSubscription() @@ -404,4 +422,78 @@ class NotificationServiceTests { verify(1, postRequestedFor(urlPathEqualTo("/notification"))) } + + @Test + fun `it should notify the subscriber and return entities without sysAttrs if sysAttrs is false`() = runTest { + val subscription = gimmeRawSubscription() + val expandedEntity = expandJsonLdEntity(entityWithSysAttrs) + + coEvery { + subscriptionService.getMatchingSubscriptions(any(), any(), any()) + } returns listOf(subscription).right() + coEvery { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } returns 1 + + stubFor( + post(urlMatching("/notification")) + .willReturn(ok()) + ) + + notificationService.notifyMatchingSubscribers( + expandedEntity, + setOf(NGSILD_NAME_PROPERTY), + ATTRIBUTE_UPDATED + ).shouldSucceedWith { + val entity = it[0].second.data[0] + assertFalse(entity.containsKey("createdAt")) + assertFalse(entity.containsKey("modifiedAt")) + + entity.filterIsInstance>() + .forEach { (_, v) -> + assertThat(v).doesNotContainKey("createdAt") + assertThat(v).doesNotContainKey("modifiedAt") + } + } + } + + @Test + fun `it should notify the subscriber and return entities with sysAttrs if sysAttrs is true`() = runTest { + val subscription = gimmeRawSubscription().copy( + notification = NotificationParams( + attributes = emptyList(), + endpoint = Endpoint( + uri = "http://localhost:8089/notification".toUri(), + accept = Endpoint.AcceptType.JSONLD + ), + sysAttrs = true + ) + ) + + val expandedEntity = expandJsonLdEntity(entityWithSysAttrs) + + coEvery { + subscriptionService.getMatchingSubscriptions(any(), any(), any()) + } returns listOf(subscription).right() + coEvery { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } returns 1 + + stubFor( + post(urlMatching("/notification")) + .willReturn(ok()) + ) + + notificationService.notifyMatchingSubscribers( + expandedEntity, + setOf(NGSILD_NAME_PROPERTY), + ATTRIBUTE_UPDATED + ).shouldSucceedWith { + val entity = it[0].second.data[0] + assertTrue(entity.containsKey("createdAt")) + assertTrue(entity.containsKey("modifiedAt")) + + entity.filterIsInstance>() + .forEach { (_, v) -> + assertThat(v).containsKey("createdAt") + assertThat(v).containsKey("modifiedAt") + } + } + } } diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/SubscriptionServiceTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/SubscriptionServiceTests.kt index 35a44cc74..88b481be3 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/SubscriptionServiceTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/service/SubscriptionServiceTests.kt @@ -149,6 +149,43 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { } } + @Test + fun `it should not allow a subscription with timeInterval and throttling`() = runTest { + val payload = mapOf( + "id" to "urn:ngsi-ld:Beehive:1234567890".toUri(), + "type" to NGSILD_SUBSCRIPTION_TERM, + "timeInterval" to 10, + "entities" to listOf(mapOf("type" to BEEHIVE_TYPE)), + "notification" to mapOf("endpoint" to mapOf("uri" to "http://my.endpoint/notifiy")), + "throttling" to 30 + ) + + val subscription = ParsingUtils.parseSubscription(payload, emptyList()).shouldSucceedAndResult() + subscriptionService.validateNewSubscription(subscription) + .shouldFailWith { + it is BadRequestDataException && + it.message == "You can't use 'timeInterval' in conjunction with 'throttling'" + } + } + + @Test + fun `it should not allow a subscription with a negative throttling`() = runTest { + val payload = mapOf( + "id" to "urn:ngsi-ld:Beehive:1234567890".toUri(), + "type" to NGSILD_SUBSCRIPTION_TERM, + "entities" to listOf(mapOf("type" to BEEHIVE_TYPE)), + "notification" to mapOf("endpoint" to mapOf("uri" to "http://my.endpoint/notifiy")), + "throttling" to -30 + ) + + val subscription = ParsingUtils.parseSubscription(payload, emptyList()).shouldSucceedAndResult() + subscriptionService.validateNewSubscription(subscription) + .shouldFailWith { + it is BadRequestDataException && + it.message == "The value of 'throttling' must be greater than zero (int)" + } + } + @Test fun `it should not allow a subscription with a negative timeInterval`() = runTest { val payload = mapOf( @@ -285,7 +322,9 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { Endpoint.AcceptType.JSON, listOf(EndpointInfo("Authorization-token", "Authorization-token-value")) ) && - it.expiresAt == ZonedDateTime.parse("2100-01-01T00:00:00Z") + it.notification.sysAttrs && + it.expiresAt == ZonedDateTime.parse("2100-01-01T00:00:00Z") && + it.throttling == 60 } } @@ -678,7 +717,8 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { "geometry" to "Point", "coordinates" to "[100.0, 0.0]", "geoproperty" to "https://uri.etsi.org/ngsi-ld/observationSpace" - ) + ), + "throttling" to 50 ) subscriptionService.update(subscription.id, parsedInput, APIC_COMPOUND_CONTEXTS) @@ -694,7 +734,8 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { it.geoQ!!.georel == "equals" && it.geoQ!!.geometry == "Point" && it.geoQ!!.coordinates == "[100.0, 0.0]" && - it.geoQ!!.geoproperty == "https://uri.etsi.org/ngsi-ld/observationSpace" + it.geoQ!!.geoproperty == "https://uri.etsi.org/ngsi-ld/observationSpace" && + it.throttling == 50 } } @@ -887,12 +928,12 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { val subscription = loadAndDeserializeSubscription("subscription_minimal_entities.json") subscriptionService.create(subscription, mockUserSub).shouldSucceed() - val parsedInput = mapOf("type" to NGSILD_SUBSCRIPTION_TERM, "throttling" to "someValue") + val parsedInput = mapOf("type" to NGSILD_SUBSCRIPTION_TERM, "csf" to "someValue") subscriptionService.update(subscription.id, parsedInput, APIC_COMPOUND_CONTEXTS) .shouldFailWith { it is NotImplementedException && - it.message == "Subscription urn:ngsi-ld:Subscription:1 has unsupported attribute: throttling" + it.message == "Subscription urn:ngsi-ld:Subscription:1 has unsupported attribute: csf" } } @@ -1062,6 +1103,101 @@ class SubscriptionServiceTests : WithTimescaleContainer, WithKafkaContainer { .shouldSucceedWith { assertEquals(0, it.size) } } + @Test + fun `it should not return a subscription if throttling has not elapsed yet`() = runTest { + val expandedEntity = expandJsonLdEntity(entity, APIC_COMPOUND_CONTEXTS) + + val payload = mapOf( + "id" to "urn:ngsi-ld:Subscription:01".toUri(), + "type" to NGSILD_SUBSCRIPTION_TERM, + "watchedAttributes" to listOf(NGSILD_LOCATION_TERM), + "notification" to mapOf( + "endpoint" to mapOf("uri" to "http://my.endpoint/notifiy") + ), + "throttling" to 300 + ) + + val subscription = ParsingUtils.parseSubscription(payload, APIC_COMPOUND_CONTEXTS).shouldSucceedAndResult() + + subscriptionService.create(subscription, mockUserSub).shouldSucceed() + subscriptionService.updateSubscriptionNotification( + subscription, + Notification(subscriptionId = subscription.id, data = emptyList()), + true + ) + + subscriptionService.getMatchingSubscriptions( + expandedEntity, + setOf(NGSILD_LOCATION_PROPERTY), + ATTRIBUTE_UPDATED + ).shouldSucceedWith { + assertEquals(0, it.size) + } + } + + @Test + fun `it should return a subscription if throttling has elapsed`() = runTest { + val expandedEntity = expandJsonLdEntity(entity, APIC_COMPOUND_CONTEXTS) + + val payload = mapOf( + "id" to "urn:ngsi-ld:Subscription:01".toUri(), + "type" to NGSILD_SUBSCRIPTION_TERM, + "watchedAttributes" to listOf(NGSILD_LOCATION_TERM), + "notification" to mapOf( + "endpoint" to mapOf("uri" to "http://my.endpoint/notifiy") + ), + "throttling" to 1 + ) + + val subscription = ParsingUtils.parseSubscription(payload, APIC_COMPOUND_CONTEXTS).shouldSucceedAndResult() + + subscriptionService.create(subscription, mockUserSub).shouldSucceed() + subscriptionService.updateSubscriptionNotification( + subscription, + Notification(subscriptionId = subscription.id, data = emptyList()), + true + ) + + // add a delay for throttling period to be elapsed + runBlocking { + delay(2000) + } + + subscriptionService.getMatchingSubscriptions( + expandedEntity, + setOf(NGSILD_LOCATION_PROPERTY), + ATTRIBUTE_UPDATED + ).shouldSucceedWith { + assertEquals(1, it.size) + } + } + + @Test + fun `it should return a subscription if throttling is not null and last_notification is null`() = runTest { + val expandedEntity = expandJsonLdEntity(entity, APIC_COMPOUND_CONTEXTS) + + val payload = mapOf( + "id" to "urn:ngsi-ld:Subscription:01".toUri(), + "type" to NGSILD_SUBSCRIPTION_TERM, + "watchedAttributes" to listOf(NGSILD_LOCATION_TERM), + "notification" to mapOf( + "endpoint" to mapOf("uri" to "http://my.endpoint/notifiy") + ), + "throttling" to 300 + ) + + val subscription = ParsingUtils.parseSubscription(payload, APIC_COMPOUND_CONTEXTS).shouldSucceedAndResult() + + subscriptionService.create(subscription, mockUserSub).shouldSucceed() + subscriptionService.getMatchingSubscriptions( + expandedEntity, + setOf(NGSILD_LOCATION_PROPERTY), + ATTRIBUTE_UPDATED + ).shouldSucceedWith { + assertEquals(1, it.size) + } + } + @ParameterizedTest @CsvSource( "near;minDistance==1000, Polygon, '[[[100.0, 0.0], [101.0, 0.0], [101.0, -1.0], [100.0, 0.0]]]', 0", diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/ParsingUtilsTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/ParsingUtilsTests.kt index 76c6797d2..c3e65d66f 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/ParsingUtilsTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/ParsingUtilsTests.kt @@ -64,8 +64,9 @@ class ParsingUtilsTests { assertTrue(it is LdContextNotAvailableException) assertEquals( "Unable to load remote context (cause was: JsonLdError[code=There was a problem encountered " + - "loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There was a problem " + - "encountered loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED].])", + "loading a remote context [code=LOADING_REMOTE_CONTEXT_FAILED]., message=There wa a problem " + + "encountered loading a remote context " + + "[https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context-non-existing.jsonld]])", it.message ) }, { diff --git a/subscription-service/src/test/resources/ngsild/subscription_full.json b/subscription-service/src/test/resources/ngsild/subscription_full.json index 584f81164..eda3bfc90 100644 --- a/subscription-service/src/test/resources/ngsild/subscription_full.json +++ b/subscription-service/src/test/resources/ngsild/subscription_full.json @@ -33,7 +33,9 @@ "receiverInfo": [ { "key": "Authorization-token", "value": "Authorization-token-value" } ] - } + }, + "sysAttrs": true }, - "expiresAt": "2100-01-01T00:00:00Z" + "expiresAt": "2100-01-01T00:00:00Z", + "throttling": 60 }