30d8352e618e59b96dd14cb572ff79ad883796ec
[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_DATA;
32 import static org.oran.smo.teiv.utils.TiesConstants.TIES_DATA_SCHEMA;
33
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38 import javax.sql.DataSource;
39
40 import org.apache.kafka.clients.consumer.ConsumerConfig;
41 import org.apache.kafka.clients.consumer.KafkaConsumer;
42 import org.apache.kafka.common.serialization.StringDeserializer;
43
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;
67
68 import com.fasterxml.jackson.databind.ObjectMapper;
69
70 import lombok.extern.slf4j.Slf4j;
71 import lombok.Getter;
72
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;
80
81 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
82 @EmbeddedKafka
83 @Slf4j
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 {
90
91     public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance();
92     private static DSLContext writeDataDslContext;
93     private static DSLContext readDataDslContext;
94     @Autowired
95     private DecoratorsService decoratorsService;
96
97     @Getter
98     @Value("${spring.embedded.kafka.brokers}")
99     private String embeddedKafkaServer;
100
101     private KafkaConsumer<String, String> testConsumer;
102
103     private static final String TABLE_NAME = String.format(TIES_DATA, "o-ran-smo-teiv-ran_ODUFunction");
104
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");
112
113     @MockBean
114     private SchemaHandler schemaHandler;
115
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());
121
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());
125     }
126
127     @BeforeAll
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();
137     }
138
139     @BeforeEach
140     public void setUp() {
141         testConsumer = createConsumerForTest(getEmbeddedKafkaServer());
142     }
143
144     @BeforeEach
145     public void reloadBeforeEach() {
146         reloadData();
147     }
148
149     @AfterAll
150     public static void reloadAfterAll() {
151         reloadData();
152     }
153
154     @AfterEach
155     public void cleanUp() {
156         testConsumer.close();
157     }
158
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();
163     }
164
165     @Test
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",
169                 123);
170
171         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
172                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
173
174         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
175     }
176
177     @Test
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",
181                 "Stockholm");
182
183         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
184                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
185
186         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
187     }
188
189     @Test
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",
193                 "Stockholm");
194
195         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
196                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
197                 .build());
198
199         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
200     }
201
202     @Test
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",
206                 "Stockholm");
207
208         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
209                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
210
211         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
212         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
213     }
214
215     @Test
216     void testAdd_entityAndRelationshipInvalidNotAvailableDecorators() {
217         Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location_WRONG", "Stockholm",
218                 "test-app-module:data_WRONG", true);
219
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);
223     }
224
225     @Test
226     void testAdd_entityAndRelationshipInvalidNotCompatibleDecorators() {
227         Map<String, Object> decoratorsToMerge = Map.of("test-app-module:location", true, "test-app-module:data",
228                 "Stockholm");
229
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);
233     }
234
235     @Test
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",
239                 123);
240
241         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
242                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.MERGE).build());
243
244         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
245     }
246
247     @Test
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",
251                 123);
252
253         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(Collections
254                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE)
255                 .build());
256
257         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
258     }
259
260     @Test
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",
264                 123);
265
266         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToMerge).entityIds(List.of(ENTITY_ID))
267                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.MERGE).build());
268
269         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
270         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
271     }
272
273     @Test
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",
277                 123);
278
279         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
280                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
281
282         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
283     }
284
285     @Test
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);
289
290         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
291                 .relationshipIds(Collections.emptyList()).operation(OranTeivDecorator.OperationEnum.DELETE).build());
292
293         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
294     }
295
296     @Test
297     void testDelete_existingEntityDecorators_withNotExistingEntityId() {
298         Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
299
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);
303     }
304
305     @Test
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);
309
310         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(Collections
311                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE)
312                 .build());
313
314         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
315     }
316
317     @Test
318     void testDelete_existingRelationshipDecorators_withNotExistingRelationshipId() {
319         Map<String, Object> decoratorsToDelete = Map.of("test-app-module:textdata", "Stockholm");
320
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);
324     }
325
326     @Test
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);
330
331         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
332                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
333
334         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
335         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
336     }
337
338     @Test
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",
342                 "Stockholm");
343
344         decoratorsService.update(OranTeivDecorator.builder().decorators(decoratorsToDelete).entityIds(List.of(ENTITY_ID))
345                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OranTeivDecorator.OperationEnum.DELETE).build());
346
347         verifyDecorators(ENTITY_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
348         verifyDecorators(RELATIONSHIP_DECORATORS, TABLE_NAME, ENTITY_ID, decoratorsExpected);
349     }
350
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));
354
355         Result<Record1<JSONB>> result = select.fetch();
356
357         Map<String, Object> decoratorsActual = JooqTypeConverter.jsonbToMap(result.get(0).value1());
358
359         assertEquals(decoratorsExpected, decoratorsActual);
360     }
361
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);
370     }
371
372 }