11/*******************************************************************************
2- * Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
2+ * Copyright (c) 2023, 2026 Eurotech and/or its affiliates and others
33 *
44 * This program and the accompanying materials are made
55 * available under the terms of the Eclipse Public License 2.0
1212 ******************************************************************************/
1313package org .eclipse .kura .internal .rest .cloudconnection .provider .test ;
1414
15+ import static org .junit .Assert .assertEquals ;
16+ import static org .junit .Assert .assertTrue ;
1517import static org .junit .Assert .fail ;
1618
1719import java .util .Arrays ;
1820import java .util .Collection ;
1921import java .util .Collections ;
22+ import java .util .HashMap ;
2023import java .util .HashSet ;
24+ import java .util .Map ;
2125import java .util .Optional ;
26+ import java .util .Properties ;
2227import java .util .Scanner ;
2328import java .util .concurrent .TimeUnit ;
29+ import java .util .function .Predicate ;
30+ import java .util .function .Supplier ;
31+
32+ import io .moquette .broker .Server ;
2433
2534import org .eclipse .kura .KuraException ;
2635import org .eclipse .kura .cloudconnection .CloudConnectionConstants ;
2736import org .eclipse .kura .cloudconnection .factory .CloudConnectionFactory ;
37+ import org .eclipse .kura .configuration .ComponentConfiguration ;
38+ import org .eclipse .kura .configuration .ConfigurableComponent ;
2839import org .eclipse .kura .configuration .ConfigurationService ;
2940import org .eclipse .kura .core .testutil .requesthandler .AbstractRequestHandlerTest ;
3041import org .eclipse .kura .core .testutil .requesthandler .MqttTransport ;
3142import org .eclipse .kura .core .testutil .requesthandler .RestTransport ;
3243import org .eclipse .kura .core .testutil .requesthandler .Transport ;
3344import org .eclipse .kura .core .testutil .requesthandler .Transport .MethodSpec ;
3445import org .eclipse .kura .core .testutil .service .ServiceUtil ;
46+ import org .eclipse .kura .data .DataTransportService ;
3547import org .eclipse .kura .internal .rest .cloudconnection .provider .dto .CloudConnectionFactoryPidAndCloudEndpointPid ;
3648import org .eclipse .kura .internal .rest .cloudconnection .provider .dto .CloudEndpointPidRequest ;
3749import org .eclipse .kura .internal .rest .cloudconnection .provider .dto .PidAndFactoryPidAndCloudEndpointPid ;
3850import org .eclipse .kura .rest .configuration .api .PidAndFactoryPid ;
3951import org .eclipse .kura .rest .configuration .api .PidSet ;
4052import org .eclipse .kura .rest .configuration .api .UpdateComponentConfigurationRequest ;
53+ import org .junit .After ;
54+ import org .junit .Before ;
4155import org .junit .BeforeClass ;
4256import org .junit .Test ;
4357import org .junit .runner .RunWith ;
4458import org .junit .runners .Parameterized ;
59+ import org .slf4j .Logger ;
60+ import org .slf4j .LoggerFactory ;
4561
4662import com .google .gson .Gson ;
63+ import com .google .gson .JsonObject ;
64+ import com .google .gson .JsonPrimitive ;
4765
4866@ RunWith (Parameterized .class )
4967public class CloudConnectionEndpointsTest extends AbstractRequestHandlerTest {
5068
51- private static final String CLOUD_ENDPOINT_INSTANCE_TEST = "org.eclipse.kura.cloud.CloudService-test" ;
69+ private static final Logger logger = LoggerFactory .getLogger (CloudConnectionEndpointsTest .class );
70+
71+ private static final String CLOUD_SERVICE_FACTORY_PID = "org.eclipse.kura.cloud.CloudService" ;
72+ private static final String MQTT_DATA_TRANSPORT_STACK_COMPONENT_PREFIX = "org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport" ;
5273
5374 private static final String MQTT_APP_ID = "CLD-V1" ;
5475
@@ -72,18 +93,6 @@ public class CloudConnectionEndpointsTest extends AbstractRequestHandlerTest {
7293 private UpdateComponentConfigurationRequest updateComponentConfigurationRequest ;
7394 private CloudEndpointPidRequest cloudEndpointPidRequest ;
7495
75- private static final String EXPECTED_GET_STACK_COMPONENT_PIDS_RESPONSE = new Scanner (
76- CloudConnectionEndpointsTest .class .getResourceAsStream ("/getStackComponentPidsResponse.json" ), "UTF-8" )
77- .useDelimiter ("\\ A" ).next ().replace (" " , "" );
78-
79- private static final String UPDATE_COMPONENT_CONFIGURATION_REQUEST = new Scanner (
80- CloudConnectionEndpointsTest .class .getResourceAsStream ("/updateConfigurationRequest.json" ), "UTF-8" )
81- .useDelimiter ("\\ A" ).next ().replace (" " , "" );
82-
83- private static final String EXPECTED_IS_ENDPOINT_CONNECTED_RESPONSE = new Scanner (
84- CloudConnectionEndpointsTest .class .getResourceAsStream ("/isConnectedResponse.json" ), "UTF-8" )
85- .useDelimiter ("\\ A" ).next ().replace (" " , "" );
86-
8796 @ Parameterized .Parameters (name = "{0}" )
8897 public static Collection <Transport > transports () {
8998 return Arrays .asList (new MqttTransport (MQTT_APP_ID ), new RestTransport (REST_APP_ID ));
@@ -101,9 +110,30 @@ public static void setup() {
101110 TimeUnit .SECONDS );
102111 cloudConnectionFactory = ServiceUtil .trackService (CloudConnectionFactory .class , Optional .empty ()).get (30 ,
103112 TimeUnit .SECONDS );
104- cloudConnectionFactory .createConfiguration (CLOUD_ENDPOINT_INSTANCE_TEST );
113+
114+ final Server server = new Server ();
115+
116+ final Properties properties = new Properties ();
117+ properties .put ("port" , "6666" );
118+ properties .put ("host" , "0.0.0.0" );
119+
120+ server .startServer (properties );
105121 } catch (Exception e ) {
106- fail ("Unable to create the test CloudEndpoint" );
122+ fail ("Test setup failed" );
123+ }
124+ }
125+
126+ @ Before
127+ public void createTestCloudStack () {
128+ givenExistingCloudEndpoint (testCloudInstancePid ());
129+ }
130+
131+ @ After
132+ public void deleteTestCloudStack () {
133+ try {
134+ cloudConnectionFactory .deleteConfiguration (testCloudInstancePid ());
135+ } catch (final Exception e ) {
136+ logger .warn ("failed to delete test cloud instance during cleanup" , e );
107137 }
108138 }
109139
@@ -118,20 +148,40 @@ public void shouldGetCloudComponentInstances() {
118148
119149 @ Test
120150 public void shouldGetStackComponentPids () {
121- givenCloudConnectionFactoryPidAndCloudEndpointPid ("org.eclipse.kura.cloud.CloudService" ,
122- CLOUD_ENDPOINT_INSTANCE_TEST );
151+ givenCloudConnectionFactoryPidAndCloudEndpointPid (CLOUD_SERVICE_FACTORY_PID , testCloudInstancePid ());
123152
124153 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/cloudEndpoint/stackComponentPids" ,
125154 gson .toJson (this .cloudConnectionFactoryPidAndCloudEndpointPid ));
126155
127156 thenRequestSucceeds ();
128- thenResponseBodyEqualsJson (EXPECTED_GET_STACK_COMPONENT_PIDS_RESPONSE );
157+ thenResponseJsonArraySizeIs ("pids" , 3 );
158+ thenResponseJsonArrayContains ("pids" , "org.eclipse.kura.cloud.CloudService-" + testCloudInstancesSuffix ());
159+ thenResponseJsonArrayContains ("pids" ,
160+ "org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport-" + testCloudInstancesSuffix ());
161+ thenResponseJsonArrayContains ("pids" , "org.eclipse.kura.data.DataService-" + testCloudInstancesSuffix ());
162+
163+ }
164+
165+ private void thenResponseJsonArraySizeIs (final String property , final int size ) {
166+ final JsonObject response = gson .fromJson (
167+ expectResponse ().getBody ().orElseThrow (() -> new IllegalStateException ("body is missing" )),
168+ JsonObject .class );
169+
170+ assertEquals (size , response .get (property ).getAsJsonArray ().size ());
171+ }
172+
173+ private void thenResponseJsonArrayContains (final String property , final String value ) {
174+ final JsonObject response = gson .fromJson (
175+ expectResponse ().getBody ().orElseThrow (() -> new IllegalStateException ("body is missing" )),
176+ JsonObject .class );
177+
178+ assertTrue (response .get (property ).getAsJsonArray ().contains (new JsonPrimitive (value )));
129179 }
130180
131181 @ Test
132182 public void shouldCreateCloudEndpoint () {
133- givenCloudConnectionFactoryPidAndCloudEndpointPid ("org.eclipse.kura.cloud.CloudService" ,
134- "org.eclipse.kura.cloud.CloudService-createTest" + this . getTransportType ());
183+ givenCloudConnectionFactoryPidAndCloudEndpointPid (CLOUD_SERVICE_FACTORY_PID ,
184+ "org.eclipse.kura.cloud.CloudService-createTest" + testCloudInstancesSuffix ());
135185
136186 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/cloudEndpoint" ,
137187 gson .toJson (this .cloudConnectionFactoryPidAndCloudEndpointPid ));
@@ -141,9 +191,7 @@ public void shouldCreateCloudEndpoint() {
141191
142192 @ Test
143193 public void shouldDeleteCloudEndpoint () {
144- givenExistingCloudEndpoint ("org.eclipse.kura.cloud.CloudService-toDelete" + this .getTransportType ());
145- givenCloudConnectionFactoryPidAndCloudEndpointPid ("org.eclipse.kura.cloud.CloudService" ,
146- "org.eclipse.kura.cloud.CloudService-toDelete" + this .getTransportType ());
194+ givenCloudConnectionFactoryPidAndCloudEndpointPid (CLOUD_SERVICE_FACTORY_PID , testCloudInstancePid ());
147195
148196 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_DELETE , MQTT_METHOD_SPEC_DEL ), "/cloudEndpoint" ,
149197 gson .toJson (this .cloudConnectionFactoryPidAndCloudEndpointPid ));
@@ -162,8 +210,8 @@ public void shouldGetCloudComponentFactories() {
162210
163211 @ Test
164212 public void shouldCreatePublisherInstance () {
165- givenPidAndFactoryPidAndCloudEndpointPid ("test-pub-" + this . getTransportType (),
166- "org.eclipse.kura.cloud.publisher.CloudPublisher" , CLOUD_ENDPOINT_INSTANCE_TEST );
213+ givenPidAndFactoryPidAndCloudEndpointPid ("test-pub-" + testCloudInstancesSuffix (),
214+ "org.eclipse.kura.cloud.publisher.CloudPublisher" , testCloudInstancePid () );
167215
168216 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/pubSub" ,
169217 gson .toJson (this .pidAndFactoryPidAndCloudEndpointPid ));
@@ -173,8 +221,8 @@ public void shouldCreatePublisherInstance() {
173221
174222 @ Test
175223 public void shouldCreateSubscriberInstance () {
176- givenPidAndFactoryPidAndCloudEndpointPid ("test-sub-" + this . getTransportType (),
177- "org.eclipse.kura.cloud.subscriber.CloudSubscriber" , CLOUD_ENDPOINT_INSTANCE_TEST );
224+ givenPidAndFactoryPidAndCloudEndpointPid ("test-sub-" + testCloudInstancesSuffix (),
225+ "org.eclipse.kura.cloud.subscriber.CloudSubscriber" , testCloudInstancePid () );
178226
179227 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/pubSub" ,
180228 gson .toJson (this .pidAndFactoryPidAndCloudEndpointPid ));
@@ -184,9 +232,9 @@ public void shouldCreateSubscriberInstance() {
184232
185233 @ Test
186234 public void shouldDeletePublisherInstance () {
187- givenPubSubInstance ("pub-to-delete-" + this . getTransportType (),
188- "org.eclipse.kura.cloud.publisher.CloudPublisher" , CLOUD_ENDPOINT_INSTANCE_TEST );
189- givenPid ("pub-to-delete-" + this . getTransportType ());
235+ givenPubSubInstance ("pub-to-delete-" + testCloudInstancesSuffix (),
236+ "org.eclipse.kura.cloud.publisher.CloudPublisher" , testCloudInstancePid () );
237+ givenPid ("pub-to-delete-" + testCloudInstancesSuffix ());
190238 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_DELETE , MQTT_METHOD_SPEC_DEL ), "/pubSub" ,
191239 gson .toJson (this .pidAndFactoryPid ));
192240
@@ -195,9 +243,9 @@ public void shouldDeletePublisherInstance() {
195243
196244 @ Test
197245 public void shouldDeleteSubscriberInstance () {
198- givenPubSubInstance ("sub-to-delete-" + this . getTransportType (),
199- "org.eclipse.kura.cloud.subscriber.CloudSubscriber" , CLOUD_ENDPOINT_INSTANCE_TEST );
200- givenPid ("sub-to-delete-" + this . getTransportType ());
246+ givenPubSubInstance ("sub-to-delete-" + testCloudInstancesSuffix (),
247+ "org.eclipse.kura.cloud.subscriber.CloudSubscriber" , testCloudInstancePid () );
248+ givenPid ("sub-to-delete-" + testCloudInstancesSuffix ());
201249
202250 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_DELETE , MQTT_METHOD_SPEC_DEL ), "/pubSub" ,
203251 gson .toJson (this .pidAndFactoryPid ));
@@ -207,8 +255,9 @@ public void shouldDeleteSubscriberInstance() {
207255
208256 @ Test
209257 public void shouldGetConfigurations () {
210- givenPidSet (CLOUD_ENDPOINT_INSTANCE_TEST , "org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport-test" , //
211- "org.eclipse.kura.data.DataService-test" );
258+ givenPidSet (CLOUD_SERVICE_FACTORY_PID + '-' + testCloudInstancesSuffix (),
259+ "org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport-" + testCloudInstancesSuffix (), //
260+ "org.eclipse.kura.data.DataService-" + testCloudInstancesSuffix ());
212261
213262 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/configurations" , gson .toJson (this .pidSet ));
214263
@@ -218,8 +267,29 @@ public void shouldGetConfigurations() {
218267
219268 @ Test
220269 public void shouldUpdateStackComponentConfigurations () {
221-
222- givenUpdateComponentConfigurationRequest (UPDATE_COMPONENT_CONFIGURATION_REQUEST );
270+ givenUpdateComponentConfigurationRequest ("{" + //
271+ "\" configs\" : [" + //
272+ "{" + //
273+ "\" pid\" : \" org.eclipse.kura.core.data.transport.mqtt.MqttDataTransport-" + testCloudInstancesSuffix ()
274+ + "\" ," + //
275+ "\" properties\" : {" + //
276+ "\" broker-url\" : {" + //
277+ "\" type\" : \" STRING\" ," + //
278+ "\" value\" : \" mqtt://test.mosquitto.org:1883\" " + //
279+ "}," + //
280+ "\" topic.context.account-name\" : {" + //
281+ "\" type\" : \" STRING\" ," + //
282+ "\" value\" : \" account-name-testX2\" " + //
283+ "}," + //
284+ "\" client-id\" : {" + //
285+ "\" type\" : \" STRING\" ," + //
286+ "\" value\" : \" foobar\" " + //
287+ "}" + //
288+ "}" + //
289+ "}" + //
290+ "]," + //
291+ "\" takeSnapshot\" : true" + //
292+ "}" );
223293
224294 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_PUT ), "/configurations" ,
225295 gson .toJson (this .updateComponentConfigurationRequest ));
@@ -229,7 +299,7 @@ public void shouldUpdateStackComponentConfigurations() {
229299
230300 @ Test
231301 public void shouldConnectEndpoint () {
232- givenCloudEndpointPidRequest (CLOUD_ENDPOINT_INSTANCE_TEST );
302+ givenCloudEndpointPidRequest (testCloudInstancePid () );
233303
234304 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/cloudEndpoint/connect" ,
235305 gson .toJson (this .cloudEndpointPidRequest ));
@@ -239,7 +309,7 @@ public void shouldConnectEndpoint() {
239309
240310 @ Test
241311 public void shouldDisconnectEndpoint () {
242- givenCloudEndpointPidRequest (CLOUD_ENDPOINT_INSTANCE_TEST );
312+ givenCloudEndpointPidRequest (testCloudInstancePid () );
243313
244314 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/cloudEndpoint/disconnect" ,
245315 gson .toJson (this .cloudEndpointPidRequest ));
@@ -249,13 +319,13 @@ public void shouldDisconnectEndpoint() {
249319
250320 @ Test
251321 public void shouldCheckEndpointStatus () {
252- givenCloudEndpointPidRequest (CLOUD_ENDPOINT_INSTANCE_TEST );
322+ givenCloudEndpointPidRequest (testCloudInstancePid () );
253323
254324 whenRequestIsPerformed (new MethodSpec (METHOD_SPEC_POST ), "/cloudEndpoint/isConnected" ,
255325 gson .toJson (this .cloudEndpointPidRequest ));
256326
257327 thenRequestSucceeds ();
258- thenResponseBodyEqualsJson (EXPECTED_IS_ENDPOINT_CONNECTED_RESPONSE );
328+ thenResponseBodyEqualsJson ("{ \" connected \" : false}" );
259329 }
260330
261331 private void givenCloudEndpointPidRequest (String pid ) {
@@ -291,9 +361,48 @@ private void givenExistingCloudEndpoint(String cloudEndpointPid) {
291361 try {
292362 cloudConnectionFactory .createConfiguration (cloudEndpointPid );
293363
294- } catch (KuraException e ) {
295- e .printStackTrace ();
296- fail ("Unable to create the test CloudService" );
364+ final String mqttDataTransportPid = cloudEndpointPid .replace (CLOUD_SERVICE_FACTORY_PID ,
365+ MQTT_DATA_TRANSPORT_STACK_COMPONENT_PREFIX );
366+
367+ final DataTransportService dataTransportService = ServiceUtil .trackService (DataTransportService .class ,
368+ Optional .of ("(kura.service.pid=" + mqttDataTransportPid + ")" )).get (30 , TimeUnit .SECONDS );
369+
370+ await (() -> {
371+ try {
372+ return configurationService .getComponentConfiguration (mqttDataTransportPid ) != null ;
373+ } catch (Exception e ) {
374+ return false ;
375+ }
376+ }, 5 , TimeUnit .SECONDS );
377+
378+ logger .info ("Test MqttDataTransport with pid {} found..." , mqttDataTransportPid );
379+
380+ final Map <String , Object > prioperties = new HashMap <>();
381+ prioperties .put ("client-id" , "testClientId" );
382+ prioperties .put ("broker-url" , "mqtt://localhost:6666" );
383+
384+ configurationService .updateConfiguration (mqttDataTransportPid , prioperties );
385+
386+ await (() -> dataTransportService .getClientId () != null , 5 , TimeUnit .SECONDS );
387+
388+ } catch (Exception e ) {
389+ final String message = "Unable to create the test CloudService with pid: " + cloudEndpointPid ;
390+ logger .warn (message , e );
391+ fail (message );
392+ }
393+ }
394+
395+ private void await (final Supplier <Boolean > predicate , final long duration , final TimeUnit timeUnit )
396+ throws InterruptedException {
397+
398+ final long deadline = System .nanoTime () + timeUnit .toNanos (duration );
399+
400+ while (!predicate .get () && System .nanoTime () < deadline ) {
401+ Thread .sleep (100 );
402+ }
403+
404+ if (!predicate .get ()) {
405+ fail ("condition did not occur" );
297406 }
298407 }
299408
@@ -302,10 +411,19 @@ private void givenPubSubInstance(String pid, String factoryPid, String cloudEndp
302411 configurationService .createFactoryConfiguration (factoryPid , pid , Collections .singletonMap (
303412 CloudConnectionConstants .CLOUD_ENDPOINT_SERVICE_PID_PROP_NAME .value (), cloudEndpointPid ), true );
304413 } catch (KuraException e ) {
305- e .printStackTrace ();
306- fail ("Unable to create pubSub instance" );
414+ final String message = "Unable to create pubSub instance" ;
415+ logger .warn (message , e );
416+ fail (message );
307417 }
308418
309419 }
310420
311- }
421+ private String testCloudInstancesSuffix () {
422+ return "test" + System .identityHashCode (this );
423+ }
424+
425+ private String testCloudInstancePid () {
426+ return "org.eclipse.kura.cloud.CloudService-" + testCloudInstancesSuffix ();
427+ }
428+
429+ }
0 commit comments