diff --git a/modules/loaders-common/src/main/scala/com/snowplowanalytics/snowplow/loaders/transform/NonAtomicFields.scala b/modules/loaders-common/src/main/scala/com/snowplowanalytics/snowplow/loaders/transform/NonAtomicFields.scala index db3d58a..0ba3e95 100644 --- a/modules/loaders-common/src/main/scala/com/snowplowanalytics/snowplow/loaders/transform/NonAtomicFields.scala +++ b/modules/loaders-common/src/main/scala/com/snowplowanalytics/snowplow/loaders/transform/NonAtomicFields.scala @@ -7,10 +7,13 @@ */ package com.snowplowanalytics.snowplow.loaders.transform +import cats.data.NonEmptyList import cats.effect.Sync import cats.implicits._ -import com.snowplowanalytics.iglu.client.resolver.registries.RegistryLookup import com.snowplowanalytics.iglu.client.resolver.Resolver +import com.snowplowanalytics.iglu.client.resolver.registries.RegistryLookup +import com.snowplowanalytics.iglu.core.{SchemaCriterion, SelfDescribingSchema} +import com.snowplowanalytics.iglu.schemaddl.jsonschema.Schema import com.snowplowanalytics.snowplow.badrows.FailureDetails object NonAtomicFields { @@ -47,18 +50,41 @@ object NonAtomicFields { def resolveTypes[F[_]: Sync: RegistryLookup]( resolver: Resolver[F], - entities: Map[TabledEntity, Set[SchemaSubVersion]] + entities: Map[TabledEntity, Set[SchemaSubVersion]], + entitiesToSkip: List[SchemaCriterion] ): F[Result] = entities.toList .traverse { case (tabledEntity, subVersions) => SchemaProvider .fetchSchemasWithSameModel(resolver, TabledEntity.toSchemaKey(tabledEntity, subVersions.max)) - .map(TypedTabledEntity.build(tabledEntity, subVersions, _)) + .map(schemas => filterOutMatchingSubversions(entitiesToSkip, schemas)) + .map(filteredSchemas => buildTypedEntities(tabledEntity, subVersions, filteredSchemas)) .leftMap(ColumnFailure(tabledEntity, subVersions, _)) .value } .map { eithers => val (failures, good) = eithers.separate - Result(good, failures) + Result(good.flatten, failures) } + + private def filterOutMatchingSubversions( + entitiesToSkip: List[SchemaCriterion], + schemas: NonEmptyList[SelfDescribingSchema[Schema]] + ): Option[NonEmptyList[SelfDescribingSchema[Schema]]] = + if (entitiesToSkip.nonEmpty) { + schemas.filterNot { sdd => + entitiesToSkip.exists(_.matches(sdd.self.schemaKey)) + }.toNel + } else { + Some(schemas) + } + + private def buildTypedEntities( + tabledEntity: TabledEntity, + subVersions: Set[(Int, Int)], + filteredSchemas: Option[NonEmptyList[SelfDescribingSchema[Schema]]] + ): Option[TypedTabledEntity] = + filteredSchemas.map { schemas => + TypedTabledEntity.build(tabledEntity, subVersions, schemas) + } } diff --git a/modules/loaders-common/src/test/resources/iglu-client-embedded/schemas/myvendor/myschema/jsonschema/9-0-0 b/modules/loaders-common/src/test/resources/iglu-client-embedded/schemas/myvendor/myschema/jsonschema/9-0-0 new file mode 100644 index 0000000..55d083c --- /dev/null +++ b/modules/loaders-common/src/test/resources/iglu-client-embedded/schemas/myvendor/myschema/jsonschema/9-0-0 @@ -0,0 +1,14 @@ +{ + "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", + "self": { + "vendor": "myvendor", + "name": "myschema", + "format": "jsonschema", + "version": "9-0-0" + }, + "type": "object", + "properties": { + "col_z": {"type": "string"} + }, + "required": ["col_z"] +} diff --git a/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/NonAtomicFieldsSpec.scala b/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/NonAtomicFieldsSpec.scala index 4c1c551..510f9b3 100644 --- a/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/NonAtomicFieldsSpec.scala +++ b/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/NonAtomicFieldsSpec.scala @@ -70,7 +70,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -105,7 +105,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -140,7 +140,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -176,7 +176,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -214,7 +214,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -253,7 +253,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -271,7 +271,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { tabledEntity2 -> Set((0, 0)) ) - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(2)) and (fields.map(_.tabledEntity) must contain(allOf(tabledEntity1, tabledEntity2))) @@ -304,7 +304,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -354,7 +354,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -405,7 +405,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { ) } - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (failures must beEmpty) and (fields must haveSize(1)) and (fields.head must beEqualTo(expected)) @@ -421,7 +421,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { tabledEntity -> Set((0, 9)) ) - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (fields must beEmpty) and (failures must haveSize(1)) and (failures.head must beLike { case failure: NonAtomicFields.ColumnFailure => @@ -441,7 +441,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { tabledEntity -> Set((0, 0)) ) - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (fields must beEmpty) and (failures must haveSize(1)) and (failures.head must beLike { case failure: NonAtomicFields.ColumnFailure => @@ -461,7 +461,7 @@ class NonAtomicFieldsSpec extends Specification with CatsEffect { tabledEntity -> Set((0, 0)) ) - NonAtomicFields.resolveTypes(embeddedResolver, input).map { case NonAtomicFields.Result(fields, failures) => + NonAtomicFields.resolveTypes(embeddedResolver, input, List.empty).map { case NonAtomicFields.Result(fields, failures) => (fields must beEmpty) and (failures must haveSize(1)) and (failures.head must beLike { case failure: NonAtomicFields.ColumnFailure => diff --git a/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/SkippingSchemasSpec.scala b/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/SkippingSchemasSpec.scala index 1b9dfce..ac9f21a 100644 --- a/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/SkippingSchemasSpec.scala +++ b/modules/loaders-common/src/test/scala/com.snowplowanalytics.snowplow.loaders/transform/SkippingSchemasSpec.scala @@ -7,11 +7,14 @@ */ package com.snowplowanalytics.snowplow.loaders.transform +import cats.effect.unsafe.implicits.global +import com.snowplowanalytics.iglu.client.resolver.registries.JavaNetRegistryLookup.ioLookupInstance import com.snowplowanalytics.iglu.core.{SchemaCriterion, SchemaKey, SelfDescribingData} import com.snowplowanalytics.iglu.schemaddl.parquet.Caster.NamedValue import com.snowplowanalytics.snowplow.analytics.scalasdk.Event import com.snowplowanalytics.snowplow.analytics.scalasdk.SnowplowEvent.{Contexts, UnstructEvent} import com.snowplowanalytics.snowplow.badrows.{Processor => BadRowProcessor} +import com.snowplowanalytics.snowplow.loaders.transform.NonAtomicFieldsSpec.embeddedResolver import io.circe.Json import io.circe.literal._ import org.specs2.Specification @@ -223,144 +226,138 @@ class SkippingSchemasSpec extends Specification { def e1 = { val schemasToSkip = List.empty - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}""")), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List.empty, shouldExist = Map( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) -> Set((0, 0)) + "unstruct_event_myvendor_myschema_7" -> Set((0, 0)), + "contexts_myvendor_myschema_8" -> Set((0, 0)), + "contexts_myvendor_myschema_9" -> Set((0, 0)) ) ) } def e2 = { - val schemasToSkip = List("iglu:com.example/mySchema/jsonschema/1-2-3") + val schemasToSkip = List("iglu:myvendor/myschema/jsonschema/7-8-9") - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}""")), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List.empty, shouldExist = Map( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) -> Set((0, 0)) + "unstruct_event_myvendor_myschema_7" -> Set((0, 0)), + "contexts_myvendor_myschema_8" -> Set((0, 0)), + "contexts_myvendor_myschema_9" -> Set((0, 0)) ) ) } def e3 = { - val schemasToSkip = List("iglu:com.example/mySchema/jsonschema/*-*-*") + val schemasToSkip = List("iglu:myvendor/myschema/jsonschema/*-*-*") - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}""")), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) + "unstruct_event_myvendor_myschema_7", + "contexts_myvendor_myschema_8", + "contexts_myvendor_myschema_9" ), shouldExist = Map.empty ) } def e4 = { - val schemasToSkip = List("iglu:com.example/mySchema/jsonschema/1-*-*") + val schemasToSkip = List("iglu:myvendor/myschema/jsonschema/7-*-*") - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}""")), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1) + "unstruct_event_myvendor_myschema_7" ), shouldExist = Map( - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) -> Set((0, 0)) + "contexts_myvendor_myschema_8" -> Set((0, 0)), + "contexts_myvendor_myschema_9" -> Set((0, 0)) ) ) } def e5 = { - val schemasToSkip = List("iglu:com.example/mySchema/jsonschema/1-0-*", "iglu:com.example/mySchema/jsonschema/2-0-*") + val schemasToSkip = List("iglu:myvendor/myschema/jsonschema/7-0-*", "iglu:myvendor/myschema/jsonschema/8-0-*") - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List( - sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}"""), - sdj(key = "iglu:com.example/mySchema/jsonschema/2-1-0", data = json"""{"field": "c2"}""") - ), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0), (1, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1) + "contexts_myvendor_myschema_8" ), shouldExist = Map( - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2) -> Set((1, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) -> Set((0, 0)) + "unstruct_event_myvendor_myschema_7" -> Set((1, 0)), + "contexts_myvendor_myschema_9" -> Set((0, 0)) ) ) } def e6 = { - val schemasToSkip = List("iglu:com.example/mySchema/jsonschema/3-0-0") + val schemasToSkip = List("iglu:myvendor/myschema/jsonschema/9-0-0") - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List( - sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}"""), - sdj(key = "iglu:com.example/mySchema/jsonschema/2-1-0", data = json"""{"field": "c2"}""") - ), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0), (1, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) assertStructured(input, schemasToSkip)( shouldNotExist = List( - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) + "contexts_myvendor_myschema_9" ), shouldExist = Map( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1) -> Set((0, 0)), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2) -> Set((0, 0), (1, 0)) + "unstruct_event_myvendor_myschema_7" -> Set((0, 0), (0, 1), (1, 0)), + "contexts_myvendor_myschema_8" -> Set((0, 0)) ) ) } def e7 = { - val input = inputEvent( - withUnstruct = Some(sdj(key = "iglu:com.example/mySchema/jsonschema/1-0-0", data = json"""{"field": "ue1"}""")), - withContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/2-0-0", data = json"""{"field": "c1"}""")), - withDerivedContexts = List(sdj(key = "iglu:com.example/mySchema/jsonschema/3-0-0", data = json"""{"field": "dc1"}""")) + val input = Map( + TabledEntity(TabledEntity.UnstructEvent, "myvendor", "myschema", 7) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 8) -> Set((0, 0)), + TabledEntity(TabledEntity.Context, "myvendor", "myschema", 9) -> Set((0, 0)) ) val schemasToSkip = List( - "iglu:com.example/mySchema/jsonschema/1-*-*", - "iglu:com.example/mySchema/jsonschema/2-0-*", - "iglu:com.example/mySchema/jsonschema/3-0-0" + "iglu:myvendor/myschema/jsonschema/7-*-*", + "iglu:myvendor/myschema/jsonschema/8-0-*", + "iglu:myvendor/myschema/jsonschema/9-0-0" ) assertStructured(input, schemasToSkip)( shouldNotExist = List( - TabledEntity(TabledEntity.UnstructEvent, "com.example", "mySchema", 1), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 2), - TabledEntity(TabledEntity.Context, "com.example", "mySchema", 3) + "unstruct_event_myvendor_myschema_7", + "contexts_myvendor_myschema_8", + "contexts_myvendor_myschema_9" ), shouldExist = Map.empty ) @@ -406,15 +403,20 @@ class SkippingSchemasSpec extends Specification { } private def assertStructured( - input: Event, + input: Map[TabledEntity, Set[SchemaSubVersion]], schemasToSkip: List[String] )( - shouldNotExist: List[TabledEntity], - shouldExist: Map[TabledEntity, Set[SchemaSubVersion]] + shouldNotExist: List[String], + shouldExist: Map[String, Set[SchemaSubVersion]] ): MatchResult[Any] = { - val output = TabledEntity.forEvent(input, schemasToSkip.map(schemas => SchemaCriterion.parse(schemas).get)) - - output.keySet.toList must not(containAnyOf(shouldNotExist)) and - (output must beEqualTo(shouldExist)) + val criterion = schemasToSkip.map(schemas => SchemaCriterion.parse(schemas).get) + NonAtomicFields + .resolveTypes(embeddedResolver, input, criterion) + .map { output => + val mapped = output.fields.map(entity => (entity.mergedField.name, entity.mergedVersions)).toMap + output.fields.map(_.mergedField.name) must not(containAnyOf(shouldNotExist)) and + (mapped must beEqualTo(shouldExist)) + } + .unsafeRunSync() } }