diff --git a/README.md b/README.md index f8d9eee17..3024e324f 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,14 @@ If you want to build only one of the services, you can launch: ./gradlew entity-service:build ``` +### Formatting the code + +To format the code, we use Intellij built-in formatter with settings that can be found in the `config/settings` directory. + +Import settings with `File/Manage IDE Settings/Import Settings...`. + +You can use plugins like [Save Actions](https://plugins.jetbrains.com/plugin/7642-save-actions) that applies changed code refactoring and optimized imports on a save. + ### Working locally with Docker images To work locally with a Docker image of a service without publishing it to Docker Hub, you can follow the below instructions: diff --git a/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/ApiGatewayApplication.kt b/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/ApiGatewayApplication.kt index 8632782b6..cfe37e19d 100644 --- a/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/ApiGatewayApplication.kt +++ b/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/ApiGatewayApplication.kt @@ -2,8 +2,8 @@ package com.egm.stellio.apigateway import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder import org.springframework.cloud.gateway.route.RouteLocator +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory import org.springframework.context.annotation.Bean diff --git a/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/config/SecurityConfig.kt b/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/config/SecurityConfig.kt index ac96c9da4..00677a3b8 100644 --- a/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/config/SecurityConfig.kt +++ b/api-gateway/src/main/kotlin/com/egm/stellio/apigateway/config/SecurityConfig.kt @@ -14,4 +14,4 @@ class SecurityConfig { fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { return http.csrf().disable().build() } -} \ No newline at end of file +} diff --git a/config/settings/stellio_format_settings.zip b/config/settings/stellio_format_settings.zip new file mode 100644 index 000000000..91c363e50 Binary files /dev/null and b/config/settings/stellio_format_settings.zip differ diff --git a/docs/cla/cla.json b/docs/cla/cla.json index 0c6c29225..6d978e7e4 100644 --- a/docs/cla/cla.json +++ b/docs/cla/cla.json @@ -1,3 +1,3 @@ { "signedContributors": [] -} \ No newline at end of file +} diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/AsynchronousEventsConfig.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/AsynchronousEventsConfig.kt index 70ff7ff1e..359a7aaaf 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/AsynchronousEventsConfig.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/AsynchronousEventsConfig.kt @@ -2,9 +2,9 @@ package com.egm.stellio.entity.config import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.core.task.SimpleAsyncTaskExecutor -import org.springframework.context.event.SimpleApplicationEventMulticaster import org.springframework.context.event.ApplicationEventMulticaster +import org.springframework.context.event.SimpleApplicationEventMulticaster +import org.springframework.core.task.SimpleAsyncTaskExecutor @Configuration class AsynchronousEventsConfig { diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/Neo4jConfiguration.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/Neo4jConfiguration.kt index 0b187ff70..6891ab95a 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/Neo4jConfiguration.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/Neo4jConfiguration.kt @@ -1,11 +1,11 @@ package com.egm.stellio.entity.config +import org.neo4j.ogm.session.SessionFactory +import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories -import org.springframework.transaction.annotation.EnableTransactionManagement import org.springframework.data.neo4j.transaction.Neo4jTransactionManager -import org.neo4j.ogm.session.SessionFactory -import org.springframework.context.annotation.Bean +import org.springframework.transaction.annotation.EnableTransactionManagement @Configuration @EnableNeo4jRepositories(basePackages = ["com.egm.stellio.entity.repository"]) diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/WebSecurityConfig.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/WebSecurityConfig.kt index 03c54c2dd..f51172d19 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/config/WebSecurityConfig.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/config/WebSecurityConfig.kt @@ -34,4 +34,4 @@ class WebSecurityConfig { return http.build() } -} \ No newline at end of file +} diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Attribute.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Attribute.kt index 3935b197f..89dc18b65 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Attribute.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Attribute.kt @@ -12,7 +12,7 @@ import org.neo4j.ogm.annotation.Transient import java.time.Instant import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* +import java.util.UUID @NodeEntity open class Attribute( diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Entity.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Entity.kt index 86cfc5b45..672cdeb03 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Entity.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Entity.kt @@ -9,7 +9,9 @@ import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_ENTITY_TYPE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_GEOPROPERTY_VALUE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_LOCATION_PROPERTY import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_MODIFIED_AT_PROPERTY -import com.fasterxml.jackson.annotation.* +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty import org.neo4j.ogm.annotation.Id import org.neo4j.ogm.annotation.Labels import org.neo4j.ogm.annotation.NodeEntity diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Relationship.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Relationship.kt index 217a0b4e8..3ca7cf5fd 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Relationship.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/model/Relationship.kt @@ -13,4 +13,4 @@ class Relationship( val type: List, observedAt: ZonedDateTime? = null -) : Attribute(attributeType = "Relationship", observedAt = observedAt) \ No newline at end of file +) : Attribute(attributeType = "Relationship", observedAt = observedAt) diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/AttributeRepository.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/AttributeRepository.kt index 9dc498cfc..ae1305ff9 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/AttributeRepository.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/AttributeRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.neo4j.repository.Neo4jRepository import org.springframework.stereotype.Repository @Repository -interface AttributeRepository : Neo4jRepository \ No newline at end of file +interface AttributeRepository : Neo4jRepository diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/EntityRepository.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/EntityRepository.kt index fd1ab5536..6b55b63d0 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/EntityRepository.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/EntityRepository.kt @@ -8,36 +8,44 @@ import org.springframework.stereotype.Repository @Repository interface EntityRepository : Neo4jRepository { - @Query(""" + @Query( + """ MATCH (entity:Entity { id: {id} }) RETURN entity - """) + """ + ) fun getEntityCoreById(id: String): List> - @Query(""" + @Query( + """ MATCH (entity:Entity { id: {id} })-[:HAS_VALUE]->(property:Property) OPTIONAL MATCH (property)-[:HAS_VALUE]->(propValue:Property) OPTIONAL MATCH (property)-[:HAS_OBJECT]->(relOfProp:Relationship)-[rel]->(relOfPropObject:Entity) WHERE type(rel) <> 'HAS_OBJECT' RETURN property, propValue, type(rel) as relType, relOfProp, relOfPropObject - """) + """ + ) fun getEntitySpecificProperties(id: String): List> - @Query(""" + @Query( + """ MATCH (entity:Entity { id: {id} })-[:HAS_VALUE]->(property:Property {id: {propertyId} }) OPTIONAL MATCH (property)-[:HAS_VALUE]->(propValue:Property) OPTIONAL MATCH (property)-[:HAS_OBJECT]->(relOfProp:Relationship)-[rel]->(relOfPropObject:Entity) WHERE type(rel) <> 'HAS_OBJECT' RETURN property, propValue, type(rel) as relType, relOfProp, relOfPropObject - """) + """ + ) fun getEntitySpecificProperty(id: String, propertyId: String): List> - @Query(""" + @Query( + """ MATCH (entity:Entity { id: {id} })-[:HAS_OBJECT]->(rel:Relationship)-[r]->(relObject:Entity) WHERE type(r) <> 'HAS_OBJECT' OPTIONAL MATCH (rel)-[:HAS_OBJECT]->(relOfRel:Relationship)-[or]->(relOfRelObject:Entity) WHERE type(or) <> 'HAS_OBJECT' RETURN rel, type(r) as relType, relObject, relOfRel, type(or) as relOfRelType, relOfRelObject - """) + """ + ) fun getEntityRelationships(id: String): List> } diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/Neo4jRepository.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/Neo4jRepository.kt index 4f820b092..90119771a 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/Neo4jRepository.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/Neo4jRepository.kt @@ -39,7 +39,7 @@ class Neo4jRepository( MATCH (subject:Attribute { id: "$relationshipId" }), (target:Entity { id: "$entityId" }) MERGE (subject)-[:$relationshipType]->(target) MERGE (subject)-[:HAS_OBJECT]->(target) - """ + """ return session.query(query, emptyMap()).queryStatistics().relationshipsCreated } @@ -52,7 +52,7 @@ class Neo4jRepository( val query = """ MERGE (subject:Entity { id: "$subjectId" }) ON MATCH SET subject.location = point({x: ${coordinates.first}, y: ${coordinates.second}, crs: 'wgs-84'}) - """ + """ return session.query(query, emptyMap()).queryStatistics().propertiesSet } @@ -71,7 +71,7 @@ class Neo4jRepository( val query = """ MATCH (a { id: '$attributeId' })-[:HAS_OBJECT]->(rel)-[:$relationshipType]->() RETURN a.id - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toList().isNotEmpty() } @@ -80,7 +80,7 @@ class Neo4jRepository( val query = """ MATCH (a { id: '$attributeId' })-[:HAS_VALUE]->(property:Property { name: "$propertyName" }) RETURN a.id - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toList().isNotEmpty() } @@ -89,7 +89,7 @@ class Neo4jRepository( val query = """ MATCH (a { id: '$attributeId' }) WHERE a.$geoPropertyName IS NOT NULL RETURN a.id - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toList().isNotEmpty() } @@ -106,17 +106,19 @@ class Neo4jRepository( (target:Entity { id: "$newRelationshipObjectId" }) DETACH DELETE v MERGE (a)-[:HAS_OBJECT]->(target) - """.trimIndent() + """.trimIndent() val relationshipTypeQuery = """ MATCH (a:Attribute { id: "$attributeId" })-[v:$relationshipType]->(e:Entity { id: '$oldRelationshipObjectId' }), (target:Entity { id: "$newRelationshipObjectId" }) DETACH DELETE v MERGE (a)-[:$relationshipType]->(target) - """.trimIndent() + """.trimIndent() - val objectQueryStatistics = session.query(hasObjectQuery, emptyMap()).queryStatistics().nodesDeleted - val relationshipTypeQueryStatistics = session.query(relationshipTypeQuery, emptyMap()).queryStatistics().nodesDeleted + val objectQueryStatistics = + session.query(hasObjectQuery, emptyMap()).queryStatistics().nodesDeleted + val relationshipTypeQueryStatistics = + session.query(relationshipTypeQuery, emptyMap()).queryStatistics().nodesDeleted return Pair(objectQueryStatistics, relationshipTypeQueryStatistics) } @@ -126,7 +128,7 @@ class Neo4jRepository( val query = """ MERGE (entity:Entity { id: "$entityId" }) ON MATCH SET entity.location = point({x: ${coordinates.first}, y: ${coordinates.second}, crs: 'wgs-84'}) - """ + """ return session.query(query, emptyMap()).queryStatistics().propertiesSet } @@ -152,7 +154,7 @@ class Neo4jRepository( OPTIONAL MATCH (rel)-[:HAS_VALUE]->(propOfRel) OPTIONAL MATCH (rel)-[:HAS_OBJECT]->(relOfRel:Relationship) DETACH DELETE n,prop,relOfProp,propOfProp,rel,propOfRel,relOfRel - """.trimIndent() + """.trimIndent() val queryStatistics = session.query(query, emptyMap()).queryStatistics() logger.debug("Deleted entity $entityId : deleted ${queryStatistics.nodesDeleted} nodes, ${queryStatistics.relationshipsDeleted} relations") @@ -180,7 +182,7 @@ class Neo4jRepository( OPTIONAL MATCH (rel)-[:HAS_VALUE]->(propOfRel) OPTIONAL MATCH (rel)-[:HAS_OBJECT]->(relOfRel:Relationship) DETACH DELETE prop,relOfProp,propOfProp,rel,propOfRel,relOfRel - """.trimIndent() + """.trimIndent() val queryStatistics = session.query(query, emptyMap()).queryStatistics() logger.debug("Deleted entity $entityId attributes : deleted ${queryStatistics.nodesDeleted} nodes, ${queryStatistics.relationshipsDeleted} relations") @@ -201,7 +203,7 @@ class Neo4jRepository( OPTIONAL MATCH (prop)-[:HAS_VALUE]->(propOfProp) OPTIONAL MATCH (prop)-[:HAS_OBJECT]->(relOfProp) DETACH DELETE prop,propOfProp,relOfProp - """.trimIndent() + """.trimIndent() val queryStatistics = session.query(query, emptyMap()).queryStatistics() logger.debug("Deleted property $propertyName : deleted ${queryStatistics.nodesDeleted} nodes, ${queryStatistics.relationshipsDeleted} relations") @@ -227,7 +229,7 @@ class Neo4jRepository( OPTIONAL MATCH (rel)-[:HAS_VALUE]->(propOfRel) OPTIONAL MATCH (rel)-[:HAS_OBJECT]->(relOfRel:Relationship) DETACH DELETE rel,propOfRel,relOfRel - """.trimIndent() + """.trimIndent() val queryStatistics = session.query(query, emptyMap()).queryStatistics() logger.debug("Deleted relationship $relationshipType : deleted ${queryStatistics.nodesDeleted} nodes, ${queryStatistics.relationshipsDeleted} relations") @@ -289,7 +291,7 @@ class Neo4jRepository( ${if (propertiesFilter.isNotEmpty() && relationshipsFilter.isNotEmpty()) " AND " else ""} $relationshipsFilter RETURN n.id as id - """ + """ logger.debug("Issuing search query $finalQuery") return session.query(finalQuery, emptyMap(), true) @@ -316,7 +318,7 @@ class Neo4jRepository( AND deviceProp.name = '$propertyName' AND toLower(deviceProp.value) = toLower('$observerId') RETURN e - """.trimIndent() + """.trimIndent() logger.debug("Issuing query $query") return session.query(query, emptyMap(), true).toMutableList() @@ -328,7 +330,7 @@ class Neo4jRepository( val query = """ MATCH (p:Property)-[:HAS_OBJECT]->(r:Relationship)-[:$relationshipType]->(e:Entity { id: '$observerId' }) RETURN p - """.trimIndent() + """.trimIndent() logger.debug("Issuing query $query") return session.query(query, emptyMap(), true).toMutableList() @@ -340,7 +342,7 @@ class Neo4jRepository( val query = """ MATCH (n:Entity)-[:HAS_VALUE]->(p:Property { id: '${property.id}' }) RETURN n - """.trimIndent() + """.trimIndent() logger.debug("Issuing query $query") return session.query(query, emptyMap(), true).toMutableList() @@ -356,7 +358,7 @@ class Neo4jRepository( MATCH (entity:Entity) WHERE entity.id IN {entitiesIds} RETURN entity.id as id - """ + """ return session.query(query, mapOf("entitiesIds" to entitiesIds), true).map { it["id"] as String } } @@ -365,7 +367,7 @@ class Neo4jRepository( val query = """ MATCH ({ id: '$subjectId' })-[:HAS_VALUE]->(p:Property { name: "$propertyName" }) RETURN p - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toMutableList() .map { it["p"] as Property } @@ -376,7 +378,7 @@ class Neo4jRepository( val query = """ MATCH ({ id: '$subjectId' })-[:HAS_OBJECT]->(r:Relationship)-[:$relationshipType]->() RETURN r - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toMutableList() .map { it["r"] as Relationship } @@ -387,7 +389,7 @@ class Neo4jRepository( val query = """ MATCH ({ id: '$subjectId' })-[:HAS_OBJECT]->(r:Relationship)-[:$relationshipType]->(e: Entity) RETURN e - """.trimIndent() + """.trimIndent() return session.query(query, emptyMap(), true).toMutableList() .map { it["e"] as Entity } .firstOrNull() diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/PropertyRepository.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/PropertyRepository.kt index 51002a137..479931d25 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/PropertyRepository.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/PropertyRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.neo4j.repository.Neo4jRepository import org.springframework.stereotype.Repository @Repository -interface PropertyRepository : Neo4jRepository \ No newline at end of file +interface PropertyRepository : Neo4jRepository diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/RelationshipRepository.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/RelationshipRepository.kt index 6ba50e0b5..4d59d05b3 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/RelationshipRepository.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/repository/RelationshipRepository.kt @@ -5,4 +5,4 @@ import org.springframework.data.neo4j.repository.Neo4jRepository import org.springframework.stereotype.Repository @Repository -interface RelationshipRepository : Neo4jRepository \ No newline at end of file +interface RelationshipRepository : Neo4jRepository diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityOperationService.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityOperationService.kt index 27349cad8..97b97f182 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityOperationService.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityOperationService.kt @@ -92,11 +92,14 @@ class EntityOperationService( }.collect( { BatchOperationResult() }, { batchOperationResult, updateResult -> - updateResult.fold({ - batchOperationResult.errors.add(it) - }, { - batchOperationResult.success.add(it) - }) + updateResult.fold( + { + batchOperationResult.errors.add(it) + }, + { + batchOperationResult.success.add(it) + } + ) }, BatchOperationResult::plusAssign ) diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityService.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityService.kt index d575d8ef8..4fbc6bf60 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityService.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/EntityService.kt @@ -1,9 +1,23 @@ package com.egm.stellio.entity.service -import com.egm.stellio.entity.model.* -import com.egm.stellio.entity.repository.* +import com.egm.stellio.entity.model.Attribute +import com.egm.stellio.entity.model.Entity +import com.egm.stellio.entity.model.NotUpdatedDetails +import com.egm.stellio.entity.model.Property +import com.egm.stellio.entity.model.Relationship +import com.egm.stellio.entity.model.UpdateResult +import com.egm.stellio.entity.repository.AttributeRepository +import com.egm.stellio.entity.repository.EntityRepository +import com.egm.stellio.entity.repository.Neo4jRepository +import com.egm.stellio.entity.repository.PropertyRepository +import com.egm.stellio.entity.repository.RelationshipRepository import com.egm.stellio.entity.util.extractComparaisonParametersFromQuery -import com.egm.stellio.shared.model.* +import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.EntityEvent +import com.egm.stellio.shared.model.EventType +import com.egm.stellio.shared.model.ExpandedEntity +import com.egm.stellio.shared.model.Observation +import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.ApiUtils.serializeObject import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_OBSERVED_BY import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_RAISED_NOTIFICATION @@ -61,7 +75,8 @@ class EntityService( @Transactional fun createEntity(expandedEntity: ExpandedEntity): Entity { - val rawEntity = Entity(id = expandedEntity.id, type = listOf(expandedEntity.type), contexts = expandedEntity.contexts) + val rawEntity = + Entity(id = expandedEntity.id, type = listOf(expandedEntity.type), contexts = expandedEntity.contexts) val entity = entityRepository.save(rawEntity) // filter the unwanted entries and expand all attributes for easier later processing @@ -111,7 +126,11 @@ class EntityService( applicationEventPublisher.publishEvent(entityEvent) } - internal fun createEntityProperty(entity: Entity, propertyKey: String, propertyValues: Map>): Property { + internal fun createEntityProperty( + entity: Entity, + propertyKey: String, + propertyValues: Map> + ): Property { val propertyValue = getPropertyValueFromMap(propertyValues, NGSILD_PROPERTY_VALUE) ?: throw BadRequestDataException("Key $NGSILD_PROPERTY_VALUE not found in $propertyValues") @@ -156,7 +175,11 @@ class EntityService( entity.relationships.add(relationship) entityRepository.save(entity) - neo4jRepository.createRelationshipToEntity(relationship.id, relationshipType.toRelationshipTypeName(), targetEntityId) + neo4jRepository.createRelationshipToEntity( + relationship.id, + relationshipType.toRelationshipTypeName(), + targetEntityId + ) createAttributeProperties(relationship, relationshipValues) createAttributeRelationships(relationship, relationshipValues) @@ -167,19 +190,17 @@ class EntityService( private fun createAttributeProperties(subject: Attribute, values: Map>) { values.filterValues { it[0] is Map<*, *> - } - .mapValues { + }.mapValues { expandValueAsMap(it.value) - } - .filter { + }.filter { !(NGSILD_PROPERTIES_CORE_MEMBERS.plus(NGSILD_RELATIONSHIPS_CORE_MEMBERS)).contains(it.key) && - isAttributeOfType(it.value, NGSILD_PROPERTY_TYPE) - } - .forEach { entry -> + isAttributeOfType(it.value, NGSILD_PROPERTY_TYPE) + }.forEach { entry -> logger.debug("Creating property ${entry.key} with values ${entry.value}") // for short-handed properties, the value is directly accessible from the map under the @value key - val propertyValue = getPropertyValueFromMap(entry.value, NGSILD_PROPERTY_VALUE) ?: entry.value["@value"]!! + val propertyValue = + getPropertyValueFromMap(entry.value, NGSILD_PROPERTY_VALUE) ?: entry.value["@value"]!! val rawProperty = Property( name = entry.key, value = propertyValue, @@ -214,7 +235,11 @@ class EntityService( subject.relationships.add(relationship) attributeRepository.save(subject) - neo4jRepository.createRelationshipToEntity(relationship.id, propEntry.key.toRelationshipTypeName(), objectEntity.get().id) + neo4jRepository.createRelationshipToEntity( + relationship.id, + propEntry.key.toRelationshipTypeName(), + objectEntity.get().id + ) } else { throw BadRequestDataException("Target entity $objectId in property $subject does not exist, create it first") } @@ -225,7 +250,11 @@ class EntityService( } } - internal fun createLocationProperty(entity: Entity, propertyKey: String, propertyValues: Map>): Int { + internal fun createLocationProperty( + entity: Entity, + propertyKey: String, + propertyValues: Map> + ): Int { logger.debug("Geo property $propertyKey has values $propertyValues") val geoPropertyValue = expandValueAsMap(propertyValues[NGSILD_GEOPROPERTY_VALUE]!!) @@ -306,7 +335,10 @@ class EntityService( return ExpandedEntity(resultEntity, entity.contexts) } - private fun buildPropertyFragment(rawProperty: List>, contexts: List): Pair> { + private fun buildPropertyFragment( + rawProperty: List>, + contexts: List + ): Pair> { val property = rawProperty[0]["property"]!! as Property val propertyKey = property.name val propertyValues = property.serializeCoreProperties() @@ -330,7 +362,8 @@ class EntityService( ) val relationshipValues = relationship.serializeCoreProperties() relationshipValues.putAll(relationshipValue) - val expandedRelationshipKey = expandRelationshipType(mapOf(relationshipKey to relationshipValue), contexts) + val expandedRelationshipKey = + expandRelationshipType(mapOf(relationshipKey to relationshipValue), contexts) propertyValues[expandedRelationshipKey] = relationshipValues } @@ -338,7 +371,8 @@ class EntityService( } fun getSerializedEntityById(entityId: String): String { - val mapper = jacksonObjectMapper().findAndRegisterModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + val mapper = + jacksonObjectMapper().findAndRegisterModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) val entity = getFullEntityById(entityId) return mapper.writeValueAsString( JsonLdProcessor.compact( @@ -381,7 +415,11 @@ class EntityService( .map { getFullEntityById(it) } } - fun appendEntityAttributes(entityId: String, attributes: Map, disallowOverwrite: Boolean): UpdateResult { + fun appendEntityAttributes( + entityId: String, + attributes: Map, + disallowOverwrite: Boolean + ): UpdateResult { val updateStatuses = attributes.map { val attributeValue = expandValueAsMap(it.value) if (!attributeValue.containsKey("@type")) @@ -391,14 +429,28 @@ class EntityService( if (attributeType == NGSILD_RELATIONSHIP_TYPE.uri) { val relationshipTypeName = it.key.extractShortTypeFromExpanded() if (!neo4jRepository.hasRelationshipOfType(entityId, relationshipTypeName.toRelationshipTypeName())) { - createEntityRelationship(entityRepository.findById(entityId).get(), it.key, attributeValue, getRelationshipObjectId(attributeValue)) + createEntityRelationship( + entityRepository.findById(entityId).get(), + it.key, + attributeValue, + getRelationshipObjectId(attributeValue) + ) Triple(it.key, true, null) } else if (disallowOverwrite) { logger.info("Relationship $relationshipTypeName already exists on $entityId and overwrite is not allowed, ignoring") - Triple(it.key, false, "Relationship $relationshipTypeName already exists on $entityId and overwrite is not allowed, ignoring") + Triple( + it.key, + false, + "Relationship $relationshipTypeName already exists on $entityId and overwrite is not allowed, ignoring" + ) } else { neo4jRepository.deleteEntityRelationship(entityId, relationshipTypeName.toRelationshipTypeName()) - createEntityRelationship(entityRepository.findById(entityId).get(), it.key, attributeValue, getRelationshipObjectId(attributeValue)) + createEntityRelationship( + entityRepository.findById(entityId).get(), + it.key, + attributeValue, + getRelationshipObjectId(attributeValue) + ) Triple(it.key, true, null) } } else if (attributeType == NGSILD_PROPERTY_TYPE.uri) { @@ -436,7 +488,7 @@ class EntityService( Triple(it.key, false, "Unknown attribute type $attributeType") } } - .toList() + .toList() val updated = updateStatuses.filter { it.second }.map { it.first } val notUpdated = updateStatuses.filter { !it.second }.map { NotUpdatedDetails(it.first, it.third!!) } @@ -466,7 +518,12 @@ class EntityService( logger.debug("Trying to update attribute $shortAttributeName of type $attributeType") if (attributeType == NGSILD_RELATIONSHIP_TYPE.uri) { if (neo4jRepository.hasRelationshipOfType(id, it.key.toRelationshipTypeName())) { - updateRelationshipOfEntity(entity, it.key, attributeValue, getRelationshipObjectId(expandValueAsMap(it.value))) + updateRelationshipOfEntity( + entity, + it.key, + attributeValue, + getRelationshipObjectId(expandValueAsMap(it.value)) + ) updatedAttributes.add(shortAttributeName) updatedAttributesPayload.add(compactAndStringifyFragment(it.key, it.value, contextLink)) } else @@ -507,7 +564,11 @@ class EntityService( return UpdateResult(updatedAttributes, notUpdatedAttributes) } - private fun updatePropertyOfEntity(entity: Entity, propertyKey: String, propertyValues: Map>): Property { + private fun updatePropertyOfEntity( + entity: Entity, + propertyKey: String, + propertyValues: Map> + ): Property { val property = updatePropertyValues(entity.id, propertyKey, propertyValues) updatePropertiesOfAttribute(property, propertyValues) updateRelationshipsOfAttribute(property, propertyValues) @@ -515,7 +576,11 @@ class EntityService( return property } - private fun updatePropertyValues(subjectId: String, propertyKey: String, propertyValues: Map>): Property { + private fun updatePropertyValues( + subjectId: String, + propertyKey: String, + propertyValues: Map> + ): Property { val unitCode = getPropertyValueFromMapAsString(propertyValues, NGSILD_UNIT_CODE_PROPERTY) val value = getPropertyValueFromMap(propertyValues, NGSILD_PROPERTY_VALUE) ?: propertyValues["@value"]!! val observedAt = getPropertyValueFromMapAsDateTime(propertyValues, NGSILD_OBSERVED_AT_PROPERTY) @@ -526,7 +591,12 @@ class EntityService( return propertyRepository.save(property) } - private fun updateRelationshipOfEntity(entity: Entity, relationshipType: String, relationshipValues: Map>, targetEntityId: String): Relationship { + private fun updateRelationshipOfEntity( + entity: Entity, + relationshipType: String, + relationshipValues: Map>, + targetEntityId: String + ): Relationship { val relationship = updateRelationshipValues(entity.id, relationshipType, relationshipValues, targetEntityId) updatePropertiesOfAttribute(relationship, relationshipValues) updateRelationshipsOfAttribute(relationship, relationshipValues) @@ -544,8 +614,10 @@ class EntityService( if (!entityRepository.findById(targetEntityId).isPresent) throw BadRequestDataException("Target entity $targetEntityId does not exist, create it first") - val relationship = neo4jRepository.getRelationshipOfSubject(subjectId, relationshipType.toRelationshipTypeName()) - val oldRelationshipTarget = neo4jRepository.getRelationshipTargetOfSubject(subjectId, relationshipType.toRelationshipTypeName())!! + val relationship = + neo4jRepository.getRelationshipOfSubject(subjectId, relationshipType.toRelationshipTypeName()) + val oldRelationshipTarget = + neo4jRepository.getRelationshipTargetOfSubject(subjectId, relationshipType.toRelationshipTypeName())!! relationship.observedAt = getPropertyValueFromMapAsDateTime(relationshipValues, NGSILD_OBSERVED_AT_PROPERTY) neo4jRepository.updateRelationshipTargetOfAttribute( relationship.id, relationshipType.toRelationshipTypeName(), @@ -558,15 +630,12 @@ class EntityService( private fun updatePropertiesOfAttribute(subject: Attribute, values: Map>) { values.filterValues { it[0] is Map<*, *> - } - .mapValues { + }.mapValues { expandValueAsMap(it.value) - } - .filter { + }.filter { !(NGSILD_PROPERTIES_CORE_MEMBERS.plus(NGSILD_RELATIONSHIPS_CORE_MEMBERS)).contains(it.key) && - isAttributeOfType(it.value, NGSILD_PROPERTY_TYPE) - } - .forEach { entry -> + isAttributeOfType(it.value, NGSILD_PROPERTY_TYPE) + }.forEach { entry -> if (!neo4jRepository.hasPropertyOfName(subject.id, entry.key)) throw BadRequestDataException("Property ${entry.key.extractShortTypeFromExpanded()} does not exist") updatePropertyValues(subject.id, entry.key, entry.value) @@ -591,7 +660,11 @@ class EntityService( } } - internal fun updateLocationPropertyOfEntity(entity: Entity, propertyKey: String, propertyValues: Map>) { + internal fun updateLocationPropertyOfEntity( + entity: Entity, + propertyKey: String, + propertyValues: Map> + ) { logger.debug("Geo property $propertyKey has values $propertyValues") val geoPropertyValue = expandValueAsMap(propertyValues[NGSILD_GEOPROPERTY_VALUE]!!) val geoPropertyType = geoPropertyValue["@type"]!![0] as String @@ -618,13 +691,17 @@ class EntityService( if (neo4jRepository.hasPropertyOfName(entityId, expandedAttributeName)) return neo4jRepository.deleteEntityProperty(entityId, expandedAttributeName) >= 1 else if (neo4jRepository.hasRelationshipOfType(entityId, expandedAttributeName.toRelationshipTypeName())) - return neo4jRepository.deleteEntityRelationship(entityId, expandedAttributeName.toRelationshipTypeName()) >= 1 + return neo4jRepository.deleteEntityRelationship( + entityId, + expandedAttributeName.toRelationshipTypeName() + ) >= 1 throw ResourceNotFoundException("Attribute $attributeName not found in entity $entityId") } fun updateEntityLastMeasure(observation: Observation) { - val observingEntity = neo4jRepository.getObservingSensorEntity(observation.observedBy, EGM_VENDOR_ID, observation.attributeName) + val observingEntity = + neo4jRepository.getObservingSensorEntity(observation.observedBy, EGM_VENDOR_ID, observation.attributeName) if (observingEntity == null) { logger.warn("Unable to find observing entity ${observation.observedBy} for property ${observation.attributeName}") return @@ -669,7 +746,12 @@ class EntityService( val subscription = Entity(id = id, type = listOf(expandJsonLdKey(type, NGSILD_CORE_CONTEXT)!!)) properties.forEach { - val property = propertyRepository.save(Property(name = expandJsonLdKey(it.key, NGSILD_CORE_CONTEXT)!!, value = serializeObject(it.value))) + val property = propertyRepository.save( + Property( + name = expandJsonLdKey(it.key, NGSILD_CORE_CONTEXT)!!, + value = serializeObject(it.value) + ) + ) subscription.properties.add(property) } subscription.contexts = listOf(NGSILD_EGM_CONTEXT, NGSILD_CORE_CONTEXT) @@ -685,18 +767,29 @@ class EntityService( val notification = Entity(id = id, type = listOf(expandJsonLdKey(type, NGSILD_CORE_CONTEXT)!!)) properties.forEach { - val property = propertyRepository.save(Property(name = expandJsonLdKey(it.key, NGSILD_CORE_CONTEXT)!!, value = serializeObject(it.value))) + val property = propertyRepository.save( + Property( + name = expandJsonLdKey(it.key, NGSILD_CORE_CONTEXT)!!, + value = serializeObject(it.value) + ) + ) notification.properties.add(property) } notification.contexts = listOf(NGSILD_EGM_CONTEXT, NGSILD_CORE_CONTEXT) entityRepository.save(notification) // Find the last notification of the subscription - val lastNotification = neo4jRepository.getRelationshipTargetOfSubject(subscriptionId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName()) + val lastNotification = neo4jRepository.getRelationshipTargetOfSubject( + subscriptionId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName() + ) // Create relationship between the subscription and the new notification if (lastNotification != null) { - val relationship = neo4jRepository.getRelationshipOfSubject(subscriptionId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName()) + val relationship = neo4jRepository.getRelationshipOfSubject( + subscriptionId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName() + ) neo4jRepository.updateRelationshipTargetOfAttribute( relationship.id, EGM_RAISED_NOTIFICATION.toRelationshipTypeName(), @@ -708,4 +801,4 @@ class EntityService( } else createEntityRelationship(subscription.get(), EGM_RAISED_NOTIFICATION, emptyMap(), notification.id) } -} \ No newline at end of file +} diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/RepositoryEventsListener.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/RepositoryEventsListener.kt index 943edc0d5..2abc70e5f 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/RepositoryEventsListener.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/RepositoryEventsListener.kt @@ -21,7 +21,8 @@ class RepositoryEventsListener( ) { private val logger = LoggerFactory.getLogger(RepositoryEventsListener::class.java) - private val mapper = jacksonObjectMapper().findAndRegisterModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + private val mapper = + jacksonObjectMapper().findAndRegisterModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // TODO: For deserialization in other modules Jackson can be used with the entityEvent model that gives a payload and an updatedEntity in a proper json format @Async @@ -31,8 +32,18 @@ class RepositoryEventsListener( // TODO BinderAwareChannelResolver is deprecated but there is no clear migration path yet, wait for maturity val result = when (entityEvent.operationType) { - EventType.CREATE -> sendCreateMessage(channelName, entityEvent.entityId, entityEvent.entityType, entityEvent.payload!!) - EventType.UPDATE -> sendUpdateMessage(channelName, entityEvent.entityId, entityEvent.entityType, entityEvent.payload!!) + EventType.CREATE -> sendCreateMessage( + channelName, + entityEvent.entityId, + entityEvent.entityType, + entityEvent.payload!! + ) + EventType.UPDATE -> sendUpdateMessage( + channelName, + entityEvent.entityId, + entityEvent.entityType, + entityEvent.payload!! + ) else -> false } @@ -43,21 +54,26 @@ class RepositoryEventsListener( } private fun sendCreateMessage(channelName: String, entityId: String, entityType: String, payload: String): Boolean { - val data = mapOf("operationType" to EventType.CREATE.name, + val data = mapOf( + "operationType" to EventType.CREATE.name, "entityId" to entityId, "entityType" to entityType, "payload" to payload ) return resolver.resolveDestination(channelName) - .send(MessageBuilder.createMessage(mapper.writeValueAsString(data), - MessageHeaders(mapOf(MessageHeaders.ID to entityId)) - )) + .send( + MessageBuilder.createMessage( + mapper.writeValueAsString(data), + MessageHeaders(mapOf(MessageHeaders.ID to entityId)) + ) + ) } private fun sendUpdateMessage(channelName: String, entityId: String, entityType: String, payload: String): Boolean { val entity = getEntityById(entityId) - val data = mapOf("operationType" to EventType.UPDATE.name, + val data = mapOf( + "operationType" to EventType.UPDATE.name, "entityId" to entityId, "entityType" to entityType, "payload" to payload, @@ -65,9 +81,12 @@ class RepositoryEventsListener( ) return resolver.resolveDestination(channelName) - .send(MessageBuilder.createMessage(mapper.writeValueAsString(data), - MessageHeaders(mapOf(MessageHeaders.ID to entityId)) - )) + .send( + MessageBuilder.createMessage( + mapper.writeValueAsString(data), + MessageHeaders(mapOf(MessageHeaders.ID to entityId)) + ) + ) } private fun getEntityById(entityId: String): String { diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/SubscriptionListener.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/SubscriptionListener.kt index 41701422e..e0bb00032 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/service/SubscriptionListener.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/service/SubscriptionListener.kt @@ -59,7 +59,12 @@ class SubscriptionListener( val subscriptionId = parsedNotification["subscriptionId"] as String parsedNotification = parsedNotification.minus("id").minus("type").minus("subscriptionId") - entityService.createNotificationEntity(entityEvent.entityId, entityEvent.entityType, subscriptionId, parsedNotification) + entityService.createNotificationEntity( + entityEvent.entityId, + entityEvent.entityType, + subscriptionId, + parsedNotification + ) } else -> logger.warn("Received unexpected event type ${entityEvent.operationType} for notification ${entityEvent.entityId}") } diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilder.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilder.kt index 47bec78e7..770498480 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilder.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilder.kt @@ -77,4 +77,4 @@ class EntitiesGraphBuilder( ?: throw BadRequestDataException("Target entity $it does not exist.") } } -} \ No newline at end of file +} diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityHandler.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityHandler.kt index b04fd4b6a..0ae83cbd9 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityHandler.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityHandler.kt @@ -1,27 +1,37 @@ package com.egm.stellio.entity.web import com.egm.stellio.entity.service.EntityService -import com.egm.stellio.shared.util.NgsiLdParsingUtils import com.egm.stellio.entity.util.decode import com.egm.stellio.shared.model.AlreadyExistsException +import com.egm.stellio.shared.model.BadRequestDataResponse +import com.egm.stellio.shared.model.InternalErrorResponse import com.egm.stellio.shared.model.ResourceNotFoundException -import com.egm.stellio.shared.util.extractContextFromLinkHeader -import com.egm.stellio.shared.util.NgsiLdParsingUtils.compactEntities -import com.egm.stellio.shared.model.* import com.egm.stellio.shared.util.ApiUtils.serializeObject import com.egm.stellio.shared.util.JSON_LD_CONTENT_TYPE import com.egm.stellio.shared.util.JSON_MERGE_PATCH_CONTENT_TYPE +import com.egm.stellio.shared.util.NgsiLdParsingUtils +import com.egm.stellio.shared.util.NgsiLdParsingUtils.compactEntities +import com.egm.stellio.shared.util.extractContextFromLinkHeader import org.slf4j.LoggerFactory import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.util.MultiValueMap -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono import java.net.URI -import java.util.* +import java.util.Optional @RestController @RequestMapping("/ngsi-ld/v1/entities") @@ -61,7 +71,7 @@ class EntityHandler( */ @GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) fun getEntities(@RequestHeader httpHeaders: HttpHeaders, @RequestParam params: MultiValueMap): - Mono> { + Mono> { val type = params.getFirst("type") ?: "" val q = params.getOrDefault("q", emptyList()) @@ -162,7 +172,10 @@ class EntityHandler( * Implements 6.6.3.2 - Update Entity Attributes * */ - @PatchMapping("/{entityId}/attrs", consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE]) + @PatchMapping( + "/{entityId}/attrs", + consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE] + ) fun updateEntityAttributes( @RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: String, @@ -190,7 +203,10 @@ class EntityHandler( * Implements 6.7.3.1 - Partial Attribute Update * Current implementation is basic and only update the value of a property. */ - @PatchMapping("/{entityId}/attrs/{attrId}", consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE]) + @PatchMapping( + "/{entityId}/attrs/{attrId}", + consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE] + ) fun partialAttributeUpdate( @RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: String, diff --git a/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityOperationHandler.kt b/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityOperationHandler.kt index 1dc2404c4..0cdac2664 100644 --- a/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityOperationHandler.kt +++ b/entity-service/src/main/kotlin/com/egm/stellio/entity/web/EntityOperationHandler.kt @@ -8,7 +8,11 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono @RestController @@ -87,4 +91,4 @@ class EntityOperationHandler( mapper.typeFactory.constructCollectionType(MutableList::class.java, Map::class.java) ) } -} \ No newline at end of file +} diff --git a/entity-service/src/main/resources/application.properties b/entity-service/src/main/resources/application.properties index 4035006c1..8f3dbedd5 100644 --- a/entity-service/src/main/resources/application.properties +++ b/entity-service/src/main/resources/application.properties @@ -29,4 +29,4 @@ org.neo4j.driver.authentication.username=neo4j org.neo4j.driver.authentication.password=neo4j_password org.neo4j.driver.uri=bolt://localhost:7687 # Disable the check if the location exists -org.neo4j.migrations.check-location=false \ No newline at end of file +org.neo4j.migrations.check-location=false diff --git a/entity-service/src/main/resources/jarcache.json.back b/entity-service/src/main/resources/jarcache.json.back index db9300961..13ed78b9d 100644 --- a/entity-service/src/main/resources/jarcache.json.back +++ b/entity-service/src/main/resources/jarcache.json.back @@ -9,4 +9,4 @@ "X-Classpath": "contexts/ngsi-ld-core-context.jsonld", "Content-Type": "application/ld+json" } -] \ No newline at end of file +] diff --git a/entity-service/src/main/resources/neo4j/migrations/V01__fix_temporal_values_data_type.cypher b/entity-service/src/main/resources/neo4j/migrations/V01__fix_temporal_values_data_type.cypher index 84c919f0b..6f07f94b9 100644 --- a/entity-service/src/main/resources/neo4j/migrations/V01__fix_temporal_values_data_type.cypher +++ b/entity-service/src/main/resources/neo4j/migrations/V01__fix_temporal_values_data_type.cypher @@ -4,4 +4,4 @@ set e.modifiedAt = dateTime(e.modifiedAt) set a.createdAt = datetime(a.createdAt) set a.modifiedAt = dateTime(a.modifiedAt) set a.observedAt = datetime(a.observedAt) -; \ No newline at end of file +; diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/config/TestContainersConfiguration.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/config/TestContainersConfiguration.kt index bbfb1d215..2b309661a 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/config/TestContainersConfiguration.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/config/TestContainersConfiguration.kt @@ -13,7 +13,10 @@ class TestContainersConfiguration { object EntityServiceTestContainers : TestContainers("neo4j", 7687) { fun getNeo4jUri(): String { - return "bolt://" + instance.getServiceHost(serviceName, servicePort) + ":" + instance.getServicePort(serviceName, servicePort) + return "bolt://" + instance.getServiceHost(serviceName, servicePort) + ":" + instance.getServicePort( + serviceName, + servicePort + ) } } @@ -26,5 +29,5 @@ class TestContainersConfiguration { .credentials(DB_USER, DB_PASSWORD) .useNativeTypes() .build() - } + } } diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/config/WebSecurityTestConfig.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/config/WebSecurityTestConfig.kt index d36d27c55..9c3cf4fd1 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/config/WebSecurityTestConfig.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/config/WebSecurityTestConfig.kt @@ -19,4 +19,4 @@ class WebSecurityTestConfig : WebSecurityConfig() { fun jwtDecoder(): ReactiveJwtDecoder { return ReactiveJwtDecoders.fromOidcIssuerLocation(issuerUri) } -} \ No newline at end of file +} diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/EntityRepositoryTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/EntityRepositoryTests.kt index dd8203294..c325d8aa6 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/EntityRepositoryTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/EntityRepositoryTests.kt @@ -3,7 +3,9 @@ package com.egm.stellio.entity.repository import com.egm.stellio.entity.config.TestContainersConfiguration import com.egm.stellio.entity.model.Entity import com.egm.stellio.entity.model.Property -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -23,7 +25,11 @@ class EntityRepositoryTests { @Test fun `it should add modifiedAt value when creating new entity`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) assertNotNull(entityRepository.findById("urn:ngsi-ld:Beekeeper:1233").get().modifiedAt) neo4jRepository.deleteEntity(entity.id) } @@ -40,7 +46,11 @@ class EntityRepositoryTests { @Test fun `it should not retrieve an entity from an unknown property id`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) val loadedProperty = entityRepository.getEntitySpecificProperty(entity.id, "unknown-property-id") assertTrue(loadedProperty.isEmpty()) neo4jRepository.deleteEntity(entity.id) diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/Neo4jRepositoryTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/Neo4jRepositoryTests.kt index 028a20792..f9b8dfa9e 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/Neo4jRepositoryTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/repository/Neo4jRepositoryTests.kt @@ -9,7 +9,11 @@ import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_IS_CONTAINED_IN import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_OBSERVED_BY import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_VENDOR_ID import com.egm.stellio.shared.util.toRelationshipTypeName -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertNotNull +import junit.framework.TestCase.assertNull +import junit.framework.TestCase.assertTrue import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -42,119 +46,221 @@ class Neo4jRepositoryTests { @Test fun `it should return an entity if type and string properties are correct`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1230", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("name", "=", "Scalpa")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1230", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("name", "=", "Scalpa"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an empty list if string properties are wrong`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1231", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("name", "=", "ScalpaXYZ")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1231", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("name", "=", "ScalpaXYZ"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if type and integer properties are correct`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BN", listOf("DeadFishes"), mutableListOf(Property(name = "fishNumber", value = 500))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishNumber", "=", "500")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BN", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishNumber", value = 500)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishNumber", "=", "500"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an empty list if integer properties are wrong`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BO", listOf("DeadFishes"), mutableListOf(Property(name = "fishNumber", value = 500))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishNumber", "=", "499")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BO", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishNumber", value = 500)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishNumber", "=", "499"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if type and float properties are correct`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BP", listOf("DeadFishes"), mutableListOf(Property(name = "fishWeight", value = 120.50))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishWeight", "=", "120.50")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BP", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishWeight", value = 120.50)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishWeight", "=", "120.50"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an empty list if float properties are wrong`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BQ", listOf("DeadFishes"), mutableListOf(Property(name = "fishWeight", value = -120.50))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishWeight", "=", "-120")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BQ", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishWeight", value = -120.50)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishWeight", "=", "-120"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an empty list if given weight equals to entity weight and comparaison parameter is wrong`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BR", listOf("DeadFishes"), mutableListOf(Property(name = "fishWeight", value = 180.9))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishWeight", ">", "180.9")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BR", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishWeight", value = 180.9)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishWeight", ">", "180.9"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if given weight equals to entity weight and comparaison parameter is correct`() { - val entity = createEntity("urn:ngsi-ld:DeadFishes:019BS", listOf("DeadFishes"), mutableListOf(Property(name = "fishWeight", value = 255))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("DeadFishes", Pair(listOf(), listOf(Triple("fishWeight", ">=", "255")))) + val entity = createEntity( + "urn:ngsi-ld:DeadFishes:019BS", + listOf("DeadFishes"), + mutableListOf(Property(name = "fishWeight", value = 255)) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "DeadFishes", + Pair(listOf(), listOf(Triple("fishWeight", ">=", "255"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an empty list if given name equals to entity name and comparaison parameter is wrong`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1232", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "ScalpaXYZ"))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("name", "<>", "ScalpaXYZ")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1232", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "ScalpaXYZ")) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("name", "<>", "ScalpaXYZ"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if given name not equals to entity name and comparaison parameter is correct`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) - val entities = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("name", "<>", "ScalpaXYZ")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) + val entities = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("name", "<>", "ScalpaXYZ"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if type and dateTime properties are correct`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1234", listOf("Beekeeper"), mutableListOf(Property(name = "testedAt", value = ZonedDateTime.parse("2018-12-04T12:00:00Z")))) - val entities = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-04T12:00:00Z")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1234", + listOf("Beekeeper"), + mutableListOf(Property(name = "testedAt", value = ZonedDateTime.parse("2018-12-04T12:00:00Z"))) + ) + val entities = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-04T12:00:00Z"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if type and date properties are correct`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1235", listOf("Beekeeper"), mutableListOf(Property(name = "testedAt", value = LocalDate.parse("2018-12-04")))) - val entities = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-04")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1235", + listOf("Beekeeper"), + mutableListOf(Property(name = "testedAt", value = LocalDate.parse("2018-12-04"))) + ) + val entities = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-04"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should not return an entity if type is correct but not the compared date`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1235", listOf("Beekeeper"), mutableListOf(Property(name = "testedAt", value = LocalDate.parse("2018-12-04")))) - val entities = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-07")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1235", + listOf("Beekeeper"), + mutableListOf(Property(name = "testedAt", value = LocalDate.parse("2018-12-04"))) + ) + val entities = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("testedAt", "=", "2018-12-07"))) + ) assertFalse(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should return an entity if type and time properties are correct`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1236", listOf("Beekeeper"), mutableListOf(Property(name = "testedAt", value = LocalTime.parse("12:00:00")))) - val entities: List = neo4jRepository.getEntitiesByTypeAndQuery("Beekeeper", Pair(listOf(), listOf(Triple("testedAt", "=", "12:00:00")))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1236", + listOf("Beekeeper"), + mutableListOf(Property(name = "testedAt", value = LocalTime.parse("12:00:00"))) + ) + val entities: List = neo4jRepository.getEntitiesByTypeAndQuery( + "Beekeeper", + Pair(listOf(), listOf(Triple("testedAt", "=", "12:00:00"))) + ) assertTrue(entities.contains(entity.id)) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should update a property value of a string type`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) neo4jRepository.updateEntityAttribute(entity.id, "name", "new name") assertEquals("new name", neo4jRepository.getPropertyOfSubject(entity.id, "name").value) neo4jRepository.deleteEntity(entity.id) @@ -162,7 +268,11 @@ class Neo4jRepositoryTests { @Test fun `it should update a property value of a numeric type`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = 100L))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = 100L)) + ) neo4jRepository.updateEntityAttribute(entity.id, "name", 200L) assertEquals(200L, neo4jRepository.getPropertyOfSubject(entity.id, "name").value) neo4jRepository.deleteEntity(entity.id) @@ -186,9 +296,17 @@ class Neo4jRepositoryTests { @Test fun `it should update modifiedAt value when updating an entity`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) val modifiedAt = entityRepository.findById("urn:ngsi-ld:Beekeeper:1233").get().modifiedAt - createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Demha"))) + createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Demha")) + ) val updatedModifiedAt = entityRepository.findById("urn:ngsi-ld:Beekeeper:1233").get().modifiedAt assertThat(updatedModifiedAt).isAfter(modifiedAt) neo4jRepository.deleteEntity(entity.id) @@ -197,7 +315,11 @@ class Neo4jRepositoryTests { @Test fun `it should find a sensor by vendor id and measured property`() { val vendorId = "urn:something:9876" - val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId))) + val entity = createEntity( + "urn:ngsi-ld:Sensor:1233", + listOf("Sensor"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId)) + ) val property = createProperty("https://ontology.eglobalmark.com/apic#outgoing", 1.0) val relationship = createRelationship(property, EGM_OBSERVED_BY, entity.id) val persistedEntity = neo4jRepository.getObservingSensorEntity(vendorId, EGM_VENDOR_ID, "outgoing") @@ -210,10 +332,15 @@ class Neo4jRepositoryTests { @Test fun `it should find a sensor by vendor id with case not matching and measured property`() { val vendorId = "urn:something:9876" - val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId))) + val entity = createEntity( + "urn:ngsi-ld:Sensor:1233", + listOf("Sensor"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId)) + ) val property = createProperty("https://ontology.eglobalmark.com/apic#outgoing", 1.0) val relationship = createRelationship(property, EGM_OBSERVED_BY, entity.id) - val persistedEntity = neo4jRepository.getObservingSensorEntity(vendorId.toUpperCase(), EGM_VENDOR_ID, "outgoing") + val persistedEntity = + neo4jRepository.getObservingSensorEntity(vendorId.toUpperCase(), EGM_VENDOR_ID, "outgoing") assertNotNull(persistedEntity) propertyRepository.delete(property) relationshipRepository.delete(relationship) @@ -224,7 +351,11 @@ class Neo4jRepositoryTests { fun `it should find a sensor by vendor id of a device and measured property`() { val vendorId = "urn:something:9876" val sensor = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf()) - val device = createEntity("urn:ngsi-ld:Device:1233", listOf("Device"), mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId))) + val device = createEntity( + "urn:ngsi-ld:Device:1233", + listOf("Device"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId)) + ) val sensorToDeviceRelationship = createRelationship(sensor, EGM_IS_CONTAINED_IN, device.id) val property = createProperty("https://ontology.eglobalmark.com/apic#outgoing", 1.0) val propertyToDeviceRelationship = createRelationship(property, EGM_OBSERVED_BY, sensor.id) @@ -240,7 +371,11 @@ class Neo4jRepositoryTests { @Test fun `it should not find a sensor by vendor id if measured property does not exist`() { val vendorId = "urn:something:9876" - val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId))) + val entity = createEntity( + "urn:ngsi-ld:Sensor:1233", + listOf("Sensor"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId)) + ) val property = createProperty("https://ontology.eglobalmark.com/apic#outgoing", 1.0) val relationship = createRelationship(property, EGM_OBSERVED_BY, entity.id) val persistedEntity = neo4jRepository.getObservingSensorEntity(vendorId, EGM_VENDOR_ID, "incoming") @@ -253,10 +388,15 @@ class Neo4jRepositoryTests { @Test fun `it should not find a sensor if measured property exists but not the vendor id`() { val vendorId = "urn:something:9876" - val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId))) + val entity = createEntity( + "urn:ngsi-ld:Sensor:1233", + listOf("Sensor"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = vendorId)) + ) val property = createProperty("https://ontology.eglobalmark.com/apic#outgoing", 1.0) val relationship = createRelationship(property, EGM_OBSERVED_BY, entity.id) - val persistedEntity = neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:Unknown", EGM_VENDOR_ID, "outgoing") + val persistedEntity = + neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:Unknown", EGM_VENDOR_ID, "outgoing") assertNull(persistedEntity) propertyRepository.delete(property) relationshipRepository.delete(relationship) @@ -266,22 +406,32 @@ class Neo4jRepositoryTests { @Test fun `it should find a sensor by NGSI-LD id`() { val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf()) - val persistedEntity = neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:1233", EGM_VENDOR_ID, "anything") + val persistedEntity = + neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:1233", EGM_VENDOR_ID, "anything") assertNotNull(persistedEntity) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should not find a sensor if neither NGSI-LD or vendor id matches`() { - val entity = createEntity("urn:ngsi-ld:Sensor:1233", listOf("Sensor"), mutableListOf(Property(name = EGM_VENDOR_ID, value = "urn:something:9876"))) - val persistedEntity = neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:Unknown", EGM_VENDOR_ID, "unknownMeasure") + val entity = createEntity( + "urn:ngsi-ld:Sensor:1233", + listOf("Sensor"), + mutableListOf(Property(name = EGM_VENDOR_ID, value = "urn:something:9876")) + ) + val persistedEntity = + neo4jRepository.getObservingSensorEntity("urn:ngsi-ld:Sensor:Unknown", EGM_VENDOR_ID, "unknownMeasure") assertNull(persistedEntity) neo4jRepository.deleteEntity(entity.id) } @Test fun `it should create createdAt property with time zone related information equal to the character "Z"`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "name", value = "Scalpa"))) + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", + listOf("Beekeeper"), + mutableListOf(Property(name = "name", value = "Scalpa")) + ) assertTrue(entity.createdAt.toString().endsWith("Z")) neo4jRepository.deleteEntity(entity.id) } @@ -316,9 +466,13 @@ class Neo4jRepositoryTests { @Test fun `it should delete an entity property`() { - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), - mutableListOf(Property(name = "firstName", value = "Scalpa"), Property(name = "lastName", value = "Charity")) + mutableListOf( + Property(name = "firstName", value = "Scalpa"), + Property(name = "lastName", value = "Charity") + ) ) neo4jRepository.deleteEntityProperty(entity.id, "lastName") @@ -347,7 +501,8 @@ class Neo4jRepositoryTests { val originProperty = createProperty("origin", "Latin") lastNameProperty.properties.add(originProperty) propertyRepository.save(lastNameProperty) - val entity = createEntity("urn:ngsi-ld:Beekeeper:1233", + val entity = createEntity( + "urn:ngsi-ld:Beekeeper:1233", listOf("Beekeeper"), mutableListOf(Property(name = "firstName", value = "Scalpa"), lastNameProperty) ) @@ -396,8 +551,10 @@ class Neo4jRepositoryTests { val persistedRelationship = relationshipRepository.save(relationship) subject.relationships.add(persistedRelationship) attributeRepository.save(subject) - neo4jRepository.createRelationshipToEntity(persistedRelationship.id, - relationshipType.toRelationshipTypeName(), objectId) + neo4jRepository.createRelationshipToEntity( + persistedRelationship.id, + relationshipType.toRelationshipTypeName(), objectId + ) return persistedRelationship } @@ -407,8 +564,10 @@ class Neo4jRepositoryTests { val persistedRelationship = relationshipRepository.save(relationship) subject.relationships.add(persistedRelationship) entityRepository.save(subject) - neo4jRepository.createRelationshipToEntity(persistedRelationship.id, - relationshipType.toRelationshipTypeName(), objectId) + neo4jRepository.createRelationshipToEntity( + persistedRelationship.id, + relationshipType.toRelationshipTypeName(), objectId + ) return persistedRelationship } diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntitiesListenerTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntitiesListenerTests.kt index dc5c3c5ab..2c4018c9b 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntitiesListenerTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntitiesListenerTests.kt @@ -3,7 +3,12 @@ package com.egm.stellio.entity.service import com.egm.stellio.shared.model.Observation import com.egm.stellio.shared.util.NgsiLdParsingUtils import com.ninjasquad.springmockk.MockkBean -import io.mockk.* +import io.mockk.Called +import io.mockk.Runs +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.just +import io.mockk.verify import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest @@ -12,7 +17,7 @@ import org.springframework.test.context.ActiveProfiles import java.time.ZonedDateTime import java.time.format.DateTimeFormatter -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ EntitiesListener::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [EntitiesListener::class]) @ActiveProfiles("test") class EntitiesListenerTests { @@ -34,15 +39,17 @@ class EntitiesListenerTests { entitiesListener.processMessage(observation.inputStream.readBytes().toString(Charsets.UTF_8)) - verify { entityService.updateEntityLastMeasure(match { observation -> - observation.attributeName == "incoming" && - observation.unitCode == "CEL" && - observation.value == 20.7 && - observation.observedAt.format(DateTimeFormatter.ISO_INSTANT) == "2019-10-18T07:31:39.770Z" && - observation.observedBy == "urn:sosa:Sensor:10e2073a01080065" && - observation.longitude == 24.30623 && - observation.latitude == 60.07966 - }) } + verify { + entityService.updateEntityLastMeasure(match { observation -> + observation.attributeName == "incoming" && + observation.unitCode == "CEL" && + observation.value == 20.7 && + observation.observedAt.format(DateTimeFormatter.ISO_INSTANT) == "2019-10-18T07:31:39.770Z" && + observation.observedBy == "urn:sosa:Sensor:10e2073a01080065" && + observation.longitude == 24.30623 && + observation.latitude == 60.07966 + }) + } confirmVerified(entityService) } @@ -55,15 +62,17 @@ class EntitiesListenerTests { entitiesListener.processMessage(observation.inputStream.readBytes().toString(Charsets.UTF_8)) - verify { entityService.updateEntityLastMeasure(match { observation -> - observation.attributeName == "incoming" && + verify { + entityService.updateEntityLastMeasure(match { observation -> + observation.attributeName == "incoming" && observation.unitCode == "CEL" && observation.value == 20.7 && observation.observedAt.format(DateTimeFormatter.ISO_INSTANT) == "2019-10-18T07:31:39.770Z" && observation.observedBy == "urn:sosa:Sensor:10e2073a01080065" && observation.latitude == null && observation.longitude == null - }) } + }) + } confirmVerified(entityService) } diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntityServiceTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntityServiceTests.kt index 29b1aa5dd..18ecad5b2 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntityServiceTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/EntityServiceTests.kt @@ -1,21 +1,35 @@ package com.egm.stellio.entity.service -import com.egm.stellio.entity.model.* -import com.egm.stellio.entity.repository.* +import com.egm.stellio.entity.model.Attribute +import com.egm.stellio.entity.model.Entity +import com.egm.stellio.entity.model.Property +import com.egm.stellio.entity.model.Relationship +import com.egm.stellio.entity.repository.AttributeRepository +import com.egm.stellio.entity.repository.EntityRepository +import com.egm.stellio.entity.repository.Neo4jRepository +import com.egm.stellio.entity.repository.PropertyRepository +import com.egm.stellio.entity.repository.RelationshipRepository import com.egm.stellio.shared.model.EventType +import com.egm.stellio.shared.model.Observation import com.egm.stellio.shared.util.NgsiLdParsingUtils +import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_RAISED_NOTIFICATION import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_VENDOR_ID +import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_DATE_TIME_TYPE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_OBSERVED_AT_PROPERTY import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_PROPERTY_TYPE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_PROPERTY_VALUE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_UNIT_CODE_PROPERTY -import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_RAISED_NOTIFICATION -import com.egm.stellio.shared.model.Observation -import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_DATE_TIME_TYPE import com.egm.stellio.shared.util.loadAndParseSampleData import com.egm.stellio.shared.util.toRelationshipTypeName import com.ninjasquad.springmockk.MockkBean -import io.mockk.* +import io.mockk.Called +import io.mockk.Runs +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.just +import io.mockk.mockkClass +import io.mockk.verify +import io.mockk.verifyAll import org.junit.jupiter.api.Test import org.neo4j.ogm.types.spatial.GeographicPoint2d import org.springframework.beans.factory.annotation.Autowired @@ -26,9 +40,10 @@ import org.springframework.test.context.ActiveProfiles import java.time.Instant import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* +import java.util.Optional +import java.util.UUID -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ EntityService::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [EntityService::class]) @ActiveProfiles("test") class EntityServiceTests { @@ -74,20 +89,25 @@ class EntityServiceTests { every { mockedBreedingService.properties } returns mutableListOf() every { mockedBreedingService.id } returns "urn:ngsi-ld:MortalityRemovalService:014YFA9Z" every { entityRepository.getEntityCoreById(any()) } returns listOf(mapOf("entity" to mockedBreedingService)) - every { mockedBreedingService.serializeCoreProperties() } returns mutableMapOf("@id" to "urn:ngsi-ld:MortalityRemovalService:014YFA9Z", "@type" to listOf("MortalityRemovalService")) + every { mockedBreedingService.serializeCoreProperties() } returns mutableMapOf( + "@id" to "urn:ngsi-ld:MortalityRemovalService:014YFA9Z", + "@type" to listOf("MortalityRemovalService") + ) every { entityRepository.getEntitySpecificProperties(any()) } returns listOf() every { entityRepository.getEntityRelationships(any()) } returns listOf() every { mockedBreedingService.contexts } returns sampleDataWithContext.contexts entityService.createEntity(sampleDataWithContext) - verify(timeout = 1000, exactly = 1) { repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> - entityEvent.entityType == "MortalityRemovalService" && + verify(timeout = 1000, exactly = 1) { + repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> + entityEvent.entityType == "MortalityRemovalService" && entityEvent.entityId == "urn:ngsi-ld:MortalityRemovalService:014YFA9Z" && entityEvent.operationType == EventType.CREATE && entityEvent.payload == expectedPayloadInEvent && entityEvent.updatedEntity == null - }) } + }) + } // I don't know where does this call come from (probably a Spring internal thing) but it is required for verification verify { repositoryEventsListener.equals(any()) } confirmVerified(repositoryEventsListener) @@ -96,7 +116,8 @@ class EntityServiceTests { @Test fun `it should notify of a two attributes update`() { - val payload = String(ClassPathResource("/ngsild/aquac/fragments/BreedingService_twoNewProperties.json").inputStream.readAllBytes()) + val payload = + String(ClassPathResource("/ngsild/aquac/fragments/BreedingService_twoNewProperties.json").inputStream.readAllBytes()) val expectedFishNumberPayloadInEvent = """ {"fishNumber":{"type":"Property","value":500}} """.trimIndent() @@ -110,9 +131,19 @@ class EntityServiceTests { every { entityRepository.findById(any()) } returns Optional.of(mockedBreedingService) every { neo4jRepository.hasPropertyOfName(any(), any()) } returns true every { mockedBreedingService.id } returns "urn:ngsi-ld:BreedingService:0214" - every { neo4jRepository.getPropertyOfSubject(any(), eq("https://ontology.eglobalmark.com/aquac#fishNumber")) } returns mockedFishNumberProperty + every { + neo4jRepository.getPropertyOfSubject( + any(), + eq("https://ontology.eglobalmark.com/aquac#fishNumber") + ) + } returns mockedFishNumberProperty every { mockedFishNumberProperty.updateValues(any(), any(), any()) } just Runs - every { neo4jRepository.getPropertyOfSubject(any(), eq("https://ontology.eglobalmark.com/aquac#fishSize")) } returns mockedFishSizeProperty + every { + neo4jRepository.getPropertyOfSubject( + any(), + eq("https://ontology.eglobalmark.com/aquac#fishSize") + ) + } returns mockedFishSizeProperty every { mockedFishSizeProperty.updateValues(any(), any(), any()) } just Runs every { propertyRepository.save(any()) } returns mockedFishNumberProperty @@ -121,20 +152,24 @@ class EntityServiceTests { entityService.updateEntityAttributes("urn:ngsi-ld:BreedingService:0214", payload, aquacContext!!) - verify(timeout = 1000) { repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> - entityEvent.entityType == "BreedingService" && + verify(timeout = 1000) { + repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> + entityEvent.entityType == "BreedingService" && entityEvent.entityId == "urn:ngsi-ld:BreedingService:0214" && entityEvent.operationType == EventType.UPDATE && entityEvent.payload == expectedFishNumberPayloadInEvent && entityEvent.updatedEntity == null - }) } - verify(timeout = 1000) { repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> - entityEvent.entityType == "BreedingService" && + }) + } + verify(timeout = 1000) { + repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> + entityEvent.entityType == "BreedingService" && entityEvent.entityId == "urn:ngsi-ld:BreedingService:0214" && entityEvent.operationType == EventType.UPDATE && entityEvent.payload == expectedFishSizePayloadInEvent && entityEvent.updatedEntity == null - }) } + }) + } // I don't know where does this call come from (probably a Spring internal thing) but it is required for verification verify { repositoryEventsListener.equals(any()) } confirmVerified(repositoryEventsListener) @@ -156,7 +191,10 @@ class EntityServiceTests { every { mockedBreedingService.id } returns "urn:ngsi-ld:BreedingService:PropWithProp" every { repositoryEventsListener.handleRepositoryEvent(any()) } just Runs every { entityRepository.getEntityCoreById(any()) } returns listOf(mapOf("entity" to mockedBreedingService)) - every { mockedBreedingService.serializeCoreProperties() } returns mutableMapOf("@id" to "urn:ngsi-ld:MortalityRemovalService:014YFA9Z", "@type" to listOf("MortalityRemovalService")) + every { mockedBreedingService.serializeCoreProperties() } returns mutableMapOf( + "@id" to "urn:ngsi-ld:MortalityRemovalService:014YFA9Z", + "@type" to listOf("MortalityRemovalService") + ) every { entityRepository.getEntitySpecificProperties(any()) } returns listOf() every { entityRepository.getEntityRelationships(any()) } returns listOf() every { mockedBreedingService.contexts } returns sampleDataWithContext.contexts @@ -254,14 +292,16 @@ class EntityServiceTests { verify { neo4jRepository.getEntityByProperty(mockkedObservation) } verify { propertyRepository.save(any()) } verify { entityRepository.save(any()) } - verify(timeout = 2000) { repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> - entityEvent.entityType == "BreedingService" && + verify(timeout = 2000) { + repositoryEventsListener.handleRepositoryEvent(match { entityEvent -> + entityEvent.entityType == "BreedingService" && entityEvent.entityId == "urn:ngsi-ld:BreedingService:01234" && entityEvent.operationType == EventType.UPDATE && entityEvent.payload != null && entityEvent.payload!!.contains("fishNumber") && entityEvent.updatedEntity == null - }) } + }) + } confirmVerified() } @@ -317,7 +357,7 @@ class EntityServiceTests { fun `it should replace an existing property`() { val sensorId = "urn:ngsi-ld:Sensor:013YFZ" - val payload = """ + val payload = """ { "fishAge": { "type": "Property", @@ -435,8 +475,10 @@ class EntityServiceTests { mapOf("@value" to "kg") ), NGSILD_OBSERVED_AT_PROPERTY to listOf( - mapOf("@type" to NGSILD_DATE_TIME_TYPE, - "@value" to "2019-12-18T10:45:44.248755Z") + mapOf( + "@type" to NGSILD_DATE_TIME_TYPE, + "@value" to "2019-12-18T10:45:44.248755Z" + ) ) ) @@ -450,12 +492,14 @@ class EntityServiceTests { entityService.createEntityProperty(mockkedEntity, "temperature", temperatureMap) - verify { propertyRepository.save(match { - it.name == "temperature" && - it.value == 250 && - it.unitCode == "kg" && - it.observedAt.toString() == "2019-12-18T10:45:44.248755Z" - }) } + verify { + propertyRepository.save(match { + it.name == "temperature" && + it.value == 250 && + it.unitCode == "kg" && + it.observedAt.toString() == "2019-12-18T10:45:44.248755Z" + }) + } verify { entityRepository.save(any()) } confirmVerified() @@ -490,9 +534,11 @@ class EntityServiceTests { every { neo4jRepository.hasRelationshipOfType(any(), any()) } returns false every { entityRepository.findById(any()) } returns Optional.of(mockkedEntity) - every { relationshipRepository.save(match { - it.type == listOf("https://ontology.eglobalmark.com/egm#connectsTo") - }) } returns mockkedRelationship + every { + relationshipRepository.save(match { + it.type == listOf("https://ontology.eglobalmark.com/egm#connectsTo") + }) + } returns mockkedRelationship every { entityRepository.save(match { it.id == entityId }) } returns mockkedEntity every { neo4jRepository.createRelationshipToEntity(any(), any(), any()) } returns 1 @@ -562,9 +608,11 @@ class EntityServiceTests { every { neo4jRepository.hasRelationshipOfType(any(), any()) } returns true every { neo4jRepository.deleteEntityRelationship(any(), any()) } returns 1 every { entityRepository.findById(any()) } returns Optional.of(mockkedEntity) - every { relationshipRepository.save(match { - it.type == listOf("https://ontology.eglobalmark.com/egm#connectsTo") - }) } returns mockkedRelationship + every { + relationshipRepository.save(match { + it.type == listOf("https://ontology.eglobalmark.com/egm#connectsTo") + }) + } returns mockkedRelationship every { entityRepository.save(match { it.id == entityId }) } returns mockkedEntity every { neo4jRepository.createRelationshipToEntity(any(), any(), any()) } returns 1 @@ -773,9 +821,20 @@ class EntityServiceTests { verify { entityRepository.findById(eq(subscriptionId)) } verify { propertyRepository.save(any()) } verify { entityRepository.save(any()) } - verify { neo4jRepository.getRelationshipTargetOfSubject(subscriptionId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName()) } + verify { + neo4jRepository.getRelationshipTargetOfSubject( + subscriptionId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName() + ) + } verify { relationshipRepository.save(any()) } - verify { neo4jRepository.createRelationshipToEntity(relationshipId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName(), notificationId) } + verify { + neo4jRepository.createRelationshipToEntity( + relationshipId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName(), + notificationId + ) + } confirmVerified() } @@ -814,9 +873,26 @@ class EntityServiceTests { verify { entityRepository.findById(eq(subscriptionId)) } verify { propertyRepository.save(any()) } verify { entityRepository.save(any()) } - verify { neo4jRepository.getRelationshipTargetOfSubject(subscriptionId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName()) } - verify { neo4jRepository.getRelationshipOfSubject(subscriptionId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName()) } - verify { neo4jRepository.updateRelationshipTargetOfAttribute(relationshipId, EGM_RAISED_NOTIFICATION.toRelationshipTypeName(), lastNotificationId, notificationId) } + verify { + neo4jRepository.getRelationshipTargetOfSubject( + subscriptionId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName() + ) + } + verify { + neo4jRepository.getRelationshipOfSubject( + subscriptionId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName() + ) + } + verify { + neo4jRepository.updateRelationshipTargetOfAttribute( + relationshipId, + EGM_RAISED_NOTIFICATION.toRelationshipTypeName(), + lastNotificationId, + notificationId + ) + } verify { relationshipRepository.save(any()) } every { neo4jRepository.deleteEntity(lastNotificationId) } @@ -843,7 +919,7 @@ class EntityServiceTests { confirmVerified() } - private fun gimmeAnObservation(): Observation { + private fun gimmeAnObservation(): Observation { return Observation( attributeName = "incoming", latitude = 43.12, diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/SubscriptionListenerTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/SubscriptionListenerTests.kt index bf76e33a2..9f1c2becf 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/service/SubscriptionListenerTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/service/SubscriptionListenerTests.kt @@ -3,13 +3,17 @@ package com.egm.stellio.entity.service import com.egm.stellio.shared.util.NgsiLdParsingUtils import com.egm.stellio.shared.util.loadSampleData import com.ninjasquad.springmockk.MockkBean -import io.mockk.* +import io.mockk.Runs +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.just +import io.mockk.verify import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ SubscriptionListener::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [SubscriptionListener::class]) @ActiveProfiles("test") class SubscriptionListenerTests { @@ -35,7 +39,13 @@ class SubscriptionListenerTests { subscriptionListener.processSubscription(subscription) - verify { entityService.createSubscriptionEntity("urn:ngsi-ld:Subscription:04", "Subscription", parsedSubscription.minus("id").minus("type")) } + verify { + entityService.createSubscriptionEntity( + "urn:ngsi-ld:Subscription:04", + "Subscription", + parsedSubscription.minus("id").minus("type") + ) + } confirmVerified(entityService) } @@ -53,8 +63,12 @@ class SubscriptionListenerTests { subscriptionListener.processNotification(notification) - verify { entityService.createNotificationEntity("urn:ngsi-ld:Notification:1234", "Notification", "urn:ngsi-ld:Subscription:1234", - parsedNotification.minus("id").minus("type").minus("subscriptionId")) } + verify { + entityService.createNotificationEntity( + "urn:ngsi-ld:Notification:1234", "Notification", "urn:ngsi-ld:Subscription:1234", + parsedNotification.minus("id").minus("type").minus("subscriptionId") + ) + } confirmVerified(entityService) } } diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilderTest.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilderTest.kt index a53b610fa..2e33b99e2 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilderTest.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/util/EntitiesGraphBuilderTest.kt @@ -116,4 +116,4 @@ class EntitiesGraphBuilderTest { ), errors ) } -} \ No newline at end of file +} diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/util/KtMatches.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/util/KtMatches.kt index bcbc649ab..f0b103c6b 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/util/KtMatches.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/util/KtMatches.kt @@ -20,4 +20,4 @@ class KtMatches(private val regex: Regex) : TypeSafeMatcher() { return KtMatches(regex.replace("\n", "").replace(" ", "").toRegex()) } } -} \ No newline at end of file +} diff --git a/entity-service/src/test/kotlin/com/egm/stellio/entity/web/EntityHandlerTests.kt b/entity-service/src/test/kotlin/com/egm/stellio/entity/web/EntityHandlerTests.kt index b217c4f12..1c7fc1fec 100644 --- a/entity-service/src/test/kotlin/com/egm/stellio/entity/web/EntityHandlerTests.kt +++ b/entity-service/src/test/kotlin/com/egm/stellio/entity/web/EntityHandlerTests.kt @@ -5,8 +5,8 @@ import com.egm.stellio.entity.model.Entity import com.egm.stellio.entity.model.NotUpdatedDetails import com.egm.stellio.entity.model.UpdateResult import com.egm.stellio.entity.service.EntityService -import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.InternalErrorException import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.JSON_LD_MEDIA_TYPE @@ -20,7 +20,10 @@ import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_TIME_TYPE import com.github.jsonldjava.core.JsonLdError import com.github.jsonldjava.core.JsonLdError.Error.LOADING_REMOTE_CONTEXT_FAILED import com.ninjasquad.springmockk.MockkBean -import io.mockk.* +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.mockkClass +import io.mockk.verify import org.hamcrest.core.Is import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @@ -36,8 +39,10 @@ import org.springframework.security.test.context.support.WithAnonymousUser import org.springframework.security.test.context.support.WithMockUser import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.reactive.server.WebTestClient -import java.lang.RuntimeException -import java.time.* +import java.time.Instant +import java.time.LocalDate +import java.time.LocalTime +import java.time.ZoneOffset @ActiveProfiles("test") @WebFluxTest(EntityHandler::class) @@ -73,12 +78,12 @@ class EntityHandlerTests { every { entityService.createEntity(any()) } returns Entity(id = breedingServiceId, type = listOf("BeeHive")) webClient.post() - .uri("/ngsi-ld/v1/entities") - .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isCreated - .expectHeader().value("Location", Is.`is`("/ngsi-ld/v1/entities/$breedingServiceId")) + .uri("/ngsi-ld/v1/entities") + .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isCreated + .expectHeader().value("Location", Is.`is`("/ngsi-ld/v1/entities/$breedingServiceId")) } @Test @@ -88,14 +93,16 @@ class EntityHandlerTests { every { entityService.exists(any()) } returns true webClient.post() - .uri("/ngsi-ld/v1/entities") - .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isEqualTo(409) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists\"," + + .uri("/ngsi-ld/v1/entities") + .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isEqualTo(409) + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists\"," + "\"title\":\"The referred element already exists\"," + - "\"detail\":\"Already Exists\"}") + "\"detail\":\"Already Exists\"}" + ) } @Test @@ -111,9 +118,11 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isEqualTo(500) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + - "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"Internal Server Exception\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + "\"title\":\"There has been an error during the operation execution\"," + + "\"detail\":\"Internal Server Exception\"}" + ) } @Test @@ -122,7 +131,10 @@ class EntityHandlerTests { webClient.post() .uri("/ngsi-ld/v1/entities") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest @@ -134,7 +146,10 @@ class EntityHandlerTests { webClient.post() .uri("/ngsi-ld/v1/entities") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest @@ -150,7 +165,10 @@ class EntityHandlerTests { webClient.post() .uri("/ngsi-ld/v1/entities") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .accept(MediaType.valueOf("application/ld+json")) .bodyValue(entityWithoutId) .exchange() @@ -167,7 +185,10 @@ class EntityHandlerTests { webClient.post() .uri("/ngsi-ld/v1/entities") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .accept(MediaType.valueOf("application/ld+json")) .bodyValue(entityWithoutType) .exchange() @@ -191,9 +212,12 @@ class EntityHandlerTests { fun `get entity by id should correctly serialize temporal properties`() { every { entityService.exists(any()) } returns true every { entityService.getFullEntityById(any()) } returns ExpandedEntity( - mapOf(NGSILD_CREATED_AT_PROPERTY to - mapOf("@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC)), + mapOf( + NGSILD_CREATED_AT_PROPERTY to + mapOf( + "@type" to NGSILD_DATE_TIME_TYPE, + "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) + ), "@id" to "urn:ngsi-ld:Beehive:4567", "@type" to listOf("Beehive") ), @@ -213,10 +237,14 @@ class EntityHandlerTests { every { entityService.exists(any()) } returns true every { entityService.getFullEntityById(any()) } returns ExpandedEntity( - mapOf("https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf("@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_DATE_TIME_TYPE, - "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC))), + mapOf( + "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( + "@type" to "https://uri.etsi.org/ngsi-ld/Property", + NGSILD_PROPERTY_VALUE to mapOf( + "@type" to NGSILD_DATE_TIME_TYPE, + "@value" to Instant.parse("2015-10-18T11:20:30.000001Z").atZone(ZoneOffset.UTC) + ) + ), "@id" to "urn:ngsi-ld:Beehive:4567", "@type" to listOf("Beehive") ), @@ -228,7 +256,9 @@ class EntityHandlerTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isOk - .expectBody().json("{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"DateTime\",\"@value\":\"2015-10-18T11:20:30.000001Z\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}") + .expectBody().json( + "{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"DateTime\",\"@value\":\"2015-10-18T11:20:30.000001Z\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}" + ) } @Test @@ -236,10 +266,14 @@ class EntityHandlerTests { every { entityService.exists(any()) } returns true every { entityService.getFullEntityById(any()) } returns ExpandedEntity( - mapOf("https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf("@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_DATE_TYPE, - "@value" to LocalDate.of(2015, 10, 18))), + mapOf( + "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( + "@type" to "https://uri.etsi.org/ngsi-ld/Property", + NGSILD_PROPERTY_VALUE to mapOf( + "@type" to NGSILD_DATE_TYPE, + "@value" to LocalDate.of(2015, 10, 18) + ) + ), "@id" to "urn:ngsi-ld:Beehive:4567", "@type" to listOf("Beehive") ), @@ -251,7 +285,9 @@ class EntityHandlerTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isOk - .expectBody().json("{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"Date\",\"@value\":\"2015-10-18\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}") + .expectBody().json( + "{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"Date\",\"@value\":\"2015-10-18\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}" + ) } @Test @@ -259,10 +295,14 @@ class EntityHandlerTests { every { entityService.exists(any()) } returns true every { entityService.getFullEntityById(any()) } returns ExpandedEntity( - mapOf("https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf("@type" to "https://uri.etsi.org/ngsi-ld/Property", - NGSILD_PROPERTY_VALUE to mapOf( - "@type" to NGSILD_TIME_TYPE, - "@value" to LocalTime.of(11, 20, 30))), + mapOf( + "https://uri.etsi.org/ngsi-ld/default-context/testedAt" to mapOf( + "@type" to "https://uri.etsi.org/ngsi-ld/Property", + NGSILD_PROPERTY_VALUE to mapOf( + "@type" to NGSILD_TIME_TYPE, + "@value" to LocalTime.of(11, 20, 30) + ) + ), "@id" to "urn:ngsi-ld:Beehive:4567", "@type" to listOf("Beehive") ), @@ -274,7 +314,9 @@ class EntityHandlerTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isOk - .expectBody().json("{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"Time\",\"@value\":\"11:20:30\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}") + .expectBody().json( + "{\"testedAt\":{\"type\":\"Property\",\"value\":{\"type\":\"Time\",\"@value\":\"11:20:30\"}},\"@context\":\"$NGSILD_CORE_CONTEXT\"}" + ) } @Test @@ -287,9 +329,11 @@ class EntityHandlerTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + - "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Entity Not Found\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + "\"title\":\"The referred resource has not been found\"," + + "\"detail\":\"Entity Not Found\"}" + ) } @Test @@ -299,9 +343,11 @@ class EntityHandlerTests { .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'q' or 'type' request parameters have to be specified (TEMP - cf 6.4.3.2\"}") + "\"detail\":\"'q' or 'type' request parameters have to be specified (TEMP - cf 6.4.3.2\"}" + ) } @Test @@ -310,7 +356,10 @@ class EntityHandlerTests { val entityId = "urn:ngsi-ld:BreedingService:0214" every { entityService.exists(any()) } returns true - every { entityService.appendEntityAttributes(any(), any(), any()) } returns UpdateResult(listOf("fishNumber"), emptyList()) + every { entityService.appendEntityAttributes(any(), any(), any()) } returns UpdateResult( + listOf("fishNumber"), + emptyList() + ) webClient.post() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -332,7 +381,12 @@ class EntityHandlerTests { every { entityService.exists(any()) } returns true every { entityService.appendEntityAttributes(any(), any(), any()) } - .returns(UpdateResult(listOf("fishNumber"), listOf(NotUpdatedDetails("wrongAttribute", "overwrite disallowed")))) + .returns( + UpdateResult( + listOf("fishNumber"), + listOf(NotUpdatedDetails("wrongAttribute", "overwrite disallowed")) + ) + ) webClient.post() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -340,7 +394,9 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isEqualTo(HttpStatus.MULTI_STATUS) - .expectBody().json("{\"updated\":[\"fishNumber\"],\"notUpdated\":[{\"attributeName\":\"wrongAttribute\",\"reason\":\"overwrite disallowed\"}]}") + .expectBody().json( + "{\"updated\":[\"fishNumber\"],\"notUpdated\":[{\"attributeName\":\"wrongAttribute\",\"reason\":\"overwrite disallowed\"}]}" + ) verify { entityService.exists(eq("urn:ngsi-ld:BreedingService:0214")) } verify { entityService.appendEntityAttributes(eq("urn:ngsi-ld:BreedingService:0214"), any(), eq(false)) } @@ -361,9 +417,11 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Entity urn:ngsi-ld:BreedingService:0214 does not exist\"}") + "\"detail\":\"Entity urn:ngsi-ld:BreedingService:0214 does not exist\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:BreedingService:0214")) } @@ -376,7 +434,13 @@ class EntityHandlerTests { val entityId = "urn:ngsi-ld:BreedingService:0214" every { entityService.exists(any()) } returns true - every { entityService.appendEntityAttributes(any(), any(), any()) } throws BadRequestDataException("@type not found") + every { + entityService.appendEntityAttributes( + any(), + any(), + any() + ) + } throws BadRequestDataException("@type not found") webClient.post() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -384,9 +448,11 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"@type not found\"}") + "\"detail\":\"@type not found\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:BreedingService:0214")) } @@ -399,7 +465,13 @@ class EntityHandlerTests { val entityId = "urn:ngsi-ld:BreedingService:0214" every { entityService.exists(any()) } returns true - every { entityService.appendEntityAttributes(any(), any(), any()) } throws BadRequestDataException("Key $NGSILD_PROPERTY_VALUE not found") + every { + entityService.appendEntityAttributes( + any(), + any(), + any() + ) + } throws BadRequestDataException("Key $NGSILD_PROPERTY_VALUE not found") webClient.post() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -407,9 +479,11 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Key https://uri.etsi.org/ngsi-ld/hasValue not found\"}") + "\"detail\":\"Key https://uri.etsi.org/ngsi-ld/hasValue not found\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:BreedingService:0214")) } @@ -418,11 +492,18 @@ class EntityHandlerTests { @Test fun `append entity attribute should return a 400 if the attribute is a relationship and is missing an object`() { - val jsonLdFile = ClassPathResource("/ngsild/aquac/fragments/BreedingService_newRelationship_missing_object.json") + val jsonLdFile = + ClassPathResource("/ngsild/aquac/fragments/BreedingService_newRelationship_missing_object.json") val entityId = "urn:ngsi-ld:BreedingService:0214" every { entityService.exists(any()) } returns true - every { entityService.appendEntityAttributes(any(), any(), any()) } throws BadRequestDataException("Key $NGSILD_RELATIONSHIP_HAS_OBJECT not found") + every { + entityService.appendEntityAttributes( + any(), + any(), + any() + ) + } throws BadRequestDataException("Key $NGSILD_RELATIONSHIP_HAS_OBJECT not found") webClient.post() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -430,9 +511,11 @@ class EntityHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Key https://uri.etsi.org/ngsi-ld/hasObject not found\"}") + "\"detail\":\"Key https://uri.etsi.org/ngsi-ld/hasObject not found\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:BreedingService:0214")) } @@ -464,7 +547,13 @@ class EntityHandlerTests { val entityId = "urn:ngsi-ld:DeadFishes:019BN" every { entityService.exists(any()) } returns true - every { entityService.updateEntityAttributes(any(), any(), any()) } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf()) + every { + entityService.updateEntityAttributes( + any(), + any(), + any() + ) + } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf()) webClient.patch() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -480,11 +569,18 @@ class EntityHandlerTests { @Test fun `entity attributes update should return a 207 if some attributes are not found`() { - val jsonLdFile = ClassPathResource("/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json") + val jsonLdFile = + ClassPathResource("/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json") val entityId = "urn:ngsi-ld:DeadFishes:019BN" val notUpdatedAttribute = NotUpdatedDetails("unknownAttribute", "Property Not Found") every { entityService.exists(any()) } returns true - every { entityService.updateEntityAttributes(any(), any(), any()) } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf(notUpdatedAttribute)) + every { + entityService.updateEntityAttributes( + any(), + any(), + any() + ) + } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf(notUpdatedAttribute)) webClient.patch() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -500,12 +596,21 @@ class EntityHandlerTests { @Test fun `entity attributes update should return a 207 if some relationships objects are not found`() { - val jsonLdFile = ClassPathResource("/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json") + val jsonLdFile = + ClassPathResource("/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json") val entityId = "urn:ngsi-ld:DeadFishes:019BN" - val notUpdatedAttribute = NotUpdatedDetails("removedFrom", - "Target entity unknownObject in property does not exist, create it first") + val notUpdatedAttribute = NotUpdatedDetails( + "removedFrom", + "Target entity unknownObject in property does not exist, create it first" + ) every { entityService.exists(any()) } returns true - every { entityService.updateEntityAttributes(any(), any(), any()) } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf(notUpdatedAttribute)) + every { + entityService.updateEntityAttributes( + any(), + any(), + any() + ) + } returns UpdateResult(updated = arrayListOf("fishNumber"), notUpdated = arrayListOf(notUpdatedAttribute)) webClient.patch() .uri("/ngsi-ld/v1/entities/$entityId/attrs") @@ -525,17 +630,25 @@ class EntityHandlerTests { val entityId = "urn:ngsi-ld:Sensor:0022CCC" every { entityService.exists(any()) } returns true - every { entityService.updateEntityAttributes(any(), any(), any()) } throws JsonLdError(LOADING_REMOTE_CONTEXT_FAILED, "http://easyglobalmarket.com/contexts/diat.jsonld") + every { entityService.updateEntityAttributes(any(), any(), any()) } throws JsonLdError( + LOADING_REMOTE_CONTEXT_FAILED, + "http://easyglobalmarket.com/contexts/diat.jsonld" + ) webClient.patch() .uri("/ngsi-ld/v1/entities/$entityId/attrs") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + - "\"title\":\"loading remote context failed\"," + - "\"detail\":\"loading remote context failed: http://easyglobalmarket.com/contexts/diat.jsonld\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + "\"title\":\"loading remote context failed\"," + + "\"detail\":\"loading remote context failed: http://easyglobalmarket.com/contexts/diat.jsonld\"}" + ) } @Test @@ -547,13 +660,18 @@ class EntityHandlerTests { webClient.patch() .uri("/ngsi-ld/v1/entities/$entityId/attrs") - .header("Link", "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") + .header( + "Link", + "; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json" + ) .bodyValue(jsonLdFile) .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Entity urn:ngsi-ld:UnknownType:0022CCC does not exist\"}") + "\"detail\":\"Entity urn:ngsi-ld:UnknownType:0022CCC does not exist\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:UnknownType:0022CCC")) } } @@ -591,9 +709,11 @@ class EntityHandlerTests { .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:Sensor:0022CCC") .exchange() .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + - "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"Unexpected server error\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + "\"title\":\"There has been an error during the operation execution\"," + + "\"detail\":\"Unexpected server error\"}" + ) } @Test @@ -609,7 +729,13 @@ class EntityHandlerTests { .expectBody().isEmpty verify { entityService.exists(eq("urn:ngsi-ld:DeadFishes:019BN")) } - verify { entityService.deleteEntityAttribute(eq("urn:ngsi-ld:DeadFishes:019BN"), eq("fishNumber"), eq(aquacContext!!)) } + verify { + entityService.deleteEntityAttribute( + eq("urn:ngsi-ld:DeadFishes:019BN"), + eq("fishNumber"), + eq(aquacContext!!) + ) + } confirmVerified(entityService) } @@ -622,26 +748,42 @@ class EntityHandlerTests { .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Entity Not Found\"}") + "\"detail\":\"Entity Not Found\"}" + ) } @Test fun `delete entity attribute should return a 404 if the attribute is not found`() { every { entityService.exists(any()) } returns true - every { entityService.deleteEntityAttribute(any(), any(), any()) } throws ResourceNotFoundException("Attribute Not Found") + every { + entityService.deleteEntityAttribute( + any(), + any(), + any() + ) + } throws ResourceNotFoundException("Attribute Not Found") webClient.delete() .uri("/ngsi-ld/v1/entities/urn:ngsi-ld:DeadFishes:019BN/attrs/fishNumber") .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Attribute Not Found\"}") + "\"detail\":\"Attribute Not Found\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:DeadFishes:019BN")) } - verify { entityService.deleteEntityAttribute(eq("urn:ngsi-ld:DeadFishes:019BN"), eq("fishNumber"), eq(aquacContext!!)) } + verify { + entityService.deleteEntityAttribute( + eq("urn:ngsi-ld:DeadFishes:019BN"), + eq("fishNumber"), + eq(aquacContext!!) + ) + } confirmVerified(entityService) } @@ -655,12 +797,20 @@ class EntityHandlerTests { .header("Link", "<$aquacContext>; rel=http://www.w3.org/ns/json-ld#context; type=application/ld+json") .exchange() .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"An error occurred while deleting fishNumber from urn:ngsi-ld:DeadFishes:019BN\"}") + "\"detail\":\"An error occurred while deleting fishNumber from urn:ngsi-ld:DeadFishes:019BN\"}" + ) verify { entityService.exists(eq("urn:ngsi-ld:DeadFishes:019BN")) } - verify { entityService.deleteEntityAttribute(eq("urn:ngsi-ld:DeadFishes:019BN"), eq("fishNumber"), eq(aquacContext!!)) } + verify { + entityService.deleteEntityAttribute( + eq("urn:ngsi-ld:DeadFishes:019BN"), + eq("fishNumber"), + eq(aquacContext!!) + ) + } confirmVerified(entityService) } diff --git a/entity-service/src/test/resources/junit-platform.properties b/entity-service/src/test/resources/junit-platform.properties index e6d55f8bd..d265fd838 100644 --- a/entity-service/src/test/resources/junit-platform.properties +++ b/entity-service/src/test/resources/junit-platform.properties @@ -1 +1 @@ -junit.jupiter.testinstance.lifecycle.default = per_class \ No newline at end of file +junit.jupiter.testinstance.lifecycle.default = per_class diff --git a/entity-service/src/test/resources/mock/problemDetails/problem_details_bad_request.json b/entity-service/src/test/resources/mock/problemDetails/problem_details_bad_request.json index d71e02d6a..66c50e9ce 100644 --- a/entity-service/src/test/resources/mock/problemDetails/problem_details_bad_request.json +++ b/entity-service/src/test/resources/mock/problemDetails/problem_details_bad_request.json @@ -1 +1 @@ -{"ProblemDetails":["Bad Request"]} \ No newline at end of file +{"ProblemDetails":["Bad Request"]} diff --git a/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_exists.json b/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_exists.json index 962e3686e..37f766437 100644 --- a/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_exists.json +++ b/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_exists.json @@ -1 +1 @@ -{"ProblemDetails":["Already Exists"]} \ No newline at end of file +{"ProblemDetails":["Already Exists"]} diff --git a/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_not_found.json b/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_not_found.json index 4462be129..2951b42b9 100644 --- a/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_not_found.json +++ b/entity-service/src/test/resources/mock/problemDetails/problem_details_entity_not_found.json @@ -1 +1 @@ -{"ProblemDetails":["Entity Not Found"]} \ No newline at end of file +{"ProblemDetails":["Entity Not Found"]} diff --git a/entity-service/src/test/resources/mock/response_entities_by_label.json b/entity-service/src/test/resources/mock/response_entities_by_label.json index 2e71cb2c7..15f9a77dc 100644 --- a/entity-service/src/test/resources/mock/response_entities_by_label.json +++ b/entity-service/src/test/resources/mock/response_entities_by_label.json @@ -29,4 +29,4 @@ "https://fiware.github.io/dataModels/fiware-datamodels-context.jsonld" ] } -] \ No newline at end of file +] diff --git a/entity-service/src/test/resources/mock/response_entities_by_label_and_query.json b/entity-service/src/test/resources/mock/response_entities_by_label_and_query.json index edb982591..3878cdff6 100644 --- a/entity-service/src/test/resources/mock/response_entities_by_label_and_query.json +++ b/entity-service/src/test/resources/mock/response_entities_by_label_and_query.json @@ -17,4 +17,4 @@ "https://fiware.github.io/dataModels/fiware-datamodels-context.jsonld" ] } -] \ No newline at end of file +] diff --git a/entity-service/src/test/resources/mock/response_entities_by_label_rel_uri.json b/entity-service/src/test/resources/mock/response_entities_by_label_rel_uri.json index ce5c1ba80..acc425b3d 100644 --- a/entity-service/src/test/resources/mock/response_entities_by_label_rel_uri.json +++ b/entity-service/src/test/resources/mock/response_entities_by_label_rel_uri.json @@ -15,4 +15,4 @@ "https://fiware.github.io/dataModels/fiware-datamodels-context.jsonld" ] } -] \ No newline at end of file +] diff --git a/entity-service/src/test/resources/ngsild/apic/Sensor0021(incoming).json b/entity-service/src/test/resources/ngsild/apic/Sensor0021(incoming).json index add36bcef..be7e30c15 100644 --- a/entity-service/src/test/resources/ngsild/apic/Sensor0021(incoming).json +++ b/entity-service/src/test/resources/ngsild/apic/Sensor0021(incoming).json @@ -14,4 +14,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/Sensor002XIO52(outgoing).json b/entity-service/src/test/resources/ngsild/apic/Sensor002XIO52(outgoing).json index 24c7529f7..196ac3de9 100644 --- a/entity-service/src/test/resources/ngsild/apic/Sensor002XIO52(outgoing).json +++ b/entity-service/src/test/resources/ngsild/apic/Sensor002XIO52(outgoing).json @@ -14,4 +14,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/Sensor016352(batteryLevel).json b/entity-service/src/test/resources/ngsild/apic/Sensor016352(batteryLevel).json index 393851240..bc6ff00dd 100644 --- a/entity-service/src/test/resources/ngsild/apic/Sensor016352(batteryLevel).json +++ b/entity-service/src/test/resources/ngsild/apic/Sensor016352(batteryLevel).json @@ -14,4 +14,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/Sensor01XYZ(temperature).json b/entity-service/src/test/resources/ngsild/apic/Sensor01XYZ(temperature).json index d7fecde2f..458e1c230 100644 --- a/entity-service/src/test/resources/ngsild/apic/Sensor01XYZ(temperature).json +++ b/entity-service/src/test/resources/ngsild/apic/Sensor01XYZ(temperature).json @@ -14,4 +14,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/Sensor1541jdh(humidity).json b/entity-service/src/test/resources/ngsild/apic/Sensor1541jdh(humidity).json index e7c940df5..8e3adcd48 100644 --- a/entity-service/src/test/resources/ngsild/apic/Sensor1541jdh(humidity).json +++ b/entity-service/src/test/resources/ngsild/apic/Sensor1541jdh(humidity).json @@ -14,4 +14,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/apiary.json b/entity-service/src/test/resources/ngsild/apic/apiary.json index e5736c8b6..ad5437307 100644 --- a/entity-service/src/test/resources/ngsild/apic/apiary.json +++ b/entity-service/src/test/resources/ngsild/apic/apiary.json @@ -20,4 +20,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/beekeeper.json b/entity-service/src/test/resources/ngsild/apic/beekeeper.json index ea36e8f7d..280434806 100644 --- a/entity-service/src/test/resources/ngsild/apic/beekeeper.json +++ b/entity-service/src/test/resources/ngsild/apic/beekeeper.json @@ -10,4 +10,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/smartDoor.json b/entity-service/src/test/resources/ngsild/apic/smartDoor.json index aa678b82f..5152df9e8 100644 --- a/entity-service/src/test/resources/ngsild/apic/smartDoor.json +++ b/entity-service/src/test/resources/ngsild/apic/smartDoor.json @@ -6,4 +6,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/apic/smartDoor_update.json b/entity-service/src/test/resources/ngsild/apic/smartDoor_update.json index 77c73742c..e1d9a0d43 100644 --- a/entity-service/src/test/resources/ngsild/apic/smartDoor_update.json +++ b/entity-service/src/test/resources/ngsild/apic/smartDoor_update.json @@ -3,4 +3,4 @@ "type":"Relationship", "object":"urn:ngsi-ld:BeeHive:TESTC" } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/DeadFishes.json b/entity-service/src/test/resources/ngsild/aquac/DeadFishes.json index e0bd04054..849b24507 100644 --- a/entity-service/src/test/resources/ngsild/aquac/DeadFishes.json +++ b/entity-service/src/test/resources/ngsild/aquac/DeadFishes.json @@ -19,4 +19,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/aquac/jsonld-contexts/aquac.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/FeedingService.json b/entity-service/src/test/resources/ngsild/aquac/FeedingService.json index ff55b4262..dd1d1c319 100644 --- a/entity-service/src/test/resources/ngsild/aquac/FeedingService.json +++ b/entity-service/src/test/resources/ngsild/aquac/FeedingService.json @@ -24,4 +24,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/aquac/jsonld-contexts/aquac.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/Observation.json b/entity-service/src/test/resources/ngsild/aquac/Observation.json index fbd47176e..075c8f97a 100644 --- a/entity-service/src/test/resources/ngsild/aquac/Observation.json +++ b/entity-service/src/test/resources/ngsild/aquac/Observation.json @@ -19,4 +19,4 @@ } } } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/ObservationWithoutLocation.json b/entity-service/src/test/resources/ngsild/aquac/ObservationWithoutLocation.json index 9da69ac50..296610149 100644 --- a/entity-service/src/test/resources/ngsild/aquac/ObservationWithoutLocation.json +++ b/entity-service/src/test/resources/ngsild/aquac/ObservationWithoutLocation.json @@ -9,4 +9,4 @@ "object": "urn:sosa:Sensor:10e2073a01080065" } } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/Observation_missingAttributes.json b/entity-service/src/test/resources/ngsild/aquac/Observation_missingAttributes.json index c4fc9dc43..e2f66fab2 100644 --- a/entity-service/src/test/resources/ngsild/aquac/Observation_missingAttributes.json +++ b/entity-service/src/test/resources/ngsild/aquac/Observation_missingAttributes.json @@ -4,4 +4,4 @@ "value": , "unitCode": "CEL" } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate.json b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate.json index 926364646..8bd492d8d 100644 --- a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate.json +++ b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate.json @@ -3,4 +3,4 @@ "type":"Property", "value":600 } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json index 2d6995776..13d4d0cf1 100644 --- a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json +++ b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_attributeNotFound.json @@ -7,4 +7,4 @@ "type":"Property", "value":"unkwnow" } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json index ba2a07d55..04da1333f 100644 --- a/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json +++ b/entity-service/src/test/resources/ngsild/aquac/fragments/DeadFishes_partialAttributeUpdate_relationshipObjectNotFound.json @@ -11,4 +11,4 @@ "object":"urn:ngsi-ld:MortalityService:014YFA9Z" } } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/fragments/Specie_entityAttributesUpdates.json b/entity-service/src/test/resources/ngsild/aquac/fragments/Specie_entityAttributesUpdates.json index 098acc7ec..28dc9dd8f 100644 --- a/entity-service/src/test/resources/ngsild/aquac/fragments/Specie_entityAttributesUpdates.json +++ b/entity-service/src/test/resources/ngsild/aquac/fragments/Specie_entityAttributesUpdates.json @@ -1,4 +1,4 @@ { "fishSize": 2, "fishNumber": 250 -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/aquac/observationWithoutUnitCode.json b/entity-service/src/test/resources/ngsild/aquac/observationWithoutUnitCode.json index 946ea75cd..1cf8fecae 100644 --- a/entity-service/src/test/resources/ngsild/aquac/observationWithoutUnitCode.json +++ b/entity-service/src/test/resources/ngsild/aquac/observationWithoutUnitCode.json @@ -18,4 +18,4 @@ } } } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/beehive_missing_context.jsonld b/entity-service/src/test/resources/ngsild/beehive_missing_context.jsonld index 9b49721f2..099dd72d8 100644 --- a/entity-service/src/test/resources/ngsild/beehive_missing_context.jsonld +++ b/entity-service/src/test/resources/ngsild/beehive_missing_context.jsonld @@ -11,4 +11,4 @@ "createdAt": "2010-10-26T21:32:52.98601Z", "@id": "urn:ngsi-ld:Beekeeper:TEST1" } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/beehive_update.json b/entity-service/src/test/resources/ngsild/beehive_update.json index 2a98dee6e..c8d34be30 100644 --- a/entity-service/src/test/resources/ngsild/beehive_update.json +++ b/entity-service/src/test/resources/ngsild/beehive_update.json @@ -3,4 +3,4 @@ "type": "Property", "value": 100 } -} \ No newline at end of file +} diff --git a/entity-service/src/test/resources/ngsild/hcmr/HCMR_test_file_not_valid.json b/entity-service/src/test/resources/ngsild/hcmr/HCMR_test_file_not_valid.json index 6c321bf33..00367ef6f 100644 --- a/entity-service/src/test/resources/ngsild/hcmr/HCMR_test_file_not_valid.json +++ b/entity-service/src/test/resources/ngsild/hcmr/HCMR_test_file_not_valid.json @@ -48,4 +48,4 @@ "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] } -] \ No newline at end of file +] diff --git a/entity-service/src/test/resources/ngsild/sensor_update.json b/entity-service/src/test/resources/ngsild/sensor_update.json index 488834d37..87c08f90c 100644 --- a/entity-service/src/test/resources/ngsild/sensor_update.json +++ b/entity-service/src/test/resources/ngsild/sensor_update.json @@ -1,4 +1,4 @@ { "name" : "My precious sensor Updated", "trigger" : "on" -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf01..f2d78fc98 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/config/EntityServiceProperties.kt b/search-service/src/main/kotlin/com/egm/stellio/search/config/EntityServiceProperties.kt index 22e6fc316..a5e4a374a 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/config/EntityServiceProperties.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/config/EntityServiceProperties.kt @@ -7,4 +7,4 @@ import org.springframework.boot.context.properties.ConstructorBinding @ConfigurationProperties("application.entity-service") data class EntityServiceProperties( val url: String -) \ No newline at end of file +) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/config/WebSecurityConfig.kt b/search-service/src/main/kotlin/com/egm/stellio/search/config/WebSecurityConfig.kt index 20bdddba9..74d246d7b 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/config/WebSecurityConfig.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/config/WebSecurityConfig.kt @@ -34,4 +34,4 @@ class WebSecurityConfig { return http.build() } -} \ No newline at end of file +} diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/listener/EntityListener.kt b/search-service/src/main/kotlin/com/egm/stellio/search/listener/EntityListener.kt index 618e7cfd7..36665e6df 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/EntityListener.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/listener/EntityListener.kt @@ -1,9 +1,9 @@ package com.egm.stellio.search.listener import com.egm.stellio.search.model.AttributeInstance -import com.egm.stellio.shared.model.EventType -import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.search.service.AttributeInstanceService +import com.egm.stellio.search.service.TemporalEntityAttributeService +import com.egm.stellio.shared.model.EventType import com.egm.stellio.shared.util.NgsiLdParsingUtils.expandJsonLdKey import com.egm.stellio.shared.util.NgsiLdParsingUtils.parseEntity import com.egm.stellio.shared.util.NgsiLdParsingUtils.parseEntityEvent diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/listener/SubscriptionListener.kt b/search-service/src/main/kotlin/com/egm/stellio/search/listener/SubscriptionListener.kt index b839713b6..c94ca5eb7 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/listener/SubscriptionListener.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/listener/SubscriptionListener.kt @@ -2,8 +2,8 @@ package com.egm.stellio.search.listener import com.egm.stellio.search.model.AttributeInstance import com.egm.stellio.search.model.TemporalEntityAttribute -import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.search.service.AttributeInstanceService +import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.shared.model.EventType import com.egm.stellio.shared.util.ApiUtils.parseNotification import com.egm.stellio.shared.util.ApiUtils.parseSubscription 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 9cf76c247..80769f41a 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 @@ -2,7 +2,7 @@ package com.egm.stellio.search.model import java.net.URI import java.time.ZonedDateTime -import java.util.* +import java.util.UUID data class AttributeInstance( val temporalEntityAttribute: UUID, 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 350e71fa7..fd6364d57 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 @@ -1,6 +1,6 @@ package com.egm.stellio.search.model -import java.util.* +import java.util.UUID data class TemporalEntityAttribute( val id: UUID = UUID.randomUUID(), diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalValue.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalValue.kt index 434e8d2ba..2b02d8380 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalValue.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/TemporalValue.kt @@ -7,4 +7,4 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize data class TemporalValue( val value: Double, val timestamp: String -) \ No newline at end of file +) diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeInstanceService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeInstanceService.kt index d9f561baa..cf42ce6a4 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeInstanceService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/AttributeInstanceService.kt @@ -1,6 +1,8 @@ package com.egm.stellio.search.service -import com.egm.stellio.search.model.* +import com.egm.stellio.search.model.AttributeInstance +import com.egm.stellio.search.model.TemporalEntityAttribute +import com.egm.stellio.search.model.TemporalQuery import com.egm.stellio.search.util.valueToDoubleOrNull import com.egm.stellio.search.util.valueToStringOrNull import com.egm.stellio.shared.util.NgsiLdParsingUtils.EGM_OBSERVED_BY @@ -10,7 +12,7 @@ import com.egm.stellio.shared.util.NgsiLdParsingUtils.getPropertyValueFromMapAsD import org.springframework.data.r2dbc.core.DatabaseClient import org.springframework.stereotype.Service import reactor.core.publisher.Mono -import java.util.* +import java.util.UUID @Service class AttributeInstanceService( @@ -27,7 +29,11 @@ class AttributeInstanceService( // TODO not totally compatible with the specification // it should accept an array of attribute instances - fun addAttributeInstances(temporalEntityAttributeUuid: UUID, attributeKey: String, attributeValues: Map>): Mono { + fun addAttributeInstances( + temporalEntityAttributeUuid: UUID, + attributeKey: String, + attributeValues: Map> + ): Mono { val attributeValue = getPropertyValueFromMap(attributeValues, NGSILD_PROPERTY_VALUE)!! val attributeInstance = AttributeInstance( temporalEntityAttribute = temporalEntityAttributeUuid, @@ -38,7 +44,10 @@ class AttributeInstanceService( return create(attributeInstance) } - fun search(temporalQuery: TemporalQuery, temporalEntityAttribute: TemporalEntityAttribute): Mono>> { + fun search( + temporalQuery: TemporalQuery, + temporalEntityAttribute: TemporalEntityAttribute + ): Mono>> { var selectQuery = when { @@ -61,7 +70,8 @@ class AttributeInstanceService( """ FROM attribute_instance WHERE temporal_entity_attribute = '${temporalEntityAttribute.id}' - """) + """ + ) selectQuery = when (temporalQuery.timerel) { TemporalQuery.Timerel.BEFORE -> selectQuery.plus(" AND observed_at < '${temporalQuery.time}'") diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityService.kt b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityService.kt index 34311f395..38b3e5ac2 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityService.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/service/EntityService.kt @@ -14,7 +14,8 @@ class EntityService( entityServiceProperties: EntityServiceProperties ) { - private final val consumer: (ClientCodecConfigurer) -> Unit = { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) } + private final val consumer: (ClientCodecConfigurer) -> Unit = + { configurer -> configurer.defaultCodecs().enableLoggingRequestDetails(true) } private var webClient = WebClient.builder() .exchangeStrategies(ExchangeStrategies.builder().codecs(consumer).build()) 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 7fa725c81..cec6af08e 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 @@ -31,7 +31,7 @@ import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* +import java.util.UUID @Service class TemporalEntityAttributeService( @@ -40,16 +40,20 @@ class TemporalEntityAttributeService( ) { fun create(temporalEntityAttribute: TemporalEntityAttribute): Mono = - databaseClient.execute(""" + databaseClient.execute( + """ INSERT INTO temporal_entity_attribute (id, entity_id, type, attribute_name, attribute_value_type, entity_payload) VALUES (:id, :entity_id, :type, :attribute_name, :attribute_value_type, :entity_payload) - """) + """ + ) .bind("id", temporalEntityAttribute.id) .bind("entity_id", temporalEntityAttribute.entityId) .bind("type", temporalEntityAttribute.type) .bind("attribute_name", temporalEntityAttribute.attributeName) .bind("attribute_value_type", temporalEntityAttribute.attributeValueType.toString()) - .bind("entity_payload", temporalEntityAttribute.entityPayload?.let { Json.of(temporalEntityAttribute.entityPayload) }) + .bind( + "entity_payload", + temporalEntityAttribute.entityPayload?.let { Json.of(temporalEntityAttribute.entityPayload) }) .fetch() .rowsUpdated() @@ -121,13 +125,13 @@ class TemporalEntityAttributeService( SELECT id, entity_id, type, attribute_name, attribute_value_type, entity_payload::TEXT FROM temporal_entity_attribute WHERE entity_id = :entity_id - """.trimIndent() + """.trimIndent() val expandedAttrsList = attrs.map { expandJsonLdKey(it, contextLink)!! } - .joinToString(",") { "'$it'" } + .joinToString(",") { "'$it'" } val finalQuery = if (attrs.isNotEmpty()) @@ -147,7 +151,7 @@ class TemporalEntityAttributeService( SELECT id FROM temporal_entity_attribute WHERE entity_id = :entity_id - """.trimIndent() + """.trimIndent() return databaseClient .execute(selectQuery) @@ -162,7 +166,7 @@ class TemporalEntityAttributeService( FROM temporal_entity_attribute WHERE entity_id = :entity_id AND attribute_name = :attribute_name - """.trimIndent() + """.trimIndent() return databaseClient .execute(selectQuery) @@ -178,7 +182,12 @@ class TemporalEntityAttributeService( entityId = row.get("entity_id", String::class.java)!!, type = row.get("type", String::class.java)!!, attributeName = row.get("attribute_name", String::class.java)!!, - attributeValueType = TemporalEntityAttribute.AttributeValueType.valueOf(row.get("attribute_value_type", String::class.java)!!), + attributeValueType = TemporalEntityAttribute.AttributeValueType.valueOf( + row.get( + "attribute_value_type", + String::class.java + )!! + ), entityPayload = row.get("entity_payload", String::class.java) ) } @@ -198,8 +207,7 @@ class TemporalEntityAttributeService( rawResults.filter { // filtering out empty lists or lists with an empty map of results it.isNotEmpty() && it[0].isNotEmpty() - } - .forEach { + }.forEach { // attribute_name is the name of the temporal property we want to update val attributeName = it.first()["attribute_name"]!! as String @@ -223,9 +231,15 @@ class TemporalEntityAttributeService( val valuesMap = it.map { if (it["value"] is Double) - TemporalValue(it["value"] as Double, (it["observed_at"] as ZonedDateTime).toInstant().atZone(ZoneOffset.UTC).toString()) + TemporalValue( + it["value"] as Double, + (it["observed_at"] as ZonedDateTime).toInstant().atZone(ZoneOffset.UTC).toString() + ) else - RawValue(it["value"]!!, (it["observed_at"] as ZonedDateTime).toInstant().atZone(ZoneOffset.UTC).toString()) + RawValue( + it["value"]!!, + (it["observed_at"] as ZonedDateTime).toInstant().atZone(ZoneOffset.UTC).toString() + ) } propertyToEnrich[NGSILD_PROPERTY_VALUES] = listOf(mapOf("@list" to valuesMap)) @@ -234,14 +248,16 @@ class TemporalEntityAttributeService( } else { val valuesMap = it.map { - mapOf(NGSILD_ENTITY_TYPE to NGSILD_PROPERTY_TYPE.uri, + mapOf( + NGSILD_ENTITY_TYPE to NGSILD_PROPERTY_TYPE.uri, NGSILD_INSTANCE_ID_PROPERTY to mapOf( NGSILD_ENTITY_ID to it["instance_id"] ), NGSILD_PROPERTY_VALUE to it["value"], NGSILD_OBSERVED_AT_PROPERTY to mapOf( NGSILD_ENTITY_TYPE to NGSILD_DATE_TIME_TYPE, - JSONLD_VALUE_KW to (it["observed_at"] as ZonedDateTime).toInstant().atZone(ZoneOffset.UTC).toString() + JSONLD_VALUE_KW to (it["observed_at"] as ZonedDateTime).toInstant() + .atZone(ZoneOffset.UTC).toString() ) ) } diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalValueSerializer.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalValueSerializer.kt index f9bcd451c..711f240c3 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalValueSerializer.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/TemporalValueSerializer.kt @@ -10,4 +10,4 @@ object TemporalValueSerializer : StdSerializer(TemporalValue::cla override fun serialize(temporalValue: TemporalValue, gen: JsonGenerator, provider: SerializerProvider) { gen.writeRawValue("[${temporalValue.value},\"${temporalValue.timestamp}\"]") } -} \ No newline at end of file +} diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityHandler.kt index 9cccf832c..65d48c5c1 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityHandler.kt @@ -9,22 +9,34 @@ import com.egm.stellio.shared.model.BadRequestDataException import com.egm.stellio.shared.model.BadRequestDataResponse import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.ResourceNotFoundException -import com.egm.stellio.shared.util.* import com.egm.stellio.shared.util.ApiUtils.serializeObject +import com.egm.stellio.shared.util.JSON_LD_CONTENT_TYPE import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_CORE_CONTEXT import com.egm.stellio.shared.util.NgsiLdParsingUtils.expandJsonLdFragment import com.egm.stellio.shared.util.NgsiLdParsingUtils.expandValueAsMap import com.egm.stellio.shared.util.NgsiLdParsingUtils.parseEntity +import com.egm.stellio.shared.util.OptionsParamValue +import com.egm.stellio.shared.util.extractContextFromLinkHeader +import com.egm.stellio.shared.util.extractShortTypeFromExpanded +import com.egm.stellio.shared.util.hasValueInOptionsParam +import com.egm.stellio.shared.util.parseTimeParameter import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.util.MultiValueMap -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestHeader +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono -import java.util.* +import java.util.Optional @RestController @RequestMapping("/ngsi-ld/v1/temporal/entities") @@ -40,8 +52,11 @@ class TemporalEntityHandler( * Implements 6.20.3.1 */ @PostMapping("/{entityId}/attrs", consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) - fun addAttrs(@RequestHeader httpHeaders: HttpHeaders, @PathVariable entityId: String, @RequestBody body: Mono): - Mono> { + fun addAttrs( + @RequestHeader httpHeaders: HttpHeaders, + @PathVariable entityId: String, + @RequestBody body: Mono + ): Mono> { val contextLink = extractContextFromLinkHeader(httpHeaders.getOrEmpty("Link")) return body @@ -51,12 +66,15 @@ class TemporalEntityHandler( .flatMap { temporalEntityAttributeService.getForEntityAndAttribute(entityId, it.key.extractShortTypeFromExpanded()) .map { temporalEntityAttributeUuid -> - Pair(temporalEntityAttributeUuid, it) } + Pair(temporalEntityAttributeUuid, it) + } } .map { - attributeInstanceService.addAttributeInstances(it.first, + attributeInstanceService.addAttributeInstances( + it.first, it.second.key.extractShortTypeFromExpanded(), - expandValueAsMap(it.second.value)) + expandValueAsMap(it.second.value) + ) } .collectList() .map { @@ -74,7 +92,8 @@ class TemporalEntityHandler( @RequestParam params: MultiValueMap ): Mono> { - val withTemporalValues = hasValueInOptionsParam(Optional.ofNullable(params.getFirst("options")), OptionsParamValue.TEMPORAL_VALUES) + val withTemporalValues = + hasValueInOptionsParam(Optional.ofNullable(params.getFirst("options")), OptionsParamValue.TEMPORAL_VALUES) val contextLink = extractContextFromLinkHeader(httpHeaders.getOrEmpty("Link")) // TODO : a quick and dirty fix to propagate the Bearer token when calling context registry @@ -119,15 +138,25 @@ class TemporalEntityHandler( /** * Get the entity payload from entity service if we don't have it locally (for legacy entries in DB) */ - private fun loadEntityPayload(temporalEntityAttribute: TemporalEntityAttribute, bearerToken: String): Mono = + private fun loadEntityPayload( + temporalEntityAttribute: TemporalEntityAttribute, + bearerToken: String + ): Mono = when { temporalEntityAttribute.entityPayload == null -> entityService.getEntityById(temporalEntityAttribute.entityId, bearerToken) .doOnSuccess { val entityPayload = it.compact() - temporalEntityAttributeService.addEntityPayload(temporalEntityAttribute, serializeObject(entityPayload)).subscribe() + temporalEntityAttributeService.addEntityPayload( + temporalEntityAttribute, + serializeObject(entityPayload) + ).subscribe() } - temporalEntityAttribute.type != "https://uri.etsi.org/ngsi-ld/Subscription" -> Mono.just(parseEntity(temporalEntityAttribute.entityPayload)) + temporalEntityAttribute.type != "https://uri.etsi.org/ngsi-ld/Subscription" -> Mono.just( + parseEntity( + temporalEntityAttribute.entityPayload + ) + ) else -> { val parsedEntity = parseEntity(temporalEntityAttribute.entityPayload, emptyList()) Mono.just(ExpandedEntity(parsedEntity.rawJsonLdProperties, listOf(NGSILD_CORE_CONTEXT))) @@ -151,7 +180,8 @@ internal fun buildTemporalQuery(params: MultiValueMap): Temporal val endTime = params.getFirst("endTime")?.parseTimeParameter("'endTime' parameter is not a valid date") if ((params.containsKey("timeBucket") && !params.containsKey("aggregate")) || - (!params.containsKey("timeBucket") && params.containsKey("aggregate"))) + (!params.containsKey("timeBucket") && params.containsKey("aggregate")) + ) throw BadRequestDataException("'timeBucket' and 'aggregate' must both be provided for aggregated queries") val aggregate = diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/config/TimescaleBasedTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/config/TimescaleBasedTests.kt index d4f792e04..7263ff919 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/config/TimescaleBasedTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/config/TimescaleBasedTests.kt @@ -14,4 +14,4 @@ open class TimescaleBasedTests { .load() .migrate() } -} \ No newline at end of file +} diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/config/WebSecurityTestConfig.kt b/search-service/src/test/kotlin/com/egm/stellio/search/config/WebSecurityTestConfig.kt index f694cec06..4449057be 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/config/WebSecurityTestConfig.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/config/WebSecurityTestConfig.kt @@ -19,4 +19,4 @@ class WebSecurityTestConfig : WebSecurityConfig() { fun jwtDecoder(): ReactiveJwtDecoder { return ReactiveJwtDecoders.fromOidcIssuerLocation(issuerUri) } -} \ No newline at end of file +} diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/listener/EntityListenerTest.kt b/search-service/src/test/kotlin/com/egm/stellio/search/listener/EntityListenerTest.kt index 17132d6ee..8386cc1ee 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/listener/EntityListenerTest.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/listener/EntityListenerTest.kt @@ -1,7 +1,7 @@ package com.egm.stellio.search.listener -import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.search.service.AttributeInstanceService +import com.egm.stellio.search.service.TemporalEntityAttributeService import com.ninjasquad.springmockk.MockkBean import io.mockk.confirmVerified import io.mockk.every @@ -12,9 +12,9 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles import reactor.core.publisher.Mono import java.time.ZonedDateTime -import java.util.* +import java.util.UUID -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ EntityListener::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [EntityListener::class]) @ActiveProfiles("test") class EntityListenerTest { @@ -46,9 +46,11 @@ class EntityListenerTest { entityListener.processMessage(content) - verify { temporalEntityAttributeService.createEntityTemporalReferences(match { - it.contains("urn:ngsi-ld:FishContainment:1234") - }) } + verify { + temporalEntityAttributeService.createEntityTemporalReferences(match { + it.contains("urn:ngsi-ld:FishContainment:1234") + }) + } confirmVerified(temporalEntityAttributeService) } @@ -86,19 +88,27 @@ class EntityListenerTest { """.trimIndent().replace("\n", "") val temporalEntityAttributeUuid = UUID.randomUUID() - every { temporalEntityAttributeService.getForEntityAndAttribute(any(), any()) } returns Mono.just(temporalEntityAttributeUuid) + every { temporalEntityAttributeService.getForEntityAndAttribute(any(), any()) } returns Mono.just( + temporalEntityAttributeUuid + ) every { attributeInstanceService.create(any()) } returns Mono.just(1) entityListener.processMessage(content) - verify { temporalEntityAttributeService.getForEntityAndAttribute(eq("urn:ngsi-ld:FishContainment:1234"), - eq("https://ontology.eglobalmark.com/aquac#totalDissolvedSolids")) } - verify { attributeInstanceService.create(match { - it.value == null && - it.measuredValue == 33869.0 && - it.observedAt == ZonedDateTime.parse("2020-03-12T08:33:38.000Z") && - it.temporalEntityAttribute == temporalEntityAttributeUuid - }) } + verify { + temporalEntityAttributeService.getForEntityAndAttribute( + eq("urn:ngsi-ld:FishContainment:1234"), + eq("https://ontology.eglobalmark.com/aquac#totalDissolvedSolids") + ) + } + verify { + attributeInstanceService.create(match { + it.value == null && + it.measuredValue == 33869.0 && + it.observedAt == ZonedDateTime.parse("2020-03-12T08:33:38.000Z") && + it.temporalEntityAttribute == temporalEntityAttributeUuid + }) + } confirmVerified(temporalEntityAttributeService) } } diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/listener/SubscriptionListenerTest.kt b/search-service/src/test/kotlin/com/egm/stellio/search/listener/SubscriptionListenerTest.kt index 173a6653a..335bff50b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/listener/SubscriptionListenerTest.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/listener/SubscriptionListenerTest.kt @@ -1,8 +1,8 @@ package com.egm.stellio.search.listener import com.egm.stellio.search.model.TemporalEntityAttribute -import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.search.service.AttributeInstanceService +import com.egm.stellio.search.service.TemporalEntityAttributeService import com.egm.stellio.shared.util.loadSampleData import com.ninjasquad.springmockk.MockkBean import io.mockk.confirmVerified @@ -13,9 +13,9 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles import reactor.core.publisher.Mono -import java.util.* +import java.util.UUID -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ SubscriptionListener::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [SubscriptionListener::class]) @ActiveProfiles("test") class SubscriptionListenerTest { @@ -36,12 +36,14 @@ class SubscriptionListenerTest { subscriptionListener.processSubscription(subscription) - verify { temporalEntityAttributeService.create(match { entityTemporalProperty -> - entityTemporalProperty.attributeName == "https://uri.etsi.org/ngsi-ld/notification" && + verify { + temporalEntityAttributeService.create(match { entityTemporalProperty -> + entityTemporalProperty.attributeName == "https://uri.etsi.org/ngsi-ld/notification" && entityTemporalProperty.attributeValueType == TemporalEntityAttribute.AttributeValueType.ANY && entityTemporalProperty.entityId == "urn:ngsi-ld:Subscription:1234" && entityTemporalProperty.type == "https://uri.etsi.org/ngsi-ld/Subscription" - }) } + }) + } confirmVerified(temporalEntityAttributeService) } @@ -57,10 +59,12 @@ class SubscriptionListenerTest { subscriptionListener.processNotification(notification) verify { temporalEntityAttributeService.getFirstForEntity(eq("urn:ngsi-ld:Subscription:1234")) } - verify { attributeInstanceService.create(match { - it.value == "urn:ngsi-ld:BeeHive:TESTC,urn:ngsi-ld:BeeHive:TESTD" && - it.temporalEntityAttribute == temporalEntityAttributeUuid - }) } + verify { + attributeInstanceService.create(match { + it.value == "urn:ngsi-ld:BeeHive:TESTC,urn:ngsi-ld:BeeHive:TESTD" && + it.temporalEntityAttribute == temporalEntityAttributeUuid + }) + } confirmVerified(temporalEntityAttributeService) confirmVerified(attributeInstanceService) } 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 2a6b45d7e..a81492549 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 @@ -1,19 +1,21 @@ package com.egm.stellio.search.service import com.egm.stellio.search.config.TimescaleBasedTests -import com.egm.stellio.search.model.* +import com.egm.stellio.search.model.AttributeInstance +import com.egm.stellio.search.model.TemporalEntityAttribute +import com.egm.stellio.search.model.TemporalQuery import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeAll -import org.springframework.boot.test.context.SpringBootTest import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest import org.springframework.data.r2dbc.core.DatabaseClient import org.springframework.test.context.ActiveProfiles import reactor.test.StepVerifier import java.time.Instant import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* +import java.util.UUID import kotlin.random.Random @SpringBootTest @@ -64,7 +66,14 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { ) attributeInstanceService.create(observation).block() - val temporalQuery = TemporalQuery(emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), null, null, null) + val temporalQuery = TemporalQuery( + emptyList(), + TemporalQuery.Timerel.AFTER, + Instant.now().atZone(ZoneOffset.UTC).minusHours(1), + null, + null, + null + ) val enrichedEntity = attributeInstanceService.search(temporalQuery, temporalEntityAttribute) StepVerifier.create(enrichedEntity) @@ -72,7 +81,8 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { it.size == 1 && it[0]["attribute_name"] == "incoming" && it[0]["value"] == 12.4 && - ZonedDateTime.parse(it[0]["observed_at"].toString()).toInstant().atZone(ZoneOffset.UTC) == observationDateTime && + ZonedDateTime.parse(it[0]["observed_at"].toString()).toInstant() + .atZone(ZoneOffset.UTC) == observationDateTime && (it[0]["instance_id"] as String).startsWith("urn:ngsi-ld:Instance:") } .expectComplete() @@ -84,8 +94,14 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { (1..10).forEach { _ -> attributeInstanceService.create(gimmeAttributeInstance()).block() } - val temporalQuery = TemporalQuery(emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), - null, null, null) + val temporalQuery = TemporalQuery( + emptyList(), + TemporalQuery.Timerel.AFTER, + Instant.now().atZone(ZoneOffset.UTC).minusHours(1), + null, + null, + null + ) val enrichedEntity = attributeInstanceService.search(temporalQuery, temporalEntityAttribute) StepVerifier.create(enrichedEntity) @@ -104,8 +120,10 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { attributeInstanceService.create(attributeInstance).block() } - val temporalQuery = TemporalQuery(emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), - null, "1 day", TemporalQuery.Aggregate.SUM) + val temporalQuery = TemporalQuery( + emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), + null, "1 day", TemporalQuery.Aggregate.SUM + ) val enrichedEntity = attributeInstanceService.search(temporalQuery, temporalEntityAttribute) StepVerifier.create(enrichedEntity) @@ -136,11 +154,18 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { (1..10).forEach { _ -> attributeInstanceService.create(gimmeAttributeInstance()).block() } (1..5).forEach { _ -> attributeInstanceService.create( - gimmeAttributeInstance().copy(temporalEntityAttribute = temporalEntityAttribute2.id)).block() + gimmeAttributeInstance().copy(temporalEntityAttribute = temporalEntityAttribute2.id) + ).block() } - val temporalQuery = TemporalQuery(emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), - null, null, null) + val temporalQuery = TemporalQuery( + emptyList(), + TemporalQuery.Timerel.AFTER, + Instant.now().atZone(ZoneOffset.UTC).minusHours(1), + null, + null, + null + ) val enrichedEntity = attributeInstanceService.search(temporalQuery, temporalEntityAttribute) StepVerifier.create(enrichedEntity) @@ -156,9 +181,16 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { (1..10).forEach { _ -> attributeInstanceService.create(gimmeAttributeInstance()).block() } - val temporalQuery = TemporalQuery(emptyList(), TemporalQuery.Timerel.AFTER, Instant.now().atZone(ZoneOffset.UTC).minusHours(1), - null, null, null) - val enrichedEntity = attributeInstanceService.search(temporalQuery, temporalEntityAttribute.copy(id = UUID.randomUUID())) + val temporalQuery = TemporalQuery( + emptyList(), + TemporalQuery.Timerel.AFTER, + Instant.now().atZone(ZoneOffset.UTC).minusHours(1), + null, + null, + null + ) + val enrichedEntity = + attributeInstanceService.search(temporalQuery, temporalEntityAttribute.copy(id = UUID.randomUUID())) StepVerifier.create(enrichedEntity) .expectNextMatches { @@ -175,4 +207,4 @@ class AttributeInstanceServiceTests : TimescaleBasedTests() { observedAt = Instant.now().atZone(ZoneOffset.UTC) ) } -} \ No newline at end of file +} diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityServiceTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityServiceTests.kt index 8f76d2b4f..ac2af7b3b 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityServiceTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/service/EntityServiceTests.kt @@ -2,14 +2,21 @@ package com.egm.stellio.search.service import com.egm.stellio.shared.util.loadSampleData import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.client.WireMock.configureFor +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.get +import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.okJson +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlMatching +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.verify import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest - import org.springframework.test.context.ActiveProfiles import reactor.test.StepVerifier @@ -42,8 +49,10 @@ class EntityServiceTests { fun `it should call the context registry with the correct entityId`() { // prepare our stub - stubFor(get(urlMatching("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TESTC")) - .willReturn(okJson(loadSampleData("beehive.jsonld")))) + stubFor( + get(urlMatching("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TESTC")) + .willReturn(okJson(loadSampleData("beehive.jsonld"))) + ) val entity = entityService.getEntityById("urn:ngsi-ld:BeeHive:TESTC", "Bearer 1234") @@ -54,7 +63,9 @@ class EntityServiceTests { .verify() // ensure external components and services have been called as expected - verify(getRequestedFor(urlPathEqualTo("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TESTC")) - .withHeader("Authorization", equalTo("Bearer 1234"))) + verify( + getRequestedFor(urlPathEqualTo("/ngsi-ld/v1/entities/urn:ngsi-ld:BeeHive:TESTC")) + .withHeader("Authorization", equalTo("Bearer 1234")) + ) } } 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 db4ac4731..bc9562790 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 @@ -32,7 +32,11 @@ class TemporalEntityAttributeServiceTests : TimescaleBasedTests() { temporalEntityAttributeService.createEntityTemporalReferences(rawEntity).block() val temporalEntityAttributes = - temporalEntityAttributeService.getForEntity("urn:ngsi-ld:BeeHive:TESTD", listOf("incoming", "outgoing"), apicContext!!) + temporalEntityAttributeService.getForEntity( + "urn:ngsi-ld:BeeHive:TESTD", + listOf("incoming", "outgoing"), + apicContext!! + ) StepVerifier.create(temporalEntityAttributes) .expectNextMatches { @@ -129,7 +133,10 @@ class TemporalEntityAttributeServiceTests : TimescaleBasedTests() { JsonLdOptions() ) val finalEntity = JsonUtils.toPrettyString(serializedEntity) - assertEquals(loadSampleData("expectations/subscription_with_notifications_temporal_values.jsonld").trim(), finalEntity) + assertEquals( + loadSampleData("expectations/subscription_with_notifications_temporal_values.jsonld").trim(), + finalEntity + ) } @Test diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/web/TemporalEntityHandlerTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/web/TemporalEntityHandlerTests.kt index ee55df09d..ee4119e91 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/web/TemporalEntityHandlerTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/web/TemporalEntityHandlerTests.kt @@ -3,9 +3,9 @@ package com.egm.stellio.search.web import com.egm.stellio.search.config.WebSecurityTestConfig import com.egm.stellio.search.model.TemporalEntityAttribute import com.egm.stellio.search.model.TemporalQuery +import com.egm.stellio.search.service.AttributeInstanceService import com.egm.stellio.search.service.EntityService import com.egm.stellio.search.service.TemporalEntityAttributeService -import com.egm.stellio.search.service.AttributeInstanceService import com.egm.stellio.shared.model.BadRequestDataException import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.util.JSON_LD_MEDIA_TYPE @@ -31,7 +31,7 @@ import org.springframework.web.reactive.function.BodyInserters import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.time.ZonedDateTime -import java.util.* +import java.util.UUID @ActiveProfiles("test") @WebFluxTest(TemporalEntityHandler::class) @@ -67,23 +67,32 @@ class TemporalEntityHandlerTests { val jsonLdObservation = ClassPathResource("/ngsild/observation.jsonld") val temporalEntityAttributeUuid = UUID.randomUUID() - every { temporalEntityAttributeService.getForEntityAndAttribute(any(), any()) } returns Mono.just(temporalEntityAttributeUuid) + every { temporalEntityAttributeService.getForEntityAndAttribute(any(), any()) } returns Mono.just( + temporalEntityAttributeUuid + ) every { attributeInstanceService.addAttributeInstances(any(), any(), any()) } returns Mono.just(1) webClient.post() - .uri("/ngsi-ld/v1/temporal/entities/urn:ngsi-ld:BeeHive:TESTC/attrs") - .body(BodyInserters.fromValue(jsonLdObservation.inputStream.readAllBytes())) - .exchange() - .expectStatus().isNoContent - - verify { temporalEntityAttributeService.getForEntityAndAttribute(eq("urn:ngsi-ld:BeeHive:TESTC"), - eq("incoming")) } - verify { attributeInstanceService.addAttributeInstances(eq(temporalEntityAttributeUuid), - eq("incoming"), - match { - it.size == 4 - } - ) } + .uri("/ngsi-ld/v1/temporal/entities/urn:ngsi-ld:BeeHive:TESTC/attrs") + .body(BodyInserters.fromValue(jsonLdObservation.inputStream.readAllBytes())) + .exchange() + .expectStatus().isNoContent + + verify { + temporalEntityAttributeService.getForEntityAndAttribute( + eq("urn:ngsi-ld:BeeHive:TESTC"), + eq("incoming") + ) + } + verify { + attributeInstanceService.addAttributeInstances( + eq(temporalEntityAttributeUuid), + eq("incoming"), + match { + it.size == 4 + } + ) + } confirmVerified(temporalEntityAttributeService) confirmVerified(attributeInstanceService) } @@ -96,23 +105,33 @@ class TemporalEntityHandlerTests { .body(BodyInserters.fromValue("{ \"id\": \"bad\" }")) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + - "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Unable to expand JSON-LD fragment : { \\\"id\\\": \\\"bad\\\" }\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + + "\"detail\":\"Unable to expand JSON-LD fragment : { \\\"id\\\": \\\"bad\\\" }\"}" + ) } @Test fun `it should return a 400 if a service throws a BadRequestDataException`() { - every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } throws BadRequestDataException("Bad request") + every { + temporalEntityAttributeService.getForEntity( + any(), + any(), + any() + ) + } throws BadRequestDataException("Bad request") webClient.get() .uri("/ngsi-ld/v1/temporal/entities/entityId") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}") + "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}" + ) } @Test @@ -122,9 +141,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?time=before") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}") + "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}" + ) } @Test @@ -134,9 +155,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=before") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}") + "\"detail\":\"'timerel and 'time' request parameters are mandatory\"}" + ) } @Test @@ -146,9 +169,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=between&time=startTime") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'endTime' request parameter is mandatory if 'timerel' is 'between'\"}") + "\"detail\":\"'endTime' request parameter is mandatory if 'timerel' is 'between'\"}" + ) } @Test @@ -158,9 +183,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=before&time=badTime") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'time' parameter is not a valid date\"}") + "\"detail\":\"'time' parameter is not a valid date\"}" + ) } @Test @@ -170,9 +197,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=befor&time=badTime") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'timerel' is not valid, it should be one of 'before', 'between', or 'after'\"}") + "\"detail\":\"'timerel' is not valid, it should be one of 'before', 'between', or 'after'\"}" + ) } @Test @@ -182,9 +211,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=between&time=2019-10-17T07:31:39Z&endTime=endTime") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'endTime' parameter is not a valid date\"}") + "\"detail\":\"'endTime' parameter is not a valid date\"}" + ) } @Test @@ -194,9 +225,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=after&time=2020-01-31T07:31:39Z&timeBucket=1 minute") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"'timeBucket' and 'aggregate' must both be provided for aggregated queries\"}") + "\"detail\":\"'timeBucket' and 'aggregate' must both be provided for aggregated queries\"}" + ) } @Test @@ -206,9 +239,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=after&time=2020-01-31T07:31:39Z&timeBucket=1 minute&aggregate=unknown") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Value 'unknown' is not supported for 'aggregate' parameter\"}") + "\"detail\":\"Value 'unknown' is not supported for 'aggregate' parameter\"}" + ) } @Test @@ -220,9 +255,11 @@ class TemporalEntityHandlerTests { .uri("/ngsi-ld/v1/temporal/entities/entityId?timerel=between&time=2019-10-17T07:31:39Z&endTime=2019-10-18T07:31:39Z") .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + - "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Entity entityId was not found\"}") + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + "\"title\":\"The referred resource has not been found\"," + + "\"detail\":\"Entity entityId was not found\"}" + ) } @Test @@ -235,10 +272,15 @@ class TemporalEntityHandlerTests { attributeValueType = TemporalEntityAttribute.AttributeValueType.MEASURE ) - every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just(entityTemporalProperty) + every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just( + entityTemporalProperty + ) every { attributeInstanceService.search(any(), any()) } returns Mono.just(emptyList()) every { entityService.getEntityById(any(), any()) } returns Mono.just(loadAndParseSampleData()) - every { temporalEntityAttributeService.injectTemporalValues(any(), any(), any()) } returns mockkClass(ExpandedEntity::class, relaxed = true) + every { temporalEntityAttributeService.injectTemporalValues(any(), any(), any()) } returns mockkClass( + ExpandedEntity::class, + relaxed = true + ) every { temporalEntityAttributeService.addEntityPayload(any(), any()) } returns Mono.just(1) webClient.get() @@ -246,10 +288,12 @@ class TemporalEntityHandlerTests { .exchange() .expectStatus().isOk - verify { attributeInstanceService.search(match { temporalQuery -> - temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && - temporalQuery.time.isEqual(ZonedDateTime.parse("2019-10-17T07:31:39Z")) - }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) } + verify { + attributeInstanceService.search(match { temporalQuery -> + temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && + temporalQuery.time.isEqual(ZonedDateTime.parse("2019-10-17T07:31:39Z")) + }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) + } confirmVerified(attributeInstanceService) verify { entityService.getEntityById(eq("entityId"), any()) } @@ -257,12 +301,14 @@ class TemporalEntityHandlerTests { verify { temporalEntityAttributeService.injectTemporalValues(any(), any(), false) } - verify(timeout = 1000) { temporalEntityAttributeService.addEntityPayload(match { - it.entityId == "entityId" - }, match { - // TODO we need a way to compare payloads with struggling with indents and carriage returns and .... - it.startsWith("{\"id\":\"urn:ngsi-ld:BeeHive:TESTC\",\"type\":\"BeeHive\"") - }) } + verify(timeout = 1000) { + temporalEntityAttributeService.addEntityPayload(match { + it.entityId == "entityId" + }, match { + // TODO we need a way to compare payloads with struggling with indents and carriage returns and .... + it.startsWith("{\"id\":\"urn:ngsi-ld:BeeHive:TESTC\",\"type\":\"BeeHive\"") + }) + } } @Test @@ -281,7 +327,10 @@ class TemporalEntityHandlerTests { attributeValueType = TemporalEntityAttribute.AttributeValueType.MEASURE ) val rawEntity = loadAndParseSampleData() - every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just(entityTemporalProperty1, entityTemporalProperty2) + every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just( + entityTemporalProperty1, + entityTemporalProperty2 + ) every { attributeInstanceService.search(any(), any()) } returns Mono.just(emptyList()) every { entityService.getEntityById(any(), any()) } returns Mono.just(rawEntity) every { temporalEntityAttributeService.injectTemporalValues(any(), any(), any()) } returns rawEntity @@ -292,10 +341,12 @@ class TemporalEntityHandlerTests { .expectStatus().isOk .expectBody().jsonPath("$").isMap - verify { attributeInstanceService.search(match { temporalQuery -> - temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && + verify { + attributeInstanceService.search(match { temporalQuery -> + temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && temporalQuery.time.isEqual(ZonedDateTime.parse("2019-10-17T07:31:39Z")) - }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) } + }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) + } confirmVerified(attributeInstanceService) } @@ -310,7 +361,9 @@ class TemporalEntityHandlerTests { ) val rawEntity = loadAndParseSampleData() - every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just(entityTemporalProperty) + every { temporalEntityAttributeService.getForEntity(any(), any(), any()) } returns Flux.just( + entityTemporalProperty + ) every { attributeInstanceService.search(any(), any()) } returns Mono.just(emptyList()) every { entityService.getEntityById(any(), any()) } returns Mono.just(rawEntity) every { temporalEntityAttributeService.injectTemporalValues(any(), any(), any()) } returns rawEntity @@ -321,10 +374,12 @@ class TemporalEntityHandlerTests { .expectStatus().isOk .expectBody().jsonPath("$").isMap - verify { attributeInstanceService.search(match { temporalQuery -> - temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && + verify { + attributeInstanceService.search(match { temporalQuery -> + temporalQuery.timerel == TemporalQuery.Timerel.BETWEEN && temporalQuery.time.isEqual(ZonedDateTime.parse("2019-10-17T07:31:39Z")) - }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) } + }, match { entityTemporalProperty -> entityTemporalProperty.entityId == "entityId" }) + } confirmVerified(attributeInstanceService) } diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt index 8ef0f56f0..b393083b6 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ErrorResponse.kt @@ -56,10 +56,10 @@ data class JsonLdErrorResponse(override val title: String, override val detail: ) data class JsonParseErrorResponse(override val detail: String) : ErrorResponse( - ErrorType.BAD_REQUEST_DATA.type, - "The request includes invalid input data, an error occurred during JSON parsing", - detail - ) + ErrorType.BAD_REQUEST_DATA.type, + "The request includes invalid input data, an error occurred during JSON parsing", + detail +) data class AccessDeniedResponse(override val detail: String) : ErrorResponse( diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/ExpandedEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/ExpandedEntity.kt index ca32a2d09..31e1ad028 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/ExpandedEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/ExpandedEntity.kt @@ -78,4 +78,4 @@ class ExpandedEntity private constructor( } } } -} \ No newline at end of file +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/Notification.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/Notification.kt index 6c26c3a96..536d1705a 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/Notification.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/Notification.kt @@ -4,7 +4,7 @@ import java.net.URI import java.time.Instant import java.time.ZoneOffset import java.time.ZonedDateTime -import java.util.* +import java.util.UUID data class Notification( val id: URI = URI.create("urn:ngsi-ld:Notification:${UUID.randomUUID()}"), diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt index 7339a5be9..534bf0292 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt @@ -1,6 +1,8 @@ package com.egm.stellio.shared.util -import com.egm.stellio.shared.model.* +import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.Notification +import com.egm.stellio.shared.model.Subscription import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature @@ -8,7 +10,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.springframework.http.MediaType import java.time.ZonedDateTime import java.time.format.DateTimeParseException -import java.util.* +import java.util.Optional object ApiUtils { @@ -63,4 +65,4 @@ fun hasValueInOptionsParam(options: Optional, optionValue: OptionsParamV options .map { it.split(",") } .filter { it.any { option -> option == optionValue.value } } - .isPresent \ No newline at end of file + .isPresent diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/HttpUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/HttpUtils.kt index 3e4e575a3..919187610 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/HttpUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/HttpUtils.kt @@ -24,4 +24,4 @@ object HttpUtils { null } } -} \ No newline at end of file +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtils.kt index cca60cf58..3e66fbc08 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtils.kt @@ -28,7 +28,8 @@ data class AttributeType(val uri: String) object NgsiLdParsingUtils { const val NGSILD_CORE_CONTEXT = "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" - const val NGSILD_EGM_CONTEXT = "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/shared-jsonld-contexts/egm.jsonld" + const val NGSILD_EGM_CONTEXT = + "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/shared-jsonld-contexts/egm.jsonld" val NGSILD_PROPERTY_TYPE = AttributeType("https://uri.etsi.org/ngsi-ld/Property") const val NGSILD_PROPERTY_VALUE = "https://uri.etsi.org/ngsi-ld/hasValue" @@ -85,15 +86,18 @@ object NgsiLdParsingUtils { .findAndRegisterModules() .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - private val localCoreContextPayload = ClassPathResource("/ngsild/ngsi-ld-core-context.jsonld").inputStream.readBytes().toString(Charsets.UTF_8) + private val localCoreContextPayload = + ClassPathResource("/ngsild/ngsi-ld-core-context.jsonld").inputStream.readBytes().toString(Charsets.UTF_8) private var BASE_CONTEXT: Map = mapOf() @PostConstruct private fun loadCoreContext() { val coreContextPayload = HttpUtils.doGet(NGSILD_CORE_CONTEXT) ?: localCoreContextPayload - val coreContext: Map = mapper.readValue(coreContextPayload, mapper.typeFactory.constructMapLikeType( - Map::class.java, String::class.java, Any::class.java - )) + val coreContext: Map = mapper.readValue( + coreContextPayload, mapper.typeFactory.constructMapLikeType( + Map::class.java, String::class.java, Any::class.java + ) + ) BASE_CONTEXT = coreContext.get("@context") as Map logger.info("Core context loaded") } @@ -106,7 +110,8 @@ object NgsiLdParsingUtils { val jsonLdOptions = JsonLdOptions() jsonLdOptions.expandContext = mapOf("@context" to usedContext) - val expandedEntity = JsonLdProcessor.expand(JsonUtils.fromInputStream(input.byteInputStream()), jsonLdOptions)[0] + val expandedEntity = + JsonLdProcessor.expand(JsonUtils.fromInputStream(input.byteInputStream()), jsonLdOptions)[0] return ExpandedEntity(expandedEntity as Map, contexts) } @@ -121,9 +126,11 @@ object NgsiLdParsingUtils { logger.debug("Expanded entity is $expandedResult") // TODO find a way to avoid this extra parsing - val parsedInput: Map = mapper.readValue(input, mapper.typeFactory.constructMapLikeType( - Map::class.java, String::class.java, Any::class.java - )) + val parsedInput: Map = mapper.readValue( + input, mapper.typeFactory.constructMapLikeType( + Map::class.java, String::class.java, Any::class.java + ) + ) val contexts = if (parsedInput["@context"] is List<*>) @@ -145,9 +152,11 @@ object NgsiLdParsingUtils { } fun parseJsonLdFragment(input: String): Map { - return mapper.readValue(input, mapper.typeFactory.constructMapLikeType( - Map::class.java, String::class.java, Any::class.java - )) + return mapper.readValue( + input, mapper.typeFactory.constructMapLikeType( + Map::class.java, String::class.java, Any::class.java + ) + ) } fun expandValueAsMap(value: Any): Map> = @@ -223,8 +232,10 @@ object NgsiLdParsingUtils { } fun extractShortTypeFromPayload(payload: Map): String = - // TODO is it always after a '/' ? can't it be after a '#' ? (https://redmine.eglobalmark.com/issues/852) - // TODO do a clean implementation using info from @context + /* + * TODO do a clean implementation using info from @context + * TODO is it always after a '/' ? can't it be after a '#' ? (https://redmine.eglobalmark.com/issues/852) + */ (payload["@type"] as List)[0].substringAfterLast("/").substringAfterLast("#") /** @@ -266,7 +277,8 @@ object NgsiLdParsingUtils { val usedContext = addCoreContext(contexts) val jsonLdOptions = JsonLdOptions() jsonLdOptions.expandContext = mapOf("@context" to usedContext) - val expandedFragment = JsonLdProcessor.expand(JsonUtils.fromInputStream(fragment.byteInputStream()), jsonLdOptions) + val expandedFragment = + JsonLdProcessor.expand(JsonUtils.fromInputStream(fragment.byteInputStream()), jsonLdOptions) logger.debug("Expanded fragment $fragment to $expandedFragment") if (expandedFragment.isEmpty()) throw BadRequestDataException("Unable to expand JSON-LD fragment : $fragment") @@ -278,12 +290,14 @@ object NgsiLdParsingUtils { } fun compactAndStringifyFragment(key: String, value: Any, context: List): String { - val compactedFragment = JsonLdProcessor.compact(mapOf(key to value), mapOf("@context" to context), JsonLdOptions()) + val compactedFragment = + JsonLdProcessor.compact(mapOf(key to value), mapOf("@context" to context), JsonLdOptions()) return mapper.writeValueAsString(compactedFragment) } fun compactAndStringifyFragment(key: String, value: Any, context: String): String { - val compactedFragment = JsonLdProcessor.compact(mapOf(key to value), mapOf("@context" to context), JsonLdOptions()) + val compactedFragment = + JsonLdProcessor.compact(mapOf(key to value), mapOf("@context" to context), JsonLdOptions()) return mapper.writeValueAsString(compactedFragment) } @@ -335,7 +349,10 @@ object NgsiLdParsingUtils { val context = rawParsedData.get("@context") ?: throw BadRequestDataException("Context not provided") return try { - mapper.readValue(context.toString(), mapper.typeFactory.constructCollectionType(List::class.java, String::class.java)) + mapper.readValue( + context.toString(), + mapper.typeFactory.constructCollectionType(List::class.java, String::class.java) + ) } catch (e: Exception) { throw BadRequestDataException(e.message ?: "Unable to parse the provided context") } @@ -350,7 +367,10 @@ object NgsiLdParsingUtils { if (geoPropertyType.extractShortTypeFromExpanded() == NGSILD_POINT_PROPERTY) { val longitude = (geoPropertyValue[0] as Map)["@value"] val latitude = (geoPropertyValue[1] as Map)["@value"] - return mapOf("geometry" to geoPropertyType.extractShortTypeFromExpanded(), "coordinates" to listOf(longitude, latitude)) + return mapOf( + "geometry" to geoPropertyType.extractShortTypeFromExpanded(), + "coordinates" to listOf(longitude, latitude) + ) } else { val res = arrayListOf>() var count = 1 @@ -360,7 +380,7 @@ object NgsiLdParsingUtils { val latitude = (geoPropertyValue[count] as Map)["@value"] res.add(listOf(longitude, latitude)) } - count ++ + count++ } return mapOf("geometry" to geoPropertyType.extractShortTypeFromExpanded(), "coordinates" to res) } @@ -389,8 +409,10 @@ object NgsiLdParsingUtils { } fun String.extractShortTypeFromExpanded(): String = - // TODO is it always after a '/' ? can't it be after a '#' ? (https://redmine.eglobalmark.com/issues/852) - // TODO do a clean implementation using info from @context + /* + * TODO is it always after a '/' ? can't it be after a '#' ? (https://redmine.eglobalmark.com/issues/852) + * TODO do a clean implementation using info from @context + */ this.substringAfterLast("/").substringAfterLast("#") fun String.toRelationshipTypeName(): String = diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/PagingUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/PagingUtils.kt index 615f56cc1..18608a662 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/PagingUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/PagingUtils.kt @@ -9,10 +9,12 @@ object PagingUtils { var nextLink: String? = null if (pageNumber > 1 && (subscriptionsCount > (pageNumber - 1) * limit)) - prevLink = ";rel=\"prev\";type=\"application/ld+json\"" + prevLink = + ";rel=\"prev\";type=\"application/ld+json\"" if (subscriptionsCount > pageNumber * limit) - nextLink = ";rel=\"next\";type=\"application/ld+json\"" + nextLink = + ";rel=\"next\";type=\"application/ld+json\"" return Pair(prevLink, nextLink) } -} \ No newline at end of file +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/AuthUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/AuthUtils.kt index b0ad43d5e..18240ce3a 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/AuthUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/AuthUtils.kt @@ -11,4 +11,4 @@ fun extractJwT(): Mono { .map(SecurityContext::getAuthentication) .map(Authentication::getPrincipal) .cast(Jwt::class.java) -} \ No newline at end of file +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/CustomWebFilter.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/CustomWebFilter.kt index 0691fea38..407a35c52 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/CustomWebFilter.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/CustomWebFilter.kt @@ -26,4 +26,4 @@ class CustomWebFilter : WebFilter { return chain.filter(exchange) } -} \ No newline at end of file +} diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt b/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt index 9f26681d5..73869f6cf 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/web/ExceptionHandler.kt @@ -1,6 +1,16 @@ package com.egm.stellio.shared.web -import com.egm.stellio.shared.model.* +import com.egm.stellio.shared.model.AccessDeniedException +import com.egm.stellio.shared.model.AccessDeniedResponse +import com.egm.stellio.shared.model.AlreadyExistsException +import com.egm.stellio.shared.model.AlreadyExistsResponse +import com.egm.stellio.shared.model.BadRequestDataException +import com.egm.stellio.shared.model.BadRequestDataResponse +import com.egm.stellio.shared.model.InternalErrorResponse +import com.egm.stellio.shared.model.JsonLdErrorResponse +import com.egm.stellio.shared.model.JsonParseErrorResponse +import com.egm.stellio.shared.model.ResourceNotFoundException +import com.egm.stellio.shared.model.ResourceNotFoundResponse import com.egm.stellio.shared.util.ApiUtils import com.fasterxml.jackson.core.JsonParseException import com.github.jsonldjava.core.JsonLdError @@ -16,17 +26,38 @@ class ExceptionHandler { @ExceptionHandler fun transformErrorResponse(throwable: Throwable): ResponseEntity = when (throwable) { - is AlreadyExistsException -> generateErrorResponse(HttpStatus.CONFLICT, AlreadyExistsResponse(throwable.message)) - is ResourceNotFoundException -> generateErrorResponse(HttpStatus.NOT_FOUND, ResourceNotFoundResponse(throwable.message)) - is BadRequestDataException -> generateErrorResponse(HttpStatus.BAD_REQUEST, BadRequestDataResponse(throwable.message)) - is JsonLdError -> generateErrorResponse(HttpStatus.BAD_REQUEST, JsonLdErrorResponse(throwable.type.toString(), throwable.message.orEmpty())) - is JsonParseException -> generateErrorResponse(HttpStatus.BAD_REQUEST, JsonParseErrorResponse(throwable.message ?: "There has been a problem during JSON parsing")) - is AccessDeniedException -> generateErrorResponse(HttpStatus.FORBIDDEN, AccessDeniedResponse(throwable.message)) - else -> generateErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, InternalErrorResponse(throwable.message ?: "There has been an error during the operation execution")) + is AlreadyExistsException -> generateErrorResponse( + HttpStatus.CONFLICT, + AlreadyExistsResponse(throwable.message) + ) + is ResourceNotFoundException -> generateErrorResponse( + HttpStatus.NOT_FOUND, + ResourceNotFoundResponse(throwable.message) + ) + is BadRequestDataException -> generateErrorResponse( + HttpStatus.BAD_REQUEST, + BadRequestDataResponse(throwable.message) + ) + is JsonLdError -> generateErrorResponse( + HttpStatus.BAD_REQUEST, + JsonLdErrorResponse(throwable.type.toString(), throwable.message.orEmpty()) + ) + is JsonParseException -> generateErrorResponse( + HttpStatus.BAD_REQUEST, + JsonParseErrorResponse(throwable.message ?: "There has been a problem during JSON parsing") + ) + is AccessDeniedException -> generateErrorResponse( + HttpStatus.FORBIDDEN, + AccessDeniedResponse(throwable.message) + ) + else -> generateErrorResponse( + HttpStatus.INTERNAL_SERVER_ERROR, + InternalErrorResponse(throwable.message ?: "There has been an error during the operation execution") + ) } private fun generateErrorResponse(status: HttpStatus, exception: Any) = ResponseEntity.status(status) .contentType(MediaType.APPLICATION_JSON) .body(ApiUtils.serializeObject(exception)) -} \ No newline at end of file +} diff --git a/shared/src/main/resources/ngsild/ngsi-ld-core-context.jsonld b/shared/src/main/resources/ngsild/ngsi-ld-core-context.jsonld index 6cee53757..d809d6968 100644 --- a/shared/src/main/resources/ngsild/ngsi-ld-core-context.jsonld +++ b/shared/src/main/resources/ngsild/ngsi-ld-core-context.jsonld @@ -155,4 +155,4 @@ }, "@vocab": "https://uri.etsi.org/ngsi-ld/default-context/" } -} \ No newline at end of file +} 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 6a29d5e31..38ebd8744 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 @@ -86,4 +86,4 @@ class ExpandedEntityTests { assertEquals(listOf("relation1", "relation2"), expandedEntity.getLinkedEntitiesIds()) } -} \ No newline at end of file +} diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/ApiUtilsTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/ApiUtilsTests.kt index 9dd130246..3aa9e53e6 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/ApiUtilsTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/ApiUtilsTests.kt @@ -1,7 +1,7 @@ package com.egm.stellio.shared.util -import com.egm.stellio.shared.web.CustomWebFilter import com.egm.stellio.shared.util.OptionsParamValue.TEMPORAL_VALUES +import com.egm.stellio.shared.web.CustomWebFilter import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test @@ -9,10 +9,10 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.test.context.ActiveProfiles -import java.util.* import org.springframework.test.web.reactive.server.WebTestClient +import java.util.Optional -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ ApiUtils::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ApiUtils::class]) @ActiveProfiles("test") class ApiUtilsTests { private val webClient = WebTestClient.bindToController(MockkedHandler()).webFilter( @@ -54,4 +54,4 @@ class ApiUtilsTests { .expectStatus().isEqualTo(HttpStatus.LENGTH_REQUIRED) .expectBody().isEmpty } -} \ No newline at end of file +} diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/MockkedHandler.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/MockkedHandler.kt index d27bc2c51..69bc3dfa2 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/MockkedHandler.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/MockkedHandler.kt @@ -3,7 +3,9 @@ package com.egm.stellio.shared.util import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/router/mockkedroute") @@ -11,4 +13,4 @@ class MockkedHandler { @PostMapping(consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE]) fun post() = ResponseEntity.status(HttpStatus.CREATED).build() -} \ No newline at end of file +} diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtilsTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtilsTests.kt index 0248f3b26..1485e6927 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtilsTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/NgsiLdParsingUtilsTests.kt @@ -1,7 +1,9 @@ package com.egm.stellio.shared.util import junit.framework.TestCase -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.springframework.core.io.ClassPathResource @@ -99,13 +101,13 @@ class NgsiLdParsingUtilsTests { TestCase.assertEquals( location, mapOf( "geometry" to "Polygon", "coordinates" to - listOf( - listOf(100.0, 0.0), - listOf(101.0, 0.0), - listOf(101.0, 1.0), - listOf(100.0, 1.0), - listOf(100.0, 0.0) - ) + listOf( + listOf(100.0, 0.0), + listOf(101.0, 0.0), + listOf(101.0, 1.0), + listOf(100.0, 1.0), + listOf(100.0, 0.0) + ) ) ) } diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/util/PagingUtilsTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/util/PagingUtilsTests.kt index e57e59f3c..1c848e82f 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/util/PagingUtilsTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/util/PagingUtilsTests.kt @@ -1,11 +1,11 @@ package com.egm.stellio.shared.util -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ PagingUtils::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [PagingUtils::class]) @ActiveProfiles("test") class PagingUtilsTests { @@ -22,7 +22,10 @@ class PagingUtilsTests { val links = PagingUtils.getSubscriptionsPagingLinks(8, 2, 5) - assertEquals(links, Pair(";rel=\"prev\";type=\"application/ld+json\"", null)) + assertEquals( + links, + Pair(";rel=\"prev\";type=\"application/ld+json\"", null) + ) } @Test @@ -30,7 +33,10 @@ class PagingUtilsTests { val links = PagingUtils.getSubscriptionsPagingLinks(8, 1, 5) - assertEquals(links, Pair(null, ";rel=\"next\";type=\"application/ld+json\"")) + assertEquals( + links, + Pair(null, ";rel=\"next\";type=\"application/ld+json\"") + ) } @Test @@ -38,7 +44,11 @@ class PagingUtilsTests { val links = PagingUtils.getSubscriptionsPagingLinks(8, 3, 1) - assertEquals(links, Pair(";rel=\"prev\";type=\"application/ld+json\"", - ";rel=\"next\";type=\"application/ld+json\"")) + assertEquals( + links, Pair( + ";rel=\"prev\";type=\"application/ld+json\"", + ";rel=\"next\";type=\"application/ld+json\"" + ) + ) } -} \ No newline at end of file +} diff --git a/shared/src/test/resources/ngsild/Observation.json b/shared/src/test/resources/ngsild/Observation.json index fbd47176e..075c8f97a 100644 --- a/shared/src/test/resources/ngsild/Observation.json +++ b/shared/src/test/resources/ngsild/Observation.json @@ -19,4 +19,4 @@ } } } -} \ No newline at end of file +} diff --git a/shared/src/test/resources/ngsild/ObservationWithoutUnitCode.json b/shared/src/test/resources/ngsild/ObservationWithoutUnitCode.json index 946ea75cd..1cf8fecae 100644 --- a/shared/src/test/resources/ngsild/ObservationWithoutUnitCode.json +++ b/shared/src/test/resources/ngsild/ObservationWithoutUnitCode.json @@ -18,4 +18,4 @@ } } } -} \ No newline at end of file +} diff --git a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/TestContainers.kt b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/TestContainers.kt index 63e9b0990..1250b5d03 100644 --- a/shared/src/testFixtures/kotlin/com/egm/stellio/shared/TestContainers.kt +++ b/shared/src/testFixtures/kotlin/com/egm/stellio/shared/TestContainers.kt @@ -8,6 +8,7 @@ open class TestContainers( val servicePort: Int ) { class KDockerComposeContainer(file: File) : DockerComposeContainer(file) + private val DOCKER_COMPOSE_FILE = File("docker-compose.yml") protected val instance: KDockerComposeContainer by lazy { defineDockerCompose() } @@ -19,4 +20,4 @@ open class TestContainers( fun startContainers() { instance.start() } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/config/WebSecurityConfig.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/config/WebSecurityConfig.kt index de1c19de2..45ec8a6fc 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/config/WebSecurityConfig.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/config/WebSecurityConfig.kt @@ -13,12 +13,12 @@ class WebSecurityConfig { @ConditionalOnProperty("application.authentication.enabled") fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { http - // disable CSRF as it does not fit with an HTTP REST API - .csrf().disable() - // tweak Actuator health endpoint security rules to grant access to anonymous users - .authorizeExchange().pathMatchers("/actuator/health").permitAll().and() - .authorizeExchange().pathMatchers("/**").authenticated().and() - .oauth2ResourceServer().jwt() + // disable CSRF as it does not fit with an HTTP REST API + .csrf().disable() + // tweak Actuator health endpoint security rules to grant access to anonymous users + .authorizeExchange().pathMatchers("/actuator/health").permitAll().and() + .authorizeExchange().pathMatchers("/**").authenticated().and() + .oauth2ResourceServer().jwt() return http.build() } @@ -27,11 +27,11 @@ class WebSecurityConfig { @ConditionalOnProperty("application.authentication.enabled", havingValue = "false") fun springNoSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain { http - // disable CSRF as it does not fit with an HTTP REST API - .csrf().disable() - // explicitly disable authentication to override Spring Security defaults - .authorizeExchange().pathMatchers("/**").permitAll() + // disable CSRF as it does not fit with an HTTP REST API + .csrf().disable() + // explicitly disable authentication to override Spring Security defaults + .authorizeExchange().pathMatchers("/**").permitAll() return http.build() } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMInitializer.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMInitializer.kt index 19e53f8e6..af49ec32a 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMInitializer.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMInitializer.kt @@ -31,4 +31,4 @@ class FCMInitializer { } } } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMService.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMService.kt index d0921613f..9a841b67a 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMService.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/firebase/FCMService.kt @@ -11,7 +11,11 @@ class FCMService { private val logger = LoggerFactory.getLogger(javaClass) - fun sendMessage(notificationData: Map, messageData: Map, deviceToken: String): String? { + fun sendMessage( + notificationData: Map, + messageData: Map, + deviceToken: String + ): String? { return try { sendAndGetResponse( createMessage(messageData, createNotification(notificationData), deviceToken) @@ -26,7 +30,11 @@ class FCMService { return FirebaseMessaging.getInstance().sendAsync(message).get() } - private fun createMessage(messageData: Map, notification: Notification, deviceToken: String): Message { + private fun createMessage( + messageData: Map, + notification: Notification, + deviceToken: String + ): Message { val message = Message.builder() messageData.forEach { message.putData(it.key, it.value) @@ -40,4 +48,4 @@ class FCMService { .setBody(notificationData["body"]) .build() } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Endpoint.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Endpoint.kt index 6cf40ba67..cd97ba1f9 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Endpoint.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/Endpoint.kt @@ -17,6 +17,7 @@ data class Endpoint( enum class AcceptType(val accept: String) { @JsonProperty("application/json") JSON("application/json"), + @JsonProperty("application/ld+json") JSONLD("application/ld+json") } diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/EntityInfo.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/EntityInfo.kt index 93cfead2a..530880e24 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/EntityInfo.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/model/EntityInfo.kt @@ -4,4 +4,4 @@ data class EntityInfo( val id: String?, val idPattern: String?, var type: String -) \ No newline at end of file +) 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 53ad7ef08..addb3d7e0 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 @@ -16,6 +16,7 @@ data class NotificationParams( enum class FormatType(val format: String) { @JsonProperty("keyValues") KEY_VALUES("keyValues"), + @JsonProperty("normalized") NORMALIZED("normalized") } @@ -23,7 +24,8 @@ data class NotificationParams( enum class StatusType(val status: String) { @JsonProperty("ok") OK("ok"), + @JsonProperty("failed") FAILED("failed") } -} \ No newline at end of file +} 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 85a5d9d60..b1f7fa6e8 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 @@ -2,7 +2,7 @@ package com.egm.stellio.subscription.model import com.egm.stellio.shared.util.NgsiLdParsingUtils import org.springframework.data.annotation.Id -import java.util.* +import java.util.UUID data class Subscription( @Id val id: String = "urn:ngsi-ld:Subscription:${UUID.randomUUID()}", @@ -18,10 +18,10 @@ data class Subscription( ) { fun expandTypes(context: List) { this.entities.forEach { - it.type = NgsiLdParsingUtils.expandJsonLdKey(it.type, context) !! + it.type = NgsiLdParsingUtils.expandJsonLdKey(it.type, context)!! } this.notification.attributes = this.notification.attributes.map { - NgsiLdParsingUtils.expandJsonLdKey(it, context) !! + NgsiLdParsingUtils.expandJsonLdKey(it, context)!! } } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/repository/SubscriptionRepository.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/repository/SubscriptionRepository.kt index b5c50efa5..734f89743 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/repository/SubscriptionRepository.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/repository/SubscriptionRepository.kt @@ -1,8 +1,8 @@ package com.egm.stellio.subscription.repository + import com.egm.stellio.subscription.model.Subscription import org.springframework.data.repository.reactive.ReactiveCrudRepository - import org.springframework.stereotype.Repository @Repository -interface SubscriptionRepository : ReactiveCrudRepository \ No newline at end of file +interface SubscriptionRepository : ReactiveCrudRepository diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EntitiesListener.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EntitiesListener.kt index f44bd6391..693e6ef8c 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EntitiesListener.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EntitiesListener.kt @@ -26,11 +26,11 @@ class EntitiesListener( val updatedFragment = parseJsonLdFragment(entityEvent.payload!!) val parsedEntity = parseEntity(it) notificationService.notifyMatchingSubscribers(it, parsedEntity, updatedFragment.keys) - .subscribe { - val succeededNotifications = it.filter { it.third }.size - val failedNotifications = it.filter { !it.third }.size - logger.debug("Notified ${it.size} subscribers (success : $succeededNotifications / failure : $failedNotifications)") - } + .subscribe { + val succeededNotifications = it.filter { it.third }.size + val failedNotifications = it.filter { !it.third }.size + logger.debug("Notified ${it.size} subscribers (success : $succeededNotifications / failure : $failedNotifications)") + } } catch (e: Exception) { logger.error("Received a non-parseable entity : $content", e) } diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EventMessageService.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EventMessageService.kt index b84af07f3..9b6c5996a 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EventMessageService.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/EventMessageService.kt @@ -12,8 +12,16 @@ class EventMessageService( private val resolver: BinderAwareChannelResolver ) { - fun sendMessage(channelName: String, eventType: EventType, entityId: String, entityType: String, payload: String?, updatedEntity: String?): Boolean { - val data = mapOf("operationType" to eventType.name, + fun sendMessage( + channelName: String, + eventType: EventType, + entityId: String, + entityType: String, + payload: String?, + updatedEntity: String? + ): Boolean { + val data = mapOf( + "operationType" to eventType.name, "entityId" to entityId, "entityType" to entityType, "payload" to payload, @@ -26,6 +34,7 @@ class EventMessageService( MessageBuilder.createMessage( serializeObject(data), MessageHeaders(mapOf(MessageHeaders.ID to entityId)) - )) + ) + ) } } 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 6d6ca27b0..1989b6945 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 @@ -4,11 +4,11 @@ import com.egm.stellio.shared.model.EntityEvent import com.egm.stellio.shared.model.EventType import com.egm.stellio.shared.model.ExpandedEntity import com.egm.stellio.shared.model.Notification -import com.egm.stellio.subscription.model.Subscription -import com.egm.stellio.shared.util.NgsiLdParsingUtils.getLocationFromEntity import com.egm.stellio.shared.util.ApiUtils.serializeObject import com.egm.stellio.shared.util.NgsiLdParsingUtils.compactEntities +import com.egm.stellio.shared.util.NgsiLdParsingUtils.getLocationFromEntity import com.egm.stellio.subscription.firebase.FCMService +import com.egm.stellio.subscription.model.Subscription import org.slf4j.LoggerFactory import org.springframework.context.ApplicationEventPublisher import org.springframework.http.HttpStatus @@ -25,24 +25,30 @@ class NotificationService( private val logger = LoggerFactory.getLogger(javaClass) - fun notifyMatchingSubscribers(rawEntity: String, expandedEntity: ExpandedEntity, updatedAttributes: Set): - Mono>> { + fun notifyMatchingSubscribers( + rawEntity: String, + expandedEntity: ExpandedEntity, + updatedAttributes: Set + ): Mono>> { val id = expandedEntity.id val type = expandedEntity.type return subscriptionService.getMatchingSubscriptions(id, type, updatedAttributes.joinToString(separator = ",")) - .filter { - subscriptionService.isMatchingQuery(it.q, rawEntity) - } - .filterWhen { - subscriptionService.isMatchingGeoQuery(it.id, getLocationFromEntity(expandedEntity)) - } - .flatMap { - callSubscriber(it, listOf(expandedEntity)) - } - .collectList() + .filter { + subscriptionService.isMatchingQuery(it.q, rawEntity) + } + .filterWhen { + subscriptionService.isMatchingGeoQuery(it.id, getLocationFromEntity(expandedEntity)) + } + .flatMap { + callSubscriber(it, listOf(expandedEntity)) + } + .collectList() } - fun callSubscriber(subscription: Subscription, entities: List): Mono> { + fun callSubscriber( + subscription: Subscription, + entities: List + ): Mono> { val notification = Notification(subscriptionId = subscription.id, data = compactEntities(entities)) if (subscription.notification.endpoint.uri.toString() == "embedded-firebase") { @@ -50,7 +56,8 @@ class NotificationService( val fcmDeviceToken = subscription.notification.endpoint.getInfoValue("deviceToken") return callFCMSubscriber(entityId, subscription, notification, fcmDeviceToken) } else { - var request = WebClient.create(subscription.notification.endpoint.uri.toString()).post() as WebClient.RequestBodySpec + var request = + WebClient.create(subscription.notification.endpoint.uri.toString()).post() as WebClient.RequestBodySpec subscription.notification.endpoint.info?.forEach { request = request.header(it.key, it.value) } @@ -64,13 +71,24 @@ class NotificationService( subscriptionService.updateSubscriptionNotification(it.first, it.second, it.third).subscribe() } .doOnNext { - val notificationEvent = EntityEvent(EventType.CREATE, it.second.id.toString(), it.second.type, serializeObject(it.second), null) + val notificationEvent = EntityEvent( + EventType.CREATE, + it.second.id.toString(), + it.second.type, + serializeObject(it.second), + null + ) applicationEventPublisher.publishEvent(notificationEvent) } } } - fun callFCMSubscriber(entityId: String, subscription: Subscription, notification: Notification, fcmDeviceToken: String?): Mono> { + fun callFCMSubscriber( + entityId: String, + subscription: Subscription, + notification: Notification, + fcmDeviceToken: String? + ): Mono> { if (fcmDeviceToken == null) { return subscriptionService.updateSubscriptionNotification(subscription, notification, false) .map { @@ -80,14 +98,25 @@ class NotificationService( val response = fcmService.sendMessage( mapOf("title" to subscription.name, "body" to subscription.description), - mapOf("id_alert" to notification.id.toString(), "id_subscription" to subscription.id, "timestamp" to notification.notifiedAt.toString(), "id_beehive" to entityId), + mapOf( + "id_alert" to notification.id.toString(), + "id_subscription" to subscription.id, + "timestamp" to notification.notifiedAt.toString(), + "id_beehive" to entityId + ), fcmDeviceToken ) val success = response != null return subscriptionService.updateSubscriptionNotification(subscription, notification, success) .doOnNext { - val notificationEvent = EntityEvent(EventType.CREATE, notification.id.toString(), notification.type, serializeObject(notification), null) + val notificationEvent = EntityEvent( + EventType.CREATE, + notification.id.toString(), + notification.type, + serializeObject(notification), + null + ) applicationEventPublisher.publishEvent(notificationEvent) } .map { diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationsEventsListener.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationsEventsListener.kt index 3f8dc6a72..6341c8f9d 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationsEventsListener.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/NotificationsEventsListener.kt @@ -20,7 +20,14 @@ class NotificationsEventsListener( fun handleNotificationEvent(notificationEvent: EntityEvent) { val result = when (notificationEvent.operationType) { - EventType.CREATE -> eventMessageService.sendMessage(channelName, EventType.CREATE, notificationEvent.entityId, notificationEvent.entityType, notificationEvent.payload, null) + EventType.CREATE -> eventMessageService.sendMessage( + channelName, + EventType.CREATE, + notificationEvent.entityId, + notificationEvent.entityType, + notificationEvent.payload, + null + ) else -> { logger.warn("${notificationEvent.operationType} operation not supported") return 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 ce020efe9..09064fd60 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 @@ -4,22 +4,26 @@ import com.egm.stellio.shared.model.BadRequestDataException import com.egm.stellio.shared.model.EntityEvent import com.egm.stellio.shared.model.EventType import com.egm.stellio.shared.model.Notification -import com.egm.stellio.shared.util.ApiUtils.serializeObject import com.egm.stellio.shared.util.ApiUtils.addContextToParsedObject -import com.egm.stellio.subscription.model.* +import com.egm.stellio.shared.util.ApiUtils.serializeObject import com.egm.stellio.shared.util.NgsiLdParsingUtils -import com.egm.stellio.subscription.utils.ParsingUtils.toSqlColumnName -import com.egm.stellio.subscription.utils.ParsingUtils.toSqlValue -import com.egm.stellio.subscription.utils.QueryUtils.createGeoQueryStatement +import com.egm.stellio.subscription.model.Endpoint +import com.egm.stellio.subscription.model.EntityInfo +import com.egm.stellio.subscription.model.GeoQuery +import com.egm.stellio.subscription.model.NotificationParams +import com.egm.stellio.subscription.model.Subscription import com.egm.stellio.subscription.repository.SubscriptionRepository -import com.egm.stellio.subscription.utils.* import com.egm.stellio.subscription.utils.ParsingUtils.endpointInfoMapToString import com.egm.stellio.subscription.utils.ParsingUtils.endpointInfoToString import com.egm.stellio.subscription.utils.ParsingUtils.parseEndpointInfo import com.egm.stellio.subscription.utils.ParsingUtils.parseEntityInfo +import com.egm.stellio.subscription.utils.ParsingUtils.toSqlColumnName +import com.egm.stellio.subscription.utils.ParsingUtils.toSqlValue +import com.egm.stellio.subscription.utils.QueryUtils +import com.egm.stellio.subscription.utils.QueryUtils.createGeoQueryStatement import com.jayway.jsonpath.JsonPath.read -import io.r2dbc.spi.Row import io.r2dbc.postgresql.codec.Json +import io.r2dbc.spi.Row import org.slf4j.LoggerFactory import org.springframework.context.ApplicationEventPublisher import org.springframework.data.r2dbc.core.DatabaseClient @@ -78,7 +82,13 @@ class SubscriptionService( } .collectList() .doOnSuccess { - val subscriptionEvent = EntityEvent(EventType.CREATE, subscription.id, subscription.type, serializeObject(subscription), null) + val subscriptionEvent = EntityEvent( + EventType.CREATE, + subscription.id, + subscription.type, + serializeObject(subscription), + null + ) applicationEventPublisher.publishEvent(subscriptionEvent) } .map { @@ -125,12 +135,12 @@ class SubscriptionService( """.trimIndent() return databaseClient.execute(selectStatement) - .bind("id", id) - .map(rowToSubscription) - .all() - .reduce { t: Subscription, u: Subscription -> - t.copy(entities = t.entities.plus(u.entities)) - } + .bind("id", id) + .map(rowToSubscription) + .all() + .reduce { t: Subscription, u: Subscription -> + t.copy(entities = t.entities.plus(u.entities)) + } } fun isCreatorOf(subscriptionId: String, sub: String): Mono { @@ -172,15 +182,18 @@ class SubscriptionService( val value = it.value.toSqlValue(it.key) val updateStatement = Update.update(columnName, value) - updates.add(databaseClient.update() - .table("subscription") - .using(updateStatement) - .matching(Criteria.where("id").`is`(subscriptionId)) - .fetch() - .rowsUpdated() - .doOnError { e -> - throw BadRequestDataException(e.message ?: "Could not update attribute ${it.key}") - } + updates.add( + databaseClient.update() + .table("subscription") + .using(updateStatement) + .matching(Criteria.where("id").`is`(subscriptionId)) + .fetch() + .rowsUpdated() + .doOnError { e -> + throw BadRequestDataException( + e.message ?: "Could not update attribute ${it.key}" + ) + } ) } } @@ -195,7 +208,13 @@ class SubscriptionService( getById(subscriptionId) } .doOnSuccess { - val subscriptionEvent = EntityEvent(EventType.UPDATE, subscriptionId, it.t2.type, serializeObject(addContextToParsedObject(subscriptionUpdateInput, contexts)), serializeObject(it.t2)) + val subscriptionEvent = EntityEvent( + EventType.UPDATE, + subscriptionId, + it.t2.type, + serializeObject(addContextToParsedObject(subscriptionUpdateInput, contexts)), + serializeObject(it.t2) + ) applicationEventPublisher.publishEvent(subscriptionEvent) } .map { it.t1.size } @@ -254,25 +273,34 @@ class SubscriptionService( } } - private fun extractParamsFromNotificationAttribute(attribute: Map.Entry, contexts: List?): List> { + private fun extractParamsFromNotificationAttribute( + attribute: Map.Entry, + contexts: List? + ): List> { return when (attribute.key) { "attributes" -> { var valueList = attribute.value as List valueList = valueList.map { - NgsiLdParsingUtils.expandJsonLdKey(it, contexts!!) !! + NgsiLdParsingUtils.expandJsonLdKey(it, contexts!!)!! } listOf(Pair("notif_attributes", valueList.joinToString(separator = ","))) } "format" -> { - val format = if (attribute.value == "keyValues") NotificationParams.FormatType.KEY_VALUES.name else NotificationParams.FormatType.NORMALIZED.name + val format = + if (attribute.value == "keyValues") NotificationParams.FormatType.KEY_VALUES.name else NotificationParams.FormatType.NORMALIZED.name listOf(Pair("notif_format", format)) } "endpoint" -> { val endpoint = attribute.value as Map - val accept = if (endpoint["accept"] == "application/json") Endpoint.AcceptType.JSON.name else Endpoint.AcceptType.JSONLD.name + val accept = + if (endpoint["accept"] == "application/json") Endpoint.AcceptType.JSON.name else Endpoint.AcceptType.JSONLD.name val endpointInfo = endpoint["info"] as List>? - listOf(Pair("endpoint_uri", endpoint["uri"]), Pair("endpoint_accept", accept), Pair("endpoint_info", Json.of(endpointInfoMapToString(endpointInfo)))) + listOf( + Pair("endpoint_uri", endpoint["uri"]), + Pair("endpoint_accept", accept), + Pair("endpoint_info", Json.of(endpointInfoMapToString(endpointInfo))) + ) } else -> { throw BadRequestDataException("Could not update attribute ${attribute.key}") @@ -295,15 +323,15 @@ class SubscriptionService( """.trimIndent() return databaseClient.execute(deleteStatement) - .bind("id", subscriptionId) - .fetch() - .rowsUpdated() - .doOnSuccess { - if (it >= 1) { - val subscriptionEvent = EntityEvent(EventType.DELETE, subscriptionId, "Subscription") - applicationEventPublisher.publishEvent(subscriptionEvent) - } + .bind("id", subscriptionId) + .fetch() + .rowsUpdated() + .doOnSuccess { + if (it >= 1) { + val subscriptionEvent = EntityEvent(EventType.DELETE, subscriptionId, "Subscription") + applicationEventPublisher.publishEvent(subscriptionEvent) } + } } fun deleteEntityInfo(subscriptionId: String): Mono = @@ -408,12 +436,12 @@ class SubscriptionService( Mono.just(true) else { getMatchingGeoQuery(subscriptionId) - .map { - createGeoQueryStatement(it, targetGeometry) - }.flatMap { - runGeoQueryStatement(it) - } - .switchIfEmpty(Mono.just(true)) + .map { + createGeoQueryStatement(it, targetGeometry) + }.flatMap { + runGeoQueryStatement(it) + } + .switchIfEmpty(Mono.just(true)) } } @@ -424,19 +452,24 @@ class SubscriptionService( WHERE subscription_id = :sub_id """.trimIndent() return databaseClient.execute(selectStatement) - .bind("sub_id", subscriptionId) - .map(rowToGeoQuery) - .first() + .bind("sub_id", subscriptionId) + .map(rowToGeoQuery) + .first() } fun runGeoQueryStatement(geoQueryStatement: String): Mono { return databaseClient.execute(geoQueryStatement.trimIndent()) - .map(matchesGeoQuery) - .first() + .map(matchesGeoQuery) + .first() } - fun updateSubscriptionNotification(subscription: Subscription, notification: Notification, success: Boolean): Mono { - val subscriptionStatus = if (success) NotificationParams.StatusType.OK.name else NotificationParams.StatusType.FAILED.name + fun updateSubscriptionNotification( + subscription: Subscription, + notification: Notification, + success: Boolean + ): Mono { + val subscriptionStatus = + if (success) NotificationParams.StatusType.OK.name else NotificationParams.StatusType.FAILED.name val lastStatusName = if (success) "last_success" else "last_failure" val updateStatement = Update.update("status", subscriptionStatus) .set("times_sent", subscription.notification.timesSent + 1) @@ -453,33 +486,36 @@ class SubscriptionService( private var rowToSubscription: ((Row) -> Subscription) = { row -> Subscription( - id = row.get("sub_id", String::class.java)!!, - type = row.get("sub_type", String::class.java)!!, - name = row.get("name", String::class.java), - description = row.get("description", String::class.java), - watchedAttributes = row.get("watched_attributes", String::class.java)?.split(","), - q = row.get("q", String::class.java), - entities = setOf(EntityInfo( - id = row.get("entity_id", String::class.java), - idPattern = row.get("id_pattern", String::class.java), - type = row.get("entity_type", String::class.java)!! - )), - geoQ = rowToGeoQuery(row), - notification = NotificationParams( - attributes = row.get("notif_attributes", String::class.java)?.split(",").orEmpty(), - format = NotificationParams.FormatType.valueOf(row.get("notif_format", String::class.java)!!), - endpoint = Endpoint( - uri = URI(row.get("endpoint_uri", String::class.java)!!), - accept = Endpoint.AcceptType.valueOf(row.get("endpoint_accept", String::class.java)!!), - info = parseEndpointInfo(row.get("endpoint_info", String::class.java)) - ), - status = row.get("status", String::class.java)?.let { NotificationParams.StatusType.valueOf(it) }, - timesSent = row.get("times_sent", Integer::class.java)!!.toInt(), - lastNotification = row.get("last_notification", ZonedDateTime::class.java)?.toInstant()?.atZone(ZoneOffset.UTC), - lastFailure = row.get("last_failure", ZonedDateTime::class.java)?.toInstant()?.atZone(ZoneOffset.UTC), - lastSuccess = row.get("last_success", ZonedDateTime::class.java)?.toInstant()?.atZone(ZoneOffset.UTC) + id = row.get("sub_id", String::class.java)!!, + type = row.get("sub_type", String::class.java)!!, + name = row.get("name", String::class.java), + description = row.get("description", String::class.java), + watchedAttributes = row.get("watched_attributes", String::class.java)?.split(","), + q = row.get("q", String::class.java), + entities = setOf( + EntityInfo( + id = row.get("entity_id", String::class.java), + idPattern = row.get("id_pattern", String::class.java), + type = row.get("entity_type", String::class.java)!! + ) + ), + geoQ = rowToGeoQuery(row), + notification = NotificationParams( + attributes = row.get("notif_attributes", String::class.java)?.split(",").orEmpty(), + format = NotificationParams.FormatType.valueOf(row.get("notif_format", String::class.java)!!), + endpoint = Endpoint( + uri = URI(row.get("endpoint_uri", String::class.java)!!), + accept = Endpoint.AcceptType.valueOf(row.get("endpoint_accept", String::class.java)!!), + info = parseEndpointInfo(row.get("endpoint_info", String::class.java)) ), - isActive = row.get("is_active", Object::class.java).toString() == "true" + status = row.get("status", String::class.java)?.let { NotificationParams.StatusType.valueOf(it) }, + timesSent = row.get("times_sent", Integer::class.java)!!.toInt(), + lastNotification = row.get("last_notification", ZonedDateTime::class.java)?.toInstant() + ?.atZone(ZoneOffset.UTC), + lastFailure = row.get("last_failure", ZonedDateTime::class.java)?.toInstant()?.atZone(ZoneOffset.UTC), + lastSuccess = row.get("last_success", ZonedDateTime::class.java)?.toInstant()?.atZone(ZoneOffset.UTC) + ), + isActive = row.get("is_active", Object::class.java).toString() == "true" ) } @@ -511,9 +547,9 @@ class SubscriptionService( private var rowToGeoQuery: ((Row) -> GeoQuery?) = { row -> if (row.get("georel", String::class.java) != null) GeoQuery( - georel = row.get("georel", String::class.java)!!, - geometry = GeoQuery.GeometryType.valueOf(row.get("geometry", String::class.java)!!), - coordinates = row.get("coordinates", String::class.java)!! + georel = row.get("georel", String::class.java)!!, + geometry = GeoQuery.GeometryType.valueOf(row.get("geometry", String::class.java)!!), + coordinates = row.get("coordinates", String::class.java)!! ) else null diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionsEventsListener.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionsEventsListener.kt index 5a9bf1e39..9b6aab763 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionsEventsListener.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/service/SubscriptionsEventsListener.kt @@ -20,9 +20,30 @@ class SubscriptionsEventsListener( fun handleSubscriptionEvent(subscriptionEvent: EntityEvent) { val result = when (subscriptionEvent.operationType) { - EventType.CREATE -> eventMessageService.sendMessage(channelName, EventType.CREATE, subscriptionEvent.entityId, subscriptionEvent.entityType, subscriptionEvent.payload, null) - EventType.UPDATE -> eventMessageService.sendMessage(channelName, EventType.UPDATE, subscriptionEvent.entityId, subscriptionEvent.entityType, subscriptionEvent.payload, subscriptionEvent.updatedEntity) - EventType.DELETE -> eventMessageService.sendMessage(channelName, EventType.DELETE, subscriptionEvent.entityId, subscriptionEvent.entityType, null, null) + EventType.CREATE -> eventMessageService.sendMessage( + channelName, + EventType.CREATE, + subscriptionEvent.entityId, + subscriptionEvent.entityType, + subscriptionEvent.payload, + null + ) + EventType.UPDATE -> eventMessageService.sendMessage( + channelName, + EventType.UPDATE, + subscriptionEvent.entityId, + subscriptionEvent.entityType, + subscriptionEvent.payload, + subscriptionEvent.updatedEntity + ) + EventType.DELETE -> eventMessageService.sendMessage( + channelName, + EventType.DELETE, + subscriptionEvent.entityId, + subscriptionEvent.entityType, + null, + null + ) else -> false } 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 bb55baa71..0ddb851e2 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 @@ -81,4 +81,4 @@ object ParsingUtils { } else -> this } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/QueryUtils.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/QueryUtils.kt index 4cade085e..0417927a4 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/QueryUtils.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/QueryUtils.kt @@ -1,10 +1,10 @@ package com.egm.stellio.subscription.utils -import com.egm.stellio.subscription.model.GeoQuery -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.egm.stellio.shared.util.NgsiLdParsingUtils import com.egm.stellio.shared.util.NgsiLdParsingUtils.NGSILD_POINT_PROPERTY +import com.egm.stellio.subscription.model.GeoQuery import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.slf4j.LoggerFactory object QueryUtils { @@ -38,8 +38,8 @@ object QueryUtils { query!!.split(';', '|').forEach { predicate -> val predicateParams = predicate.split("==", "!=", ">", ">=", "<", "<=") val attribute = predicateParams[0] - .replace("(", "") - .replace(")", "") + .replace("(", "") + .replace(")", "") val jsonPathAttribute = if ((attribute.isCompoundAttribute())) { when (NgsiLdParsingUtils.getAttributeType(attribute, parsedEntity, "[").toString()) { @@ -66,14 +66,15 @@ object QueryUtils { } fun String.isCompoundAttribute(): Boolean = - this.contains("\\[.*?]".toRegex()) + this.contains("\\[.*?]".toRegex()) fun String.addQuotesToBrackets(): String = - this.replace("[", "['").replace("]", "']") + this.replace("[", "['").replace("]", "']") fun createGeoQueryStatement(geoQuery: GeoQuery?, targetGeometry: Map): String { val refGeometryStatement = createSqlGeometry(geoQuery!!.geometry.name, geoQuery.coordinates) - val targetGeometryStatement = createSqlGeometry(targetGeometry.get("geometry").toString(), targetGeometry.get("coordinates").toString()) + val targetGeometryStatement = + createSqlGeometry(targetGeometry.get("geometry").toString(), targetGeometry.get("coordinates").toString()) val georelParams = extractGeorelParams(geoQuery.georel) if (georelParams.first == DISTANCE_QUERY_CLAUSE) @@ -109,7 +110,10 @@ object QueryUtils { else coordinates.append(initialCoordinates) - return mapper.readValue(coordinates.toString(), mapper.typeFactory.constructCollectionType(List::class.java, Any::class.java)) + return mapper.readValue( + coordinates.toString(), + mapper.typeFactory.constructCollectionType(List::class.java, Any::class.java) + ) } fun extractGeorelParams(georel: String): Triple { @@ -121,4 +125,4 @@ object QueryUtils { } return Triple(georel, null, null) } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/SubscriptionBootstrapper.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/SubscriptionBootstrapper.kt index f0496cd4a..303e5cd6b 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/SubscriptionBootstrapper.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/utils/SubscriptionBootstrapper.kt @@ -1,6 +1,10 @@ package com.egm.stellio.subscription.utils -import com.egm.stellio.subscription.model.* +import com.egm.stellio.subscription.model.Endpoint +import com.egm.stellio.subscription.model.EntityInfo +import com.egm.stellio.subscription.model.GeoQuery +import com.egm.stellio.subscription.model.NotificationParams +import com.egm.stellio.subscription.model.Subscription import com.egm.stellio.subscription.service.SubscriptionService import org.slf4j.LoggerFactory import org.springframework.boot.CommandLineRunner @@ -19,11 +23,20 @@ class SubscriptionBootstrapper( override fun run(vararg args: String) { - val subscription = Subscription(name = "My Subscription", + val subscription = Subscription( + name = "My Subscription", entities = setOf( EntityInfo(id = null, idPattern = null, type = "https://ontology.eglobalmark.com/aquac#FeedingService"), - EntityInfo(id = "urn:ngsi-ld:FeedingService:018z59", idPattern = null, type = "https://ontology.eglobalmark.com/aquac#FeedingService"), - EntityInfo(id = null, idPattern = "urn:ngsi-ld:FeedingService:018*", type = "https://ontology.eglobalmark.com/aquac#FeedingService") + EntityInfo( + id = "urn:ngsi-ld:FeedingService:018z59", + idPattern = null, + type = "https://ontology.eglobalmark.com/aquac#FeedingService" + ), + EntityInfo( + id = null, + idPattern = "urn:ngsi-ld:FeedingService:018*", + type = "https://ontology.eglobalmark.com/aquac#FeedingService" + ) ), geoQ = GeoQuery( georel = "within", @@ -41,7 +54,8 @@ class SubscriptionBootstrapper( lastNotification = null, lastFailure = null, lastSuccess = null - )) + ) + ) subscriptionService.create(subscription, "subscription-bootstrapper") .thenEmpty { @@ -49,4 +63,4 @@ class SubscriptionBootstrapper( } .subscribe() } -} \ No newline at end of file +} diff --git a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt index 30726aa03..e664cfa6c 100644 --- a/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt +++ b/subscription-service/src/main/kotlin/com/egm/stellio/subscription/web/SubscriptionHandler.kt @@ -1,25 +1,33 @@ package com.egm.stellio.subscription.web -import com.egm.stellio.shared.model.AlreadyExistsException -import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.model.AccessDeniedException +import com.egm.stellio.shared.model.AlreadyExistsException import com.egm.stellio.shared.model.BadRequestDataResponse +import com.egm.stellio.shared.model.ResourceNotFoundException import com.egm.stellio.shared.util.ApiUtils.serializeObject -import com.egm.stellio.shared.util.NgsiLdParsingUtils -import com.egm.stellio.subscription.utils.ParsingUtils.parseSubscription -import com.egm.stellio.subscription.utils.ParsingUtils.parseSubscriptionUpdate -import com.egm.stellio.subscription.service.SubscriptionService -import com.egm.stellio.shared.util.PagingUtils.getSubscriptionsPagingLinks -import com.egm.stellio.shared.util.PagingUtils.SUBSCRIPTION_QUERY_PAGING_LIMIT import com.egm.stellio.shared.util.JSON_LD_CONTENT_TYPE import com.egm.stellio.shared.util.JSON_MERGE_PATCH_CONTENT_TYPE +import com.egm.stellio.shared.util.NgsiLdParsingUtils +import com.egm.stellio.shared.util.PagingUtils.SUBSCRIPTION_QUERY_PAGING_LIMIT +import com.egm.stellio.shared.util.PagingUtils.getSubscriptionsPagingLinks import com.egm.stellio.shared.web.extractJwT import com.egm.stellio.subscription.model.Subscription +import com.egm.stellio.subscription.service.SubscriptionService +import com.egm.stellio.subscription.utils.ParsingUtils.parseSubscription +import com.egm.stellio.subscription.utils.ParsingUtils.parseSubscriptionUpdate import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.DeleteMapping +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController import reactor.core.publisher.Mono import reactor.kotlin.core.publisher.toMono import java.net.URI @@ -47,10 +55,12 @@ class SubscriptionHandler( } .zipWith(extractJwT()) .flatMap { subscriptionAndSubject -> - subscriptionService.create(subscriptionAndSubject.t1, subscriptionAndSubject.t2.subject).map { subscriptionAndSubject.t1 } + subscriptionService.create(subscriptionAndSubject.t1, subscriptionAndSubject.t2.subject) + .map { subscriptionAndSubject.t1 } } .map { - ResponseEntity.status(HttpStatus.CREATED).location(URI("/ngsi-ld/v1/subscriptions/${it.id}")).build() + ResponseEntity.status(HttpStatus.CREATED).location(URI("/ngsi-ld/v1/subscriptions/${it.id}")) + .build() } } @@ -66,7 +76,6 @@ class SubscriptionHandler( ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON) .body(BadRequestDataResponse("Page number and Limit must be greater than zero")) .toMono() - else extractJwT() .flatMap { subscriptionService.getSubscriptionsCount(it.subject).flatMap { count -> @@ -74,9 +83,10 @@ class SubscriptionHandler( } } .flatMap { subscriptionsCountAndSubject -> - subscriptionService.getSubscriptions(limit, (page - 1) * limit, subscriptionsCountAndSubject.second).collectList().flatMap { - Mono.just(Pair(subscriptionsCountAndSubject.first, it)) - } + subscriptionService.getSubscriptions(limit, (page - 1) * limit, subscriptionsCountAndSubject.second) + .collectList().flatMap { + Mono.just(Pair(subscriptionsCountAndSubject.first, it)) + } } .map { val prevLink = getSubscriptionsPagingLinks(it.first, page, limit).first @@ -85,7 +95,8 @@ class SubscriptionHandler( } .map { if (it.second != null && it.third != null) - ResponseEntity.status(HttpStatus.OK).header("Link", it.second).header("Link", it.third).body(it.first) + ResponseEntity.status(HttpStatus.OK).header("Link", it.second).header("Link", it.third) + .body(it.first) else if (it.second != null) ResponseEntity.status(HttpStatus.OK).header("Link", it.second).body(it.first) else if (it.third != null) @@ -118,7 +129,10 @@ class SubscriptionHandler( /** * Implements 6.11.3.2 - Update Subscription */ - @PatchMapping("/{subscriptionId}", consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE]) + @PatchMapping( + "/{subscriptionId}", + consumes = [MediaType.APPLICATION_JSON_VALUE, JSON_LD_CONTENT_TYPE, JSON_MERGE_PATCH_CONTENT_TYPE] + ) fun update(@PathVariable subscriptionId: String, @RequestBody body: Mono): Mono> { return checkSubscriptionExists(subscriptionId) .flatMap { diff --git a/subscription-service/src/main/resources/db/migration/V0_10__add_watched_attributes_to_subscription_table.sql b/subscription-service/src/main/resources/db/migration/V0_10__add_watched_attributes_to_subscription_table.sql index b66c8d7f6..abab7bf09 100644 --- a/subscription-service/src/main/resources/db/migration/V0_10__add_watched_attributes_to_subscription_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_10__add_watched_attributes_to_subscription_table.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ADD watched_attributes VARCHAR(1024); \ No newline at end of file +ADD watched_attributes VARCHAR(1024); diff --git a/subscription-service/src/main/resources/db/migration/V0_1__init_tables.sql b/subscription-service/src/main/resources/db/migration/V0_1__init_tables.sql index 66156b83a..6018545d2 100644 --- a/subscription-service/src/main/resources/db/migration/V0_1__init_tables.sql +++ b/subscription-service/src/main/resources/db/migration/V0_1__init_tables.sql @@ -19,4 +19,4 @@ CREATE TABLE entity_info( id_pattern VARCHAR(255), type VARCHAR(64), subscription_id VARCHAR(64) -); \ No newline at end of file +); diff --git a/subscription-service/src/main/resources/db/migration/V0_2__add_geometry_query_table.sql b/subscription-service/src/main/resources/db/migration/V0_2__add_geometry_query_table.sql index 5d0ad2cc7..b39b93eb0 100644 --- a/subscription-service/src/main/resources/db/migration/V0_2__add_geometry_query_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_2__add_geometry_query_table.sql @@ -4,4 +4,4 @@ CREATE TABLE geometry_query( coordinates VARCHAR(255) NOT NULL, geoproperty VARCHAR(64), subscription_id VARCHAR(64) NOT NULL -); \ No newline at end of file +); diff --git a/subscription-service/src/main/resources/db/migration/V0_3__add_foreign_key_constraint_for_subscription_id.sql b/subscription-service/src/main/resources/db/migration/V0_3__add_foreign_key_constraint_for_subscription_id.sql index f1796dc8b..9a6523c10 100644 --- a/subscription-service/src/main/resources/db/migration/V0_3__add_foreign_key_constraint_for_subscription_id.sql +++ b/subscription-service/src/main/resources/db/migration/V0_3__add_foreign_key_constraint_for_subscription_id.sql @@ -6,4 +6,4 @@ on delete cascade; ALTER TABLE geometry_query ADD CONSTRAINT FK_SubscriptionId FOREIGN KEY (subscription_id) REFERENCES subscription(id) -on delete cascade; \ No newline at end of file +on delete cascade; diff --git a/subscription-service/src/main/resources/db/migration/V0_4__add_query_to_subscription_table.sql b/subscription-service/src/main/resources/db/migration/V0_4__add_query_to_subscription_table.sql index 6e72c64c5..da54e8d8f 100644 --- a/subscription-service/src/main/resources/db/migration/V0_4__add_query_to_subscription_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_4__add_query_to_subscription_table.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ADD q VARCHAR(1024); \ No newline at end of file +ADD q VARCHAR(1024); diff --git a/subscription-service/src/main/resources/db/migration/V0_5__add_endpoint_info_to_subscription_table.sql b/subscription-service/src/main/resources/db/migration/V0_5__add_endpoint_info_to_subscription_table.sql index 9c7e928e0..6a38e8824 100644 --- a/subscription-service/src/main/resources/db/migration/V0_5__add_endpoint_info_to_subscription_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_5__add_endpoint_info_to_subscription_table.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ADD endpoint_info jsonb; \ No newline at end of file +ADD endpoint_info jsonb; diff --git a/subscription-service/src/main/resources/db/migration/V0_6__add_isActive_to_subscription_table.sql b/subscription-service/src/main/resources/db/migration/V0_6__add_isActive_to_subscription_table.sql index 14983f1c1..e2f32d3d9 100644 --- a/subscription-service/src/main/resources/db/migration/V0_6__add_isActive_to_subscription_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_6__add_isActive_to_subscription_table.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ADD is_active boolean DEFAULT true; \ No newline at end of file +ADD is_active boolean DEFAULT true; diff --git a/subscription-service/src/main/resources/db/migration/V0_7__add_request_subject_to_subscription_table.sql b/subscription-service/src/main/resources/db/migration/V0_7__add_request_subject_to_subscription_table.sql index 6fcac426e..e411ed8fd 100644 --- a/subscription-service/src/main/resources/db/migration/V0_7__add_request_subject_to_subscription_table.sql +++ b/subscription-service/src/main/resources/db/migration/V0_7__add_request_subject_to_subscription_table.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ADD sub VARCHAR(256); \ No newline at end of file +ADD sub VARCHAR(256); diff --git a/subscription-service/src/main/resources/db/migration/V0_8__increase_subscription_id_length.sql b/subscription-service/src/main/resources/db/migration/V0_8__increase_subscription_id_length.sql index ea660adb9..4cda46686 100644 --- a/subscription-service/src/main/resources/db/migration/V0_8__increase_subscription_id_length.sql +++ b/subscription-service/src/main/resources/db/migration/V0_8__increase_subscription_id_length.sql @@ -1,2 +1,2 @@ ALTER TABLE subscription -ALTER COLUMN id TYPE VARCHAR(255); \ No newline at end of file +ALTER COLUMN id TYPE VARCHAR(255); diff --git a/subscription-service/src/main/resources/db/migration/V0_9__increase_subscription_id_foreign_key_length.sql b/subscription-service/src/main/resources/db/migration/V0_9__increase_subscription_id_foreign_key_length.sql index 90488a220..39f5f812e 100644 --- a/subscription-service/src/main/resources/db/migration/V0_9__increase_subscription_id_foreign_key_length.sql +++ b/subscription-service/src/main/resources/db/migration/V0_9__increase_subscription_id_foreign_key_length.sql @@ -1,2 +1,2 @@ ALTER TABLE entity_info ALTER COLUMN subscription_id TYPE VARCHAR(255); -ALTER TABLE geometry_query ALTER COLUMN subscription_id TYPE VARCHAR(255); \ No newline at end of file +ALTER TABLE geometry_query ALTER COLUMN subscription_id TYPE VARCHAR(255); diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/config/MockSecurityConfig.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/config/MockSecurityConfig.kt index 02b40e94e..49451667e 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/config/MockSecurityConfig.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/config/MockSecurityConfig.kt @@ -21,4 +21,4 @@ class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory - entityEvent.entityType == "Notification" && - entityEvent.operationType == EventType.CREATE && - read(entityEvent.payload!!, "$.subscriptionId") as String == subscription.id && - read(entityEvent.payload!!, "$.data[0].id") as String == "urn:ngsi-ld:Apiary:XYZ01" && - entityEvent.updatedEntity == null - }) } - - verify { subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Apiary:XYZ01", "https://ontology.eglobalmark.com/apic#Apiary", "name") } + verify(timeout = 1000, exactly = 1) { + notificationsEventsListener.handleNotificationEvent(match { entityEvent -> + entityEvent.entityType == "Notification" && + entityEvent.operationType == EventType.CREATE && + read(entityEvent.payload!!, "$.subscriptionId") as String == subscription.id && + read(entityEvent.payload!!, "$.data[0].id") as String == "urn:ngsi-ld:Apiary:XYZ01" && + entityEvent.updatedEntity == null + }) + } + + verify { + subscriptionService.getMatchingSubscriptions( + "urn:ngsi-ld:Apiary:XYZ01", + "https://ontology.eglobalmark.com/apic#Apiary", + "name" + ) + } verify { subscriptionService.isMatchingQuery(subscription.q, any()) } verify { subscriptionService.isMatchingGeoQuery(subscription.id, any()) } verify { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } @@ -141,14 +164,19 @@ class NotificationServiceTests { val subscription1 = gimmeRawSubscription() val subscription2 = gimmeRawSubscription() - every { subscriptionService.getMatchingSubscriptions(any(), any(), any()) } returns Flux.just(subscription1, subscription2) + every { subscriptionService.getMatchingSubscriptions(any(), any(), any()) } returns Flux.just( + subscription1, + subscription2 + ) every { subscriptionService.isMatchingQuery(any(), any()) } answers { true } every { subscriptionService.isMatchingGeoQuery(any(), any()) } answers { Mono.just(true) } every { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } answers { Mono.just(1) } every { notificationsEventsListener.handleNotificationEvent(any()) } just Runs - stubFor(post(urlMatching("/notification")) - .willReturn(ok())) + stubFor( + post(urlMatching("/notification")) + .willReturn(ok()) + ) val notificationResult = notificationService.notifyMatchingSubscribers(rawEntity, parsedEntity, setOf("name")) @@ -159,7 +187,13 @@ class NotificationServiceTests { .expectComplete() .verify() - verify { subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Apiary:XYZ01", "https://ontology.eglobalmark.com/apic#Apiary", "name") } + verify { + subscriptionService.getMatchingSubscriptions( + "urn:ngsi-ld:Apiary:XYZ01", + "https://ontology.eglobalmark.com/apic#Apiary", + "name" + ) + } verify { subscriptionService.isMatchingQuery(subscription1.q, any()) } verify { subscriptionService.isMatchingQuery(subscription2.q, any()) } verify { subscriptionService.isMatchingGeoQuery(subscription1.id, any()) } @@ -174,26 +208,37 @@ class NotificationServiceTests { val subscription1 = gimmeRawSubscription() val subscription2 = gimmeRawSubscription() - every { subscriptionService.getMatchingSubscriptions(any(), any(), any()) } returns Flux.just(subscription1, subscription2) + every { subscriptionService.getMatchingSubscriptions(any(), any(), any()) } returns Flux.just( + subscription1, + subscription2 + ) every { subscriptionService.isMatchingQuery(any(), any()) } answers { true } every { subscriptionService.isMatchingGeoQuery(subscription1.id, any()) } answers { Mono.just(true) } every { subscriptionService.isMatchingGeoQuery(subscription2.id, any()) } answers { Mono.just(false) } every { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } answers { Mono.just(1) } every { notificationsEventsListener.handleNotificationEvent(any()) } just Runs - stubFor(post(urlMatching("/notification")) - .willReturn(ok())) + stubFor( + post(urlMatching("/notification")) + .willReturn(ok()) + ) val notificationResult = notificationService.notifyMatchingSubscribers(rawEntity, parsedEntity, setOf("name")) StepVerifier.create(notificationResult) - .expectNextMatches { - it.size == 1 - } - .expectComplete() - .verify() + .expectNextMatches { + it.size == 1 + } + .expectComplete() + .verify() - verify { subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Apiary:XYZ01", "https://ontology.eglobalmark.com/apic#Apiary", "name") } + verify { + subscriptionService.getMatchingSubscriptions( + "urn:ngsi-ld:Apiary:XYZ01", + "https://ontology.eglobalmark.com/apic#Apiary", + "name" + ) + } verify { subscriptionService.isMatchingQuery(subscription1.q, any()) } verify { subscriptionService.isMatchingQuery(subscription2.q, any()) } verify { subscriptionService.isMatchingGeoQuery(subscription1.id, any()) } @@ -212,15 +257,17 @@ class NotificationServiceTests { every { subscriptionService.updateSubscriptionNotification(any(), any(), any()) } answers { Mono.just(1) } every { notificationsEventsListener.handleNotificationEvent(any()) } just Runs - stubFor(post(urlMatching("/notification")) - .willReturn(ok())) + stubFor( + post(urlMatching("/notification")) + .willReturn(ok()) + ) StepVerifier.create(notificationService.callSubscriber(subscription, listOf(parsedEntity))) .expectNextMatches { it.first.id == subscription.id && - it.second.subscriptionId == subscription.id && - it.second.data.size == 1 && - it.third + it.second.subscriptionId == subscription.id && + it.second.data.size == 1 && + it.third } .expectComplete() .verify() @@ -256,8 +303,8 @@ class NotificationServiceTests { StepVerifier.create(notificationService.callSubscriber(subscription, listOf(parsedEntity))) .expectNextMatches { it.first.id == subscription.id && - it.second.subscriptionId == subscription.id && - it.third + it.second.subscriptionId == subscription.id && + it.third } .expectComplete() .verify() @@ -290,8 +337,8 @@ class NotificationServiceTests { StepVerifier.create(notificationService.callSubscriber(subscription, listOf(parsedEntity))) .expectNextMatches { it.first.id == subscription.id && - it.second.subscriptionId == subscription.id && - !it.third + it.second.subscriptionId == subscription.id && + !it.third } .expectComplete() .verify() 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 690a3d16c..fc275bc7e 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 @@ -3,16 +3,20 @@ package com.egm.stellio.subscription.service import com.egm.stellio.shared.model.EventType import com.egm.stellio.shared.model.Notification import com.egm.stellio.subscription.config.TimescaleBasedTests -import com.egm.stellio.subscription.model.* +import com.egm.stellio.subscription.model.Endpoint +import com.egm.stellio.subscription.model.EndpointInfo +import com.egm.stellio.subscription.model.EntityInfo +import com.egm.stellio.subscription.model.GeoQuery +import com.egm.stellio.subscription.model.NotificationParams import com.egm.stellio.subscription.utils.gimmeRawSubscription +import com.jayway.jsonpath.JsonPath.read import com.ninjasquad.springmockk.MockkBean import com.ninjasquad.springmockk.SpykBean -import com.jayway.jsonpath.JsonPath.read import io.mockk.Runs import io.mockk.every import io.mockk.just import io.mockk.verify -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired @@ -56,7 +60,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { private lateinit var subscription5Id: String private lateinit var subscription6Id: String - private val entity = ClassPathResource("/ngsild/aquac/FeedingService.json").inputStream.readBytes().toString(Charsets.UTF_8) + private val entity = + ClassPathResource("/ngsild/aquac/FeedingService.json").inputStream.readBytes().toString(Charsets.UTF_8) @BeforeAll fun bootstrapSubscriptions() { @@ -143,18 +148,22 @@ class SubscriptionServiceTests : TimescaleBasedTests() { .verify() // TODO this is not totally satisfying but well it's a first check that our inserts are triggered - verify { databaseClient.execute(match { - it.startsWith("INSERT INTO subscription") - }) } + verify { + databaseClient.execute(match { + it.startsWith("INSERT INTO subscription") + }) + } verify(atLeast = 2) { databaseClient.execute("INSERT INTO entity_info (id, id_pattern, type, subscription_id) VALUES (:id, :id_pattern, :type, :subscription_id)") } verify(atLeast = 1) { databaseClient.execute("INSERT INTO geometry_query (georel, geometry, coordinates, subscription_id) VALUES (:georel, :geometry, :coordinates, :subscription_id)") } - verify(timeout = 1000, exactly = 1) { subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> - entityEvent.entityType == "Subscription" && - entityEvent.entityId == subscription.id && - entityEvent.operationType == EventType.CREATE && - entityEvent.payload != null && - entityEvent.updatedEntity == null - }) } + verify(timeout = 1000, exactly = 1) { + subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> + entityEvent.entityType == "Subscription" && + entityEvent.entityId == subscription.id && + entityEvent.operationType == EventType.CREATE && + entityEvent.payload != null && + entityEvent.updatedEntity == null + }) + } } @Test @@ -165,14 +174,18 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 1" && - it.description == "My beautiful subscription" && - it.notification.attributes == listOf("incoming") && - it.notification.format == NotificationParams.FormatType.KEY_VALUES && - it.notification.endpoint == Endpoint(URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD, null) && - it.entities.size == 2 && - it.entities.any { it.type == "Beekeeper" && it.id == null && it.idPattern == "urn:ngsi-ld:Beekeeper:1234*" } && - it.entities.any { it.type == "Beehive" && it.id == null && it.idPattern == null } && - it.geoQ == null + it.description == "My beautiful subscription" && + it.notification.attributes == listOf("incoming") && + it.notification.format == NotificationParams.FormatType.KEY_VALUES && + it.notification.endpoint == Endpoint( + URI("http://localhost:8089/notification"), + Endpoint.AcceptType.JSONLD, + null + ) && + it.entities.size == 2 && + it.entities.any { it.type == "Beekeeper" && it.id == null && it.idPattern == "urn:ngsi-ld:Beekeeper:1234*" } && + it.entities.any { it.type == "Beehive" && it.id == null && it.idPattern == null } && + it.geoQ == null } .verifyComplete() } @@ -185,12 +198,16 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 3" && - it.description == "My beautiful subscription" && - it.q == "speed>50;foodName==dietary fibres" && - it.notification.attributes == listOf("incoming") && - it.notification.format == NotificationParams.FormatType.KEY_VALUES && - it.notification.endpoint == Endpoint(URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD, null) && - it.entities.size == 1 + it.description == "My beautiful subscription" && + it.q == "speed>50;foodName==dietary fibres" && + it.notification.attributes == listOf("incoming") && + it.notification.format == NotificationParams.FormatType.KEY_VALUES && + it.notification.endpoint == Endpoint( + URI("http://localhost:8089/notification"), + Endpoint.AcceptType.JSONLD, + null + ) && + it.entities.size == 1 } .verifyComplete() } @@ -203,16 +220,20 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 2" && - it.description == "My beautiful subscription" && - it.notification.attributes == listOf("incoming") && - it.notification.format == NotificationParams.FormatType.KEY_VALUES && - it.notification.endpoint == Endpoint( + it.description == "My beautiful subscription" && + it.notification.attributes == listOf("incoming") && + it.notification.format == NotificationParams.FormatType.KEY_VALUES && + it.notification.endpoint == Endpoint( URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD, listOf(EndpointInfo("Authorization-token", "Authorization-token-value")) ) && - it.entities.size == 2 && - it.geoQ == GeoQuery(georel = "within", geometry = GeoQuery.GeometryType.Polygon, coordinates = "[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]") + it.entities.size == 2 && + it.geoQ == GeoQuery( + georel = "within", + geometry = GeoQuery.GeometryType.Polygon, + coordinates = "[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]" + ) } .verifyComplete() } @@ -225,16 +246,16 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 2" && - it.description == "My beautiful subscription" && - it.notification.attributes == listOf("incoming") && - it.notification.format == NotificationParams.FormatType.KEY_VALUES && - it.notification.endpoint == Endpoint( + it.description == "My beautiful subscription" && + it.notification.attributes == listOf("incoming") && + it.notification.format == NotificationParams.FormatType.KEY_VALUES && + it.notification.endpoint == Endpoint( URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD, listOf(EndpointInfo("Authorization-token", "Authorization-token-value")) ) && - it.entities.size == 2 && - it.isActive + it.entities.size == 2 && + it.isActive } .verifyComplete() } @@ -247,12 +268,16 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 4" && - it.description == "My beautiful subscription" && - it.notification.attributes == listOf("incoming") && - it.notification.format == NotificationParams.FormatType.KEY_VALUES && - it.notification.endpoint == Endpoint(URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD, null) && - it.entities.size == 1 && - !it.isActive + it.description == "My beautiful subscription" && + it.notification.attributes == listOf("incoming") && + it.notification.format == NotificationParams.FormatType.KEY_VALUES && + it.notification.endpoint == Endpoint( + URI("http://localhost:8089/notification"), + Endpoint.AcceptType.JSONLD, + null + ) && + it.entities.size == 1 && + !it.isActive } .verifyComplete() } @@ -265,9 +290,9 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 4" && - it.description == "My beautiful subscription" && - it.entities.size == 1 && - it.watchedAttributes!! == listOf("incoming", "outgoing") + it.description == "My beautiful subscription" && + it.entities.size == 1 && + it.watchedAttributes!! == listOf("incoming", "outgoing") } .verifyComplete() } @@ -280,9 +305,9 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 2" && - it.description == "My beautiful subscription" && - it.entities.size == 2 && - it.watchedAttributes == null + it.description == "My beautiful subscription" && + it.entities.size == 2 && + it.watchedAttributes == null } .verifyComplete() } @@ -299,14 +324,18 @@ class SubscriptionServiceTests : TimescaleBasedTests() { every { subscriptionsEventsListener.handleSubscriptionEvent(any()) } just Runs subscriptionService.create(subscription, MOCK_USER_SUB).block() - subscriptionService.updateSubscriptionNotification(subscription, Notification(subscriptionId = subscription.id, notifiedAt = notifiedAt, data = emptyList()), true).block() + subscriptionService.updateSubscriptionNotification( + subscription, + Notification(subscriptionId = subscription.id, notifiedAt = notifiedAt, data = emptyList()), + true + ).block() val persistedSubscription = subscriptionService.getById(subscription.id) StepVerifier.create(persistedSubscription) .expectNextMatches { it.notification.lastNotification == notifiedAt && - it.notification.lastSuccess == notifiedAt + it.notification.lastSuccess == notifiedAt } .verifyComplete() } @@ -321,13 +350,15 @@ class SubscriptionServiceTests : TimescaleBasedTests() { val deletionResult = subscriptionService.delete(subscription.id).block() - verify(timeout = 1000, exactly = 1) { subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> - entityEvent.entityType == "Subscription" && - entityEvent.entityId == subscription.id && - entityEvent.operationType == EventType.DELETE && - entityEvent.payload == null && - entityEvent.updatedEntity == null - }) } + verify(timeout = 1000, exactly = 1) { + subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> + entityEvent.entityType == "Subscription" && + entityEvent.entityId == subscription.id && + entityEvent.operationType == EventType.DELETE && + entityEvent.payload == null && + entityEvent.updatedEntity == null + }) + } assertEquals(deletionResult, 1) } @@ -342,7 +373,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should retrieve a subscription matching an idPattern`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beekeeper:12345678", "Beekeeper", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beekeeper:12345678", "Beekeeper", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(2L) @@ -352,7 +384,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should not retrieve a subscription if idPattern does not match`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beekeeper:9876543", "Beekeeper", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beekeeper:9876543", "Beekeeper", "incoming") StepVerifier.create(persistedSubscription) .expectNextMatches { @@ -364,13 +397,17 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should retrieve a subscription matching a type and not one with non matching id`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:ABCD", "Beehive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:ABCD", "Beehive", "incoming") StepVerifier.create(persistedSubscription) .expectNextMatches { it.name == "Subscription 1" && - it.notification.endpoint == Endpoint(URI("http://localhost:8089/notification"), Endpoint.AcceptType.JSONLD) && - it.entities.isEmpty() + it.notification.endpoint == Endpoint( + URI("http://localhost:8089/notification"), + Endpoint.AcceptType.JSONLD + ) && + it.entities.isEmpty() } .verifyComplete() } @@ -378,7 +415,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should retrieve a subscription matching a type and an exact id`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "Beehive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "Beehive", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(2) @@ -388,7 +426,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should retrieve a subscription matching an id`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "Beehive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "Beehive", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(1) @@ -398,7 +437,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should not retrieve a subscription if type does not match`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Sensor:1234567890", "Sensor", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Sensor:1234567890", "Sensor", "incoming") StepVerifier.create(persistedSubscription) .expectComplete() @@ -408,7 +448,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should retrieve an activated subscription matching an id`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:smartDoor:77", "smartDoor", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:smartDoor:77", "smartDoor", "incoming") StepVerifier.create(persistedSubscription) .expectNextMatches { @@ -420,7 +461,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should not retrieve a deactivated subscription matching an id`() { - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:smartDoor:88", "smartDoor", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:smartDoor:88", "smartDoor", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(0) @@ -441,7 +483,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { subscriptionService.create(subscription, MOCK_USER_SUB).block() - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(1) @@ -464,7 +507,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { subscriptionService.create(subscription, MOCK_USER_SUB).block() - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(1) @@ -487,7 +531,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { subscriptionService.create(subscription, MOCK_USER_SUB).block() - val persistedSubscription = subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") + val persistedSubscription = + subscriptionService.getMatchingSubscriptions("urn:ngsi-ld:Beehive:1234567890", "BeeHive", "incoming") StepVerifier.create(persistedSubscription) .expectNextCount(0) @@ -499,10 +544,14 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should update a subscription `() { - val parsedInput = Pair(mapOf("name" to "My Subscription Updated", + val parsedInput = Pair( + mapOf( + "name" to "My Subscription Updated", "description" to "My beautiful subscription has been updated", "q" to "foodQuantity>=150", - "geoQ" to mapOf("georel" to "equals", "geometry" to "Point", "coordinates" to "[100.0, 0.0]")), listOf(apicContext)) + "geoQ" to mapOf("georel" to "equals", "geometry" to "Point", "coordinates" to "[100.0, 0.0]") + ), listOf(apicContext) + ) every { subscriptionsEventsListener.handleSubscriptionEvent(any()) } just Runs @@ -512,35 +561,44 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(updateResult) .expectNextMatches { it.name == "My Subscription Updated" && - it.description == "My beautiful subscription has been updated" && - it.q == "foodQuantity>=150" && - it.geoQ!!.georel == "equals" && - it.geoQ!!.geometry.name == "Point" && - it.geoQ!!.coordinates == "[100.0, 0.0]" + it.description == "My beautiful subscription has been updated" && + it.q == "foodQuantity>=150" && + it.geoQ!!.georel == "equals" && + it.geoQ!!.geometry.name == "Point" && + it.geoQ!!.coordinates == "[100.0, 0.0]" } .verifyComplete() - verify(timeout = 1000, exactly = 1) { subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> - entityEvent.entityType == "Subscription" && - entityEvent.entityId == subscription4Id && - entityEvent.operationType == EventType.UPDATE && - entityEvent.payload != null && - read(entityEvent.updatedEntity, "$.name") as String == "My Subscription Updated" && - read(entityEvent.updatedEntity, "$.description") as String == "My beautiful subscription has been updated" && - read(entityEvent.updatedEntity, "$.q") as String == "foodQuantity>=150" - }) } + verify(timeout = 1000, exactly = 1) { + subscriptionsEventsListener.handleSubscriptionEvent(match { entityEvent -> + entityEvent.entityType == "Subscription" && + entityEvent.entityId == subscription4Id && + entityEvent.operationType == EventType.UPDATE && + entityEvent.payload != null && + read(entityEvent.updatedEntity, "$.name") as String == "My Subscription Updated" && + read( + entityEvent.updatedEntity, + "$.description" + ) as String == "My beautiful subscription has been updated" && + read(entityEvent.updatedEntity, "$.q") as String == "foodQuantity>=150" + }) + } } @Test fun `it should update a subscription notification`() { - val parsedInput = mapOf("attributes" to listOf("outgoing"), + val parsedInput = mapOf( + "attributes" to listOf("outgoing"), "format" to "keyValues", - "endpoint" to mapOf("accept" to "application/ld+json", + "endpoint" to mapOf( + "accept" to "application/ld+json", "uri" to "http://localhost:8080", "info" to listOf( mapOf("key" to "Authorization-token", "value" to "Authorization-token-newValue") - ))) + ) + ) + ) subscriptionService.updateNotification(subscription4Id, parsedInput, listOf(apicContext)).block() val updateResult = subscriptionService.getById(subscription4Id) @@ -548,13 +606,13 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(updateResult) .expectNextMatches { it.notification.attributes == listOf("https://ontology.eglobalmark.com/apic#outgoing") && - it.notification.format.name == "KEY_VALUES" && - it.notification.endpoint.accept.name == "JSONLD" && - it.notification.endpoint.uri.toString() == "http://localhost:8080" && - it.notification.endpoint.info == listOf( + it.notification.format.name == "KEY_VALUES" && + it.notification.endpoint.accept.name == "JSONLD" && + it.notification.endpoint.uri.toString() == "http://localhost:8080" && + it.notification.endpoint.info == listOf( EndpointInfo("Authorization-token", "Authorization-token-newValue") ) && - it.notification.endpoint.info!!.size == 1 + it.notification.endpoint.info!!.size == 1 } .verifyComplete() } @@ -563,10 +621,14 @@ class SubscriptionServiceTests : TimescaleBasedTests() { fun `it should update a subscription entities`() { val parsedInput = listOf( - mapOf("id" to "urn:ngsi-ld:Beehive:123", - "type" to "Beehive"), - mapOf("idPattern" to "urn:ngsi-ld:Beehive:12*", - "type" to "Beehive") + mapOf( + "id" to "urn:ngsi-ld:Beehive:123", + "type" to "Beehive" + ), + mapOf( + "idPattern" to "urn:ngsi-ld:Beehive:12*", + "type" to "Beehive" + ) ) subscriptionService.updateEntities(subscription4Id, parsedInput, listOf(apicContext)).doOnNext { @@ -574,9 +636,21 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(updateResult) .expectNextMatches { - it.entities.contains(EntityInfo(id = "urn:ngsi-ld:Beehive:123", idPattern = null, type = "https://uri.etsi.org/ngsi-ld/default-context/Beehive")) && - it.entities.contains(EntityInfo(id = null, idPattern = "urn:ngsi-ld:Beehive:12*", type = "https://uri.etsi.org/ngsi-ld/default-context/Beehive")) && - it.entities.size == 2 + it.entities.contains( + EntityInfo( + id = "urn:ngsi-ld:Beehive:123", + idPattern = null, + type = "https://uri.etsi.org/ngsi-ld/default-context/Beehive" + ) + ) && + it.entities.contains( + EntityInfo( + id = null, + idPattern = "urn:ngsi-ld:Beehive:12*", + type = "https://uri.etsi.org/ngsi-ld/default-context/Beehive" + ) + ) && + it.entities.size == 2 } .verifyComplete() } @@ -612,7 +686,8 @@ class SubscriptionServiceTests : TimescaleBasedTests() { @Test fun `it should update a subscription watched attributes`() { - val parsedInput = Pair(mapOf("watchedAttributes" to arrayListOf("incoming", "temperature")), listOf(apicContext)) + val parsedInput = + Pair(mapOf("watchedAttributes" to arrayListOf("incoming", "temperature")), listOf(apicContext)) subscriptionService.update(subscription5Id, parsedInput).block() val updateResult = subscriptionService.getById(subscription5Id) @@ -623,6 +698,7 @@ class SubscriptionServiceTests : TimescaleBasedTests() { } .verifyComplete() } + @Test fun `it should update a subscription with a notification result`() { @@ -635,11 +711,11 @@ class SubscriptionServiceTests : TimescaleBasedTests() { StepVerifier.create(updateResult) .expectNextMatches { it.id == subscription1Id && - it.notification.status == NotificationParams.StatusType.OK && - it.notification.timesSent == 1 && - it.notification.lastNotification != null && - it.notification.lastSuccess != null && - it.notification.lastFailure == null + it.notification.status == NotificationParams.StatusType.OK && + it.notification.timesSent == 1 && + it.notification.lastNotification != null && + it.notification.lastSuccess != null && + it.notification.lastFailure == null } .verifyComplete() } diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/FixtureUtils.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/FixtureUtils.kt index 8a21fba08..a01fa21d0 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/FixtureUtils.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/FixtureUtils.kt @@ -1,20 +1,33 @@ package com.egm.stellio.subscription.utils -import com.egm.stellio.subscription.model.* +import com.egm.stellio.subscription.model.Endpoint +import com.egm.stellio.subscription.model.EndpointInfo +import com.egm.stellio.subscription.model.GeoQuery +import com.egm.stellio.subscription.model.NotificationParams +import com.egm.stellio.subscription.model.Subscription import java.net.URI -fun gimmeRawSubscription(withQuery: Boolean = true, withGeoQuery: Boolean = true, withEndpointInfo: Boolean = true, georel: String = "within"): Subscription { +fun gimmeRawSubscription( + withQuery: Boolean = true, + withGeoQuery: Boolean = true, + withEndpointInfo: Boolean = true, + georel: String = "within" +): Subscription { val q = - if (withQuery) - "speed>50;foodName==dietary fibres" - else - null + if (withQuery) + "speed>50;foodName==dietary fibres" + else + null val geoQuery = - if (withGeoQuery) - GeoQuery(georel = georel, geometry = GeoQuery.GeometryType.Polygon, coordinates = "[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]") - else - null + if (withGeoQuery) + GeoQuery( + georel = georel, + geometry = GeoQuery.GeometryType.Polygon, + coordinates = "[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]" + ) + else + null val endpointInfo = if (withEndpointInfo) @@ -22,7 +35,8 @@ fun gimmeRawSubscription(withQuery: Boolean = true, withGeoQuery: Boolean = true else null - return Subscription(name = "My Subscription", + return Subscription( + name = "My Subscription", description = "My beautiful subscription", q = q, entities = emptySet(), @@ -41,4 +55,4 @@ fun gimmeRawSubscription(withQuery: Boolean = true, withGeoQuery: Boolean = true lastSuccess = null ) ) -} \ No newline at end of file +} 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 9103623ff..b768da2d8 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 @@ -1,12 +1,12 @@ package com.egm.stellio.subscription.utils import com.egm.stellio.subscription.model.EndpointInfo -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ ParsingUtils::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ParsingUtils::class]) @ActiveProfiles("test") class ParsingUtilsTests { @@ -27,4 +27,4 @@ class ParsingUtilsTests { assertEquals(info, null) } -} \ No newline at end of file +} diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/QueryUtilsTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/QueryUtilsTests.kt index 96bbe8c63..0ecfff2ec 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/QueryUtilsTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/utils/QueryUtilsTests.kt @@ -1,11 +1,11 @@ package com.egm.stellio.subscription.utils -import junit.framework.TestCase.* +import junit.framework.TestCase.assertEquals import org.junit.jupiter.api.Test import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [ QueryUtils::class ]) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = [QueryUtils::class]) @ActiveProfiles("test") class QueryUtilsTests { @@ -17,9 +17,11 @@ class QueryUtilsTests { val queryStatement = QueryUtils.createGeoQueryStatement(geoQuery, targetGeometry) - assertEquals(queryStatement, - "SELECT ST_disjoint(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + - "ST_GeomFromText('Point(24.30623 60.07966)')) as geoquery_result") + assertEquals( + queryStatement, + "SELECT ST_disjoint(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + + "ST_GeomFromText('Point(24.30623 60.07966)')) as geoquery_result" + ) } @Test @@ -30,9 +32,11 @@ class QueryUtilsTests { val queryStatement = QueryUtils.createGeoQueryStatement(geoQuery, targetGeometry) - assertEquals(queryStatement, - "SELECT ST_distance(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + - "ST_GeomFromText('Point(60.30623 30.07966)')) <= 2000 as geoquery_result") + assertEquals( + queryStatement, + "SELECT ST_distance(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + + "ST_GeomFromText('Point(60.30623 30.07966)')) <= 2000 as geoquery_result" + ) } @Test @@ -43,9 +47,11 @@ class QueryUtilsTests { val queryStatement = QueryUtils.createGeoQueryStatement(geoQuery, targetGeometry) - assertEquals(queryStatement, - "SELECT ST_distance(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + - "ST_GeomFromText('Point(60.30623 30.07966)')) >= 15 as geoquery_result") + assertEquals( + queryStatement, + "SELECT ST_distance(ST_GeomFromText('Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))'), " + + "ST_GeomFromText('Point(60.30623 30.07966)')) >= 15 as geoquery_result" + ) } @Test @@ -55,8 +61,10 @@ class QueryUtilsTests { val queryStatement = QueryUtils.createSqlGeometry(geoQuery!!.geometry.name, geoQuery.coordinates) - assertEquals(queryStatement, - "Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))") + assertEquals( + queryStatement, + "Polygon((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))" + ) } @Test @@ -64,10 +72,15 @@ class QueryUtilsTests { val targetGeometry = mapOf("geometry" to "Point", "coordinates" to "[60.30623, 30.07966]") - val queryStatement = QueryUtils.createSqlGeometry(targetGeometry["geometry"].toString(), targetGeometry["coordinates"].toString()) + val queryStatement = QueryUtils.createSqlGeometry( + targetGeometry["geometry"].toString(), + targetGeometry["coordinates"].toString() + ) - assertEquals(queryStatement, - "Point(60.30623 30.07966)") + assertEquals( + queryStatement, + "Point(60.30623 30.07966)" + ) } @Test @@ -77,8 +90,10 @@ class QueryUtilsTests { val parsedCoordinates = QueryUtils.parseCoordinates(geoQuery!!.geometry.name, geoQuery.coordinates) - assertEquals(parsedCoordinates, - listOf(listOf(100.0, 0.0), listOf(101.0, 0.0), listOf(101.0, 1.0), listOf(100.0, 1.0), listOf(100.0, 0.0))) + assertEquals( + parsedCoordinates, + listOf(listOf(100.0, 0.0), listOf(101.0, 0.0), listOf(101.0, 1.0), listOf(100.0, 1.0), listOf(100.0, 0.0)) + ) } @Test @@ -86,9 +101,12 @@ class QueryUtilsTests { val targetGeometry = mapOf("geometry" to "Point", "coordinates" to "[90.30623, 15.07966]") - val parsedCoordinates = QueryUtils.parseCoordinates(targetGeometry["geometry"].toString(), targetGeometry["coordinates"].toString()) + val parsedCoordinates = + QueryUtils.parseCoordinates(targetGeometry["geometry"].toString(), targetGeometry["coordinates"].toString()) - assertEquals(parsedCoordinates, - listOf(listOf(90.30623, 15.07966))) + assertEquals( + parsedCoordinates, + listOf(listOf(90.30623, 15.07966)) + ) } -} \ No newline at end of file +} diff --git a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt index da073c99c..2448cae28 100644 --- a/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt +++ b/subscription-service/src/test/kotlin/com/egm/stellio/subscription/web/SubscriptionHandlerTests.kt @@ -5,10 +5,12 @@ import com.egm.stellio.shared.util.JSON_LD_MEDIA_TYPE import com.egm.stellio.subscription.config.WebSecurityTestConfig import com.egm.stellio.subscription.config.WithMockCustomUser import com.egm.stellio.subscription.service.SubscriptionService -import com.egm.stellio.subscription.utils.gimmeRawSubscription import com.egm.stellio.subscription.utils.ParsingUtils.parseSubscriptionUpdate +import com.egm.stellio.subscription.utils.gimmeRawSubscription import com.ninjasquad.springmockk.MockkBean -import io.mockk.* +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.verify import org.hamcrest.core.Is import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @@ -18,12 +20,11 @@ import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest import org.springframework.context.annotation.Import import org.springframework.core.io.ClassPathResource import org.springframework.http.HttpStatus +import org.springframework.security.test.context.support.WithAnonymousUser import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.security.test.context.support.WithAnonymousUser import reactor.core.publisher.Flux import reactor.core.publisher.Mono -import java.lang.RuntimeException @AutoConfigureWebTestClient(timeout = "30000") @ActiveProfiles("test") @@ -57,9 +58,9 @@ class SubscriptionHandlerTests { every { subscriptionService.getById(any()) } returns Mono.just(subscription) webClient.get() - .uri("/ngsi-ld/v1/subscriptions/${subscription.id}") - .exchange() - .expectStatus().isOk + .uri("/ngsi-ld/v1/subscriptions/${subscription.id}") + .exchange() + .expectStatus().isOk verify { subscriptionService.exists(subscription.id) } verify { subscriptionService.isCreatorOf(subscription.id, "mock-user") } @@ -71,12 +72,14 @@ class SubscriptionHandlerTests { every { subscriptionService.exists(any()) } returns Mono.just(false) webClient.get() - .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") - .exchange() - .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") + .exchange() + .expectStatus().isNotFound + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Could not find a subscription with id urn:ngsi-ld:Subscription:1\"}") + "\"detail\":\"Could not find a subscription with id urn:ngsi-ld:Subscription:1\"}" + ) verify { subscriptionService.exists("urn:ngsi-ld:Subscription:1") } } @@ -90,13 +93,15 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") .exchange() .expectStatus().isForbidden - .expectBody().json(""" + .expectBody().json( + """ { "detail":"User is not authorized to access subscription urn:ngsi-ld:Subscription:1", "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", "title":"The request tried to access an unauthorized resource" } - """.trimIndent()) + """.trimIndent() + ) verify { subscriptionService.exists("urn:ngsi-ld:Subscription:1") } verify { subscriptionService.isCreatorOf("urn:ngsi-ld:Subscription:1", "mock-user") } @@ -110,11 +115,11 @@ class SubscriptionHandlerTests { every { subscriptionService.create(any(), any()) } returns Mono.just(1) webClient.post() - .uri("/ngsi-ld/v1/subscriptions") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isCreated - .expectHeader().value("Location", Is.`is`("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:04")) + .uri("/ngsi-ld/v1/subscriptions") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isCreated + .expectHeader().value("Location", Is.`is`("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:04")) } @Test @@ -124,13 +129,15 @@ class SubscriptionHandlerTests { every { subscriptionService.exists(any()) } returns Mono.just(true) webClient.post() - .uri("/ngsi-ld/v1/subscriptions") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isEqualTo(409) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists\"," + + .uri("/ngsi-ld/v1/subscriptions") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isEqualTo(409) + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/AlreadyExists\"," + "\"title\":\"The referred element already exists\"," + - "\"detail\":\"A subscription with id urn:ngsi-ld:Subscription:04 already exists\"}") + "\"detail\":\"A subscription with id urn:ngsi-ld:Subscription:04 already exists\"}" + ) } @Test @@ -141,13 +148,15 @@ class SubscriptionHandlerTests { every { subscriptionService.create(any(), any()) } throws InternalErrorException("Internal Server Exception") webClient.post() - .uri("/ngsi-ld/v1/subscriptions") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isEqualTo(500) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + .uri("/ngsi-ld/v1/subscriptions") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isEqualTo(500) + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"Internal Server Exception\"}") + "\"detail\":\"Internal Server Exception\"}" + ) } @Test @@ -155,13 +164,15 @@ class SubscriptionHandlerTests { val jsonLdFile = ClassPathResource("/ngsild/subscription_incorrect_payload.json") webClient.post() - .uri("/ngsi-ld/v1/subscriptions") - .bodyValue(jsonLdFile) - .exchange() - .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .uri("/ngsi-ld/v1/subscriptions") + .bodyValue(jsonLdFile) + .exchange() + .expectStatus().isBadRequest + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Context not provided\"}") + "\"detail\":\"Context not provided\"}" + ) } @Test @@ -189,7 +200,8 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/?limit=1&page=2") .exchange() .expectStatus().isOk - .expectHeader().valueEquals("Link", ";rel=\"prev\";type=\"application/ld+json\"") + .expectHeader() + .valueEquals("Link", ";rel=\"prev\";type=\"application/ld+json\"") } @Test @@ -203,7 +215,8 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/?limit=1&page=1") .exchange() .expectStatus().isOk - .expectHeader().valueEquals("Link", ";rel=\"next\";type=\"application/ld+json\"") + .expectHeader() + .valueEquals("Link", ";rel=\"next\";type=\"application/ld+json\"") } @Test @@ -217,8 +230,11 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/?limit=1&page=2") .exchange() .expectStatus().isOk - .expectHeader().valueEquals("Link", - ";rel=\"prev\";type=\"application/ld+json\"", ";rel=\"next\";type=\"application/ld+json\"") + .expectHeader().valueEquals( + "Link", + ";rel=\"prev\";type=\"application/ld+json\"", + ";rel=\"next\";type=\"application/ld+json\"" + ) } @Test @@ -245,9 +261,11 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/?limit=1&page=0") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Page number and Limit must be greater than zero\"}") + "\"detail\":\"Page number and Limit must be greater than zero\"}" + ) } @Test @@ -261,9 +279,11 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/?limit=-1&page=1") .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Page number and Limit must be greater than zero\"}") + "\"detail\":\"Page number and Limit must be greater than zero\"}" + ) } @Test @@ -303,9 +323,11 @@ class SubscriptionHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().is5xxServerError - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"Update failed\"}") + "\"detail\":\"Update failed\"}" + ) verify { subscriptionService.exists(eq(subscriptionId)) } verify { subscriptionService.isCreatorOf(subscriptionId, "mock-user") } @@ -325,9 +347,11 @@ class SubscriptionHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isNotFound - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound\"," + "\"title\":\"The referred resource has not been found\"," + - "\"detail\":\"Could not find a subscription with id urn:ngsi-ld:Subscription:04\"}") + "\"detail\":\"Could not find a subscription with id urn:ngsi-ld:Subscription:04\"}" + ) verify { subscriptionService.exists(eq(subscriptionId)) } } @@ -345,9 +369,11 @@ class SubscriptionHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isBadRequest - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/BadRequestData\"," + "\"title\":\"The request includes input data which does not meet the requirements of the operation\"," + - "\"detail\":\"Context not provided\"}") + "\"detail\":\"Context not provided\"}" + ) verify { subscriptionService.exists(eq(subscriptionId)) } verify { subscriptionService.isCreatorOf(subscriptionId, "mock-user") } @@ -366,13 +392,15 @@ class SubscriptionHandlerTests { .bodyValue(jsonLdFile) .exchange() .expectStatus().isForbidden - .expectBody().json(""" + .expectBody().json( + """ { "detail":"User is not authorized to access subscription urn:ngsi-ld:Subscription:04", "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", "title":"The request tried to access an unauthorized resource" } - """.trimIndent()) + """.trimIndent() + ) verify { subscriptionService.exists(eq(subscriptionId)) } verify { subscriptionService.isCreatorOf(subscriptionId, "mock-user") } @@ -388,10 +416,10 @@ class SubscriptionHandlerTests { every { subscriptionService.delete(any()) } returns Mono.just(1) webClient.delete() - .uri("/ngsi-ld/v1/subscriptions/${subscription.id}") - .exchange() - .expectStatus().isNoContent - .expectBody().isEmpty + .uri("/ngsi-ld/v1/subscriptions/${subscription.id}") + .exchange() + .expectStatus().isNoContent + .expectBody().isEmpty verify { subscriptionService.exists(subscription.id) } verify { subscriptionService.isCreatorOf(subscription.id, "mock-user") } @@ -405,16 +433,18 @@ class SubscriptionHandlerTests { every { subscriptionService.exists(any()) } returns Mono.just(false) webClient.delete() - .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") - .exchange() - .expectStatus().isNotFound - .expectBody().json(""" + .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") + .exchange() + .expectStatus().isNotFound + .expectBody().json( + """ { "detail":"Could not find a subscription with id urn:ngsi-ld:Subscription:1", "type":"https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound", "title":"The referred resource has not been found" } - """.trimIndent()) + """.trimIndent() + ) verify { subscriptionService.exists("urn:ngsi-ld:Subscription:1") } @@ -428,12 +458,14 @@ class SubscriptionHandlerTests { every { subscriptionService.delete(any()) } throws RuntimeException("Unexpected server error") webClient.delete() - .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") - .exchange() - .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody().json("{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + + .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") + .exchange() + .expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) + .expectBody().json( + "{\"type\":\"https://uri.etsi.org/ngsi-ld/errors/InternalError\"," + "\"title\":\"There has been an error during the operation execution\"," + - "\"detail\":\"Unexpected server error\"}") + "\"detail\":\"Unexpected server error\"}" + ) verify { subscriptionService.exists("urn:ngsi-ld:Subscription:1") } verify { subscriptionService.isCreatorOf("urn:ngsi-ld:Subscription:1", "mock-user") } @@ -449,13 +481,15 @@ class SubscriptionHandlerTests { .uri("/ngsi-ld/v1/subscriptions/urn:ngsi-ld:Subscription:1") .exchange() .expectStatus().isForbidden - .expectBody().json(""" + .expectBody().json( + """ { "detail":"User is not authorized to access subscription urn:ngsi-ld:Subscription:1", "type":"https://uri.etsi.org/ngsi-ld/errors/AccessDenied", "title":"The request tried to access an unauthorized resource" } - """.trimIndent()) + """.trimIndent() + ) verify { subscriptionService.exists("urn:ngsi-ld:Subscription:1") } verify { subscriptionService.isCreatorOf("urn:ngsi-ld:Subscription:1", "mock-user") } @@ -465,8 +499,8 @@ class SubscriptionHandlerTests { @WithAnonymousUser fun `it should not authorize an anonymous to call the API`() { webClient.post() - .uri("/ngsi-ld/v1/subscriptions") - .exchange() - .expectStatus().isForbidden + .uri("/ngsi-ld/v1/subscriptions") + .exchange() + .expectStatus().isForbidden } } diff --git a/subscription-service/src/test/resources/application-test.properties b/subscription-service/src/test/resources/application-test.properties index 2da389f44..bf93653f1 100644 --- a/subscription-service/src/test/resources/application-test.properties +++ b/subscription-service/src/test/resources/application-test.properties @@ -4,4 +4,4 @@ application.jsonld.apic_context=https://raw.githubusercontent.com/easy-global-ma # the running PG container spring.flyway.enabled = false -application.authentication.enabled = true \ No newline at end of file +application.authentication.enabled = true diff --git a/subscription-service/src/test/resources/ngsild/apic/BeekeeperUpdateEvent.json b/subscription-service/src/test/resources/ngsild/apic/BeekeeperUpdateEvent.json index ec0bcc19a..4ad95e833 100644 --- a/subscription-service/src/test/resources/ngsild/apic/BeekeeperUpdateEvent.json +++ b/subscription-service/src/test/resources/ngsild/apic/BeekeeperUpdateEvent.json @@ -4,4 +4,4 @@ "entityType": "Beekeeper", "payload": "{\"name\":{\"type\":\"Property\",\"value\":\"newName\"}}", "updatedEntity": "{\"id\":\"urn:ngsi-ld:Beekeeper:01\",\"type\":\"Beekeeper\",\"createdAt\":\"2020-03-03T14:33:08.925873Z\",\"modifiedAt\":\"2020-03-03T14:33:08.958137Z\",\"name\":{\"type\":\"Property\",\"createdAt\":\"2020-03-03T14:33:08.941678Z\",\"value\":\"test\",\"modifiedAt\":\"2020-03-03T15:23:51.730936Z\"},\"@context\":[\"https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/shared-jsonld-contexts/egm.jsonld\",\"https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/apic/jsonld-contexts/apic.jsonld\",\"http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld\"]}" -} \ No newline at end of file +} diff --git a/subscription-service/src/test/resources/ngsild/aquac/FeedingService.json b/subscription-service/src/test/resources/ngsild/aquac/FeedingService.json index 221c277ae..773fe341d 100644 --- a/subscription-service/src/test/resources/ngsild/aquac/FeedingService.json +++ b/subscription-service/src/test/resources/ngsild/aquac/FeedingService.json @@ -34,4 +34,4 @@ "https://raw.githubusercontent.com/easy-global-market/ngsild-api-data-models/master/aquac/jsonld-contexts/aquac.jsonld", "http://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld" ] -} \ No newline at end of file +} diff --git a/subscription-service/src/test/resources/ngsild/subscription.json b/subscription-service/src/test/resources/ngsild/subscription.json index f9ea1dab3..9f5325ce0 100644 --- a/subscription-service/src/test/resources/ngsild/subscription.json +++ b/subscription-service/src/test/resources/ngsild/subscription.json @@ -27,4 +27,4 @@ "https://gist.githubusercontent.com/bobeal/2e5905a069ad534b4919839b6b4c1245/raw/ed0b0103c8b498c034d8ad367d3494d02a9ad28b/apic.jsonld", "https://gist.githubusercontent.com/bobeal/4a836c81b837673b12e9db9916b1cd35/raw/82fba02005f3fc572d60744e38f6591bbaa09d6d/egm.jsonld" ] -} \ No newline at end of file +} diff --git a/subscription-service/src/test/resources/ngsild/subscription_incorrect_payload.json b/subscription-service/src/test/resources/ngsild/subscription_incorrect_payload.json index b601439a3..40d8c8b4a 100644 --- a/subscription-service/src/test/resources/ngsild/subscription_incorrect_payload.json +++ b/subscription-service/src/test/resources/ngsild/subscription_incorrect_payload.json @@ -13,4 +13,4 @@ "type": "Beehive" } ] -} \ No newline at end of file +} diff --git a/subscription-service/src/test/resources/ngsild/subscription_update.json b/subscription-service/src/test/resources/ngsild/subscription_update.json index aaaba169b..411d4e2b6 100644 --- a/subscription-service/src/test/resources/ngsild/subscription_update.json +++ b/subscription-service/src/test/resources/ngsild/subscription_update.json @@ -19,4 +19,4 @@ "https://gist.githubusercontent.com/bobeal/2e5905a069ad534b4919839b6b4c1245/raw/ed0b0103c8b498c034d8ad367d3494d02a9ad28b/apic.jsonld", "https://gist.githubusercontent.com/bobeal/4a836c81b837673b12e9db9916b1cd35/raw/82fba02005f3fc572d60744e38f6591bbaa09d6d/egm.jsonld" ] -} \ No newline at end of file +} diff --git a/subscription-service/src/test/resources/ngsild/subscription_update_incorrect_payload.json b/subscription-service/src/test/resources/ngsild/subscription_update_incorrect_payload.json index d6ad6fbc5..765bcf8b2 100644 --- a/subscription-service/src/test/resources/ngsild/subscription_update_incorrect_payload.json +++ b/subscription-service/src/test/resources/ngsild/subscription_update_incorrect_payload.json @@ -14,4 +14,4 @@ "accept": "application/ld+json" } } -} \ No newline at end of file +}