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.decorators.api;
23 import static org.assertj.core.api.Assertions.assertThatThrownBy;
24 import static org.junit.jupiter.api.Assertions.assertEquals;
25 import static org.jooq.impl.DSL.field;
26 import static org.jooq.impl.DSL.table;
27 import static org.oran.smo.teiv.utils.TiesConstants.CONSUMER_DATA_PREFIX;
28 import static org.oran.smo.teiv.utils.TiesConstants.DECORATORS;
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 java.util.Collections;
35 import java.util.List;
37 import java.util.Properties;
38 import javax.sql.DataSource;
40 import org.apache.kafka.clients.consumer.ConsumerConfig;
41 import org.apache.kafka.clients.consumer.KafkaConsumer;
42 import org.apache.kafka.common.serialization.StringDeserializer;
44 import org.jooq.DSLContext;
45 import org.jooq.JSONB;
46 import org.jooq.Record1;
47 import org.jooq.Result;
48 import org.jooq.SQLDialect;
49 import org.jooq.SelectConditionStep;
50 import org.jooq.impl.DSL;
51 import org.junit.jupiter.api.AfterAll;
52 import org.junit.jupiter.api.BeforeAll;
53 import org.junit.jupiter.api.BeforeEach;
54 import org.junit.jupiter.api.AfterEach;
55 import org.junit.jupiter.api.Test;
56 import org.springframework.beans.factory.annotation.Autowired;
57 import org.springframework.beans.factory.annotation.Value;
58 import org.springframework.test.annotation.DirtiesContext;
59 import org.springframework.test.annotation.DirtiesContext.ClassMode;
60 import org.springframework.boot.jdbc.DataSourceBuilder;
61 import org.springframework.boot.test.context.SpringBootTest;
62 import org.springframework.boot.test.mock.mockito.MockBean;
63 import org.springframework.test.context.ActiveProfiles;
64 import org.springframework.test.context.DynamicPropertyRegistry;
65 import org.springframework.test.context.DynamicPropertySource;
66 import org.springframework.kafka.test.context.EmbeddedKafka;
68 import com.fasterxml.jackson.databind.ObjectMapper;
70 import lombok.extern.slf4j.Slf4j;
73 import org.oran.smo.teiv.api.model.OranTeivDecorator;
74 import org.oran.smo.teiv.db.TestPostgresqlContainerV1;
75 import org.oran.smo.teiv.exception.TiesException;
76 import org.oran.smo.teiv.schema.PostgresSchemaLoader;
77 import org.oran.smo.teiv.schema.SchemaLoaderException;
78 import org.oran.smo.teiv.startup.SchemaHandler;
79 import org.oran.smo.teiv.utils.JooqTypeConverter;
81 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
84 @SpringBootTest(properties = {
85 "kafka.server.bootstrap-server-host:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[0]}",
86 "kafka.server.bootstrap-server-port:#{environment.getProperty(\"spring.embedded.kafka.brokers\").split(\":\")[1]}",
87 "kafka.availability.retryIntervalMs:10", "notification.consumer-data.enabled:true" })
88 @ActiveProfiles({ "test", "exposure" })
89 class DecoratorsServiceContainerizedTest {
91 public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance();
92 private static DSLContext writeDataDslContext;
93 private static DSLContext readDataDslContext;
95 private DecoratorsService decoratorsService;
98 @Value("${spring.embedded.kafka.brokers}")
99 private String embeddedKafkaServer;
101 private KafkaConsumer<String, String> testConsumer;
103 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 ENTITY_TYPE = "ODUFunction";
107 private static final String RELATIONSHIP_ID = "urn:o-ran:smo:teiv:sha512:MANAGEDELEMENT_MANAGES_ODUFUNCTION=D67357F682531C7B068486313B0FDAC3E719A166229520196FB9CE917E0236754226A5BCBF7BB7240E516D7ED3FEA852855EC3F121DD4BAFEC5646F2A37F57EE";
108 private static final String ENTITY_DECORATORS = String.format(QUOTED_STRING, CONSUMER_DATA_PREFIX + DECORATORS);
109 private static final String RELATIONSHIP_TYPE = "MANAGEDELEMENT_MANAGES_ODUFUNCTION";
110 private static final String RELATIONSHIP_DECORATORS = String.format(QUOTED_STRING,
111 REL_PREFIX + CONSUMER_DATA_PREFIX + DECORATORS + "_MANAGEDELEMENT_MANAGES_ODUFUNCTION");
114 private SchemaHandler schemaHandler;
116 @DynamicPropertySource
117 static void setProperties(DynamicPropertyRegistry registry) {
118 registry.add("spring.datasource.read.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
119 registry.add("spring.datasource.read.username", () -> postgreSQLContainer.getUsername());
120 registry.add("spring.datasource.read.password", () -> postgreSQLContainer.getPassword());
122 registry.add("spring.datasource.write.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
123 registry.add("spring.datasource.write.username", () -> postgreSQLContainer.getUsername());
124 registry.add("spring.datasource.write.password", () -> postgreSQLContainer.getPassword());
128 static void setUpAll() throws SchemaLoaderException {
129 String url = postgreSQLContainer.getJdbcUrl();
130 DataSource ds = DataSourceBuilder.create().url(url).username("test").password("test").build();
131 DSLContext dslContext = DSL.using(ds, SQLDialect.POSTGRES);
132 PostgresSchemaLoader postgresSchemaLoader = new PostgresSchemaLoader(dslContext, new ObjectMapper());
133 writeDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
134 readDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
135 postgresSchemaLoader.loadSchemaRegistry();
136 TestPostgresqlContainerV1.loadSampleData();
140 public void setUp() {
141 testConsumer = createConsumerForTest(getEmbeddedKafkaServer());
145 public void reloadBeforeEach() {
150 public static void reloadAfterAll() {
155 public void cleanUp() {
156 testConsumer.close();
159 private static void reloadData() {
160 writeDataDslContext.meta().filterSchemas(s -> s.getName().equals(TIES_DATA_SCHEMA)).getTables().forEach(
161 t -> writeDataDslContext.truncate(t).cascade().execute());
162 TestPostgresqlContainerV1.loadSampleData();
166 void testAdd_emptyEntityDecorators() {
167 Map<String, Object> decoratorsToMerge = Collections.emptyMap();
168 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata",
171 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
172 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
174 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
178 void testAdd_entityDecorators() {
179 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
180 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
183 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
184 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
186 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
190 void testAdd_relationshipDecorators() {
191 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
192 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
195 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
196 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
199 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
203 void testAdd_entityAndRelationshipDecorators() {
204 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
205 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
208 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
209 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
211 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
212 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
216 void testAdd_entityAndRelationshipInvalidNotAvailableDecorators() {
217 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location_WRONG", "Stockholm",
218 "test-app-module:data_WRONG", true);
220 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge)
221 .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(
222 OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class);
226 void testAdd_entityAndRelationshipInvalidNotCompatibleDecorators() {
227 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location", true, "test-app-module:data",
230 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge)
231 .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(
232 OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class);
236 void testUpdate_entityDecorators() {
237 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
238 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
241 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
242 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
244 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
248 void testUpdate_relationshipDecorators() {
249 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
250 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
253 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
254 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
257 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
261 void testUpdate_entityAndRelationshipDecorators() {
262 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
263 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
266 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
267 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
269 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
270 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
274 void testDelete_emptyEntityDecorators() {
275 Map<String, Object> decoratorsToDelete = Collections.emptyMap();
276 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata",
279 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
280 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
282 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
286 void testDelete_existingEntityDecorators() {
287 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
288 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
290 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
291 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
293 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
297 void testDelete_existingEntityDecorators_withNotExistingEntityId() {
298 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
300 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete)
301 .entityIds(List.of("WRONG_ID")).relationshipIds(Collections.emptyList()).operation(
302 OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class);
306 void testDelete_existingRelationshipDecorators() {
307 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
308 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
310 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(Collections
311 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE)
314 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
318 void testDelete_existingRelationshipDecorators_withNotExistingRelationshipId() {
319 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
321 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete)
322 .entityIds(Collections.emptyList()).relationshipIds(List.of("WRONG_ID")).operation(
323 OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class);
327 void testDelete_existingEntityAndRelationshipDecorators() {
328 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
329 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
331 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
332 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
334 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
335 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
339 void testDelete_notExistingEntityAndRelationshipDecorators() {
340 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:location_WRONG", "Stockholm");
341 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123, "test-app-module:textdata",
344 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
345 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
347 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
348 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
351 private void verifyDecorators(String fieldName, String tableName, String id, Map<String, Object> decoratorsExpected) {
352 SelectConditionStep<Record1<JSONB>> select = readDataDslContext.select(field(fieldName, JSONB.class)).from(table(
353 tableName)).where(field("id").eq(id));
355 Result<Record1<JSONB>> result = select.fetch();
357 Map<String, Object> decoratorsActual = JooqTypeConverter.jsonbToMap(result.get(0).value1());
359 assertEquals(decoratorsExpected, decoratorsActual);
362 // TODO: create common utility lib
363 private KafkaConsumer<String, String> createConsumerForTest(String server) {
364 Properties properties = new Properties();
365 properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, server);
366 properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
367 properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
368 properties.put("auto.offset.reset", "earliest");
369 return new KafkaConsumer<>(properties);