de2ecc1cc11074fa573a9e5caebab6d48a3c4c16
[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.classifiers.api;
22
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;
33
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;
61
62 import lombok.Getter;
63
64 import javax.sql.DataSource;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.Properties;
68
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;
77
78 import lombok.extern.slf4j.Slf4j;
79
80 @DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
81 @EmbeddedKafka
82 @Slf4j
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 {
90
91     public static TestPostgresqlContainerV1 postgreSQLContainer = TestPostgresqlContainerV1.getInstance();
92     private static DSLContext writeDataDslContext;
93     private static DSLContext readDataDslContext;
94     @Autowired
95     private ClassifiersService classifiersService;
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 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);
111
112     @MockBean
113     private SchemaHandler schemaHandler;
114
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());
120
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());
124     }
125
126     @BeforeAll
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();
136     }
137
138     @BeforeEach
139     public void setUp() {
140         testConsumer = createConsumerForTest(getEmbeddedKafkaServer());
141     }
142
143     @BeforeEach
144     public void reloadBeforeEach() {
145         reloadData();
146     }
147
148     @AfterAll
149     public static void reloadAfterAll() {
150         reloadData();
151     }
152
153     @AfterEach
154     public void cleanUp() {
155         testConsumer.close();
156     }
157
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();
162     }
163
164     @Test
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");
169
170         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
171                 .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build());
172
173         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
174     }
175
176     @Test
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");
181
182         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
183                 .relationshipIds(Collections.emptyList()).operation(OperationEnum.MERGE).build());
184
185         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
186     }
187
188     @Test
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");
193
194         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(Collections
195                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
196
197         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
198     }
199
200     @Test
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");
205
206         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
207                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
208
209         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
210         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
211     }
212
213     @Test
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");
218
219         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToMerge).entityIds(List.of(ENTITY_ID))
220                 .relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.MERGE).build());
221
222         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
223         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
224     }
225
226     @Test
227     void testAdd_entityAndRelationshipInvalidClassifiers() {
228         List<String> classifiersToMerge = List.of("test-app-module:Indoor_WRONG", "test-app-module:Rural_WRONG");
229
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);
233     }
234
235     @Test
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");
240
241         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
242                 ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build());
243
244         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
245     }
246
247     @Test
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");
251
252         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
253                 ENTITY_ID)).relationshipIds(Collections.emptyList()).operation(OperationEnum.DELETE).build());
254
255         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
256     }
257
258     @Test
259     void testDelete_entityClassifiers_withNotExistingEntityId() {
260         List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
261
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);
265     }
266
267     @Test
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");
271
272         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(Collections
273                 .emptyList()).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
274
275         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
276     }
277
278     @Test
279     void testDelete_relationshipClassifiers_withNotExistingRelationshipId() {
280         List<String> classifiersToDelete = List.of("test-app-module:Rural", "test-app-module:Weekend");
281
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);
285     }
286
287     @Test
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");
291
292         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
293                 ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
294
295         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
296         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
297     }
298
299     @Test
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");
304
305         classifiersService.update(OranTeivClassifier.builder().classifiers(classifiersToDelete).entityIds(List.of(
306                 ENTITY_ID)).relationshipIds(List.of(RELATIONSHIP_ID)).operation(OperationEnum.DELETE).build());
307
308         verifyClassifiers(ENTITY_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
309         verifyClassifiers(RELATIONSHIP_CLASSIFIERS, TABLE_NAME, ENTITY_ID, classifiersExpected);
310     }
311
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));
315
316         Result<Record1<JSONB>> result = select.fetch();
317
318         List<String> classifiersActual = JooqTypeConverter.jsonbToList(result.get(0).value1());
319
320         assertEquals(classifiersExpected, classifiersActual);
321     }
322
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);
331     }
332 }