]> git.basschouten.com Git - openhab-addons.git/blob
b4de21d03c6ad6a6105e5e6d5ffd9d3070fe0de2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.fsinternetradio.test;
14
15 import static org.hamcrest.CoreMatchers.is;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.junit.jupiter.api.Assertions.*;
18 import static org.mockito.ArgumentMatchers.isA;
19 import static org.mockito.Mockito.*;
20 import static org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants.*;
21
22 import java.io.IOException;
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.apache.commons.lang.StringUtils;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.eclipse.jetty.servlet.ServletHolder;
33 import org.junit.jupiter.api.AfterAll;
34 import org.junit.jupiter.api.BeforeAll;
35 import org.junit.jupiter.api.BeforeEach;
36 import org.junit.jupiter.api.Test;
37 import org.mockito.ArgumentCaptor;
38 import org.openhab.binding.fsinternetradio.internal.FSInternetRadioBindingConstants;
39 import org.openhab.binding.fsinternetradio.internal.handler.FSInternetRadioHandler;
40 import org.openhab.binding.fsinternetradio.internal.handler.HandlerUtils;
41 import org.openhab.binding.fsinternetradio.internal.radio.FrontierSiliconRadio;
42 import org.openhab.core.config.core.Configuration;
43 import org.openhab.core.items.Item;
44 import org.openhab.core.library.items.DimmerItem;
45 import org.openhab.core.library.items.NumberItem;
46 import org.openhab.core.library.items.StringItem;
47 import org.openhab.core.library.items.SwitchItem;
48 import org.openhab.core.library.types.DecimalType;
49 import org.openhab.core.library.types.IncreaseDecreaseType;
50 import org.openhab.core.library.types.OnOffType;
51 import org.openhab.core.library.types.PercentType;
52 import org.openhab.core.library.types.UpDownType;
53 import org.openhab.core.test.TestPortUtil;
54 import org.openhab.core.test.TestServer;
55 import org.openhab.core.test.java.JavaTest;
56 import org.openhab.core.thing.Channel;
57 import org.openhab.core.thing.ChannelUID;
58 import org.openhab.core.thing.Thing;
59 import org.openhab.core.thing.ThingStatus;
60 import org.openhab.core.thing.ThingStatusDetail;
61 import org.openhab.core.thing.ThingStatusInfo;
62 import org.openhab.core.thing.ThingTypeUID;
63 import org.openhab.core.thing.ThingUID;
64 import org.openhab.core.thing.binding.ThingHandlerCallback;
65 import org.openhab.core.thing.binding.builder.ChannelBuilder;
66 import org.openhab.core.thing.binding.builder.ThingBuilder;
67 import org.openhab.core.thing.binding.builder.ThingStatusInfoBuilder;
68 import org.openhab.core.types.UnDefType;
69
70 /**
71  * OSGi tests for the {@link FSInternetRadioHandler}.
72  *
73  * @author Mihaela Memova - Initial contribution
74  * @author Markus Rathgeb - Migrated from Groovy to pure Java test, made more robust
75  * @author Velin Yordanov - Migrated to mockito
76  *
77  */
78 public class FSInternetRadioHandlerJavaTest extends JavaTest {
79     private static final String DEFAULT_TEST_THING_NAME = "testRadioThing";
80     private static final String DEFAULT_TEST_ITEM_NAME = "testItem";
81     private final String VOLUME = "volume";
82
83     // The request send for preset is "SET/netRemote.nav.action.selectPreset";
84     private static final String PRESET = "Preset";
85     private static final int TIMEOUT = 10 * 1000;
86     private static final ThingTypeUID DEFAULT_THING_TYPE_UID = FSInternetRadioBindingConstants.THING_TYPE_RADIO;
87     private static final ThingUID DEFAULT_THING_UID = new ThingUID(DEFAULT_THING_TYPE_UID, DEFAULT_TEST_THING_NAME);
88     private static final RadioServiceDummy radioServiceDummy = new RadioServiceDummy();
89
90     /**
91      * In order to test a specific channel, it is necessary to create a Thing with two channels - CHANNEL_POWER
92      * and the tested channel. So before each test, the power channel is created and added
93      * to an ArrayList of channels. Then in the tests an additional channel is created and added to the ArrayList
94      * when it's needed.
95      */
96     private Channel powerChannel;
97
98     private ThingHandlerCallback callback;
99
100     private static TestServer server;
101
102     /**
103      * A HashMap which saves all the 'channel-acceppted_item_type' pairs.
104      * It is set before all the tests.
105      */
106     private static Map<String, String> acceptedItemTypes;
107
108     /**
109      * ArrayList of channels which is used to initialize a radioThing in the test cases.
110      */
111     private final List<Channel> channels = new ArrayList<>();
112
113     private FSInternetRadioHandler radioHandler;
114     private Thing radioThing;
115
116     private static HttpClient httpClient;
117
118     // default configuration properties
119     private static final String DEFAULT_CONFIG_PROPERTY_IP = "127.0.0.1";
120     private static final String DEFAULT_CONFIG_PROPERTY_PIN = "1234";
121     private static final int DEFAULT_CONFIG_PROPERTY_PORT = TestPortUtil.findFreePort();
122
123     /** The default refresh interval is 60 seconds. For the purposes of the tests it is set to 1 second */
124     private static final String DEFAULT_CONFIG_PROPERTY_REFRESH = "1";
125     private static final Configuration DEFAULT_COMPLETE_CONFIGURATION = createDefaultConfiguration();
126
127     @BeforeAll
128     public static void setUpClass() throws Exception {
129         ServletHolder holder = new ServletHolder(radioServiceDummy);
130         server = new TestServer(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PORT, TIMEOUT, holder);
131         setTheChannelsMap();
132         server.startServer();
133         httpClient = new HttpClient();
134         httpClient.start();
135     }
136
137     @BeforeEach
138     public void setUp() {
139         createThePowerChannel();
140     }
141
142     @AfterAll
143     public static void tearDownClass() throws Exception {
144         server.stopServer();
145         httpClient.stop();
146     }
147
148     private static @NonNull Channel getChannel(final @NonNull Thing thing, final @NonNull String channelId) {
149         final Channel channel = thing.getChannel(channelId);
150         assertNotNull(channel);
151         return channel;
152     }
153
154     private static @NonNull ChannelUID getChannelUID(final @NonNull Thing thing, final @NonNull String channelId) {
155         final ChannelUID channelUID = getChannel(thing, channelId).getUID();
156         assertNotNull(channelUID);
157         return channelUID;
158     }
159
160     /**
161      * Verify OFFLINE Thing status when the IP is NULL.
162      */
163     @Test
164     public void offlineIfNullIp() {
165         Configuration config = createConfiguration(null, DEFAULT_CONFIG_PROPERTY_PIN,
166                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
167         Thing radioThingWithNullIP = initializeRadioThing(config);
168         testRadioThingConsideringConfiguration(radioThingWithNullIP);
169     }
170
171     /**
172      * Verify OFFLINE Thing status when the PIN is empty String.
173      */
174     @Test
175     public void offlineIfEmptyPIN() {
176         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, "",
177                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
178         Thing radioThingWithEmptyPIN = initializeRadioThing(config);
179         testRadioThingConsideringConfiguration(radioThingWithEmptyPIN);
180     }
181
182     /**
183      * Verify OFFLINE Thing status when the PORT is zero.
184      */
185     @Test
186     public void offlineIfZeroPort() {
187         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN, "0",
188                 DEFAULT_CONFIG_PROPERTY_REFRESH);
189         Thing radioThingWithZeroPort = initializeRadioThing(config);
190         testRadioThingConsideringConfiguration(radioThingWithZeroPort);
191     }
192
193     /**
194      * Verify OFFLINE Thing status when the PIN is wrong.
195      */
196     @Test
197     public void offlineIfWrongPIN() {
198         final String wrongPin = "5678";
199         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, wrongPin,
200                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
201         initializeRadioThing(config);
202         waitForAssert(() -> {
203             String exceptionMessage = "Radio does not allow connection, maybe wrong pin?";
204             verifyCommunicationError(exceptionMessage);
205         });
206     }
207
208     /**
209      * Verify OFFLINE Thing status when the HTTP response cannot be parsed correctly.
210      */
211     @Test
212     public void offlineIfParseError() {
213         // create a thing with two channels - the power channel and any of the others
214         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
215         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
216         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
217
218         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
219         testRadioThingConsideringConfiguration(radioThing);
220
221         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
222
223         /*
224          * Setting the isInvalidResponseExpected variable to true
225          * in order to get the incorrect XML response from the servlet
226          */
227         radioServiceDummy.setInvalidResponse(true);
228
229         // try to handle a command
230         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
231
232         waitForAssert(() -> {
233             String exceptionMessage = "java.io.IOException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 2;";
234             verifyCommunicationError(exceptionMessage);
235         });
236         radioServiceDummy.setInvalidResponse(false);
237     }
238
239     /**
240      * Verify the HTTP status is handled correctly when it is not OK_200.
241      */
242     @Test
243     public void httpStatusNokHandling() {
244         // create a thing with two channels - the power channel and any of the others
245         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
246         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
247         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
248
249         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
250         testRadioThingConsideringConfiguration(radioThing);
251
252         // turn-on the radio
253         turnTheRadioOn(radioThing);
254
255         /*
256          * Setting the needed boolean variable to false, so we can be sure
257          * that the XML response won't have a OK_200 status
258          */
259         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
260         Item modeTestItem = initializeItem(modeChannelUID, CHANNEL_MODE, acceptedItemType);
261
262         // try to handle a command
263         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
264
265         waitForAssert(() -> {
266             assertSame(UnDefType.NULL, modeTestItem.getState());
267         });
268     }
269
270     /**
271      * Verify ONLINE status of a Thing with complete configuration.
272      */
273     @Test
274     public void verifyOnlineStatus() {
275         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
276         testRadioThingConsideringConfiguration(radioThing);
277     }
278
279     /**
280      * Verify the power channel is updated.
281      */
282     @Test
283     public void powerChannelUpdated() {
284         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
285         testRadioThingConsideringConfiguration(radioThing);
286
287         ChannelUID powerChannelUID = powerChannel.getUID();
288         initializeItem(powerChannelUID, DEFAULT_TEST_ITEM_NAME,
289                 acceptedItemTypes.get(FSInternetRadioBindingConstants.CHANNEL_POWER));
290
291         radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
292         waitForAssert(() -> {
293             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
294                     "We should be able to turn on the radio");
295             radioServiceDummy.clearRequestParameters();
296         });
297
298         radioHandler.handleCommand(powerChannelUID, OnOffType.OFF);
299         waitForAssert(() -> {
300             assertTrue(radioServiceDummy.containsRequestParameter(0, CHANNEL_POWER),
301                     "We should be able to turn off the radio");
302             radioServiceDummy.clearRequestParameters();
303         });
304
305         /*
306          * Setting the needed boolean variable to true, so we can be sure
307          * that an invalid value will be returned in the XML response
308          */
309         radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
310         waitForAssert(() -> {
311             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
312                     "We should be able to turn on the radio");
313             radioServiceDummy.clearRequestParameters();
314         });
315     }
316
317     /**
318      * Verify the mute channel is updated.
319      */
320     @Test
321     public void muteChhannelUpdated() {
322         String muteChannelID = FSInternetRadioBindingConstants.CHANNEL_MUTE;
323         String acceptedItemType = acceptedItemTypes.get(muteChannelID);
324         createChannel(DEFAULT_THING_UID, muteChannelID, acceptedItemType);
325
326         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
327         testRadioThingConsideringConfiguration(radioThing);
328
329         turnTheRadioOn(radioThing);
330
331         ChannelUID muteChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_MUTE);
332         initializeItem(muteChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
333
334         radioHandler.handleCommand(muteChannelUID, OnOffType.ON);
335         waitForAssert(() -> {
336             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_MUTE),
337                     "We should be able to mute the radio");
338             radioServiceDummy.clearRequestParameters();
339         });
340
341         radioHandler.handleCommand(muteChannelUID, OnOffType.OFF);
342         waitForAssert(() -> {
343             assertTrue(radioServiceDummy.containsRequestParameter(0, CHANNEL_MUTE),
344                     "We should be able to unmute the radio");
345             radioServiceDummy.clearRequestParameters();
346         });
347
348         /*
349          * Setting the needed boolean variable to true, so we can be sure
350          * that an invalid value will be returned in the XML response
351          */
352     }
353
354     /**
355      * Verify the mode channel is updated.
356      */
357     @Test
358     public void modeChannelUdpated() {
359         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
360         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
361         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
362
363         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
364         testRadioThingConsideringConfiguration(radioThing);
365
366         turnTheRadioOn(radioThing);
367
368         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
369         initializeItem(modeChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
370
371         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
372         waitForAssert(() -> {
373             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_MODE),
374                     "We should be able to update the mode channel correctly");
375             radioServiceDummy.clearRequestParameters();
376         });
377
378         /*
379          * Setting the needed boolean variable to true, so we can be sure
380          * that an invalid value will be returned in the XML response
381          */
382         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("3"));
383         waitForAssert(() -> {
384             assertTrue(radioServiceDummy.containsRequestParameter(3, CHANNEL_MODE),
385                     "We should be able to update the mode channel correctly");
386             radioServiceDummy.clearRequestParameters();
387         });
388     }
389
390     /**
391      * Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using INCREASE and DECREASE commands.
392      */
393     @Test
394     public void volumechannelUpdatedAbsIncDec() {
395         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
396         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
397         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
398
399         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
400         testRadioThingConsideringConfiguration(radioThing);
401
402         turnTheRadioOn(radioThing);
403
404         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
405         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
406                 absoluteAcceptedItemType);
407
408         testChannelWithINCREASEAndDECREASECommands(absoluteVolumeChannelUID, volumeTestItem);
409     }
410
411     /**
412      * Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using UP and DOWN commands.
413      */
414     @Test
415     public void volumeChannelUpdatedAbsUpDown() {
416         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
417         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
418         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
419
420         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
421         testRadioThingConsideringConfiguration(radioThing);
422
423         turnTheRadioOn(radioThing);
424
425         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
426         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
427                 absoluteAcceptedItemType);
428
429         testChannelWithUPAndDOWNCommands(absoluteVolumeChannelUID, volumeTestItem);
430     }
431
432     /**
433      * Verify the invalid values when updating CHANNEL_VOLUME_ABSOLUTE are handled correctly.
434      */
435     @Test
436     public void invalidAbsVolumeValues() {
437         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
438         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
439         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
440
441         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
442         testRadioThingConsideringConfiguration(radioThing);
443
444         turnTheRadioOn(radioThing);
445
446         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
447         initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
448
449         // Trying to set a value that is greater than the maximum volume
450         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("36"));
451
452         waitForAssert(() -> {
453             assertTrue(radioServiceDummy.containsRequestParameter(32, VOLUME),
454                     "The volume should not exceed the maximum value");
455             radioServiceDummy.clearRequestParameters();
456         });
457
458         // Trying to increase the volume more than its maximum value using the INCREASE command
459         radioHandler.handleCommand(absoluteVolumeChannelUID, IncreaseDecreaseType.INCREASE);
460
461         waitForAssert(() -> {
462             assertTrue(radioServiceDummy.areRequestParametersEmpty(),
463                     "The volume should not be increased above the maximum value");
464             radioServiceDummy.clearRequestParameters();
465         });
466
467         // Trying to increase the volume more than its maximum value using the UP command
468         radioHandler.handleCommand(absoluteVolumeChannelUID, UpDownType.UP);
469
470         waitForAssert(() -> {
471             assertTrue(radioServiceDummy.areRequestParametersEmpty(),
472                     "The volume should not be increased above the maximum value");
473             radioServiceDummy.clearRequestParameters();
474         });
475
476         // Trying to set a value that is lower than the minimum volume value
477         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("-10"));
478         waitForAssert(() -> {
479             assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
480                     "The volume should not be decreased below 0");
481             radioServiceDummy.clearRequestParameters();
482         });
483
484         /*
485          * Setting the needed boolean variable to true, so we can be sure
486          * that an invalid value will be returned in the XML response
487          */
488
489         // trying to set the volume
490         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("15"));
491         waitForAssert(() -> {
492             assertTrue(radioServiceDummy.containsRequestParameter(15, VOLUME),
493                     "We should be able to set the volume correctly");
494             radioServiceDummy.clearRequestParameters();
495         });
496     }
497
498     /**
499      * Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using INCREASE and DECREASE commands.
500      */
501     @Test
502     public void volumeChannelUpdatedPercIncDec() {
503         /*
504          * The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
505          * the absolute volume will be updated properly.
506          */
507         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
508         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
509         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
510
511         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
512         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
513         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
514
515         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
516         testRadioThingConsideringConfiguration(radioThing);
517
518         turnTheRadioOn(radioThing);
519
520         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
521         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
522                 absoluteAcceptedItemType);
523
524         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
525
526         testChannelWithINCREASEAndDECREASECommands(percentVolumeChannelUID, volumeTestItem);
527     }
528
529     /**
530      * Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using UP and DOWN commands.
531      */
532     @Test
533     public void volumeChannelUpdatedPercUpDown() {
534         /*
535          * The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
536          * the absolute volume will be updated properly.
537          */
538         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
539         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
540         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
541
542         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
543         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
544         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
545
546         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
547         testRadioThingConsideringConfiguration(radioThing);
548
549         turnTheRadioOn(radioThing);
550
551         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
552         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
553                 absoluteAcceptedItemType);
554
555         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
556
557         testChannelWithUPAndDOWNCommands(percentVolumeChannelUID, volumeTestItem);
558     }
559
560     /**
561      * Verify the valid and invalid values when updating CHANNEL_VOLUME_PERCENT are handled correctly.
562      */
563     @Test
564     public void validInvalidPercVolume() {
565         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
566         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
567         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
568
569         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
570         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
571         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
572
573         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
574         testRadioThingConsideringConfiguration(radioThing);
575
576         turnTheRadioOn(radioThing);
577
578         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
579         initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
580
581         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
582
583         /*
584          * Giving the handler a valid percent value. According to the FrontierSiliconRadio's
585          * documentation 100 percents correspond to 32 absolute value
586          */
587         radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("50"));
588         waitForAssert(() -> {
589             assertTrue(radioServiceDummy.containsRequestParameter(16, VOLUME),
590                     "We should be able to set the volume correctly using percentages.");
591             radioServiceDummy.clearRequestParameters();
592         });
593
594         radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("15"));
595
596         waitForAssert(() -> {
597             assertTrue(radioServiceDummy.containsRequestParameter(4, VOLUME),
598                     "We should be able to set the volume correctly using percentages.");
599             radioServiceDummy.clearRequestParameters();
600         });
601     }
602
603     private void testChannelWithINCREASEAndDECREASECommands(ChannelUID channelUID, Item item) {
604         synchronized (channelUID) {
605             // First we have to make sure that the item state is 0
606             radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
607             waitForAssert(() -> {
608                 assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
609                         "We should be able to turn on the radio");
610                 radioServiceDummy.clearRequestParameters();
611             });
612
613             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.INCREASE);
614
615             waitForAssert(() -> {
616                 assertTrue(radioServiceDummy.containsRequestParameter(1, VOLUME),
617                         "We should be able to increase the volume correctly");
618                 radioServiceDummy.clearRequestParameters();
619             });
620
621             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
622             waitForAssert(() -> {
623                 assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
624                         "We should be able to increase the volume correctly");
625                 radioServiceDummy.clearRequestParameters();
626             });
627
628             // Trying to decrease one more time
629             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
630             waitForAssert(() -> {
631                 assertFalse(radioServiceDummy.containsRequestParameter(0, VOLUME),
632                         "We should be able to decrease the volume correctly");
633                 radioServiceDummy.clearRequestParameters();
634             });
635         }
636     }
637
638     private void testChannelWithUPAndDOWNCommands(ChannelUID channelUID, Item item) {
639         synchronized (channelUID) {
640             // First we have to make sure that the item state is 0
641             radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
642             waitForAssert(() -> {
643                 assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
644                         "We should be able to turn on the radio");
645                 radioServiceDummy.clearRequestParameters();
646             });
647
648             radioHandler.handleCommand(channelUID, UpDownType.UP);
649             waitForAssert(() -> {
650                 assertTrue(radioServiceDummy.containsRequestParameter(1, VOLUME),
651                         "We should be able to increase the volume correctly");
652                 radioServiceDummy.clearRequestParameters();
653             });
654
655             radioHandler.handleCommand(channelUID, UpDownType.DOWN);
656             waitForAssert(() -> {
657                 assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
658                         "We should be able to decrease the volume correctly");
659                 radioServiceDummy.clearRequestParameters();
660             });
661
662             // Trying to decrease one more time
663             radioHandler.handleCommand(channelUID, UpDownType.DOWN);
664             waitForAssert(() -> {
665                 assertTrue(radioServiceDummy.areRequestParametersEmpty(),
666                         "We shouldn't be able to decrease the volume below 0");
667                 radioServiceDummy.clearRequestParameters();
668             });
669         }
670     }
671
672     /**
673      * Verify the preset channel is updated.
674      */
675     @Test
676     public void presetChannelUpdated() {
677         String presetChannelID = FSInternetRadioBindingConstants.CHANNEL_PRESET;
678         String acceptedItemType = acceptedItemTypes.get(presetChannelID);
679         createChannel(DEFAULT_THING_UID, presetChannelID, acceptedItemType);
680
681         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
682         testRadioThingConsideringConfiguration(radioThing);
683         turnTheRadioOn(radioThing);
684
685         ChannelUID presetChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_PRESET);
686         initializeItem(presetChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
687
688         radioHandler.handleCommand(presetChannelUID, DecimalType.valueOf("100"));
689         waitForAssert(() -> {
690             assertTrue(radioServiceDummy.containsRequestParameter(100, PRESET),
691                     "We should be able to set value to the preset");
692             radioServiceDummy.clearRequestParameters();
693         });
694     }
695
696     /**
697      * Verify the playInfoName channel is updated.
698      */
699     @Test
700     public void playInfoNameChannelUpdated() {
701         String playInfoNameChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME;
702         String acceptedItemType = acceptedItemTypes.get(playInfoNameChannelID);
703         createChannel(DEFAULT_THING_UID, playInfoNameChannelID, acceptedItemType);
704
705         Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
706         testRadioThingConsideringConfiguration(radioThing);
707
708         turnTheRadioOn(radioThing);
709
710         ChannelUID playInfoNameChannelUID = getChannelUID(radioThing,
711                 FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME);
712         initializeItem(playInfoNameChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
713
714         waitForAssert(() -> {
715             verifyOnlineStatusIsSet();
716         });
717     }
718
719     /**
720      * Verify the playInfoText channel is updated.
721      */
722     @Test
723     public void playInfoTextChannelUpdated() {
724         String playInfoTextChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT;
725         String acceptedItemType = acceptedItemTypes.get(playInfoTextChannelID);
726         createChannel(DEFAULT_THING_UID, playInfoTextChannelID, acceptedItemType);
727
728         Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
729         testRadioThingConsideringConfiguration(radioThing);
730
731         turnTheRadioOn(radioThing);
732         ChannelUID playInfoTextChannelUID = getChannelUID(radioThing,
733                 FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT);
734         initializeItem(playInfoTextChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
735
736         waitForAssert(() -> {
737             verifyOnlineStatusIsSet();
738         });
739     }
740
741     private static Configuration createDefaultConfiguration() {
742         return createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN,
743                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
744     }
745
746     private static Configuration createConfiguration(String ip, String pin, String port, String refresh) {
747         Configuration config = new Configuration();
748         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP, ip);
749         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN, pin);
750         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT, new BigDecimal(port));
751         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_REFRESH, new BigDecimal(refresh));
752         return config;
753     }
754
755     private static void setTheChannelsMap() {
756         acceptedItemTypes = new HashMap<>();
757         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_POWER, "Switch");
758         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MODE, "Number");
759         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MUTE, "Switch");
760         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME, "String");
761         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT, "String");
762         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PRESET, "Number");
763         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE, "Number");
764         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT, "Dimmer");
765     }
766
767     private void createThePowerChannel() {
768         String powerChannelID = FSInternetRadioBindingConstants.CHANNEL_POWER;
769         String acceptedItemType = acceptedItemTypes.get(powerChannelID);
770         powerChannel = createChannel(DEFAULT_THING_UID, powerChannelID, acceptedItemType);
771     }
772
773     private Item initializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
774         Item item = null;
775
776         switch (acceptedItemType) {
777             case "Number":
778                 item = new NumberItem(itemName);
779                 break;
780
781             case "String":
782                 item = new StringItem(itemName);
783                 break;
784
785             case "Switch":
786                 item = new SwitchItem(itemName);
787                 break;
788
789             case "Dimmer":
790                 item = new DimmerItem(itemName);
791                 break;
792         }
793
794         return item;
795     }
796
797     private Channel createChannel(ThingUID thingUID, String channelID, String acceptedItemType) {
798         ChannelUID channelUID = new ChannelUID(thingUID, channelID);
799
800         Channel radioChannel = ChannelBuilder.create(channelUID, acceptedItemType).build();
801         channels.add(radioChannel);
802         return radioChannel;
803     }
804
805     private void testRadioThingConsideringConfiguration(Thing thing) {
806         Configuration config = thing.getConfiguration();
807         if (isConfigurationComplete(config)) {
808             waitForAssert(() -> {
809                 verifyOnlineStatusIsSet();
810             });
811         } else {
812             waitForAssert(() -> {
813                 verifyConfigurationError();
814             });
815         }
816     }
817
818     private boolean isConfigurationComplete(Configuration config) {
819         String ip = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP);
820         BigDecimal port = (BigDecimal) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT.toString());
821         String pin = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN.toString());
822
823         if (ip == null || port.compareTo(BigDecimal.ZERO) == 0 || StringUtils.isEmpty(pin)) {
824             return false;
825         }
826         return true;
827     }
828
829     @SuppressWarnings("null")
830     private Thing initializeRadioThing(Configuration config) {
831         radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
832                 .withChannels(channels).build();
833
834         callback = mock(ThingHandlerCallback.class);
835
836         radioHandler = new FSInternetRadioHandler(radioThing, httpClient);
837         radioHandler.setCallback(callback);
838         radioThing.setHandler(radioHandler);
839         radioThing.getHandler().initialize();
840
841         return radioThing;
842     }
843
844     @SuppressWarnings("null")
845     private Thing initializeRadioThingWithMockedHandler(Configuration config) {
846         radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
847                 .withChannels(channels).build();
848
849         callback = mock(ThingHandlerCallback.class);
850
851         radioHandler = new MockedRadioHandler(radioThing, httpClient);
852         radioHandler.setCallback(callback);
853         radioThing.setHandler(radioHandler);
854         radioThing.getHandler().initialize();
855
856         return radioThing;
857     }
858
859     private void turnTheRadioOn(Thing radioThing) {
860         radioHandler.handleCommand(getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_POWER),
861                 OnOffType.ON);
862
863         final FrontierSiliconRadio radio = HandlerUtils.getRadio(radioHandler);
864
865         waitForAssert(() -> {
866             try {
867                 assertTrue(radio.getPower());
868             } catch (IOException ex) {
869                 throw new AssertionError("I/O error", ex);
870             }
871         });
872     }
873
874     private void verifyOnlineStatusIsSet() {
875         ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.ONLINE,
876                 ThingStatusDetail.NONE);
877         ThingStatusInfo statusInfo = statusBuilder.withDescription(null).build();
878         verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
879     }
880
881     private void verifyConfigurationError() {
882         ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.OFFLINE,
883                 ThingStatusDetail.CONFIGURATION_ERROR);
884         ThingStatusInfo statusInfo = statusBuilder.withDescription("Configuration incomplete").build();
885         verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
886     }
887
888     private void verifyCommunicationError(String exceptionMessage) {
889         ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
890         verify(callback, atLeast(1)).statusUpdated(isA(Thing.class), captor.capture());
891         ThingStatusInfo status = captor.getValue();
892         assertThat(status.getStatus(), is(ThingStatus.OFFLINE));
893         assertThat(status.getStatusDetail(), is(ThingStatusDetail.COMMUNICATION_ERROR));
894         assertThat(status.getDescription().contains(exceptionMessage), is(true));
895     }
896 }