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