04c81e6ce6b0c737c0c167c54e7afe952456ff31
[smo/teiv.git] /
1 /*
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
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
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.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21 package org.oran.smo.teiv.exposure.decorators.api;
22
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;
37
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Properties;
42 import java.util.function.Supplier;
43 import javax.sql.DataSource;
44
45 import org.apache.kafka.clients.consumer.ConsumerConfig;
46 import org.apache.kafka.clients.consumer.KafkaConsumer;
47 import org.apache.kafka.common.serialization.StringDeserializer;
48
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;
73
74 import com.fasterxml.jackson.databind.ObjectMapper;
75
76 import lombok.Getter;
77
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;
84
85 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
86 @EmbeddedKafka
87 @ActiveProfiles({ "test", "exposure" })
88 @SpringBootTest(properties = { SPRING_BOOT_SERVER_HOST, SPRING_BOOT_SERVER_PORT, KAFKA_RETRY_INTERVAL_10_MS })
89 class DecoratorsServiceContainerizedTest {
90
91     public static TestPostgresqlContainer postgreSQLContainer = TestPostgresqlContainer.getInstance();
92     private static DSLContext writeDataDslContext;
93     private static DSLContext readDataDslContext;
94
95     private KafkaConsumer<String, String> testConsumer;
96
97     private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction");
98
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");
106
107     @Getter
108     @Value("${spring.embedded.kafka.brokers}")
109     private String embeddedKafkaServer;
110
111     @Autowired
112     private DecoratorsService decoratorsService;
113
114     @Autowired
115     private ObjectMapper objectMapper;
116
117     @MockBean
118     private SchemaHandler schemaHandler;
119
120     @MockBean
121     private SchemaCleanUpHandler schemaCleanUpHandler;
122
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());
128
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());
132     }
133
134     @BeforeAll
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();
144     }
145
146     @BeforeEach
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());
152     }
153
154     @AfterEach
155     public void cleanupEach() {
156         testConsumer.close();
157     }
158
159     @Test
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",
163                 123);
164
165         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
166                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
167
168         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
169     }
170
171     @Test
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",
175                 "Stockholm");
176
177         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
178                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
179
180         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
181     }
182
183     @Test
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",
187                 "Stockholm");
188
189         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
190                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
191                 .build());
192
193         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
194     }
195
196     @Test
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",
200                 "Stockholm");
201
202         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
203                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
204
205         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
206         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
207     }
208
209     @Test
210     void testAdd_entityAndRelationshipInvalidNotAvailableDecorators() {
211         Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location_WRONG", "Stockholm",
212                 "test-app-module:data_WRONG", true);
213
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);
217     }
218
219     @Test
220     void testAdd_entityAndRelationshipInvalidNotCompatibleDecorators() {
221         Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location", true, "test-app-module:data",
222                 "Stockholm");
223
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);
227     }
228
229     @Test
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",
233                 123);
234
235         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
236                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
237
238         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
239     }
240
241     @Test
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",
245                 123);
246
247         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
248                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
249                 .build());
250
251         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
252     }
253
254     @Test
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",
258                 123);
259
260         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
261                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
262
263         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
264         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
265     }
266
267     @Test
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",
271                 123);
272
273         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
274                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
275
276         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
277     }
278
279     @Test
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);
283
284         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
285                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
286
287         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
288     }
289
290     @Test
291     void testDelete_existingEntityDecorators_withNotExistingEntityId() {
292         Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
293
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);
297     }
298
299     @Test
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);
303
304         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(Collections
305                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE)
306                 .build());
307
308         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
309     }
310
311     @Test
312     void testDelete_existingRelationshipDecorators_withNotExistingRelationshipId() {
313         Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
314
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);
318     }
319
320     @Test
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);
324
325         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
326                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
327
328         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
329         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
330     }
331
332     @Test
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",
336                 "Stockholm");
337
338         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
339                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
340
341         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
342         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
343     }
344
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));
348
349         Result<Record1<JSONB>> result = select.fetch();
350
351         Map<String, Object> decoratorsActual = JooqTypeConverter.jsonbToMap(result.get(0).value1());
352
353         assertEquals(decoratorsExpected, decoratorsActual);
354     }
355
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);
364     }
365
366 }