2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Ericsson
4 * Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.teiv.exposure.classifiers.api;
23 import static org.assertj.core.api.Assertions.assertThatThrownBy;
24 import static org.jooq.impl.DSL.field;
25 import static org.jooq.impl.DSL.table;
26 import static org.junit.jupiter.api.Assertions.assertEquals;
27 import static org.oran.smo.teiv.utils.TiesConstants.CLASSIFIERS;
28 import static org.oran.smo.teiv.utils.TiesConstants.CONSUMER_DATA_PREFIX;
29 import static org.oran.smo.teiv.utils.TiesConstants.QUOTED_STRING;
30 import static org.oran.smo.teiv.utils.TiesConstants.REL_PREFIX;
31 import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA;
32 import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA_SCHEMA;
34 import com.fasterxml.jackson.databind.ObjectMapper;
35 import org.apache.kafka.clients.consumer.ConsumerConfig;
36 import org.apache.kafka.clients.consumer.KafkaConsumer;
37 import org.jooq.DSLContext;
38 import org.jooq.JSONB;
39 import org.jooq.Record1;
40 import org.jooq.Result;
41 import org.jooq.SQLDialect;
42 import org.jooq.SelectConditionStep;
43 import org.jooq.impl.DSL;
44 import org.junit.jupiter.api.AfterAll;
45 import org.junit.jupiter.api.AfterEach;
46 import org.junit.jupiter.api.BeforeAll;
47 import org.junit.jupiter.api.BeforeEach;
48 import org.junit.jupiter.api.Test;
49 import org.springframework.beans.factory.annotation.Autowired;
50 import org.springframework.beans.factory.annotation.Value;
51 import org.springframework.boot.jdbc.DataSourceBuilder;
52 import org.springframework.boot.test.context.SpringBootTest;
53 import org.springframework.boot.test.mock.mockito.MockBean;
54 import org.springframework.kafka.test.context.EmbeddedKafka;
55 import org.springframework.test.annotation.DirtiesContext;
56 import org.springframework.test.annotation.DirtiesContext.ClassMode;
57 import org.springframework.test.context.ActiveProfiles;
58 import org.springframework.test.context.DynamicPropertyRegistry;
59 import org.springframework.test.context.DynamicPropertySource;
60 import org.apache.kafka.common.serialization.StringDeserializer;
64 import javax.sql.DataSource;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.Properties;
69 import org.oran.smo.teiv.api.model.OranTeivClassifier;
70 import org.oran.smo.teiv.api.model.OranTeivClassifier.OperationEnum;
71 import org.oran.smo.teiv.db.TestPostgresqlContainerV1;
72 import org.oran.smo.teiv.exception.TiesException;
73 import org.oran.smo.teiv.schema.PostgresSchemaLoader;
74 import org.oran.smo.teiv.schema.SchemaLoaderException;
75 import org.oran.smo.teiv.startup.SchemaHandler;
76 import org.oran.smo.teiv.utils.JooqTypeConverter;
78 import lombok.extern.slf4j.Slf4j;
80 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
83 @SpringBootTest(properties = {
84 "kafka.server.bootstrap-server-host:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[0]}",
85 "kafka.server.bootstrap-server-port:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[1]}",
86 "kafka.availability.retryIntervalMs:10", "data-catalog.use-dc-for-kafka-bootstrap:true",
87 "notification.consumer-data.enabled:true" })
88 @ActiveProfiles({ "test", "exposure" })
89 class ClassifiersServiceContainerizedTest {
91 public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance();
92 private static DSLContext writeDataDslContext;
93 private static DSLContext readDataDslContext;
95 private ClassifiersService classifiersService;
98 @Value("${spring.embedded.kafka.brokers}")
99 private String embeddedKafkaServer;
101 private KafkaConsumer<String, String> testConsumer;
103 private static final String ENTITY_TYPE = "ODUFunction";
104 private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction");
105 private static final String ENTITY_ID = "urn:3gpp:dn:SubNetwork=Europe,SubNetwork=Hungary,MeContext=1,ManagedElement=16,ODUFunction=16";
106 private static final String RELATIONSHIP_ID = "urn:o-ran:smo:teiv:sha512:MANAGEDELEMENT_MANAGES_ODUFUNCTION=D67357F682531C7B068486313B0FDAC3E719A166229520196FB9CE917E0236754226A5BCBF7BB7240E516D7ED3FEA852855EC3F121DD4BAFEC5646F2A37F57EE";
107 private static final String RELATIONSHIP_TYPE = "MANAGEDELEMENT_MANAGES_ODUFUNCTION";
108 private static final String ENTITY_CLASSIFIERS = String.format(QUOTED_STRING, CONSUMER_DATA_PREFIX + CLASSIFIERS);
109 private static final String RELATIONSHIP_CLASSIFIERS = String.format(QUOTED_STRING,
110 REL_PREFIX + CONSUMER_DATA_PREFIX + CLASSIFIERS + "_" + RELATIONSHIP_TYPE);
113 private SchemaHandler schemaHandler;
115 @DynamicPropertySource
116 static void setProperties(DynamicPropertyRegistry registry) {
117 registry.add("spring.datasource.read.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
118 registry.add("spring.datasource.read.username", () -> postgreSQLContainer.getUsername());
119 registry.add("spring.datasource.read.password", () -> postgreSQLContainer.getPassword());
121 registry.add("spring.datasource.write.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
122 registry.add("spring.datasource.write.username", () -> postgreSQLContainer.getUsername());
123 registry.add("spring.datasource.write.password", () -> postgreSQLContainer.getPassword());
127 static void setUpAll() throws SchemaLoaderException {
128 String url = postgreSQLContainer.getJdbcUrl();
129 DataSource ds = DataSourceBuilder.create().url(url).username("test").password("test").build();
130 DSLContext dslContext = DSL.using(ds, SQLDialect.POSTGRES);
131 PostgresSchemaLoader postgresSchemaLoader = new PostgresSchemaLoader(dslContext, new ObjectMapper());
132 writeDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
133 readDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
134 postgresSchemaLoader.loadSchemaRegistry();
135 TestPostgresqlContainerV1.loadSampleData();
139 public void setUp() {
140 testConsumer = createConsumerForTest(getEmbeddedKafkaServer());
144 public void reloadBeforeEach() {
149 public static void reloadAfterAll() {
154 public void cleanUp() {
155 testConsumer.close();
158 private static void reloadData() {
159 writeDataDslContext.meta().filterSchemas(s -> s.getName().equals(TIES_DATA_SCHEMA)).getTables().forEach(
160 t -> writeDataDslContext.truncate(t).cascade().execute());
161 TestPostgresqlContainerV1.loadSampleData();
165 void testAdd_entityClassifiers_emptyList() {
166 List<String> classifiersToMerge = Collections.emptyList();
167 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
168 "test-app-module:Weekend");
170 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
171 .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build());
173 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
177 void testAdd_entityClassifiers() {
178 List<String> classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday");
179 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
180 "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday");
182 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
183 .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build());
185 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
189 void testAdd_relationshipClassifiers() {
190 List<String> classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday");
191 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
192 "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday");
194 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(Collections
195 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
197 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
201 void testAdd_entityAndRelationshipClassifiers() {
202 List<String> classifiersToMerge = List.of("test-app-module:Outdoor", "test-app-module:Weekday");
203 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
204 "test-app-module:Weekend", "test-app-module:Outdoor", "test-app-module:Weekday");
206 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
207 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
209 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
210 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
214 void testAddDuplicates_entityAndRelationshipClassifiers() {
215 List<String> classifiersToMerge = List.of("test-app-module:Indoor", "test-app-module:Rural");
216 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
217 "test-app-module:Weekend");
219 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
220 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
222 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
223 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
227 void testAdd_entityAndRelationshipInvalidClassifiers() {
228 List<String> classifiersToMerge = List.of("test-app-module:Indoor_WRONG", "test-app-module:Rural_WRONG");
230 assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge)
231 .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE)
232 .build())).isInstanceOf(TiesException.class);
236 void testDelete_emptyEntityClassifiers() {
237 List<String> classifiersToDelete = Collections.emptyList();
238 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
239 "test-app-module:Weekend");
241 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
242 ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build());
244 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
248 void testDelete_existingEntityClassifiers() {
249 List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
250 List<String> classifiersExpected = List.of("test-app-module:Indoor");
252 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
253 ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build());
255 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
259 void testDelete_entityClassifiers_withNotExistingEntityId() {
260 List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
262 assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete)
263 .entityIds(List.of("WRONG_ID")).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE)
264 .build())).isInstanceOf(TiesException.class);
268 void testDelete_existingRelationshipClassifiers() {
269 List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
270 List<String> classifiersExpected = List.of("test-app-module:Indoor");
272 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(Collections
273 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
275 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
279 void testDelete_relationshipClassifiers_withNotExistingRelationshipId() {
280 List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
282 assertThatThrownBy(() -> classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete)
283 .entityIds(Collections.emptyList()).relationshipIds(List.of("WRONG_ID")).operation(OperationEnum.DELETE)
284 .build())).isInstanceOf(TiesException.class);
288 void testDelete_existingEntityAndRelationshipClassifiers() {
289 List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
290 List<String> classifiersExpected = List.of("test-app-module:Indoor");
292 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
293 ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
295 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
296 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
300 void testDelete_NotExistingEntityAndRelationshipClassifiers() {
301 List<String> classifiersToDelete = List.of("test-app-module:Rural_WRONG", "test-app-module:Weekend_WRONG");
302 List<String> classifiersExpected = List.of("test-app-module:Indoor", "test-app-module:Rural",
303 "test-app-module:Weekend");
305 classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
306 ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
308 verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
309 verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
312 private void verifyClassifiers(String fieldName, String tableName, String id, List<String> classifiersExpected) {
313 SelectConditionStep<Record1<JSONB>> select = readDataDslContext.select(field(fieldName, JSONB.class)).from(table(
314 tableName)).where(field("id").eq(id));
316 Result<Record1<JSONB>> result = select.fetch();
318 List<String> classifiersActual = JooqTypeConverter.jsonbToList(result.get(0).value1());
320 assertEquals(classifiersExpected, classifiersActual);
323 // TODO: create common utility lib
324 private KafkaConsumer<String, String> createConsumerForTest(String server) {
325 Properties properties = new Properties();
326 properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, server);
327 properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
328 properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
329 properties.put("auto.offset.reset", "earliest");
330 return new KafkaConsumer<>(properties);