diff --git a/.github/workflows/ci-delta-ship.yml b/.github/workflows/ci-delta-ship.yml index ecf8e997c5..30d134749a 100644 --- a/.github/workflows/ci-delta-ship.yml +++ b/.github/workflows/ci-delta-ship.yml @@ -35,7 +35,6 @@ jobs: - name: Clean, build Delta & Storage images run: | sbt -Dsbt.color=always -Dsbt.supershell=false \ - clean \ app/Docker/publishLocal - name: Start services run: docker-compose -f tests/docker/docker-compose.yml up -d @@ -46,4 +45,8 @@ jobs: - name: Unit tests run: | sbt -Dsbt.color=always -Dsbt.supershell=false \ - ship-unit-tests + "ship/testOnly *Suite" + - name: Integration tests + run: | + sbt -Dsbt.color=always -Dsbt.supershell=false \ + "ship/testOnly *Spec" diff --git a/build.sbt b/build.sbt index e4d2157161..1ba8f8da8f 100755 --- a/build.sbt +++ b/build.sbt @@ -743,16 +743,18 @@ lazy val delta = project lazy val ship = project .in(file("ship")) .settings( - name := "nexus-ship", - moduleName := "nexus-ship" + name := "nexus-ship", + moduleName := "nexus-ship", + Test / parallelExecution := false ) .enablePlugins(UniversalPlugin, JavaAppPackaging, JavaAgent, DockerPlugin, BuildInfoPlugin) .settings(shared, compilation, servicePackaging, assertJavaVersion, kamonSettings, coverage, release) .dependsOn( - sdk % "compile->compile;test->test", - blazegraphPlugin % "compile->compile", - elasticsearchPlugin % "compile->compile", - tests % "test->compile;test->test" + sdk % "compile->compile;test->test", + blazegraphPlugin % "compile->compile", + compositeViewsPlugin % "compile->compile", + elasticsearchPlugin % "compile->compile", + tests % "test->compile;test->test" ) .settings( libraryDependencies ++= Seq(declineEffect), diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViews.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViews.scala index 2e83f1080b..2ed6a3209b 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViews.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViews.scala @@ -5,7 +5,6 @@ import ch.epfl.bluebrain.nexus.delta.kernel.kamon.KamonMetricComponent import ch.epfl.bluebrain.nexus.delta.kernel.search.Pagination.FromPagination import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.CompositeViews._ -import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.config.CompositeViewsConfig import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.indexing.CompositeViewDef import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.indexing.CompositeViewDef.{ActiveViewDef, DeprecatedViewDef} import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewCommand._ @@ -27,6 +26,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.projects.{FetchContext, Projects} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution import ch.epfl.bluebrain.nexus.delta.sdk.views.IndexingRev import ch.epfl.bluebrain.nexus.delta.sourcing._ +import ch.epfl.bluebrain.nexus.delta.sourcing.config.EventLogConfig import ch.epfl.bluebrain.nexus.delta.sourcing.model.EntityDependency.DependsOn import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject import ch.epfl.bluebrain.nexus.delta.sourcing.model._ @@ -34,6 +34,8 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.offset.Offset import ch.epfl.bluebrain.nexus.delta.sourcing.stream.Elem import io.circe.Json +import scala.concurrent.duration.FiniteDuration + /** * Composite views resource lifecycle operations. */ @@ -467,7 +469,8 @@ object CompositeViews { fetchContext: FetchContext, contextResolution: ResolverContextResolution, validate: ValidateCompositeView, - config: CompositeViewsConfig, + minIntervalRebuild: FiniteDuration, + eventLogConfig: EventLogConfig, xas: Transactors, clock: Clock[IO] )(implicit @@ -476,13 +479,13 @@ object CompositeViews { ): IO[CompositeViews] = IO .delay( - CompositeViewFieldsJsonLdSourceDecoder(uuidF, contextResolution, config.minIntervalRebuild) + CompositeViewFieldsJsonLdSourceDecoder(uuidF, contextResolution, minIntervalRebuild) ) .map { sourceDecoder => new CompositeViews( ScopedEventLog( definition(validate, clock), - config.eventLog, + eventLogConfig, xas ), fetchContext, diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsPluginModule.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsPluginModule.scala index 5a787cd83d..760f1fcc1e 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsPluginModule.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsPluginModule.scala @@ -129,7 +129,8 @@ class CompositeViewsPluginModule(priority: Int) extends ModuleDef { fetchContext, contextResolution, validate, - config, + config.minIntervalRebuild, + config.eventLog, xas, clock )( diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewFields.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewFields.scala index e177e0c029..31e1a66cd9 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewFields.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewFields.scala @@ -4,9 +4,11 @@ import cats.data.NonEmptyList import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeView.RebuildStrategy import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoder +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.{Configuration, JsonLdDecoder} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError.ParsingFailure +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.configuration.semiauto.deriveConfigJsonLdDecoder import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.semiauto.deriveDefaultJsonLdDecoder +import ch.epfl.bluebrain.nexus.delta.rdf.syntax.iriStringContextSyntax import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri import io.circe.syntax.EncoderOps import io.circe.{Encoder, Json} @@ -73,6 +75,12 @@ object CompositeViewFields { } deriveDefaultJsonLdDecoder[RebuildStrategy] } - deriveDefaultJsonLdDecoder[CompositeViewFields] + + val ctx = Configuration.default.context + .addAliasIdType("description", iri"http://schema.org/description") + .addAliasIdType("name", iri"http://schema.org/name") + implicit val config = Configuration.default.copy(context = ctx) + + deriveConfigJsonLdDecoder[CompositeViewFields] } } diff --git a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsSpec.scala b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsSpec.scala index 5beb5f54e9..b15b086f7b 100644 --- a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsSpec.scala +++ b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewsSpec.scala @@ -52,7 +52,8 @@ class CompositeViewsSpec fetchContext, ResolverContextResolution(rcr), alwaysValidate, - config, + config.minIntervalRebuild, + config.eventLog, xas, clock ).accepted diff --git a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutesSpec.scala b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutesSpec.scala index 896d1680f7..ca1bd7b49e 100644 --- a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutesSpec.scala +++ b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutesSpec.scala @@ -50,7 +50,8 @@ class CompositeViewsRoutesSpec extends CompositeViewsRoutesFixtures { fetchContext, ResolverContextResolution(rcr), alwaysValidate, - config, + config.minIntervalRebuild, + config.eventLog, xas, clock ).accepted diff --git a/delta/plugins/search/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/search/SearchScopeInitializationSpec.scala b/delta/plugins/search/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/search/SearchScopeInitializationSpec.scala index 0ead8e8e9c..875cdc7241 100644 --- a/delta/plugins/search/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/search/SearchScopeInitializationSpec.scala +++ b/delta/plugins/search/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/search/SearchScopeInitializationSpec.scala @@ -35,7 +35,8 @@ class SearchScopeInitializationSpec fetchContext, ResolverContextResolution(rcr), alwaysValidate, - config, + config.minIntervalRebuild, + config.eventLog, xas, clock ).accepted diff --git a/ship/src/main/resources/default.conf b/ship/src/main/resources/ship-default.conf similarity index 100% rename from ship/src/main/resources/default.conf rename to ship/src/main/resources/ship-default.conf diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/ContextWiring.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/ContextWiring.scala index 5aaea9751f..e060fbe719 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/ContextWiring.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/ContextWiring.scala @@ -16,6 +16,7 @@ import ch.epfl.bluebrain.nexus.ship.resolvers.ResolverWiring import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.{contexts => esContexts} import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.{contexts => bgContexts} +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.{contexts => compositeViewContexts} object ContextWiring { @@ -23,21 +24,26 @@ object ContextWiring { def remoteContextResolution: IO[RemoteContextResolution] = for { + metadataCtx <- ContextValue.fromFile("contexts/metadata.json") pipelineCtx <- ContextValue.fromFile("contexts/pipeline.json") shaclCtx <- ContextValue.fromFile("contexts/shacl.json") schemasMetaCtx <- ContextValue.fromFile("contexts/schemas-metadata.json") elasticsearchCtx <- ContextValue.fromFile("contexts/elasticsearch.json") blazegraphCtx <- ContextValue.fromFile("contexts/sparql.json") + compositeCtx <- ContextValue.fromFile("contexts/composite-views.json") } yield RemoteContextResolution.fixed( // Delta - contexts.pipeline -> pipelineCtx, + contexts.metadata -> metadataCtx, + contexts.pipeline -> pipelineCtx, // Schema - contexts.shacl -> shaclCtx, - contexts.schemasMetadata -> schemasMetaCtx, + contexts.shacl -> shaclCtx, + contexts.schemasMetadata -> schemasMetaCtx, // ElasticSearch - esContexts.elasticsearch -> elasticsearchCtx, + esContexts.elasticsearch -> elasticsearchCtx, // Blazegraph - bgContexts.blazegraph -> blazegraphCtx + bgContexts.blazegraph -> blazegraphCtx, + // Composite views + compositeViewContexts.compositeViews -> compositeCtx ) def resolverContextResolution( diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala index f5acee3a10..c38f78a7b4 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/RunShip.scala @@ -16,7 +16,7 @@ import ch.epfl.bluebrain.nexus.ship.projects.ProjectProcessor import ch.epfl.bluebrain.nexus.ship.resolvers.ResolverProcessor import ch.epfl.bluebrain.nexus.ship.resources.{ResourceProcessor, ResourceWiring} import ch.epfl.bluebrain.nexus.ship.schemas.{SchemaProcessor, SchemaWiring} -import ch.epfl.bluebrain.nexus.ship.views.{BlazegraphViewProcessor, ElasticSearchViewProcessor} +import ch.epfl.bluebrain.nexus.ship.views.{BlazegraphViewProcessor, CompositeViewProcessor, ElasticSearchViewProcessor} import fs2.Stream import fs2.io.file.{Files, Path} import io.circe.parser.decode @@ -66,6 +66,7 @@ class RunShip { resourceProcessor = ResourceProcessor(resourceLog, fetchContext, eventClock) esViewsProcessor <- ElasticSearchViewProcessor(fetchContext, rcr, eventLogConfig, eventClock, xas) bgViewsProcessor = BlazegraphViewProcessor(fetchContext, rcr, eventLogConfig, eventClock, xas) + compositeViewsProcessor = CompositeViewProcessor(fetchContext, rcr, eventLogConfig, eventClock, xas) report <- EventProcessor .run( events, @@ -74,7 +75,8 @@ class RunShip { schemaProcessor, resourceProcessor, esViewsProcessor, - bgViewsProcessor + bgViewsProcessor, + compositeViewsProcessor ) } yield report } diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfig.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfig.scala index 5f5aec7502..e83228e609 100644 --- a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfig.scala +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/config/ShipConfig.scala @@ -25,7 +25,7 @@ object ShipConfig { def merge(externalConfigPath: Option[Path]): IO[(ShipConfig, Config)] = for { externalConfig <- Configs.parseFile(externalConfigPath.map(_.toNioPath.toFile)) - defaultConfig <- Configs.parseResource("default.conf") + defaultConfig <- Configs.parseResource("ship-default.conf") result <- Configs.merge[ShipConfig]("ship", externalConfig, defaultConfig) } yield result diff --git a/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/views/CompositeViewProcessor.scala b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/views/CompositeViewProcessor.scala new file mode 100644 index 0000000000..b17e2223d9 --- /dev/null +++ b/ship/src/main/scala/ch/epfl/bluebrain/nexus/ship/views/CompositeViewProcessor.scala @@ -0,0 +1,91 @@ +package ch.epfl.bluebrain.nexus.ship.views + +import cats.effect.IO +import ch.epfl.bluebrain.nexus.delta.kernel.Logger +import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewEvent._ +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewRejection.{IncorrectRev, ResourceAlreadyExists} +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.{CompositeViewEvent, CompositeViewValue} +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.{CompositeViews, ValidateCompositeView} +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi +import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller +import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext +import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution +import ch.epfl.bluebrain.nexus.delta.sourcing.Transactors +import ch.epfl.bluebrain.nexus.delta.sourcing.config.EventLogConfig +import ch.epfl.bluebrain.nexus.delta.sourcing.model.EntityType +import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject +import ch.epfl.bluebrain.nexus.ship.views.CompositeViewProcessor.logger +import ch.epfl.bluebrain.nexus.ship.{EventClock, EventProcessor, ImportStatus} +import io.circe.Decoder + +import java.util.UUID +import scala.concurrent.duration.DurationInt + +class CompositeViewProcessor(views: UUID => IO[CompositeViews], clock: EventClock) + extends EventProcessor[CompositeViewEvent] { + override def resourceType: EntityType = CompositeViews.entityType + + override def decoder: Decoder[CompositeViewEvent] = CompositeViewEvent.serializer.codec + + override def evaluate(event: CompositeViewEvent): IO[ImportStatus] = + for { + _ <- clock.setInstant(event.instant) + result <- evaluateInternal(event) + } yield result + + private def evaluateInternal(event: CompositeViewEvent): IO[ImportStatus] = { + implicit val s: Subject = event.subject + implicit val c: Caller = Caller(s, Set.empty) + val cRev = event.rev - 1 + + event match { + case e: CompositeViewCreated => views(event.uuid).flatMap(_.create(e.project, e.source)) + case e: CompositeViewUpdated => views(event.uuid).flatMap(_.update(e.id, e.project, cRev, e.source)) + case e: CompositeViewDeprecated => views(event.uuid).flatMap(_.deprecate(e.id, e.project, cRev)) + case e: CompositeViewUndeprecated => views(event.uuid).flatMap(_.undeprecate(e.id, e.project, cRev)) + case _: CompositeViewTagAdded => IO.unit // TODO: Can/should we tag? + } + }.redeemWith( + { + case a: ResourceAlreadyExists => logger.warn(a)("The resource already exists").as(ImportStatus.Dropped) + case i: IncorrectRev => logger.warn(i)("An incorrect revision has been provided").as(ImportStatus.Dropped) + case other => IO.raiseError(other) + }, + _ => IO.pure(ImportStatus.Success) + ) +} + +object CompositeViewProcessor { + + private val logger = Logger[CompositeViewProcessor] + + def apply( + fetchContext: FetchContext, + rcr: ResolverContextResolution, + config: EventLogConfig, + clock: EventClock, + xas: Transactors + )(implicit + jsonLdApi: JsonLdApi + ): CompositeViewProcessor = { + val noValidation = new ValidateCompositeView { + override def apply(uuid: UUID, value: CompositeViewValue): IO[Unit] = IO.unit + } + + val views = (uuid: UUID) => + CompositeViews( + fetchContext, + rcr, + noValidation, + 3.seconds, + config, + xas, + clock + )(jsonLdApi, UUIDF.fixed(uuid)) + + new CompositeViewProcessor(views, clock) + + } + +} diff --git a/ship/src/test/resources/default.conf b/ship/src/test/resources/default.conf deleted file mode 100644 index fba48d6ebe..0000000000 --- a/ship/src/test/resources/default.conf +++ /dev/null @@ -1,55 +0,0 @@ -ship { - base-uri = "http://localhost:8080/v1" - - database { - read = ${ship.database.access} - # Access to database for write access - write = ${ship.database.access} - # Access to database for streaming access (indexing / SSEs) - streaming = ${ship.database.access} - - # when true it creates the tables on service boot - tables-autocreate = false - - cache { - # The max number of tokens in the partition cache - max-size = 1000 - # The duration after an entry in the cache expires - expire-after = 10 minutes - } - - access { - # the database host - host = 127.0.0.1 - # the database port - port = 5432 - # the pool size - pool-size = 10 - } - - name = "postgres" - username = "postgres" - password = "postgres" - } - - event-log { - query-config = { - batch-size = 30 - refresh-strategy = 3s - } - max-duration = 14 seconds - } - - organizations { - values { - # organization example - #obp = "The Open Brain Platform Organization" - } - } - - # Service account configuration for internal operations - service-account { - subject: "delta" - realm: "internal" - } -} \ No newline at end of file diff --git a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/EndToEndTest.scala b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/ShipIntegrationSpec.scala similarity index 88% rename from ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/EndToEndTest.scala rename to ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/ShipIntegrationSpec.scala index b6ee9b2877..f95011dab1 100644 --- a/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/EndToEndTest.scala +++ b/ship/src/test/scala/ch/epfl/bluebrain/nexus/ship/ShipIntegrationSpec.scala @@ -22,7 +22,7 @@ import java.nio.file.{Files, Paths} import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters.IteratorHasAsScala -class EndToEndTest extends BaseIntegrationSpec { +class ShipIntegrationSpec extends BaseIntegrationSpec { override def beforeAll(): Unit = { super.beforeAll() @@ -128,8 +128,31 @@ class EndToEndTest extends BaseIntegrationSpec { thereShouldBeAView(project, bgView, bgViewJson) } - def thereShouldBeAView(project: ProjectRef, schema: Iri, originalJson: Json): Assertion = { - val encodedIri = UrlUtils.encode(schema.toString) + "transfer a search view" in { + val (project, _, _) = thereIsAProject() + val (searchView, searchViewJson) = thereIsASearchView(project) + + whenTheExportIsRunOnProject(project) + theOldProjectIsDeleted(project) + + weRunTheImporter(project) + weFixThePermissions(project) + + thereShouldBeAViewIgnoringUUID(project, searchView, searchViewJson) + } + + def thereIsASearchView(project: ProjectRef): (Iri, Json) = { + val searchView = nxv + "searchView" + val encodedView = UrlUtils.encode(searchView.toString) + val (viewJson, status) = deltaClient + .getJsonAndStatus(s"/views/${project.organization}/${project.project}/$encodedView", writer) + .accepted + status shouldEqual StatusCodes.OK + searchView -> viewJson + } + + def thereShouldBeAView(project: ProjectRef, view: Iri, originalJson: Json): Assertion = { + val encodedIri = UrlUtils.encode(view.toString) deltaClient .get[Json](s"/views/${project.organization}/${project.project}/$encodedIri", writer) { (json, response) => { @@ -140,6 +163,28 @@ class EndToEndTest extends BaseIntegrationSpec { .accepted } + def thereShouldBeAViewIgnoringUUID(project: ProjectRef, view: Iri, originalJson: Json): Assertion = { + val encodedIri = UrlUtils.encode(view.toString) + + import io.circe.optics.JsonPath.root + val ignoreSourceUUID = root.sources.each.at("_uuid").replace(None) + val ignoreProjectionUUID = root.projections.each.at("_uuid").replace(None) + val ignoreUUID = root.at("_uuid").replace(None) + + val filter = ignoreUUID andThen ignoreSourceUUID andThen ignoreProjectionUUID + + root.sources.`null` + + deltaClient + .get[Json](s"/views/${project.organization}/${project.project}/$encodedIri", writer) { (json, response) => + { + response.status shouldEqual StatusCodes.OK + filter(json) shouldEqual filter(originalJson) + } + } + .accepted + } + def thereIsABlazegraphView(project: ProjectRef): (Iri, Json) = { val simpleBgView = json"""{ "@type": "SparqlView", @@ -392,6 +437,7 @@ class EndToEndTest extends BaseIntegrationSpec { } .accepted } + } }