]> git.basschouten.com Git - openhab-addons.git/blob
72b0c2692e02f4dea1b7b5fc0e200eb50fd0c856
[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.upnpcontrol.internal.handler;
14
15 import static org.eclipse.jdt.annotation.Checks.requireNonNull;
16 import static org.hamcrest.MatcherAssert.assertThat;
17 import static org.hamcrest.Matchers.is;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.Mockito.*;
20 import static org.openhab.binding.upnpcontrol.internal.UpnpControlBindingConstants.*;
21
22 import java.util.Arrays;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ConcurrentMap;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.junit.jupiter.api.AfterEach;
32 import org.junit.jupiter.api.BeforeEach;
33 import org.junit.jupiter.api.Test;
34 import org.mockito.ArgumentCaptor;
35 import org.mockito.Mock;
36 import org.openhab.binding.upnpcontrol.internal.config.UpnpControlServerConfiguration;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingUID;
43 import org.openhab.core.thing.binding.builder.ChannelBuilder;
44 import org.openhab.core.types.CommandOption;
45 import org.openhab.core.types.StateOption;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 /**
50  * Unit tests for {@link UpnpServerHandler}.
51  *
52  * @author Mark Herwege - Initial contribution
53  */
54 @SuppressWarnings({ "null", "unchecked" })
55 @NonNullByDefault
56 public class UpnpServerHandlerTest extends UpnpHandlerTest {
57
58     private final Logger logger = LoggerFactory.getLogger(UpnpServerHandlerTest.class);
59
60     private static final String THING_TYPE_UID = "upnpcontrol:upnpserver";
61     private static final String THING_UID = THING_TYPE_UID + ":mockserver";
62
63     private static final String RESPONSE_HEADER = "<DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
64             + "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
65             + "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">";
66     private static final String RESPONSE_FOOTER = "</DIDL-Lite>";
67
68     private static final String BASE_CONTAINER = RESPONSE_HEADER
69             + "<container id=\"C1\" searchable=\"0\" parentID=\"0\" restricted=\"1\" childCount=\"2\">"
70             + "<dc:title>All Audio Items</dc:title><upnp:class>object.container</upnp:class>"
71             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></container>"
72             + "<container id=\"C2\" searchable=\"0\" parentID=\"0\" restricted=\"1\" childCount=\"0\">"
73             + "<dc:title>All Image Items</dc:title><upnp:class>object.container</upnp:class>"
74             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></container>" + RESPONSE_FOOTER;
75
76     private static final String SINGLE_CONTAINER = RESPONSE_HEADER
77             + "<container id=\"C11\" searchable=\"0\" parentID=\"C1\" restricted=\"1\" childCount=\"2\">"
78             + "<dc:title>Morning Music</dc:title><upnp:class>object.container</upnp:class>"
79             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></container>" + RESPONSE_FOOTER;
80
81     private static final String DOUBLE_CONTAINER = RESPONSE_HEADER
82             + "<container id=\"C11\" searchable=\"0\" parentID=\"C1\" restricted=\"1\" childCount=\"2\">"
83             + "<dc:title>Morning Music</dc:title><upnp:class>object.container</upnp:class>"
84             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></container>"
85             + "<container id=\"C12\" searchable=\"0\" parentID=\"C1\" restricted=\"1\" childCount=\"1\">"
86             + "<dc:title>Evening Music</dc:title><upnp:class>object.container</upnp:class>"
87             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></container>" + RESPONSE_FOOTER;
88
89     private static final String DOUBLE_MEDIA = RESPONSE_HEADER + "<item id=\"M1\" parentID=\"C11\" restricted=\"1\">"
90             + "<dc:title>Music_01</dc:title><upnp:class>object.item.audioItem</upnp:class>"
91             + "<dc:creator>Creator_1</dc:creator>"
92             + "<res protocolInfo=\"http-get:*:audio/mpeg:*\" size=\"8054458\" importUri=\"http://MediaServerContent_0/1/M1/\">http://MediaServerContent_0/1/M1/Test_1.mp3</res>"
93             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></item>"
94             + "<item id=\"M2\" parentID=\"C11\" restricted=\"1\">"
95             + "<dc:title>Music_02</dc:title><upnp:class>object.item.audioItem</upnp:class>"
96             + "<dc:creator>Creator_2</dc:creator>"
97             + "<res protocolInfo=\"http-get:*:audio/wav:*\" size=\"1156598\" importUri=\"http://MediaServerContent_0/3/M2/\">http://MediaServerContent_0/3/M2/Test_2.wav</res>"
98             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></item>" + RESPONSE_FOOTER;
99
100     private static final String EXTRA_MEDIA = RESPONSE_HEADER + "<item id=\"M3\" parentID=\"C12\" restricted=\"1\">"
101             + "<dc:title>Extra_01</dc:title><upnp:class>object.item.audioItem</upnp:class>"
102             + "<dc:creator>Creator_3</dc:creator>"
103             + "<res protocolInfo=\"http-get:*:audio/mpeg:*\" size=\"8054458\" importUri=\"http://MediaServerContent_0/1/M3/\">http://MediaServerContent_0/1/M3/Test_3.mp3</res>"
104             + "<upnp:writeStatus>UNKNOWN</upnp:writeStatus></item>" + RESPONSE_FOOTER;
105
106     protected @Nullable UpnpServerHandler handler;
107
108     private ChannelUID rendererChannelUID = new ChannelUID(THING_UID + ":" + UPNPRENDERER);
109     private Channel rendererChannel = ChannelBuilder.create(rendererChannelUID, "String").build();
110
111     private ChannelUID browseChannelUID = new ChannelUID(THING_UID + ":" + BROWSE);
112     private Channel browseChannel = ChannelBuilder.create(browseChannelUID, "String").build();
113
114     private ChannelUID currentTitleChannelUID = new ChannelUID(THING_UID + ":" + CURRENTTITLE);
115     private Channel currentTitleChannel = ChannelBuilder.create(currentTitleChannelUID, "String").build();
116
117     private ChannelUID searchChannelUID = new ChannelUID(THING_UID + ":" + SEARCH);
118     private Channel searchChannel = ChannelBuilder.create(searchChannelUID, "String").build();
119
120     private ChannelUID playlistSelectChannelUID = new ChannelUID(THING_UID + ":" + PLAYLIST_SELECT);
121     private Channel playlistSelectChannel = ChannelBuilder.create(playlistSelectChannelUID, "String").build();
122
123     private ChannelUID playlistChannelUID = new ChannelUID(THING_UID + ":" + PLAYLIST);
124     private Channel playlistChannel = ChannelBuilder.create(playlistChannelUID, "String").build();
125
126     private ChannelUID playlistActionChannelUID = new ChannelUID(THING_UID + ":" + PLAYLIST_ACTION);
127     private Channel playlistActionChannel = ChannelBuilder.create(playlistActionChannelUID, "String").build();
128
129     private ConcurrentMap<String, UpnpRendererHandler> upnpRenderers = new ConcurrentHashMap<>();
130
131     @Mock
132     private @Nullable UpnpRendererHandler rendererHandler;
133     @Mock
134     private @Nullable Thing rendererThing;
135
136     @Override
137     @BeforeEach
138     public void setUp() {
139         super.setUp();
140
141         // stub thing methods
142         when(thing.getUID()).thenReturn(new ThingUID("upnpcontrol", "upnpserver", "mockserver"));
143         when(thing.getLabel()).thenReturn("MockServer");
144         when(thing.getStatus()).thenReturn(ThingStatus.OFFLINE);
145
146         // stub upnpIOService methods for initialize
147         Map<String, String> result = new HashMap<>();
148         result.put("Result", BASE_CONTAINER);
149         when(upnpIOService.invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap())).thenReturn(result);
150
151         // stub rendererHandler, so that only one protocol is supported and results should be filtered when filter true
152         when(rendererHandler.getSink()).thenReturn(Arrays.asList("http-get:*:audio/mpeg:*"));
153         when(rendererHandler.getThing()).thenReturn(requireNonNull(rendererThing));
154         when(rendererThing.getUID()).thenReturn(new ThingUID("upnpcontrol", "upnprenderer", "mockrenderer"));
155         when(rendererThing.getLabel()).thenReturn("MockRenderer");
156         upnpRenderers.put(rendererThing.getUID().toString(), requireNonNull(rendererHandler));
157
158         // stub channels
159         when(thing.getChannel(UPNPRENDERER)).thenReturn(rendererChannel);
160         when(thing.getChannel(BROWSE)).thenReturn(browseChannel);
161         when(thing.getChannel(CURRENTTITLE)).thenReturn(currentTitleChannel);
162         when(thing.getChannel(SEARCH)).thenReturn(searchChannel);
163         when(thing.getChannel(PLAYLIST_SELECT)).thenReturn(playlistSelectChannel);
164         when(thing.getChannel(PLAYLIST)).thenReturn(playlistChannel);
165         when(thing.getChannel(PLAYLIST_ACTION)).thenReturn(playlistActionChannel);
166
167         // stub config for initialize
168         when(config.as(UpnpControlServerConfiguration.class)).thenReturn(new UpnpControlServerConfiguration());
169
170         handler = spy(new UpnpServerHandler(requireNonNull(thing), requireNonNull(upnpIOService),
171                 requireNonNull(upnpRenderers), requireNonNull(upnpStateDescriptionProvider),
172                 requireNonNull(upnpCommandDescriptionProvider), configuration));
173
174         initHandler(requireNonNull(handler));
175
176         handler.initialize();
177     }
178
179     @Override
180     @AfterEach
181     public void tearDown() {
182         handler.dispose();
183
184         super.tearDown();
185     }
186
187     @Test
188     public void testBase() {
189         logger.info("testBase");
190
191         handler.config.filter = false;
192         handler.config.browseDown = false;
193         handler.config.searchFromRoot = false;
194
195         // Check currentEntry
196         assertThat(handler.currentEntry.getId(), is(UpnpServerHandler.DIRECTORY_ROOT));
197
198         // Check BROWSE
199         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
200         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
201         assertThat(stringCaptor.getValue(), is(StringType.valueOf("0")));
202
203         // Check CURRENTTITLE
204         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
205                 stringCaptor.capture());
206         assertThat(stringCaptor.getValue(), is(StringType.valueOf("")));
207
208         // Check entries
209         assertThat(handler.entries.size(), is(2));
210         assertThat(handler.entries.get(0).getId(), is("C1"));
211         assertThat(handler.entries.get(0).getTitle(), is("All Audio Items"));
212         assertThat(handler.entries.get(1).getId(), is("C2"));
213         assertThat(handler.entries.get(1).getTitle(), is("All Image Items"));
214
215         // Check that BROWSE channel gets the correct command options, no UP should be added
216         ArgumentCaptor<List<StateOption>> commandOptionListCaptor = ArgumentCaptor.forClass(List.class);
217         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
218                 commandOptionListCaptor.capture());
219         assertThat(commandOptionListCaptor.getValue().size(), is(2));
220         assertThat(commandOptionListCaptor.getValue().get(0).getValue(), is("C1"));
221         assertThat(commandOptionListCaptor.getValue().get(0).getLabel(), is("All Audio Items"));
222         assertThat(commandOptionListCaptor.getValue().get(1).getValue(), is("C2"));
223         assertThat(commandOptionListCaptor.getValue().get(1).getLabel(), is("All Image Items"));
224
225         // Check media queue serving
226         verify(rendererHandler, times(0)).registerQueue(any());
227     }
228
229     @Test
230     public void testSetBrowse() {
231         logger.info("testSetBrowse");
232
233         handler.config.filter = false;
234         handler.config.browseDown = false;
235         handler.config.searchFromRoot = false;
236
237         Map<String, String> result = new HashMap<>();
238         result.put("Result", DOUBLE_MEDIA);
239         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
240
241         handler.handleCommand(browseChannelUID, StringType.valueOf("C11"));
242
243         // Check currentEntry
244         assertThat(handler.currentEntry.getId(), is("C11"));
245
246         // Check BROWSE
247         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
248         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
249         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C11")));
250
251         // Check CURRENTTITLE
252         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
253                 stringCaptor.capture());
254         assertThat(stringCaptor.getValue(), is(StringType.valueOf("")));
255
256         // Check entries
257         assertThat(handler.entries.size(), is(2));
258         assertThat(handler.entries.get(0).getId(), is("M1"));
259         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
260         assertThat(handler.entries.get(1).getId(), is("M2"));
261         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
262
263         // Check that BROWSE channel gets the correct state options
264         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
265         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
266                 stateOptionListCaptor.capture());
267         assertThat(stateOptionListCaptor.getValue().size(), is(3));
268         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
269         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
270         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
271         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
272         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("M2"));
273         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Music_02"));
274
275         // Check media queue serving
276         verify(rendererHandler, times(0)).registerQueue(any());
277     }
278
279     @Test
280     public void testSetBrowseRendererFilter() {
281         logger.info("testSetBrowseRendererFilter");
282
283         handler.config.filter = true;
284         handler.config.browseDown = false;
285         handler.config.searchFromRoot = false;
286
287         handler.handleCommand(rendererChannelUID, StringType.valueOf(rendererThing.getUID().toString()));
288
289         Map<String, String> result = new HashMap<>();
290         result.put("Result", DOUBLE_MEDIA);
291         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
292
293         handler.handleCommand(browseChannelUID, StringType.valueOf("C11"));
294
295         // Check currentEntry
296         assertThat(handler.currentEntry.getId(), is("C11"));
297
298         // Check BROWSE
299         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
300         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
301         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C11")));
302
303         // Check CURRENTTITLE
304         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
305                 stringCaptor.capture());
306         assertThat(stringCaptor.getValue(), is(StringType.valueOf("")));
307
308         // Check entries
309         assertThat(handler.entries.size(), is(1));
310         assertThat(handler.entries.get(0).getId(), is("M1"));
311         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
312
313         // Check that BROWSE channel gets the correct state options
314         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
315         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
316                 stateOptionListCaptor.capture());
317         assertThat(stateOptionListCaptor.getValue().size(), is(2));
318         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
319         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
320         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
321         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
322
323         // Check media queue serving
324         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(UPNPRENDERER).getUID()),
325                 stringCaptor.capture());
326         assertThat(stringCaptor.getValue(), is(StringType.valueOf(rendererThing.getUID().toString())));
327
328         // Check media queue serving
329         verify(rendererHandler).registerQueue(any());
330     }
331
332     @Test
333     public void testBrowseContainers() {
334         logger.info("testBrowseContainers");
335
336         handler.config.filter = false;
337         handler.config.browseDown = false;
338         handler.config.searchFromRoot = false;
339
340         Map<String, String> result = new HashMap<>();
341         result.put("Result", DOUBLE_CONTAINER);
342         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
343
344         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
345
346         // Check currentEntry
347         assertThat(handler.currentEntry.getId(), is("C1"));
348
349         // Check BROWSE
350         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
351         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
352         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C1")));
353
354         // Check CURRENTTITLE
355         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
356                 stringCaptor.capture());
357         assertThat(stringCaptor.getValue(), is(StringType.valueOf("All Audio Items")));
358
359         // Check entries
360         assertThat(handler.entries.size(), is(2));
361         assertThat(handler.entries.get(0).getId(), is("C11"));
362         assertThat(handler.entries.get(0).getTitle(), is("Morning Music"));
363         assertThat(handler.entries.get(1).getId(), is("C12"));
364         assertThat(handler.entries.get(1).getTitle(), is("Evening Music"));
365
366         // Check that BROWSE channel gets the correct state options
367         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
368         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
369                 stateOptionListCaptor.capture());
370         assertThat(stateOptionListCaptor.getValue().size(), is(3));
371         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
372         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
373         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("C11"));
374         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Morning Music"));
375         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("C12"));
376         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Evening Music"));
377
378         // Check media queue serving
379         verify(rendererHandler, times(0)).registerQueue(any());
380     }
381
382     @Test
383     public void testBrowseOneContainerNoBrowseDown() {
384         logger.info("testBrowseOneContainerNoBrowseDown");
385
386         handler.config.filter = false;
387         handler.config.browseDown = false;
388         handler.config.searchFromRoot = false;
389
390         Map<String, String> resultContainer = new HashMap<>();
391         resultContainer.put("Result", SINGLE_CONTAINER);
392         Map<String, String> resultMedia = new HashMap<>();
393         resultMedia.put("Result", DOUBLE_MEDIA);
394         doReturn(resultContainer).doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"),
395                 eq("Browse"), anyMap());
396
397         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
398
399         // Check currentEntry
400         assertThat(handler.currentEntry.getId(), is("C1"));
401
402         // Check BROWSE
403         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
404         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
405         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C1")));
406
407         // Check CURRENTTITLE
408         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
409                 stringCaptor.capture());
410         assertThat(stringCaptor.getValue(), is(StringType.valueOf("All Audio Items")));
411
412         // Check entries
413         assertThat(handler.entries.size(), is(1));
414         assertThat(handler.entries.get(0).getId(), is("C11"));
415         assertThat(handler.entries.get(0).getTitle(), is("Morning Music"));
416
417         // Check that BROWSE channel gets the correct state options
418         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
419         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
420                 stateOptionListCaptor.capture());
421         assertThat(stateOptionListCaptor.getValue().size(), is(2));
422         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
423         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
424         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("C11"));
425         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Morning Music"));
426
427         // Check that a no media queue is being served as there is no renderer selected
428         verify(rendererHandler, times(0)).registerQueue(any());
429     }
430
431     @Test
432     public void testBrowseOneContainerBrowseDown() {
433         logger.info("testBrowseOneContainerBrowseDown");
434
435         handler.config.filter = false;
436         handler.config.browseDown = true;
437         handler.config.searchFromRoot = false;
438
439         Map<String, String> resultContainer = new HashMap<>();
440         resultContainer.put("Result", SINGLE_CONTAINER);
441         Map<String, String> resultMedia = new HashMap<>();
442         resultMedia.put("Result", DOUBLE_MEDIA);
443         doReturn(resultContainer).doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"),
444                 eq("Browse"), anyMap());
445
446         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
447
448         // Check currentEntry
449         assertThat(handler.currentEntry.getId(), is("C11"));
450
451         // Check BROWSE
452         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
453         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
454         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C11")));
455
456         // Check CURRENTTITLE
457         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
458                 stringCaptor.capture());
459         assertThat(stringCaptor.getValue(), is(StringType.valueOf("Morning Music")));
460
461         // Check entries
462         assertThat(handler.entries.size(), is(2));
463         assertThat(handler.entries.get(0).getId(), is("M1"));
464         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
465         assertThat(handler.entries.get(1).getId(), is("M2"));
466         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
467
468         // Check that BROWSE channel gets the correct state options
469         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
470         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
471                 stateOptionListCaptor.capture());
472         assertThat(stateOptionListCaptor.getValue().size(), is(3));
473         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
474         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
475         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
476         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
477         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("M2"));
478         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Music_02"));
479
480         // Check media queue serving
481         verify(rendererHandler, times(0)).registerQueue(any());
482     }
483
484     @Test
485     public void testSearchOneContainerNotFromRootNoBrowseDown() {
486         logger.info("testSearchOneContainerNotFromRootNoBrowseDown");
487
488         handler.config.filter = false;
489         handler.config.browseDown = false;
490         handler.config.searchFromRoot = false;
491
492         // First navigate away from root
493         Map<String, String> result = new HashMap<>();
494         result.put("Result", DOUBLE_CONTAINER);
495         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
496         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
497
498         Map<String, String> resultContainer = new HashMap<>();
499         resultContainer.put("Result", SINGLE_CONTAINER);
500         Map<String, String> resultMedia = new HashMap<>();
501         resultMedia.put("Result", DOUBLE_MEDIA);
502         doReturn(resultContainer).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"),
503                 anyMap());
504         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
505
506         String searchString = "dc:title contains \"Morning\" and upnp:class derivedfrom \"object.container\"";
507         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
508
509         // Check currentEntry
510         assertThat(handler.currentEntry.getId(), is("C1"));
511
512         // Check BROWSE
513         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
514         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
515         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C1")));
516
517         // Check CURRENTTITLE
518         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
519                 stringCaptor.capture());
520         assertThat(stringCaptor.getValue(), is(StringType.valueOf("All Audio Items")));
521
522         // Check entries
523         assertThat(handler.entries.size(), is(1));
524         assertThat(handler.entries.get(0).getId(), is("C11"));
525         assertThat(handler.entries.get(0).getTitle(), is("Morning Music"));
526
527         // Check that BROWSE channel gets the correct state options
528         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
529         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
530                 stateOptionListCaptor.capture());
531         assertThat(stateOptionListCaptor.getValue().size(), is(2));
532         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
533         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
534         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("C11"));
535         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Morning Music"));
536
537         // Check that a no media queue is being served as there is no renderer selected
538         verify(rendererHandler, times(0)).registerQueue(any());
539     }
540
541     @Test
542     public void testSearchOneContainerNotFromRootBrowseDown() {
543         logger.info("testSearchOneContainerNotFromRootBrowseDown");
544
545         handler.config.filter = false;
546         handler.config.browseDown = true;
547         handler.config.searchFromRoot = false;
548
549         // First navigate away from root
550         Map<String, String> result = new HashMap<>();
551         result.put("Result", DOUBLE_CONTAINER);
552         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
553         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
554
555         Map<String, String> resultContainer = new HashMap<>();
556         resultContainer.put("Result", SINGLE_CONTAINER);
557         Map<String, String> resultMedia = new HashMap<>();
558         resultMedia.put("Result", DOUBLE_MEDIA);
559         doReturn(resultContainer).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"),
560                 anyMap());
561         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
562
563         String searchString = "dc:title contains \"Morning\" and upnp:class derivedfrom \"object.container\"";
564         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
565
566         // Check currentEntry
567         assertThat(handler.currentEntry.getId(), is("C11"));
568
569         // Check BROWSE
570         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
571         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
572         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C11")));
573
574         // Check CURRENTTITLE
575         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
576                 stringCaptor.capture());
577         assertThat(stringCaptor.getValue(), is(StringType.valueOf("Morning Music")));
578
579         // Check entries
580         assertThat(handler.entries.size(), is(2));
581         assertThat(handler.entries.get(0).getId(), is("M1"));
582         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
583         assertThat(handler.entries.get(1).getId(), is("M2"));
584         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
585
586         // Check that BROWSE channel gets the correct state options
587         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
588         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
589                 stateOptionListCaptor.capture());
590         assertThat(stateOptionListCaptor.getValue().size(), is(3));
591         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
592         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
593         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
594         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
595         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("M2"));
596         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Music_02"));
597
598         // Check that a no media queue is being served as there is no renderer selected
599         verify(rendererHandler, times(0)).registerQueue(any());
600     }
601
602     @Test
603     public void testSearchOneContainerFromRootNoBrowseDown() {
604         logger.info("testSearchOneContainerFromRootNoBrowseDown");
605
606         handler.config.filter = false;
607         handler.config.browseDown = false;
608         handler.config.searchFromRoot = true;
609
610         // First navigate away from root
611         Map<String, String> result = new HashMap<>();
612         result.put("Result", DOUBLE_CONTAINER);
613         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
614         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
615
616         Map<String, String> resultContainer = new HashMap<>();
617         resultContainer.put("Result", SINGLE_CONTAINER);
618         Map<String, String> resultMedia = new HashMap<>();
619         resultMedia.put("Result", DOUBLE_MEDIA);
620         doReturn(resultContainer).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"),
621                 anyMap());
622         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
623
624         String searchString = "dc:title contains \"Morning\" and upnp:class derivedfrom \"object.container\"";
625         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
626
627         // Check currentEntry
628         assertThat(handler.currentEntry.getId(), is("0"));
629
630         // Check BROWSE
631         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
632         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
633         assertThat(stringCaptor.getValue(), is(StringType.valueOf("0")));
634
635         // Check CURRENTTITLE
636         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
637                 stringCaptor.capture());
638         assertThat(stringCaptor.getValue(), is(StringType.valueOf("")));
639
640         // Check entries
641         assertThat(handler.entries.size(), is(1));
642         assertThat(handler.entries.get(0).getId(), is("C11"));
643         assertThat(handler.entries.get(0).getTitle(), is("Morning Music"));
644
645         // Check that BROWSE channel gets the correct state options
646         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
647         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
648                 stateOptionListCaptor.capture());
649         assertThat(stateOptionListCaptor.getValue().size(), is(2));
650         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
651         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
652         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("C11"));
653         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Morning Music"));
654
655         // Check that a no media queue is being served as there is no renderer selected
656         verify(rendererHandler, times(0)).registerQueue(any());
657     }
658
659     @Test
660     public void testSearchOneContainerFromRootBrowseDown() {
661         logger.info("testSearchOneContainerFromRootBrowseDown");
662
663         handler.config.filter = false;
664         handler.config.browseDown = true;
665         handler.config.searchFromRoot = true;
666
667         // First navigate away from root
668         Map<String, String> result = new HashMap<>();
669         result.put("Result", DOUBLE_CONTAINER);
670         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
671         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
672
673         Map<String, String> resultContainer = new HashMap<>();
674         resultContainer.put("Result", SINGLE_CONTAINER);
675         Map<String, String> resultMedia = new HashMap<>();
676         resultMedia.put("Result", DOUBLE_MEDIA);
677         doReturn(resultContainer).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"),
678                 anyMap());
679         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
680
681         String searchString = "dc:title contains \"Morning\" and upnp:class derivedfrom \"object.container\"";
682         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
683
684         // Check currentEntry
685         assertThat(handler.currentEntry.getId(), is("C11"));
686
687         // Check BROWSE
688         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
689         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
690         assertThat(stringCaptor.getValue(), is(StringType.valueOf("C11")));
691
692         // Check CURRENTTITLE
693         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
694                 stringCaptor.capture());
695         assertThat(stringCaptor.getValue(), is(StringType.valueOf("Morning Music")));
696
697         // Check entries
698         assertThat(handler.entries.size(), is(2));
699         assertThat(handler.entries.get(0).getId(), is("M1"));
700         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
701         assertThat(handler.entries.get(1).getId(), is("M2"));
702         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
703
704         // Check that BROWSE channel gets the correct state options
705         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
706         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
707                 stateOptionListCaptor.capture());
708         assertThat(stateOptionListCaptor.getValue().size(), is(3));
709         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
710         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
711         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
712         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
713         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("M2"));
714         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Music_02"));
715
716         // Check that a no media queue is being served as there is no renderer selected
717         verify(rendererHandler, times(0)).registerQueue(any());
718     }
719
720     @Test
721     public void testSearchMediaFromRootBrowseDownFilter() {
722         logger.info("testSearchMediaFromRootBrowseDownFilter");
723
724         handler.config.filter = true;
725         handler.config.browseDown = true;
726         handler.config.searchFromRoot = true;
727
728         // First navigate away from root
729         Map<String, String> result = new HashMap<>();
730         result.put("Result", DOUBLE_CONTAINER);
731         doReturn(result).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Browse"), anyMap());
732         handler.handleCommand(browseChannelUID, StringType.valueOf("C1"));
733
734         Map<String, String> resultMedia = new HashMap<>();
735         resultMedia.put("Result", DOUBLE_MEDIA);
736         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"), anyMap());
737
738         String searchString = "dc:title contains \"Music\"";
739         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
740
741         // Check currentEntry
742         assertThat(handler.currentEntry.getId(), is("0"));
743
744         // Check BROWSE
745         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
746         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(BROWSE).getUID()), stringCaptor.capture());
747         assertThat(stringCaptor.getValue(), is(StringType.valueOf("0")));
748
749         // Check CURRENTTITLE
750         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(CURRENTTITLE).getUID()),
751                 stringCaptor.capture());
752         assertThat(stringCaptor.getValue(), is(StringType.valueOf("")));
753
754         // Check entries
755         assertThat(handler.entries.size(), is(2));
756         assertThat(handler.entries.get(0).getId(), is("M1"));
757         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
758         assertThat(handler.entries.get(1).getId(), is("M2"));
759         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
760
761         // Check that BROWSE channel gets the correct state options
762         ArgumentCaptor<List<StateOption>> stateOptionListCaptor = ArgumentCaptor.forClass(List.class);
763         verify(handler, atLeastOnce()).updateStateDescription(eq(thing.getChannel(BROWSE).getUID()),
764                 stateOptionListCaptor.capture());
765         assertThat(stateOptionListCaptor.getValue().size(), is(3));
766         assertThat(stateOptionListCaptor.getValue().get(0).getValue(), is(".."));
767         assertThat(stateOptionListCaptor.getValue().get(0).getLabel(), is(".."));
768         assertThat(stateOptionListCaptor.getValue().get(1).getValue(), is("M1"));
769         assertThat(stateOptionListCaptor.getValue().get(1).getLabel(), is("Music_01"));
770         assertThat(stateOptionListCaptor.getValue().get(2).getValue(), is("M2"));
771         assertThat(stateOptionListCaptor.getValue().get(2).getLabel(), is("Music_02"));
772
773         // Check that a no media queue is being served as there is no renderer selected
774         verify(rendererHandler, times(0)).registerQueue(any());
775     }
776
777     @Test
778     public void testPlaylist() {
779         logger.info("testPlaylist");
780
781         handler.config.filter = false;
782         handler.config.browseDown = false;
783         handler.config.searchFromRoot = true;
784
785         // Check already called in initialize
786         verify(handler).playlistsListChanged();
787
788         // First search for media
789         Map<String, String> resultMedia = new HashMap<>();
790         resultMedia.put("Result", DOUBLE_MEDIA);
791         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"), anyMap());
792         String searchString = "dc:title contains \"Music\"";
793         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
794
795         // Save playlist
796         handler.handleCommand(playlistChannelUID, StringType.valueOf("Test_Playlist"));
797         handler.handleCommand(playlistActionChannelUID, StringType.valueOf("SAVE"));
798
799         // Check called after saving playlist
800         verify(handler, times(2)).playlistsListChanged();
801
802         // Check that PLAYLIST_SELECT channel now has the playlist as a state option
803         ArgumentCaptor<List<CommandOption>> commandOptionListCaptor = ArgumentCaptor.forClass(List.class);
804         verify(handler, atLeastOnce()).updateCommandDescription(eq(thing.getChannel(PLAYLIST_SELECT).getUID()),
805                 commandOptionListCaptor.capture());
806         assertThat(commandOptionListCaptor.getValue().size(), is(1));
807         assertThat(commandOptionListCaptor.getValue().get(0).getCommand(), is("Test_Playlist"));
808         assertThat(commandOptionListCaptor.getValue().get(0).getLabel(), is("Test_Playlist"));
809
810         // Clear PLAYLIST channel
811         handler.handleCommand(playlistChannelUID, StringType.valueOf(""));
812
813         // Search for some extra media
814         resultMedia = new HashMap<>();
815         resultMedia.put("Result", EXTRA_MEDIA);
816         doReturn(resultMedia).when(upnpIOService).invokeAction(any(), eq("ContentDirectory"), eq("Search"), anyMap());
817         searchString = "dc:title contains \"Extra\"";
818         handler.handleCommand(searchChannelUID, StringType.valueOf(searchString));
819
820         // Append to playlist
821         handler.handleCommand(playlistSelectChannelUID, StringType.valueOf("Test_Playlist"));
822         handler.handleCommand(playlistActionChannelUID, StringType.valueOf("APPEND"));
823
824         // Check called after appending to playlist
825         verify(handler, times(3)).playlistsListChanged();
826
827         // Check that PLAYLIST channel received "Test_Playlist"
828         ArgumentCaptor<StringType> stringCaptor = ArgumentCaptor.forClass(StringType.class);
829         verify(callback, atLeastOnce()).stateUpdated(eq(thing.getChannel(PLAYLIST).getUID()), stringCaptor.capture());
830         assertThat(stringCaptor.getValue(), is(StringType.valueOf("Test_Playlist")));
831
832         // Clear PLAYLIST channel
833         handler.handleCommand(playlistChannelUID, StringType.valueOf(""));
834
835         // Restore playlist
836         handler.handleCommand(playlistSelectChannelUID, StringType.valueOf("Test_Playlist"));
837         handler.handleCommand(playlistActionChannelUID, StringType.valueOf("RESTORE"));
838
839         // Check currentEntry
840         assertThat(handler.currentEntry.getId(), is("C11"));
841
842         // Check entries
843         assertThat(handler.entries.size(), is(3));
844         assertThat(handler.entries.get(0).getId(), is("M1"));
845         assertThat(handler.entries.get(0).getTitle(), is("Music_01"));
846         assertThat(handler.entries.get(1).getId(), is("M2"));
847         assertThat(handler.entries.get(1).getTitle(), is("Music_02"));
848         assertThat(handler.entries.get(2).getId(), is("M3"));
849         assertThat(handler.entries.get(2).getTitle(), is("Extra_01"));
850
851         // Delete playlist
852         handler.handleCommand(playlistSelectChannelUID, StringType.valueOf("Test_Playlist"));
853         handler.handleCommand(playlistActionChannelUID, StringType.valueOf("DELETE"));
854
855         // Check called after deleting playlist
856         verify(handler, times(4)).playlistsListChanged();
857
858         // Check that PLAYLIST_SELECT channel is empty again
859         commandOptionListCaptor = ArgumentCaptor.forClass(List.class);
860         verify(handler, atLeastOnce()).updateCommandDescription(eq(thing.getChannel(PLAYLIST_SELECT).getUID()),
861                 commandOptionListCaptor.capture());
862         assertThat(commandOptionListCaptor.getValue().size(), is(0));
863
864         // select a renderer, so we expect the "current" playlist to be created
865         handler.handleCommand(rendererChannelUID, StringType.valueOf(rendererThing.getUID().toString()));
866
867         // Check called after selecting renderer
868         verify(handler, times(5)).playlistsListChanged();
869
870         // Check that PLAYLIST_SELECT channel received "current" playlist
871         verify(handler, atLeastOnce()).updateCommandDescription(eq(thing.getChannel(PLAYLIST_SELECT).getUID()),
872                 commandOptionListCaptor.capture());
873         assertThat(commandOptionListCaptor.getValue().size(), is(1));
874         assertThat(commandOptionListCaptor.getValue().get(0).getCommand(), is("current"));
875         assertThat(commandOptionListCaptor.getValue().get(0).getLabel(), is("current"));
876     }
877 }