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