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_CONSUMER_DATA_SCHEMA;
32 import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA;
33 import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA_SCHEMA;
34 import static org.oran.smo.teiv.utils.TiesTestConstants.KAFKA_RETRY_INTERVAL_10_MS;
35 import static org.oran.smo.teiv.utils.TiesTestConstants.SPRING_BOOT_SERVER_HOST;
36 import static org.oran.smo.teiv.utils.TiesTestConstants.SPRING_BOOT_SERVER_PORT;
38 import java.util.Collections;
39 import java.util.List;
41 import java.util.Properties;
42 import java.util.function.Supplier;
43 import javax.sql.DataSource;
45 import org.apache.kafka.clients.consumer.ConsumerConfig;
46 import org.apache.kafka.clients.consumer.KafkaConsumer;
47 import org.apache.kafka.common.serialization.StringDeserializer;
49 import org.jooq.DSLContext;
50 import org.jooq.JSONB;
51 import org.jooq.Record1;
52 import org.jooq.Result;
53 import org.jooq.SQLDialect;
54 import org.jooq.SelectConditionStep;
55 import org.jooq.impl.DSL;
56 import org.junit.jupiter.api.BeforeAll;
57 import org.junit.jupiter.api.BeforeEach;
58 import org.junit.jupiter.api.AfterEach;
59 import org.junit.jupiter.api.Test;
60 import org.oran.smo.teiv.db.TestPostgresqlContainer;
61 import org.oran.smo.teiv.startup.SchemaCleanUpHandler;
62 import org.springframework.beans.factory.annotation.Autowired;
63 import org.springframework.beans.factory.annotation.Value;
64 import org.springframework.test.annotation.DirtiesContext;
65 import org.springframework.test.annotation.DirtiesContext.ClassMode;
66 import org.springframework.boot.jdbc.DataSourceBuilder;
67 import org.springframework.boot.test.context.SpringBootTest;
68 import org.springframework.boot.test.mock.mockito.MockBean;
69 import org.springframework.test.context.ActiveProfiles;
70 import org.springframework.test.context.DynamicPropertyRegistry;
71 import org.springframework.test.context.DynamicPropertySource;
72 import org.springframework.kafka.test.context.EmbeddedKafka;
74 import com.fasterxml.jackson.databind.ObjectMapper;
78 import org.oran.smo.teiv.api.model.OranTeivDecorator;
79 import org.oran.smo.teiv.exception.TiesException;
80 import org.oran.smo.teiv.schema.PostgresSchemaLoader;
81 import org.oran.smo.teiv.schema.SchemaLoaderException;
82 import org.oran.smo.teiv.startup.SchemaHandler;
83 import org.oran.smo.teiv.utils.JooqTypeConverter;
85 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
87 @ActiveProfiles({ "test", "exposure" })
88 @SpringBootTest(properties = { SPRING_BOOT_SERVER_HOST, SPRING_BOOT_SERVER_PORT, KAFKA_RETRY_INTERVAL_10_MS })
89 class DecoratorsServiceContainerizedTest {
91 public static TestPostgresqlContainer postgreSQLContainer = TestPostgresqlContainer.getInstance();
92 private static DSLContext writeDataDslContext;
93 private static DSLContext readDataDslContext;
95 private KafkaConsumer<String, String> testConsumer;
97 private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction");
99 private static final String ENTITY_ID = "urn:3gpp:dn:SubNetwork=Europe,SubNetwork=Hungary,MeContext=1,ManagedElement=16,ODUFunction=16";
100 private static final String ENTITY_TYPE = "ODUFunction";
101 private static final String RELATIONSHIP_ID = "urn:o-ran:smo:teiv:sha512:MANAGEDELEMENT_MANAGES_ODUFUNCTION=D67357F682531C7B068486313B0FDAC3E719A166229520196FB9CE917E0236754226A5BCBF7BB7240E516D7ED3FEA852855EC3F121DD4BAFEC5646F2A37F57EE";
102 private static final String ENTITY_DECORATORS = String.format(QUOTED_STRING, CONSUMER_DATA_PREFIX + DECORATORS);
103 private static final String RELATIONSHIP_TYPE = "MANAGEDELEMENT_MANAGES_ODUFUNCTION";
104 private static final String RELATIONSHIP_DECORATORS = String.format(QUOTED_STRING,
105 REL_PREFIX + CONSUMER_DATA_PREFIX + DECORATORS + "_MANAGEDELEMENT_MANAGES_ODUFUNCTION");
108 @Value("${spring.embedded.kafka.brokers}")
109 private String embeddedKafkaServer;
112 private DecoratorsService decoratorsService;
115 private ObjectMapper objectMapper;
118 private SchemaHandler schemaHandler;
121 private SchemaCleanUpHandler schemaCleanUpHandler;
123 @DynamicPropertySource
124 static void setProperties(DynamicPropertyRegistry registry) {
125 registry.add("spring.datasource.read.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
126 registry.add("spring.datasource.read.username", () -> postgreSQLContainer.getUsername());
127 registry.add("spring.datasource.read.password", () -> postgreSQLContainer.getPassword());
129 registry.add("spring.datasource.write.jdbc-url", () -> postgreSQLContainer.getJdbcUrl());
130 registry.add("spring.datasource.write.username", () -> postgreSQLContainer.getUsername());
131 registry.add("spring.datasource.write.password", () -> postgreSQLContainer.getPassword());
135 static void setupAll() throws SchemaLoaderException {
136 String url = postgreSQLContainer.getJdbcUrl();
137 DataSource ds = DataSourceBuilder.create().url(url).username("test").password("test").build();
138 DSLContext dslContext = DSL.using(ds, SQLDialect.POSTGRES);
139 PostgresSchemaLoader postgresSchemaLoader = new PostgresSchemaLoader(dslContext, new ObjectMapper());
140 writeDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
141 readDataDslContext = DSL.using(ds, SQLDialect.POSTGRES);
142 postgresSchemaLoader.loadSchemaRegistry();
143 TestPostgresqlContainer.loadSampleData();
147 public void setupEach() {
148 TestPostgresqlContainer.truncateSchemas(List.of(TIES_DATA_SCHEMA, TIES_CONSUMER_DATA_SCHEMA), writeDataDslContext);
149 TestPostgresqlContainer.loadSampleData();
150 Supplier<String> brokers = this::getEmbeddedKafkaServer;
151 testConsumer = createConsumerForTest(getEmbeddedKafkaServer());
155 public void cleanupEach() {
156 testConsumer.close();
160 void testAdd_emptyEntityDecorators() {
161 Map<String, Object> decoratorsToMerge = Collections.emptyMap();
162 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata",
165 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
166 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
168 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
172 void testAdd_entityDecorators() {
173 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
174 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
177 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
178 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
180 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
184 void testAdd_relationshipDecorators() {
185 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
186 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
189 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
190 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
193 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
197 void testAdd_entityAndRelationshipDecorators() {
198 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:intdata", 456);
199 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 456, "test-app-module:textdata",
202 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
203 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
205 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
206 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
210 void testAdd_entityAndRelationshipInvalidNotAvailableDecorators() {
211 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location_WRONG", "Stockholm",
212 "test-app-module:data_WRONG", true);
214 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge)
215 .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(
216 OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class);
220 void testAdd_entityAndRelationshipInvalidNotCompatibleDecorators() {
221 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location", true, "test-app-module:data",
224 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge)
225 .entityIds(List.of(ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(
226 OranTeivDecorator.OperationEnum.MERGE).build())).isInstanceOf(TiesException.class);
230 void testUpdate_entityDecorators() {
231 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
232 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
235 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
236 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
238 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
242 void testUpdate_relationshipDecorators() {
243 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
244 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
247 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
248 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
251 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
255 void testUpdate_entityAndRelationshipDecorators() {
256 Map<String, Object> decoratorsToMerge = Map.of("test-app-module:textdata", "Budapest");
257 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Budapest", "test-app-module:intdata",
260 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
261 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
263 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
264 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
268 void testDelete_emptyEntityDecorators() {
269 Map<String, Object> decoratorsToDelete = Collections.emptyMap();
270 Map<String, Object> decoratorsExpected = Map.of("test-app-module:textdata", "Stockholm", "test-app-module:intdata",
273 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
274 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
276 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
280 void testDelete_existingEntityDecorators() {
281 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
282 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
284 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
285 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
287 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
291 void testDelete_existingEntityDecorators_withNotExistingEntityId() {
292 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
294 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete)
295 .entityIds(List.of("WRONG_ID")).relationshipIds(Collections.emptyList()).operation(
296 OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class);
300 void testDelete_existingRelationshipDecorators() {
301 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
302 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
304 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(Collections
305 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE)
308 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
312 void testDelete_existingRelationshipDecorators_withNotExistingRelationshipId() {
313 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
315 assertThatThrownBy(() -> decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete)
316 .entityIds(Collections.emptyList()).relationshipIds(List.of("WRONG_ID")).operation(
317 OranTeivDecorator.OperationEnum.DELETE).build())).isInstanceOf(TiesException.class);
321 void testDelete_existingEntityAndRelationshipDecorators() {
322 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
323 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123);
325 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
326 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
328 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
329 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
333 void testDelete_notExistingEntityAndRelationshipDecorators() {
334 Map<String, Object> decoratorsToDelete = Map.of("test-app-module:location_WRONG", "Stockholm");
335 Map<String, Object> decoratorsExpected = Map.of("test-app-module:intdata", 123, "test-app-module:textdata",
338 decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
339 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
341 verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
342 verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
345 private void verifyDecorators(String fieldName, String tableName, String id, Map<String, Object> decoratorsExpected) {
346 SelectConditionStep<Record1<JSONB>> select = readDataDslContext.select(field(fieldName, JSONB.class)).from(table(
347 tableName)).where(field("id").eq(id));
349 Result<Record1<JSONB>> result = select.fetch();
351 Map<String, Object> decoratorsActual = JooqTypeConverter.jsonbToMap(result.get(0).value1());
353 assertEquals(decoratorsExpected, decoratorsActual);
356 // TODO: create common utility lib
357 private KafkaConsumer<String, String> createConsumerForTest(String server) {
358 Properties properties = new Properties();
359 properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, server);
360 properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
361 properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
362 properties.put("auto.offset.reset", "earliest");
363 return new KafkaConsumer<>(properties);