From: JvD_Ericsson Date: Mon, 14 Oct 2024 14:57:14 +0000 (+0100) Subject: TEIV: Add tests for Classifiers and Decorators X-Git-Tag: 0.1.0~8^2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F14%2F13614%2F1;p=smo%2Fteiv.git TEIV: Add tests for Classifiers and Decorators Issue-ID: SMO-162 Change-Id: If15f8f7b1811b562d0eeb9724c7562e8f898aa39 Signed-off-by: JvD_Ericsson --- diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 3976d79..d8b149c 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -87,6 +87,10 @@ services: image: o-ran-sc/smo-teiv-exposure:latest ports: - 31074:8080 +# Uncomment for debug +# - 5005:5005 +# environment: +# JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 depends_on: - dbpostgresql deploy: diff --git a/teiv/src/test/java/org/oran/smo/teiv/exposure/classifiers/api/ClassifiersServiceContainerizedTest.java b/teiv/src/test/java/org/oran/smo/teiv/exposure/classifiers/api/ClassifiersServiceContainerizedTest.java new file mode 100644 index 0000000..de2ecc1 --- /dev/null +++ b/teiv/src/test/java/org/oran/smo/teiv/exposure/classifiers/api/ClassifiersServiceContainerizedTest.java @@ -0,0 +1,332 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Ericsson + * Modifications Copyright (C) 2024 OpenInfra Foundation Europe + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.oran.smo.teiv.exposure.classifiers.api; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.table; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.oran.smo.teiv.utils.TiesConstants.CLASSIFIERS; +import static org.oran.smo.teiv.utils.TiesConstants.CONSUMER_DATA_PREFIX; +import static org.oran.smo.teiv.utils.TiesConstants.QUOTED_STRING; +import static org.oran.smo.teiv.utils.TiesConstants.REL_PREFIX; +import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA; +import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA_SCHEMA; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.jooq.DSLContext; +import org.jooq.JSONB; +import org.jooq.Record1; +import org.jooq.Result; +import org.jooq.SQLDialect; +import org.jooq.SelectConditionStep; +import org.jooq.impl.DSL; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.kafka.test.context.EmbeddedKafka; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.apache.kafka.common.serialization.StringDeserializer; + +import lombok.Getter; + +import javax.sql.DataSource; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +import org.oran.smo.teiv.api.model.OranTeivClassifier; +import org.oran.smo.teiv.api.model.OranTeivClassifier.OperationEnum; +import org.oran.smo.teiv.db.TestPostgresqlContainerV1; +import org.oran.smo.teiv.exception.TiesException; +import org.oran.smo.teiv.schema.PostgresSchemaLoader; +import org.oran.smo.teiv.schema.SchemaLoaderException; +import org.oran.smo.teiv.startup.SchemaHandler; +import org.oran.smo.teiv.utils.JooqTypeConverter; + +import lombok.extern.slf4j.Slf4j; + +@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) +@EmbeddedKafka +@Slf4j +@SpringBootTest(properties = { + "kafka.server.bootstrap-server-host:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[0]}", + "kafka.server.bootstrap-server-port:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[1]}", + "kafka.availability.retryIntervalMs:10", "data-catalog.use-dc-for-kafka-bootstrap:true", + "notification.consumer-data.enabled:true" }) +@ActiveProfiles({ "test", "exposure" }) +class ClassifiersServiceContainerizedTest { + + public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance(); + private static DSLContext writeDataDslContext; + private static DSLContext readDataDslContext; + @Autowired + private ClassifiersService classifiersService; + + @Getter + @Value("${spring.embedded.kafka.brokers}") + private String embeddedKafkaServer; + + private KafkaConsumer testConsumer; + + private static final String ENTITY_TYPE = "ODUFunction"; + private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction"); + private static final String ENTITY_ID = "urn:3gpp:dn:SubNetwork=Europe,SubNetwork=Hungary,MeContext=1,ManagedElement=16,ODUFunction=16"; + private static final String RELATIONSHIP_ID = "urn:o-ran:smo:teiv:sha512:MANAGEDELEMENT_MANAGES_ODUFUNCTION=D67357F682531C7B068486313B0FDAC3E719A166229520196FB9CE917E0236754226A5BCBF7BB7240E516D7ED3FEA852855EC3F121DD4BAFEC5646F2A37F57EE"; + private static final String RELATIONSHIP_TYPE = "MANAGEDELEMENT_MANAGES_ODUFUNCTION"; + private static final String ENTITY_CLASSIFIERS = String.format(QUOTED_STRING, CONSUMER_DATA_PREFIX + CLASSIFIERS); + private static final String RELATIONSHIP_CLASSIFIERS = String.format(QUOTED_STRING, + REL_PREFIX + CONSUMER_DATA_PREFIX + CLASSIFIERS + "_" + RELATIONSHIP_TYPE); + + @MockBean + private SchemaHandler schemaHandler; + + @DynamicPropertySource + static void setProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.read.jdbc-url", () -> postgreSQLContainer.getJdbcUrl()); + registry.add("spring.datasource.read.username", () -> postgreSQLContainer.getUsername()); + registry.add("spring.datasource.read.password", () -> postgreSQLContainer.getPassword()); + + registry.add("spring.datasource.write.jdbc-url", () -> postgreSQLContainer.getJdbcUrl()); + registry.add("spring.datasource.write.username", () -> postgreSQLContainer.getUsername()); + registry.add("spring.datasource.write.password", () -> postgreSQLContainer.getPassword()); + } + + @BeforeAll + static void setUpAll() throws SchemaLoaderException { + String url = postgreSQLContainer.getJdbcUrl(); + DataSource ds = DataSourceBuilder.create().url(url).username("test").password("test").build(); + DSLContext dslContext = DSL.using(ds, SQLDialect.POSTGRES); + PostgresSchemaLoader postgresSchemaLoader = new PostgresSchemaLoader(dslContext, new ObjectMapper()); + writeDataDslContext = DSL.using(ds, SQLDialect.POSTGRES); + readDataDslContext = DSL.using(ds, SQLDialect.POSTGRES); + postgresSchemaLoader.loadSchemaRegistry(); + TestPostgresqlContainerV1.loadSampleData(); + } + + @BeforeEach + public void setUp() { + testConsumer = createConsumerForTest(getEmbeddedKafkaServer()); + } + + @BeforeEach + public void reloadBeforeEach() { + reloadData(); + } + + @AfterAll + public static void reloadAfterAll() { + reloadData(); + } + + @AfterEach + public void cleanUp() { + testConsumer.close(); + } + + private static void reloadData() { + writeDataDslContext.meta().filterSchemas(s -> s.getName().equals(TIES_DATA_SCHEMA)).getTables().forEach( + t -> writeDataDslContext.truncate(t).cascade().execute()); + TestPostgresqlContainerV1.loadSampleData(); + } + + @Test + void testAdd_entityClassifiers_emptyList() { + List classifiersToMerge = Collections.emptyList(); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testAdd_entityClassifiers() { + List classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday"); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testAdd_relationshipClassifiers() { + List classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday"); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(Collections + .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build()); + + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testAdd_entityAndRelationshipClassifiers() { + List classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday"); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testAddDuplicates_entityAndRelationshipClassifiers() { + List classifiersToMerge = List.of("test-app-module:Indoor", "test-app-module:Rural"); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testAdd_entityAndRelationshipInvalidClassifiers() { + List classifiersToMerge = List.of("test-app-module:Indoor_WRONG", "test-app-module:Rural_WRONG"); + + assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge) + .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE) + .build())).isInstanceOf(TiesException.class); + } + + @Test + void testDelete_emptyEntityClassifiers() { + List classifiersToDelete = Collections.emptyList(); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of( + ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testDelete_existingEntityClassifiers() { + List classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend"); + List classifiersExpected = List.of("test-app-module:Indoor"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of( + ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testDelete_entityClassifiers_withNotExistingEntityId() { + List classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend"); + + assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete) + .entityIds(List.of("WRONG_ID")).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE) + .build())).isInstanceOf(TiesException.class); + } + + @Test + void testDelete_existingRelationshipClassifiers() { + List classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend"); + List classifiersExpected = List.of("test-app-module:Indoor"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(Collections + .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build()); + + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testDelete_relationshipClassifiers_withNotExistingRelationshipId() { + List classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend"); + + assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete) + .entityIds(Collections.emptyList()).relationshipIds(List.of("WRONG_ID")).operation(OperationEnum.DELETE) + .build())).isInstanceOf(TiesException.class); + } + + @Test + void testDelete_existingEntityAndRelationshipClassifiers() { + List classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend"); + List classifiersExpected = List.of("test-app-module:Indoor"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of( + ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + @Test + void testDelete_NotExistingEntityAndRelationshipClassifiers() { + List classifiersToDelete = List.of("test-app-module:Rural_WRONG", "test-app-module:Weekend_WRONG"); + List classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural", + "test-app-module:Weekend"); + + classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of( + ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build()); + + verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected); + } + + private void verifyClassifiers(String fieldName, String tableName, String id, List classifiersExpected) { + SelectConditionStep> select = readDataDslContext.select(field(fieldName, JSONB.class)).from(table( + tableName)).where(field("id").eq(id)); + + Result> result = select.fetch(); + + List classifiersActual = JooqTypeConverter.jsonbToList(result.get(0).value1()); + + assertEquals(classifiersExpected, classifiersActual); + } + + // TODO: create common utility lib + private KafkaConsumer createConsumerForTest(String server) { + Properties properties = new Properties(); + properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, server); + properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put("auto.offset.reset", "earliest"); + return new KafkaConsumer<>(properties); + } +} diff --git a/teiv/src/test/java/org/oran/smo/teiv/exposure/decorators/api/DecoratorsServiceContainerizedTest.java b/teiv/src/test/java/org/oran/smo/teiv/exposure/decorators/api/DecoratorsServiceContainerizedTest.java new file mode 100644 index 0000000..30d8352 --- /dev/null +++ b/teiv/src/test/java/org/oran/smo/teiv/exposure/decorators/api/DecoratorsServiceContainerizedTest.java @@ -0,0 +1,372 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Ericsson + * Modifications Copyright (C) 2024 OpenInfra Foundation Europe + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +package org.oran.smo.teiv.exposure.decorators.api; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.table; +import static org.oran.smo.teiv.utils.TiesConstants.CONSUMER_DATA_PREFIX; +import static org.oran.smo.teiv.utils.TiesConstants.DECORATORS; +import static org.oran.smo.teiv.utils.TiesConstants.QUOTED_STRING; +import static org.oran.smo.teiv.utils.TiesConstants.REL_PREFIX; +import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA; +import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA_SCHEMA; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.sql.DataSource; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.common.serialization.StringDeserializer; + +import org.jooq.DSLContext; +import org.jooq.JSONB; +import org.jooq.Record1; +import org.jooq.Result; +import org.jooq.SQLDialect; +import org.jooq.SelectConditionStep; +import org.jooq.impl.DSL; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.kafka.test.context.EmbeddedKafka; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.extern.slf4j.Slf4j; +import lombok.Getter; + +import org.oran.smo.teiv.api.model.OranTeivDecorator; +import org.oran.smo.teiv.db.TestPostgresqlContainerV1; +import org.oran.smo.teiv.exception.TiesException; +import org.oran.smo.teiv.schema.PostgresSchemaLoader; +import org.oran.smo.teiv.schema.SchemaLoaderException; +import org.oran.smo.teiv.startup.SchemaHandler; +import org.oran.smo.teiv.utils.JooqTypeConverter; + +@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) +@EmbeddedKafka +@Slf4j +@SpringBootTest(properties = { + "kafka.server.bootstrap-server-host:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[0]}", + "kafka.server.bootstrap-server-port:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[1]}", + "kafka.availability.retryIntervalMs:10", "notification.consumer-data.enabled:true" }) +@ActiveProfiles({ "test", "exposure" }) +class DecoratorsServiceContainerizedTest { + + public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance(); + private static DSLContext writeDataDslContext; + private static DSLContext readDataDslContext; + @Autowired + private DecoratorsService decoratorsService; + + @Getter + @Value("${spring.embedded.kafka.brokers}") + private String embeddedKafkaServer; + + private KafkaConsumer testConsumer; + + private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction"); + + private static final String ENTITY_ID = "urn:3gpp:dn:SubNetwork=Europe,SubNetwork=Hungary,MeContext=1,ManagedElement=16,ODUFunction=16"; + private static final String ENTITY_TYPE = "ODUFunction"; + private static final String RELATIONSHIP_ID = "urn:o-ran:smo:teiv:sha512:MANAGEDELEMENT_MANAGES_ODUFUNCTION=D67357F682531C7B068486313B0FDAC3E719A166229520196FB9CE917E0236754226A5BCBF7BB7240E516D7ED3FEA852855EC3F121DD4BAFEC5646F2A37F57EE"; + private static final String ENTITY_DECORATORS = String.format(QUOTED_STRING, CONSUMER_DATA_PREFIX + DECORATORS); + private static final String RELATIONSHIP_TYPE = "MANAGEDELEMENT_MANAGES_ODUFUNCTION"; + private static final String RELATIONSHIP_DECORATORS = String.format(QUOTED_STRING, + REL_PREFIX + CONSUMER_DATA_PREFIX + DECORATORS + "_MANAGEDELEMENT_MANAGES_ODUFUNCTION"); + + @MockBean + private SchemaHandler schemaHandler; + + @DynamicPropertySource + static void setProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.read.jdbc-url", () -> postgreSQLContainer.getJdbcUrl()); + registry.add("spring.datasource.read.username", () -> postgreSQLContainer.getUsername()); + registry.add("spring.datasource.read.password", () -> postgreSQLContainer.getPassword()); + + registry.add("spring.datasource.write.jdbc-url", () -> postgreSQLContainer.getJdbcUrl()); + registry.add("spring.datasource.write.username", () -> postgreSQLContainer.getUsername()); + registry.add("spring.datasource.write.password", () -> postgreSQLContainer.getPassword()); + } + + @BeforeAll + static void setUpAll() throws SchemaLoaderException { + String url = postgreSQLContainer.getJdbcUrl(); + DataSource ds = DataSourceBuilder.create().url(url).username("test").password("test").build(); + DSLContext dslContext = DSL.using(ds, SQLDialect.POSTGRES); + PostgresSchemaLoader postgresSchemaLoader = new PostgresSchemaLoader(dslContext, new ObjectMapper()); + writeDataDslContext = DSL.using(ds, SQLDialect.POSTGRES); + readDataDslContext = DSL.using(ds, SQLDialect.POSTGRES); + postgresSchemaLoader.loadSchemaRegistry(); + TestPostgresqlContainerV1.loadSampleData(); + } + + @BeforeEach + public void setUp() { + testConsumer = createConsumerForTest(getEmbeddedKafkaServer()); + } + + @BeforeEach + public void reloadBeforeEach() { + reloadData(); + } + + @AfterAll + public static void reloadAfterAll() { + reloadData(); + } + + @AfterEach + public void cleanUp() { + testConsumer.close(); + } + + private static void reloadData() { + writeDataDslContext.meta().filterSchemas(s -> s.getName().equals(TIES_DATA_SCHEMA)).getTables().forEach( + t -> writeDataDslContext.truncate(t).cascade().execute()); + TestPostgresqlContainerV1.loadSampleData(); + } + + @Test + void testAdd_emptyEntityDecorators() { + Map decoratorsToMerge = Collections.emptyMap(); + Map decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata", + 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testAdd_entityDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:intdata", 456); + Map decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata", + "Stockholm"); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testAdd_relationshipDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:intdata", 456); + Map decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata", + "Stockholm"); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections + .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE) + .build()); + + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testAdd_entityAndRelationshipDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:intdata", 456); + Map decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata", + "Stockholm"); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testAdd_entityAndRelationshipInvalidNotAvailableDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:location_WRONG", "Stockholm", + "test-app-module:data_WRONG", true); + + assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge) + .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation( + OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class); + } + + @Test + void testAdd_entityAndRelationshipInvalidNotCompatibleDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:location", true, "test-app-module:data", + "Stockholm"); + + assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge) + .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation( + OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class); + } + + @Test + void testUpdate_entityDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest"); + Map decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata", + 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testUpdate_relationshipDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest"); + Map decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata", + 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections + .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE) + .build()); + + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testUpdate_entityAndRelationshipDecorators() { + Map decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest"); + Map decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata", + 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testDelete_emptyEntityDecorators() { + Map decoratorsToDelete = Collections.emptyMap(); + Map decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata", + 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testDelete_existingEntityDecorators() { + Map decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm"); + Map decoratorsExpected = Map.of("test-app-module:intdata", 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID)) + .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testDelete_existingEntityDecorators_withNotExistingEntityId() { + Map decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm"); + + assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete) + .entityIds(List.of("WRONG_ID")).relationshipIds(Collections.emptyList()).operation( + OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class); + } + + @Test + void testDelete_existingRelationshipDecorators() { + Map decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm"); + Map decoratorsExpected = Map.of("test-app-module:intdata", 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(Collections + .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE) + .build()); + + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testDelete_existingRelationshipDecorators_withNotExistingRelationshipId() { + Map decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm"); + + assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete) + .entityIds(Collections.emptyList()).relationshipIds(List.of("WRONG_ID")).operation( + OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class); + } + + @Test + void testDelete_existingEntityAndRelationshipDecorators() { + Map decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm"); + Map decoratorsExpected = Map.of("test-app-module:intdata", 123); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + @Test + void testDelete_notExistingEntityAndRelationshipDecorators() { + Map decoratorsToDelete = Map.of("test-app-module:location_WRONG", "Stockholm"); + Map decoratorsExpected = Map.of("test-app-module:intdata", 123, "test-app-module:textdata", + "Stockholm"); + + decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID)) + .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build()); + + verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected); + } + + private void verifyDecorators(String fieldName, String tableName, String id, Map decoratorsExpected) { + SelectConditionStep> select = readDataDslContext.select(field(fieldName, JSONB.class)).from(table( + tableName)).where(field("id").eq(id)); + + Result> result = select.fetch(); + + Map decoratorsActual = JooqTypeConverter.jsonbToMap(result.get(0).value1()); + + assertEquals(decoratorsExpected, decoratorsActual); + } + + // TODO: create common utility lib + private KafkaConsumer createConsumerForTest(String server) { + Properties properties = new Properties(); + properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, server); + properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + properties.put("auto.offset.reset", "earliest"); + return new KafkaConsumer<>(properties); + } + +}