]> git.basschouten.com Git - openhab-addons.git/blob
5f08b267d77a72f12aa3ecf1c140efedecef2c33
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 import java.util.Objects;
29
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         Objects.requireNonNull(channel);
152         return channel;
153     }
154
155     private static @NonNull ChannelUID getChannelUID(final @NonNull Thing thing, final @NonNull String channelId) {
156         final ChannelUID channelUID = getChannel(thing, channelId).getUID();
157         assertNotNull(channelUID);
158         return channelUID;
159     }
160
161     /**
162      * Verify OFFLINE Thing status when the IP is NULL.
163      */
164     @Test
165     public void offlineIfNullIp() {
166         Configuration config = createConfiguration(null, DEFAULT_CONFIG_PROPERTY_PIN,
167                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
168         Thing radioThingWithNullIP = initializeRadioThing(config);
169         testRadioThingConsideringConfiguration(radioThingWithNullIP);
170     }
171
172     /**
173      * Verify OFFLINE Thing status when the PIN is empty String.
174      */
175     @Test
176     public void offlineIfEmptyPIN() {
177         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, "",
178                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
179         Thing radioThingWithEmptyPIN = initializeRadioThing(config);
180         testRadioThingConsideringConfiguration(radioThingWithEmptyPIN);
181     }
182
183     /**
184      * Verify OFFLINE Thing status when the PORT is zero.
185      */
186     @Test
187     public void offlineIfZeroPort() {
188         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN, "0",
189                 DEFAULT_CONFIG_PROPERTY_REFRESH);
190         Thing radioThingWithZeroPort = initializeRadioThing(config);
191         testRadioThingConsideringConfiguration(radioThingWithZeroPort);
192     }
193
194     /**
195      * Verify OFFLINE Thing status when the PIN is wrong.
196      */
197     @Test
198     public void offlineIfWrongPIN() {
199         final String wrongPin = "5678";
200         Configuration config = createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, wrongPin,
201                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
202         initializeRadioThing(config);
203         waitForAssert(() -> {
204             String exceptionMessage = "Radio does not allow connection, maybe wrong pin?";
205             verifyCommunicationError(exceptionMessage);
206         });
207     }
208
209     /**
210      * Verify OFFLINE Thing status when the HTTP response cannot be parsed correctly.
211      */
212     @Test
213     public void offlineIfParseError() {
214         // create a thing with two channels - the power channel and any of the others
215         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
216         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
217         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
218
219         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
220         testRadioThingConsideringConfiguration(radioThing);
221
222         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
223
224         /*
225          * Setting the isInvalidResponseExpected variable to true
226          * in order to get the incorrect XML response from the servlet
227          */
228         radioServiceDummy.setInvalidResponse(true);
229
230         // try to handle a command
231         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
232
233         waitForAssert(() -> {
234             String exceptionMessage = "java.io.IOException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 2;";
235             verifyCommunicationError(exceptionMessage);
236         });
237         radioServiceDummy.setInvalidResponse(false);
238     }
239
240     /**
241      * Verify the HTTP status is handled correctly when it is not OK_200.
242      */
243     @Test
244     public void httpStatusNokHandling() {
245         // create a thing with two channels - the power channel and any of the others
246         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
247         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
248         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
249
250         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
251         testRadioThingConsideringConfiguration(radioThing);
252
253         // turn-on the radio
254         turnTheRadioOn(radioThing);
255
256         /*
257          * Setting the needed boolean variable to false, so we can be sure
258          * that the XML response won't have an OK_200 status
259          */
260         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
261         Item modeTestItem = initializeItem(modeChannelUID, CHANNEL_MODE, acceptedItemType);
262
263         // try to handle a command
264         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
265
266         waitForAssert(() -> {
267             assertSame(UnDefType.NULL, modeTestItem.getState());
268         });
269     }
270
271     /**
272      * Verify ONLINE status of a Thing with complete configuration.
273      */
274     @Test
275     public void verifyOnlineStatus() {
276         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
277         testRadioThingConsideringConfiguration(radioThing);
278     }
279
280     /**
281      * Verify the power channel is updated.
282      */
283     @Test
284     public void powerChannelUpdated() {
285         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
286         testRadioThingConsideringConfiguration(radioThing);
287
288         ChannelUID powerChannelUID = powerChannel.getUID();
289         initializeItem(powerChannelUID, DEFAULT_TEST_ITEM_NAME,
290                 acceptedItemTypes.get(FSInternetRadioBindingConstants.CHANNEL_POWER));
291
292         radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
293         waitForAssert(() -> {
294             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
295                     "We should be able to turn on the radio");
296             radioServiceDummy.clearRequestParameters();
297         });
298
299         radioHandler.handleCommand(powerChannelUID, OnOffType.OFF);
300         waitForAssert(() -> {
301             assertTrue(radioServiceDummy.containsRequestParameter(0, CHANNEL_POWER),
302                     "We should be able to turn off the radio");
303             radioServiceDummy.clearRequestParameters();
304         });
305
306         /*
307          * Setting the needed boolean variable to true, so we can be sure
308          * that an invalid value will be returned in the XML response
309          */
310         radioHandler.handleCommand(powerChannelUID, OnOffType.ON);
311         waitForAssert(() -> {
312             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
313                     "We should be able to turn on the radio");
314             radioServiceDummy.clearRequestParameters();
315         });
316     }
317
318     /**
319      * Verify the mute channel is updated.
320      */
321     @Test
322     public void muteChhannelUpdated() {
323         String muteChannelID = FSInternetRadioBindingConstants.CHANNEL_MUTE;
324         String acceptedItemType = acceptedItemTypes.get(muteChannelID);
325         createChannel(DEFAULT_THING_UID, muteChannelID, acceptedItemType);
326
327         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
328         testRadioThingConsideringConfiguration(radioThing);
329
330         turnTheRadioOn(radioThing);
331
332         ChannelUID muteChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_MUTE);
333         initializeItem(muteChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
334
335         radioHandler.handleCommand(muteChannelUID, OnOffType.ON);
336         waitForAssert(() -> {
337             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_MUTE),
338                     "We should be able to mute the radio");
339             radioServiceDummy.clearRequestParameters();
340         });
341
342         radioHandler.handleCommand(muteChannelUID, OnOffType.OFF);
343         waitForAssert(() -> {
344             assertTrue(radioServiceDummy.containsRequestParameter(0, CHANNEL_MUTE),
345                     "We should be able to unmute the radio");
346             radioServiceDummy.clearRequestParameters();
347         });
348
349         /*
350          * Setting the needed boolean variable to true, so we can be sure
351          * that an invalid value will be returned in the XML response
352          */
353     }
354
355     /**
356      * Verify the mode channel is updated.
357      */
358     @Test
359     public void modeChannelUdpated() {
360         String modeChannelID = FSInternetRadioBindingConstants.CHANNEL_MODE;
361         String acceptedItemType = acceptedItemTypes.get(modeChannelID);
362         createChannel(DEFAULT_THING_UID, modeChannelID, acceptedItemType);
363
364         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
365         testRadioThingConsideringConfiguration(radioThing);
366
367         turnTheRadioOn(radioThing);
368
369         ChannelUID modeChannelUID = getChannelUID(radioThing, modeChannelID);
370         initializeItem(modeChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
371
372         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("1"));
373         waitForAssert(() -> {
374             assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_MODE),
375                     "We should be able to update the mode channel correctly");
376             radioServiceDummy.clearRequestParameters();
377         });
378
379         /*
380          * Setting the needed boolean variable to true, so we can be sure
381          * that an invalid value will be returned in the XML response
382          */
383         radioHandler.handleCommand(modeChannelUID, DecimalType.valueOf("3"));
384         waitForAssert(() -> {
385             assertTrue(radioServiceDummy.containsRequestParameter(3, CHANNEL_MODE),
386                     "We should be able to update the mode channel correctly");
387             radioServiceDummy.clearRequestParameters();
388         });
389     }
390
391     /**
392      * Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using INCREASE and DECREASE commands.
393      */
394     @Test
395     public void volumechannelUpdatedAbsIncDec() {
396         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
397         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
398         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
399
400         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
401         testRadioThingConsideringConfiguration(radioThing);
402
403         turnTheRadioOn(radioThing);
404
405         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
406         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
407                 absoluteAcceptedItemType);
408
409         testChannelWithINCREASEAndDECREASECommands(absoluteVolumeChannelUID, volumeTestItem);
410     }
411
412     /**
413      * Verify the volume is updated through the CHANNEL_VOLUME_ABSOLUTE using UP and DOWN commands.
414      */
415     @Test
416     public void volumeChannelUpdatedAbsUpDown() {
417         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
418         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
419         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
420
421         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
422         testRadioThingConsideringConfiguration(radioThing);
423
424         turnTheRadioOn(radioThing);
425
426         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
427         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
428                 absoluteAcceptedItemType);
429
430         testChannelWithUPAndDOWNCommands(absoluteVolumeChannelUID, volumeTestItem);
431     }
432
433     /**
434      * Verify the invalid values when updating CHANNEL_VOLUME_ABSOLUTE are handled correctly.
435      */
436     @Test
437     public void invalidAbsVolumeValues() {
438         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
439         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
440         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
441
442         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
443         testRadioThingConsideringConfiguration(radioThing);
444
445         turnTheRadioOn(radioThing);
446
447         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
448         initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
449
450         // Trying to set a value that is greater than the maximum volume
451         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("36"));
452
453         waitForAssert(() -> {
454             assertTrue(radioServiceDummy.containsRequestParameter(32, VOLUME),
455                     "The volume should not exceed the maximum value");
456             radioServiceDummy.clearRequestParameters();
457         });
458
459         // Trying to increase the volume more than its maximum value using the INCREASE command
460         radioHandler.handleCommand(absoluteVolumeChannelUID, IncreaseDecreaseType.INCREASE);
461
462         waitForAssert(() -> {
463             assertTrue(radioServiceDummy.areRequestParametersEmpty(),
464                     "The volume should not be increased above the maximum value");
465             radioServiceDummy.clearRequestParameters();
466         });
467
468         // Trying to increase the volume more than its maximum value using the UP command
469         radioHandler.handleCommand(absoluteVolumeChannelUID, UpDownType.UP);
470
471         waitForAssert(() -> {
472             assertTrue(radioServiceDummy.areRequestParametersEmpty(),
473                     "The volume should not be increased above the maximum value");
474             radioServiceDummy.clearRequestParameters();
475         });
476
477         // Trying to set a value that is lower than the minimum volume value
478         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("-10"));
479         waitForAssert(() -> {
480             assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
481                     "The volume should not be decreased below 0");
482             radioServiceDummy.clearRequestParameters();
483         });
484
485         /*
486          * Setting the needed boolean variable to true, so we can be sure
487          * that an invalid value will be returned in the XML response
488          */
489
490         // trying to set the volume
491         radioHandler.handleCommand(absoluteVolumeChannelUID, DecimalType.valueOf("15"));
492         waitForAssert(() -> {
493             assertTrue(radioServiceDummy.containsRequestParameter(15, VOLUME),
494                     "We should be able to set the volume correctly");
495             radioServiceDummy.clearRequestParameters();
496         });
497     }
498
499     /**
500      * Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using INCREASE and DECREASE commands.
501      */
502     @Test
503     public void volumeChannelUpdatedPercIncDec() {
504         /*
505          * The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
506          * the absolute volume will be updated properly.
507          */
508         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
509         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
510         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
511
512         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
513         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
514         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
515
516         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
517         testRadioThingConsideringConfiguration(radioThing);
518
519         turnTheRadioOn(radioThing);
520
521         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
522         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
523                 absoluteAcceptedItemType);
524
525         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
526
527         testChannelWithINCREASEAndDECREASECommands(percentVolumeChannelUID, volumeTestItem);
528     }
529
530     /**
531      * Verify the volume is updated through the CHANNEL_VOLUME_PERCENT using UP and DOWN commands.
532      */
533     @Test
534     public void volumeChannelUpdatedPercUpDown() {
535         /*
536          * The volume is set through the CHANNEL_VOLUME_PERCENT in order to check if
537          * the absolute volume will be updated properly.
538          */
539         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
540         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
541         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
542
543         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
544         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
545         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
546
547         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
548         testRadioThingConsideringConfiguration(radioThing);
549
550         turnTheRadioOn(radioThing);
551
552         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
553         Item volumeTestItem = initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME,
554                 absoluteAcceptedItemType);
555
556         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
557
558         testChannelWithUPAndDOWNCommands(percentVolumeChannelUID, volumeTestItem);
559     }
560
561     /**
562      * Verify the valid and invalid values when updating CHANNEL_VOLUME_PERCENT are handled correctly.
563      */
564     @Test
565     public void validInvalidPercVolume() {
566         String absoluteVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE;
567         String absoluteAcceptedItemType = acceptedItemTypes.get(absoluteVolumeChannelID);
568         createChannel(DEFAULT_THING_UID, absoluteVolumeChannelID, absoluteAcceptedItemType);
569
570         String percentVolumeChannelID = FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT;
571         String percentAcceptedItemType = acceptedItemTypes.get(percentVolumeChannelID);
572         createChannel(DEFAULT_THING_UID, percentVolumeChannelID, percentAcceptedItemType);
573
574         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
575         testRadioThingConsideringConfiguration(radioThing);
576
577         turnTheRadioOn(radioThing);
578
579         ChannelUID absoluteVolumeChannelUID = getChannelUID(radioThing, absoluteVolumeChannelID);
580         initializeItem(absoluteVolumeChannelUID, DEFAULT_TEST_ITEM_NAME, absoluteAcceptedItemType);
581
582         ChannelUID percentVolumeChannelUID = getChannelUID(radioThing, percentVolumeChannelID);
583
584         /*
585          * Giving the handler a valid percent value. According to the FrontierSiliconRadio's
586          * documentation 100 percents correspond to 32 absolute value
587          */
588         radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("50"));
589         waitForAssert(() -> {
590             assertTrue(radioServiceDummy.containsRequestParameter(16, VOLUME),
591                     "We should be able to set the volume correctly using percentages.");
592             radioServiceDummy.clearRequestParameters();
593         });
594
595         radioHandler.handleCommand(percentVolumeChannelUID, PercentType.valueOf("15"));
596
597         waitForAssert(() -> {
598             assertTrue(radioServiceDummy.containsRequestParameter(4, VOLUME),
599                     "We should be able to set the volume correctly using percentages.");
600             radioServiceDummy.clearRequestParameters();
601         });
602     }
603
604     private void testChannelWithINCREASEAndDECREASECommands(ChannelUID channelUID, Item item) {
605         synchronized (channelUID) {
606             // First we have to make sure that the item state is 0
607             radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
608             waitForAssert(() -> {
609                 assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
610                         "We should be able to turn on the radio");
611                 radioServiceDummy.clearRequestParameters();
612             });
613
614             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.INCREASE);
615
616             waitForAssert(() -> {
617                 assertTrue(radioServiceDummy.containsRequestParameter(1, VOLUME),
618                         "We should be able to increase the volume correctly");
619                 radioServiceDummy.clearRequestParameters();
620             });
621
622             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
623             waitForAssert(() -> {
624                 assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
625                         "We should be able to increase the volume correctly");
626                 radioServiceDummy.clearRequestParameters();
627             });
628
629             // Trying to decrease one more time
630             radioHandler.handleCommand(channelUID, IncreaseDecreaseType.DECREASE);
631             waitForAssert(() -> {
632                 assertFalse(radioServiceDummy.containsRequestParameter(0, VOLUME),
633                         "We should be able to decrease the volume correctly");
634                 radioServiceDummy.clearRequestParameters();
635             });
636         }
637     }
638
639     private void testChannelWithUPAndDOWNCommands(ChannelUID channelUID, Item item) {
640         synchronized (channelUID) {
641             // First we have to make sure that the item state is 0
642             radioHandler.handleCommand(channelUID, DecimalType.valueOf("0"));
643             waitForAssert(() -> {
644                 assertTrue(radioServiceDummy.containsRequestParameter(1, CHANNEL_POWER),
645                         "We should be able to turn on the radio");
646                 radioServiceDummy.clearRequestParameters();
647             });
648
649             radioHandler.handleCommand(channelUID, UpDownType.UP);
650             waitForAssert(() -> {
651                 assertTrue(radioServiceDummy.containsRequestParameter(1, VOLUME),
652                         "We should be able to increase the volume correctly");
653                 radioServiceDummy.clearRequestParameters();
654             });
655
656             radioHandler.handleCommand(channelUID, UpDownType.DOWN);
657             waitForAssert(() -> {
658                 assertTrue(radioServiceDummy.containsRequestParameter(0, VOLUME),
659                         "We should be able to decrease the volume correctly");
660                 radioServiceDummy.clearRequestParameters();
661             });
662
663             // Trying to decrease one more time
664             radioHandler.handleCommand(channelUID, UpDownType.DOWN);
665             waitForAssert(() -> {
666                 assertTrue(radioServiceDummy.areRequestParametersEmpty(),
667                         "We shouldn't be able to decrease the volume below 0");
668                 radioServiceDummy.clearRequestParameters();
669             });
670         }
671     }
672
673     /**
674      * Verify the preset channel is updated.
675      */
676     @Test
677     public void presetChannelUpdated() {
678         String presetChannelID = FSInternetRadioBindingConstants.CHANNEL_PRESET;
679         String acceptedItemType = acceptedItemTypes.get(presetChannelID);
680         createChannel(DEFAULT_THING_UID, presetChannelID, acceptedItemType);
681
682         Thing radioThing = initializeRadioThing(DEFAULT_COMPLETE_CONFIGURATION);
683         testRadioThingConsideringConfiguration(radioThing);
684         turnTheRadioOn(radioThing);
685
686         ChannelUID presetChannelUID = getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_PRESET);
687         initializeItem(presetChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
688
689         radioHandler.handleCommand(presetChannelUID, DecimalType.valueOf("100"));
690         waitForAssert(() -> {
691             assertTrue(radioServiceDummy.containsRequestParameter(100, PRESET),
692                     "We should be able to set value to the preset");
693             radioServiceDummy.clearRequestParameters();
694         });
695     }
696
697     /**
698      * Verify the playInfoName channel is updated.
699      */
700     @Test
701     public void playInfoNameChannelUpdated() {
702         String playInfoNameChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME;
703         String acceptedItemType = acceptedItemTypes.get(playInfoNameChannelID);
704         createChannel(DEFAULT_THING_UID, playInfoNameChannelID, acceptedItemType);
705
706         Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
707         testRadioThingConsideringConfiguration(radioThing);
708
709         turnTheRadioOn(radioThing);
710
711         ChannelUID playInfoNameChannelUID = getChannelUID(radioThing,
712                 FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME);
713         initializeItem(playInfoNameChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
714
715         waitForAssert(() -> {
716             verifyOnlineStatusIsSet();
717         });
718     }
719
720     /**
721      * Verify the playInfoText channel is updated.
722      */
723     @Test
724     public void playInfoTextChannelUpdated() {
725         String playInfoTextChannelID = FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT;
726         String acceptedItemType = acceptedItemTypes.get(playInfoTextChannelID);
727         createChannel(DEFAULT_THING_UID, playInfoTextChannelID, acceptedItemType);
728
729         Thing radioThing = initializeRadioThingWithMockedHandler(DEFAULT_COMPLETE_CONFIGURATION);
730         testRadioThingConsideringConfiguration(radioThing);
731
732         turnTheRadioOn(radioThing);
733         ChannelUID playInfoTextChannelUID = getChannelUID(radioThing,
734                 FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT);
735         initializeItem(playInfoTextChannelUID, DEFAULT_TEST_ITEM_NAME, acceptedItemType);
736
737         waitForAssert(() -> {
738             verifyOnlineStatusIsSet();
739         });
740     }
741
742     private static Configuration createDefaultConfiguration() {
743         return createConfiguration(DEFAULT_CONFIG_PROPERTY_IP, DEFAULT_CONFIG_PROPERTY_PIN,
744                 String.valueOf(DEFAULT_CONFIG_PROPERTY_PORT), DEFAULT_CONFIG_PROPERTY_REFRESH);
745     }
746
747     private static Configuration createConfiguration(String ip, String pin, String port, String refresh) {
748         Configuration config = new Configuration();
749         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP, ip);
750         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN, pin);
751         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT, new BigDecimal(port));
752         config.put(FSInternetRadioBindingConstants.CONFIG_PROPERTY_REFRESH, new BigDecimal(refresh));
753         return config;
754     }
755
756     private static void setTheChannelsMap() {
757         acceptedItemTypes = new HashMap<>();
758         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_POWER, "Switch");
759         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MODE, "Number");
760         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_MUTE, "Switch");
761         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_NAME, "String");
762         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PLAY_INFO_TEXT, "String");
763         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_PRESET, "Number");
764         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_ABSOLUTE, "Number");
765         acceptedItemTypes.put(FSInternetRadioBindingConstants.CHANNEL_VOLUME_PERCENT, "Dimmer");
766     }
767
768     private void createThePowerChannel() {
769         String powerChannelID = FSInternetRadioBindingConstants.CHANNEL_POWER;
770         String acceptedItemType = acceptedItemTypes.get(powerChannelID);
771         powerChannel = createChannel(DEFAULT_THING_UID, powerChannelID, acceptedItemType);
772     }
773
774     private Item initializeItem(ChannelUID channelUID, String itemName, String acceptedItemType) {
775         Item item = null;
776
777         switch (acceptedItemType) {
778             case "Number":
779                 item = new NumberItem(itemName);
780                 break;
781
782             case "String":
783                 item = new StringItem(itemName);
784                 break;
785
786             case "Switch":
787                 item = new SwitchItem(itemName);
788                 break;
789
790             case "Dimmer":
791                 item = new DimmerItem(itemName);
792                 break;
793         }
794
795         return item;
796     }
797
798     private Channel createChannel(ThingUID thingUID, String channelID, String acceptedItemType) {
799         ChannelUID channelUID = new ChannelUID(thingUID, channelID);
800
801         Channel radioChannel = ChannelBuilder.create(channelUID, acceptedItemType).build();
802         channels.add(radioChannel);
803         return radioChannel;
804     }
805
806     private void testRadioThingConsideringConfiguration(Thing thing) {
807         Configuration config = thing.getConfiguration();
808         if (isConfigurationComplete(config)) {
809             waitForAssert(() -> {
810                 verifyOnlineStatusIsSet();
811             });
812         } else {
813             waitForAssert(() -> {
814                 verifyConfigurationError();
815             });
816         }
817     }
818
819     private boolean isConfigurationComplete(Configuration config) {
820         String ip = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_IP);
821         BigDecimal port = (BigDecimal) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PORT);
822         String pin = (String) config.get(FSInternetRadioBindingConstants.CONFIG_PROPERTY_PIN);
823
824         return !(ip == null || port.compareTo(BigDecimal.ZERO) == 0 || pin == null || pin.isEmpty());
825     }
826
827     @SuppressWarnings("null")
828     private Thing initializeRadioThing(Configuration config) {
829         radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
830                 .withChannels(channels).build();
831
832         callback = mock(ThingHandlerCallback.class);
833
834         radioHandler = new FSInternetRadioHandler(radioThing, httpClient);
835         radioHandler.setCallback(callback);
836         radioThing.setHandler(radioHandler);
837         radioThing.getHandler().initialize();
838
839         return radioThing;
840     }
841
842     @SuppressWarnings("null")
843     private Thing initializeRadioThingWithMockedHandler(Configuration config) {
844         radioThing = ThingBuilder.create(DEFAULT_THING_TYPE_UID, DEFAULT_THING_UID).withConfiguration(config)
845                 .withChannels(channels).build();
846
847         callback = mock(ThingHandlerCallback.class);
848
849         radioHandler = new MockedRadioHandler(radioThing, httpClient);
850         radioHandler.setCallback(callback);
851         radioThing.setHandler(radioHandler);
852         radioThing.getHandler().initialize();
853
854         return radioThing;
855     }
856
857     private void turnTheRadioOn(Thing radioThing) {
858         radioHandler.handleCommand(getChannelUID(radioThing, FSInternetRadioBindingConstants.CHANNEL_POWER),
859                 OnOffType.ON);
860
861         final FrontierSiliconRadio radio = HandlerUtils.getRadio(radioHandler);
862
863         waitForAssert(() -> {
864             try {
865                 assertTrue(radio.getPower());
866             } catch (IOException ex) {
867                 throw new AssertionError("I/O error", ex);
868             }
869         });
870     }
871
872     private void verifyOnlineStatusIsSet() {
873         ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.ONLINE,
874                 ThingStatusDetail.NONE);
875         ThingStatusInfo statusInfo = statusBuilder.withDescription(null).build();
876         verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
877     }
878
879     private void verifyConfigurationError() {
880         ThingStatusInfoBuilder statusBuilder = ThingStatusInfoBuilder.create(ThingStatus.OFFLINE,
881                 ThingStatusDetail.CONFIGURATION_ERROR);
882         ThingStatusInfo statusInfo = statusBuilder.withDescription("Configuration incomplete").build();
883         verify(callback, atLeast(1)).statusUpdated(radioThing, statusInfo);
884     }
885
886     private void verifyCommunicationError(String exceptionMessage) {
887         ArgumentCaptor<ThingStatusInfo> captor = ArgumentCaptor.forClass(ThingStatusInfo.class);
888         verify(callback, atLeast(1)).statusUpdated(isA(Thing.class), captor.capture());
889         ThingStatusInfo status = captor.getValue();
890         assertThat(status.getStatus(), is(ThingStatus.OFFLINE));
891         assertThat(status.getStatusDetail(), is(ThingStatusDetail.COMMUNICATION_ERROR));
892         assertThat(status.getDescription().contains(exceptionMessage), is(true));
893     }
894 }