Merge "Bug 1283: Made return types of AsyncDataChangeEvent consistent"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / RestGetOperationTest.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.sal.restconf.impl.test;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertTrue;
14 import static org.junit.Assert.fail;
15 import static org.mockito.Matchers.any;
16 import static org.mockito.Matchers.eq;
17 import static org.mockito.Mockito.mock;
18 import static org.mockito.Mockito.when;
19
20 import com.google.common.collect.Lists;
21 import com.google.common.collect.Maps;
22 import java.io.FileNotFoundException;
23 import java.io.InputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.net.URI;
26 import java.net.URISyntaxException;
27 import java.text.ParseException;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Date;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import javax.ws.rs.core.Application;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.MultivaluedHashMap;
38 import javax.ws.rs.core.MultivaluedMap;
39 import javax.ws.rs.core.Response;
40 import javax.ws.rs.core.UriInfo;
41 import org.glassfish.jersey.server.ResourceConfig;
42 import org.glassfish.jersey.test.JerseyTest;
43 import org.junit.BeforeClass;
44 import org.junit.Test;
45 import org.mockito.invocation.InvocationOnMock;
46 import org.mockito.stubbing.Answer;
47 import org.opendaylight.controller.sal.core.api.mount.MountInstance;
48 import org.opendaylight.controller.sal.core.api.mount.MountService;
49 import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider;
50 import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
51 import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
52 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
53 import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider;
54 import org.opendaylight.controller.sal.restconf.impl.BrokerFacade;
55 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
56 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
57 import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
58 import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
59 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
60 import org.opendaylight.yangtools.yang.common.QName;
61 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
62 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
63 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
64 import org.opendaylight.yangtools.yang.data.api.Node;
65 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
66 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
67 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
68 import org.w3c.dom.Document;
69 import org.w3c.dom.Element;
70 import org.w3c.dom.NodeList;
71
72 public class RestGetOperationTest extends JerseyTest {
73
74     static class NodeData {
75         Object key;
76         Object data; // List for a CompositeNode, value Object for a SimpleNode
77
78         NodeData( final Object key, final Object data ) {
79             this.key = key;
80             this.data = data;
81         }
82     }
83
84     private static BrokerFacade brokerFacade;
85     private static RestconfImpl restconfImpl;
86     private static SchemaContext schemaContextYangsIetf;
87     private static SchemaContext schemaContextTestModule;
88     private static CompositeNode answerFromGet;
89
90     private static SchemaContext schemaContextModules;
91     private static SchemaContext schemaContextBehindMountPoint;
92
93     @BeforeClass
94     public static void init() throws FileNotFoundException {
95         schemaContextYangsIetf = TestUtils.loadSchemaContext("/full-versions/yangs");
96         schemaContextTestModule = TestUtils.loadSchemaContext("/full-versions/test-module");
97         ControllerContext controllerContext = ControllerContext.getInstance();
98         controllerContext.setSchemas(schemaContextYangsIetf);
99         brokerFacade = mock(BrokerFacade.class);
100         restconfImpl = RestconfImpl.getInstance();
101         restconfImpl.setBroker(brokerFacade);
102         restconfImpl.setControllerContext(controllerContext);
103         answerFromGet = prepareCompositeNodeWithIetfInterfacesInterfacesData();
104
105         schemaContextModules = TestUtils.loadSchemaContext("/modules");
106         schemaContextBehindMountPoint = TestUtils.loadSchemaContext("/modules/modules-behind-mount-point");
107     }
108
109     @Override
110     protected Application configure() {
111         /* enable/disable Jersey logs to console */
112         // enable(TestProperties.LOG_TRAFFIC);
113         // enable(TestProperties.DUMP_ENTITY);
114         // enable(TestProperties.RECORD_LOG_LEVEL);
115         // set(TestProperties.RECORD_LOG_LEVEL, Level.ALL.intValue());
116         ResourceConfig resourceConfig = new ResourceConfig();
117         resourceConfig = resourceConfig.registerInstances(restconfImpl, StructuredDataToXmlProvider.INSTANCE,
118                 StructuredDataToJsonProvider.INSTANCE, XmlToCompositeNodeProvider.INSTANCE,
119                 JsonToCompositeNodeProvider.INSTANCE);
120         resourceConfig.registerClasses( RestconfDocumentedExceptionMapper.class );
121         return resourceConfig;
122     }
123
124     /**
125      * Tests of status codes for "/operational/{identifier}".
126      */
127     @Test
128     public void getOperationalStatusCodes() throws UnsupportedEncodingException {
129         mockReadOperationalDataMethod();
130         String uri = "/operational/ietf-interfaces:interfaces/interface/eth0";
131         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
132
133         uri = "/operational/wrong-module:interfaces/interface/eth0";
134         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
135     }
136
137     /**
138      * Tests of status codes for "/config/{identifier}".
139      */
140     @Test
141     public void getConfigStatusCodes() throws UnsupportedEncodingException {
142         mockReadConfigurationDataMethod();
143         String uri = "/config/ietf-interfaces:interfaces/interface/eth0";
144         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
145
146         uri = "/config/wrong-module:interfaces/interface/eth0";
147         assertEquals(400, get(uri, MediaType.APPLICATION_XML));
148     }
149
150     /**
151      * MountPoint test. URI represents mount point.
152      */
153     @Test
154     public void getDataWithUrlMountPoint() throws UnsupportedEncodingException, URISyntaxException {
155         when(
156                 brokerFacade.readConfigurationDataBehindMountPoint(any(MountInstance.class),
157                         any(InstanceIdentifier.class))).thenReturn(prepareCnDataForMountPointTest());
158         MountInstance mountInstance = mock(MountInstance.class);
159         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
160         MountService mockMountService = mock(MountService.class);
161         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
162
163         ControllerContext.getInstance().setMountService(mockMountService);
164
165         String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/cont1";
166         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
167
168         uri = "/config/ietf-interfaces:interfaces/yang-ext:mount/test-module:cont/cont1";
169         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
170     }
171
172     /**
173      * MountPoint test. URI represents mount point.
174      *
175      * Slashes in URI behind mount point. lst1 element with key
176      * GigabitEthernet0%2F0%2F0%2F0 (GigabitEthernet0/0/0/0) is requested via
177      * GET HTTP operation. It is tested whether %2F character is replaced with
178      * simple / in InstanceIdentifier parameter in method
179      * {@link BrokerFacade#readConfigurationDataBehindMountPoint(MountInstance, InstanceIdentifier)}
180      * which is called in method {@link RestconfImpl#readConfigurationData}
181      *
182      *
183      * @throws ParseException
184      */
185     @Test
186     public void getDataWithSlashesBehindMountPoint() throws UnsupportedEncodingException, URISyntaxException,
187     ParseException {
188         InstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList();
189         when(
190                 brokerFacade.readConfigurationDataBehindMountPoint(any(MountInstance.class),
191                         eq(awaitedInstanceIdentifier))).thenReturn(prepareCnDataForMountPointTest());
192         MountInstance mountInstance = mock(MountInstance.class);
193         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
194         MountService mockMountService = mock(MountService.class);
195         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
196
197         ControllerContext.getInstance().setMountService(mockMountService);
198
199         String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/test-module:cont/lst1/GigabitEthernet0%2F0%2F0%2F0";
200         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
201     }
202
203     private InstanceIdentifier prepareInstanceIdentifierForList() throws URISyntaxException, ParseException {
204         List<PathArgument> parameters = new ArrayList<>();
205
206         Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
207         URI uri = new URI("test:module");
208         QName qNameCont = QName.create(uri, revision, "cont");
209         QName qNameList = QName.create(uri, revision, "lst1");
210         QName qNameKeyList = QName.create(uri, revision, "lf11");
211
212         parameters.add(new InstanceIdentifier.NodeIdentifier(qNameCont));
213         parameters.add(new InstanceIdentifier.NodeIdentifierWithPredicates(qNameList, qNameKeyList,
214                 "GigabitEthernet0/0/0/0"));
215         return InstanceIdentifier.create(parameters);
216     }
217
218     @Test
219     public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException {
220         when(
221                 brokerFacade.readConfigurationDataBehindMountPoint(any(MountInstance.class),
222                         any(InstanceIdentifier.class))).thenReturn(prepareCnDataForMountPointTest());
223         MountInstance mountInstance = mock(MountInstance.class);
224         when(mountInstance.getSchemaContext()).thenReturn(schemaContextTestModule);
225         MountService mockMountService = mock(MountService.class);
226         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
227
228         ControllerContext.getInstance().setMountService(mockMountService);
229
230         String uri = "/config/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
231         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
232     }
233
234     // /modules
235     @Test
236     public void getModulesTest() throws UnsupportedEncodingException, FileNotFoundException {
237         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
238
239         String uri = "/modules";
240
241         Response response = target(uri).request("application/yang.api+json").get();
242         validateModulesResponseJson(response);
243
244         response = target(uri).request("application/yang.api+xml").get();
245         validateModulesResponseXml(response);
246     }
247
248     // /streams/
249     @Test
250     public void getStreamsTest() throws UnsupportedEncodingException, FileNotFoundException {
251         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
252
253         String uri = "/streams";
254
255         Response response = target(uri).request("application/yang.api+json").get();
256         String responseBody = response.readEntity(String.class);
257         assertNotNull(responseBody);
258         assertTrue(responseBody.contains("streams"));
259
260         response = target(uri).request("application/yang.api+xml").get();
261         responseBody = response.readEntity(String.class);
262         assertNotNull(responseBody);
263         assertTrue(responseBody.contains("<streams xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\""));
264     }
265
266     // /modules/module
267     @Test
268     public void getModuleTest() throws FileNotFoundException, UnsupportedEncodingException {
269         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
270
271         String uri = "/modules/module/module2/2014-01-02";
272
273         Response response = target(uri).request("application/yang.api+xml").get();
274         assertEquals(200, response.getStatus());
275         String responseBody = response.readEntity(String.class);
276         assertTrue("Module2 in xml wasn't found", prepareXmlRegex("module2", "2014-01-02", "module:2", responseBody)
277                 .find());
278         String[] split = responseBody.split("<module");
279         assertEquals("<module element is returned more then once", 2, split.length);
280
281         response = target(uri).request("application/yang.api+json").get();
282         assertEquals(200, response.getStatus());
283         responseBody = response.readEntity(String.class);
284         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
285                 .find());
286         split = responseBody.split("\"module\"");
287         assertEquals("\"module\" element is returned more then once", 2, split.length);
288
289     }
290
291     // /operations
292     @Test
293     public void getOperationsTest() throws FileNotFoundException, UnsupportedEncodingException {
294         ControllerContext.getInstance().setGlobalSchema(schemaContextModules);
295
296         String uri = "/operations";
297
298         Response response = target(uri).request("application/yang.api+xml").get();
299         assertEquals(200, response.getStatus());
300         String responseBody = response.readEntity(String.class);
301         assertTrue("Xml response for /operations dummy-rpc1-module1 is incorrect",
302                 validateOperationsResponseXml(responseBody, "dummy-rpc1-module1", "module:1").find());
303         assertTrue("Xml response for /operations dummy-rpc2-module1 is incorrect",
304                 validateOperationsResponseXml(responseBody, "dummy-rpc2-module1", "module:1").find());
305         assertTrue("Xml response for /operations dummy-rpc1-module2 is incorrect",
306                 validateOperationsResponseXml(responseBody, "dummy-rpc1-module2", "module:2").find());
307         assertTrue("Xml response for /operations dummy-rpc2-module2 is incorrect",
308                 validateOperationsResponseXml(responseBody, "dummy-rpc2-module2", "module:2").find());
309
310         response = target(uri).request("application/yang.api+json").get();
311         assertEquals(200, response.getStatus());
312         responseBody = response.readEntity(String.class);
313         assertTrue("Json response for /operations dummy-rpc1-module1 is incorrect",
314                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module1", "module1").find());
315         assertTrue("Json response for /operations dummy-rpc2-module1 is incorrect",
316                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module1", "module1").find());
317         assertTrue("Json response for /operations dummy-rpc1-module2 is incorrect",
318                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module2", "module2").find());
319         assertTrue("Json response for /operations dummy-rpc2-module2 is incorrect",
320                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module2", "module2").find());
321
322     }
323
324     // /operations/pathToMountPoint/yang-ext:mount
325     @Test
326     public void getOperationsBehindMountPointTest() throws FileNotFoundException, UnsupportedEncodingException {
327         ControllerContext controllerContext = ControllerContext.getInstance();
328         controllerContext.setGlobalSchema(schemaContextModules);
329
330         MountInstance mountInstance = mock(MountInstance.class);
331         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
332         MountService mockMountService = mock(MountService.class);
333         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
334
335         controllerContext.setMountService(mockMountService);
336
337         String uri = "/operations/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
338
339         Response response = target(uri).request("application/yang.api+xml").get();
340         assertEquals(200, response.getStatus());
341         String responseBody = response.readEntity(String.class);
342         assertTrue("Xml response for /operations/mount_point rpc-behind-module1 is incorrect",
343                 validateOperationsResponseXml(responseBody, "rpc-behind-module1", "module:1:behind:mount:point").find());
344         assertTrue("Xml response for /operations/mount_point rpc-behind-module2 is incorrect",
345                 validateOperationsResponseXml(responseBody, "rpc-behind-module2", "module:2:behind:mount:point").find());
346
347         response = target(uri).request("application/yang.api+json").get();
348         assertEquals(200, response.getStatus());
349         responseBody = response.readEntity(String.class);
350         assertTrue("Json response for /operations/mount_point rpc-behind-module1 is incorrect",
351                 validateOperationsResponseJson(responseBody, "rpc-behind-module1", "module1-behind-mount-point").find());
352         assertTrue("Json response for /operations/mount_point rpc-behind-module2 is incorrect",
353                 validateOperationsResponseJson(responseBody, "rpc-behind-module2", "module2-behind-mount-point").find());
354
355     }
356
357     private Matcher validateOperationsResponseJson(final String searchIn, final String rpcName, final String moduleName) {
358         StringBuilder regex = new StringBuilder();
359         regex.append("^");
360
361         regex.append(".*\\{");
362         regex.append(".*\"");
363
364         // operations prefix optional
365         regex.append("(");
366         regex.append("ietf-restconf:");
367         regex.append("|)");
368         // :operations prefix optional
369
370         regex.append("operations\"");
371         regex.append(".*:");
372         regex.append(".*\\{");
373
374         regex.append(".*\"" + moduleName);
375         regex.append(":");
376         regex.append(rpcName + "\"");
377         regex.append(".*\\[");
378         regex.append(".*null");
379         regex.append(".*\\]");
380
381         regex.append(".*\\}");
382         regex.append(".*\\}");
383
384         regex.append(".*");
385         regex.append("$");
386         Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
387         return ptrn.matcher(searchIn);
388
389     }
390
391     private Matcher validateOperationsResponseXml(final String searchIn, final String rpcName, final String namespace) {
392         StringBuilder regex = new StringBuilder();
393
394         regex.append("^");
395
396         regex.append(".*<operations");
397         regex.append(".*xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
398         regex.append(".*>");
399
400         regex.append(".*<");
401         regex.append(".*" + rpcName);
402         regex.append(".*" + namespace);
403         regex.append(".*/");
404         regex.append(".*>");
405
406         regex.append(".*</operations.*");
407         regex.append(".*>");
408
409         regex.append(".*");
410         regex.append("$");
411         Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
412         return ptrn.matcher(searchIn);
413     }
414
415     // /restconf/modules/pathToMountPoint/yang-ext:mount
416     @Test
417     public void getModulesBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
418         ControllerContext controllerContext = ControllerContext.getInstance();
419         controllerContext.setGlobalSchema(schemaContextModules);
420
421         MountInstance mountInstance = mock(MountInstance.class);
422         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
423         MountService mockMountService = mock(MountService.class);
424         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
425
426         controllerContext.setMountService(mockMountService);
427
428         String uri = "/modules/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
429
430         Response response = target(uri).request("application/yang.api+json").get();
431         assertEquals(200, response.getStatus());
432         String responseBody = response.readEntity(String.class);
433
434         assertTrue(
435                 "module1-behind-mount-point in json wasn't found",
436                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
437                         responseBody).find());
438         assertTrue(
439                 "module2-behind-mount-point in json wasn't found",
440                 prepareJsonRegex("module2-behind-mount-point", "2014-02-04", "module:2:behind:mount:point",
441                         responseBody).find());
442
443         response = target(uri).request("application/yang.api+xml").get();
444         assertEquals(200, response.getStatus());
445         responseBody = response.readEntity(String.class);
446         assertTrue(
447                 "module1-behind-mount-point in json wasn't found",
448                 prepareXmlRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point", responseBody)
449                 .find());
450         assertTrue(
451                 "module2-behind-mount-point in json wasn't found",
452                 prepareXmlRegex("module2-behind-mount-point", "2014-02-04", "module:2:behind:mount:point", responseBody)
453                 .find());
454
455     }
456
457     // /restconf/modules/module/pathToMountPoint/yang-ext:mount/moduleName/revision
458     @Test
459     public void getModuleBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
460         ControllerContext controllerContext = ControllerContext.getInstance();
461         controllerContext.setGlobalSchema(schemaContextModules);
462
463         MountInstance mountInstance = mock(MountInstance.class);
464         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
465         MountService mockMountService = mock(MountService.class);
466         when(mockMountService.getMountPoint(any(InstanceIdentifier.class))).thenReturn(mountInstance);
467
468         controllerContext.setMountService(mockMountService);
469
470         String uri = "/modules/module/ietf-interfaces:interfaces/interface/0/yang-ext:mount/module1-behind-mount-point/2014-02-03";
471
472         Response response = target(uri).request("application/yang.api+json").get();
473         assertEquals(200, response.getStatus());
474         String responseBody = response.readEntity(String.class);
475
476         assertTrue(
477                 "module1-behind-mount-point in json wasn't found",
478                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
479                         responseBody).find());
480         String[] split = responseBody.split("\"module\"");
481         assertEquals("\"module\" element is returned more then once", 2, split.length);
482
483         response = target(uri).request("application/yang.api+xml").get();
484         assertEquals(200, response.getStatus());
485         responseBody = response.readEntity(String.class);
486         assertTrue(
487                 "module1-behind-mount-point in json wasn't found",
488                 prepareXmlRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point", responseBody)
489                 .find());
490         split = responseBody.split("<module");
491         assertEquals("<module element is returned more then once", 2, split.length);
492
493     }
494
495     private void validateModulesResponseXml(final Response response) {
496         assertEquals(200, response.getStatus());
497         String responseBody = response.readEntity(String.class);
498
499         assertTrue("Module1 in xml wasn't found", prepareXmlRegex("module1", "2014-01-01", "module:1", responseBody)
500                 .find());
501         assertTrue("Module2 in xml wasn't found", prepareXmlRegex("module2", "2014-01-02", "module:2", responseBody)
502                 .find());
503         assertTrue("Module3 in xml wasn't found", prepareXmlRegex("module3", "2014-01-03", "module:3", responseBody)
504                 .find());
505     }
506
507     private void validateModulesResponseJson(final Response response) {
508         assertEquals(200, response.getStatus());
509         String responseBody = response.readEntity(String.class);
510
511         assertTrue("Module1 in json wasn't found", prepareJsonRegex("module1", "2014-01-01", "module:1", responseBody)
512                 .find());
513         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
514                 .find());
515         assertTrue("Module3 in json wasn't found", prepareJsonRegex("module3", "2014-01-03", "module:3", responseBody)
516                 .find());
517     }
518
519     private Matcher prepareJsonRegex(final String module, final String revision, final String namespace, final String searchIn) {
520         StringBuilder regex = new StringBuilder();
521         regex.append("^");
522
523         regex.append(".*\\{");
524         regex.append(".*\"name\"");
525         regex.append(".*:");
526         regex.append(".*\"" + module + "\",");
527
528         regex.append(".*\"revision\"");
529         regex.append(".*:");
530         regex.append(".*\"" + revision + "\",");
531
532         regex.append(".*\"namespace\"");
533         regex.append(".*:");
534         regex.append(".*\"" + namespace + "\"");
535
536         regex.append(".*\\}");
537
538         regex.append(".*");
539         regex.append("$");
540         Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
541         return ptrn.matcher(searchIn);
542
543     }
544
545     private Matcher prepareXmlRegex(final String module, final String revision, final String namespace, final String searchIn) {
546         StringBuilder regex = new StringBuilder();
547         regex.append("^");
548
549         regex.append(".*<module.*");
550         regex.append(".*>");
551
552         regex.append(".*<name>");
553         regex.append(".*" + module);
554         regex.append(".*<\\/name>");
555
556         regex.append(".*<revision>");
557         regex.append(".*" + revision);
558         regex.append(".*<\\/revision>");
559
560         regex.append(".*<namespace>");
561         regex.append(".*" + namespace);
562         regex.append(".*<\\/namespace>");
563
564         regex.append(".*<\\/module.*>");
565
566         regex.append(".*");
567         regex.append("$");
568
569         Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
570         return ptrn.matcher(searchIn);
571     }
572
573     private void prepareMockForModulesTest(final ControllerContext mockedControllerContext) throws FileNotFoundException {
574         SchemaContext schemaContext = TestUtils.loadSchemaContext("/modules");
575         mockedControllerContext.setGlobalSchema(schemaContext);
576         // when(mockedControllerContext.getGlobalSchema()).thenReturn(schemaContext);
577     }
578
579     private int get(final String uri, final String mediaType) {
580         return target(uri).request(mediaType).get().getStatus();
581     }
582
583     private CompositeNode prepareCnDataForMountPointTest() throws URISyntaxException {
584         CompositeNodeWrapper cont1 = new CompositeNodeWrapper(new URI("test:module"), "cont1");
585         SimpleNodeWrapper lf11 = new SimpleNodeWrapper(new URI("test:module"), "lf11", "lf11 value");
586         cont1.addValue(lf11);
587         return cont1.unwrap();
588     }
589
590     private void mockReadOperationalDataMethod() {
591         when(brokerFacade.readOperationalData(any(InstanceIdentifier.class))).thenReturn(answerFromGet);
592     }
593
594     private void mockReadConfigurationDataMethod() {
595         when(brokerFacade.readConfigurationData(any(InstanceIdentifier.class))).thenReturn(answerFromGet);
596     }
597
598     private static CompositeNode prepareCompositeNodeWithIetfInterfacesInterfacesData() {
599         CompositeNode intface;
600         try {
601             intface = new CompositeNodeWrapper(new URI("interface"), "interface");
602             List<Node<?>> childs = new ArrayList<>();
603
604             childs.add(new SimpleNodeWrapper(new URI("name"), "name", "eth0"));
605             childs.add(new SimpleNodeWrapper(new URI("type"), "type", "ethernetCsmacd"));
606             childs.add(new SimpleNodeWrapper(new URI("enabled"), "enabled", Boolean.FALSE));
607             childs.add(new SimpleNodeWrapper(new URI("description"), "description", "some interface"));
608             intface.setValue(childs);
609             return intface;
610         } catch (URISyntaxException e) {
611         }
612
613         return null;
614     }
615
616     /**
617      * If includeWhiteChars URI parameter is set to false then no white
618      * characters can be included in returned output
619      * @throws UnsupportedEncodingException
620      */
621     @Test
622     public void getDataWithUriIncludeWhiteCharsParameterTest() throws UnsupportedEncodingException {
623         getDataWithUriIncludeWhiteCharsParameter("config");
624         getDataWithUriIncludeWhiteCharsParameter("operational");
625     }
626
627
628     private void getDataWithUriIncludeWhiteCharsParameter(String target) throws UnsupportedEncodingException {
629         mockReadConfigurationDataMethod();
630         String uri = "/"+target+"/ietf-interfaces:interfaces/interface/eth0";
631         Response response = target(uri).queryParam("prettyPrint", "false").request("application/xml").get();
632         String xmlData = response.readEntity(String.class);
633
634         Pattern pattern = Pattern.compile(".*(>\\s+|\\s+<).*", Pattern.DOTALL);
635         Matcher matcher = pattern.matcher(xmlData);
636         // XML element can't surrounded with white character (e.g ">    " or
637         // "    <")
638         assertFalse(matcher.matches());
639
640         response = target(uri).queryParam("prettyPrint", "false").request("application/json").get();
641         String jsonData = response.readEntity(String.class);
642         pattern = Pattern.compile(".*(\\}\\s+|\\s+\\{|\\]\\s+|\\s+\\[|\\s+:|:\\s+).*", Pattern.DOTALL);
643         matcher = pattern.matcher(jsonData);
644         // JSON element can't surrounded with white character (e.g "} ", " {",
645         // "] ", " [", " :" or ": ")
646         assertFalse(matcher.matches());
647     }
648
649     @Test
650     public void getDataWithUriDepthParameterTest() throws UnsupportedEncodingException {
651
652         ControllerContext.getInstance().setGlobalSchema( schemaContextModules );
653
654         CompositeNode depth1Cont = toCompositeNode(
655                 toCompositeNodeData( toNestedQName( "depth1-cont" ),
656                         toCompositeNodeData( toNestedQName( "depth2-cont1" ),
657                                 toCompositeNodeData( toNestedQName( "depth3-cont1" ),
658                                         toCompositeNodeData( toNestedQName( "depth4-cont1" ),
659                                                 toSimpleNodeData( toNestedQName( "depth5-leaf1" ), "depth5-leaf1-value" )
660                                                 ),
661                                                 toSimpleNodeData( toNestedQName( "depth4-leaf1" ), "depth4-leaf1-value" )
662                                         ),
663                                         toSimpleNodeData( toNestedQName( "depth3-leaf1" ), "depth3-leaf1-value" )
664                                 ),
665                                 toCompositeNodeData( toNestedQName( "depth2-cont2" ),
666                                         toCompositeNodeData( toNestedQName( "depth3-cont2" ),
667                                                 toCompositeNodeData( toNestedQName( "depth4-cont2" ),
668                                                         toSimpleNodeData( toNestedQName( "depth5-leaf2" ), "depth5-leaf2-value" )
669                                                         ),
670                                                         toSimpleNodeData( toNestedQName( "depth4-leaf2" ), "depth4-leaf2-value" )
671                                                 ),
672                                                 toSimpleNodeData( toNestedQName( "depth3-leaf2" ), "depth3-leaf2-value" )
673                                         ),
674                                         toSimpleNodeData( toNestedQName( "depth2-leaf1" ), "depth2-leaf1-value" )
675                         ) );
676
677         when( brokerFacade.readConfigurationData( any( InstanceIdentifier.class ) ) )
678         .thenReturn( depth1Cont );
679
680         // Test config with depth 1
681
682         Response response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "1" )
683                 .request( "application/xml" ).get();
684
685         verifyXMLResponse( response, expectEmptyContainer( "depth1-cont" ) );
686
687         // Test config with depth 2
688
689         response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "2" )
690                 .request( "application/xml" ).get();
691
692         //        String xml="<depth1-cont><depth2-cont1/><depth2-cont2/><depth2-leaf1>depth2-leaf1-value</depth2-leaf1></depth1-cont>";
693         //        Response mr=mock(Response.class);
694         //        when(mr.getEntity()).thenReturn( new java.io.StringBufferInputStream(xml) );
695
696         verifyXMLResponse( response,
697                 expectContainer( "depth1-cont",
698                         expectEmptyContainer( "depth2-cont1" ),
699                         expectEmptyContainer( "depth2-cont2" ),
700                         expectLeaf( "depth2-leaf1", "depth2-leaf1-value" )
701                         ) );
702
703         // Test config with depth 3
704
705         response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "3" )
706                 .request( "application/xml" ).get();
707
708         verifyXMLResponse( response,
709                 expectContainer( "depth1-cont",
710                         expectContainer( "depth2-cont1",
711                                 expectEmptyContainer( "depth3-cont1" ),
712                                 expectLeaf( "depth3-leaf1", "depth3-leaf1-value" )
713                                 ),
714                                 expectContainer( "depth2-cont2",
715                                         expectEmptyContainer( "depth3-cont2" ),
716                                         expectLeaf( "depth3-leaf2", "depth3-leaf2-value" )
717                                         ),
718                                         expectLeaf( "depth2-leaf1", "depth2-leaf1-value" )
719                         ) );
720
721         // Test config with depth 4
722
723         response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "4" )
724                 .request( "application/xml" ).get();
725
726         verifyXMLResponse( response,
727                 expectContainer( "depth1-cont",
728                         expectContainer( "depth2-cont1",
729                                 expectContainer( "depth3-cont1",
730                                         expectEmptyContainer( "depth4-cont1" ),
731                                         expectLeaf( "depth4-leaf1", "depth4-leaf1-value" )
732                                         ),
733                                         expectLeaf( "depth3-leaf1", "depth3-leaf1-value" )
734                                 ),
735                                 expectContainer( "depth2-cont2",
736                                         expectContainer( "depth3-cont2",
737                                                 expectEmptyContainer( "depth4-cont2" ),
738                                                 expectLeaf( "depth4-leaf2", "depth4-leaf2-value" )
739                                                 ),
740                                                 expectLeaf( "depth3-leaf2", "depth3-leaf2-value" )
741                                         ),
742                                         expectLeaf( "depth2-leaf1", "depth2-leaf1-value" )
743                         ) );
744
745         // Test config with depth 5
746
747         response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "5" )
748                 .request( "application/xml" ).get();
749
750         verifyXMLResponse( response,
751                 expectContainer( "depth1-cont",
752                         expectContainer( "depth2-cont1",
753                                 expectContainer( "depth3-cont1",
754                                         expectContainer( "depth4-cont1",
755                                                 expectLeaf( "depth5-leaf1", "depth5-leaf1-value" )
756                                                 ),
757                                                 expectLeaf( "depth4-leaf1", "depth4-leaf1-value" )
758                                         ),
759                                         expectLeaf( "depth3-leaf1", "depth3-leaf1-value" )
760                                 ),
761                                 expectContainer( "depth2-cont2",
762                                         expectContainer( "depth3-cont2",
763                                                 expectContainer( "depth4-cont2",
764                                                         expectLeaf( "depth5-leaf2", "depth5-leaf2-value" )
765                                                         ),
766                                                         expectLeaf( "depth4-leaf2", "depth4-leaf2-value" )
767                                                 ),
768                                                 expectLeaf( "depth3-leaf2", "depth3-leaf2-value" )
769                                         ),
770                                         expectLeaf( "depth2-leaf1", "depth2-leaf1-value" )
771                         ) );
772
773         // Test config with depth unbounded
774
775         response = target( "/config/nested-module:depth1-cont" ).queryParam( "depth", "unbounded" )
776                 .request( "application/xml" ).get();
777
778         verifyXMLResponse( response,
779                 expectContainer( "depth1-cont",
780                         expectContainer( "depth2-cont1",
781                                 expectContainer( "depth3-cont1",
782                                         expectContainer( "depth4-cont1",
783                                                 expectLeaf( "depth5-leaf1", "depth5-leaf1-value" )
784                                                 ),
785                                                 expectLeaf( "depth4-leaf1", "depth4-leaf1-value" )
786                                         ),
787                                         expectLeaf( "depth3-leaf1", "depth3-leaf1-value" )
788                                 ),
789                                 expectContainer( "depth2-cont2",
790                                         expectContainer( "depth3-cont2",
791                                                 expectContainer( "depth4-cont2",
792                                                         expectLeaf( "depth5-leaf2", "depth5-leaf2-value" )
793                                                         ),
794                                                         expectLeaf( "depth4-leaf2", "depth4-leaf2-value" )
795                                                 ),
796                                                 expectLeaf( "depth3-leaf2", "depth3-leaf2-value" )
797                                         ),
798                                         expectLeaf( "depth2-leaf1", "depth2-leaf1-value" )
799                         ) );
800
801         // Test operational
802
803         CompositeNode depth2Cont1 = toCompositeNode(
804                 toCompositeNodeData( toNestedQName( "depth2-cont1" ),
805                         toCompositeNodeData( toNestedQName( "depth3-cont1" ),
806                                 toCompositeNodeData( toNestedQName( "depth4-cont1" ),
807                                         toSimpleNodeData( toNestedQName( "depth5-leaf1" ), "depth5-leaf1-value" )
808                                         ),
809                                         toSimpleNodeData( toNestedQName( "depth4-leaf1" ), "depth4-leaf1-value" )
810                                 ),
811                                 toSimpleNodeData( toNestedQName( "depth3-leaf1" ), "depth3-leaf1-value" )
812                         ) );
813
814         when( brokerFacade.readOperationalData( any( InstanceIdentifier.class ) ) )
815         .thenReturn( depth2Cont1 );
816
817         response = target( "/operational/nested-module:depth1-cont/depth2-cont1" )
818                 .queryParam( "depth", "3" ).request( "application/xml" ).get();
819
820         verifyXMLResponse( response,
821                 expectContainer( "depth2-cont1",
822                         expectContainer( "depth3-cont1",
823                                 expectEmptyContainer( "depth4-cont1" ),
824                                 expectLeaf( "depth4-leaf1", "depth4-leaf1-value" )
825                                 ),
826                                 expectLeaf( "depth3-leaf1", "depth3-leaf1-value" )
827                         ) );
828     }
829
830     @Test
831     public void getDataWithInvalidDepthParameterTest() {
832
833         ControllerContext.getInstance().setGlobalSchema( schemaContextModules );
834
835         final MultivaluedMap<String,String> paramMap = new MultivaluedHashMap<>();
836         paramMap.putSingle( "depth", "1o" );
837         UriInfo mockInfo = mock( UriInfo.class );
838         when( mockInfo.getQueryParameters( false ) ).thenAnswer(
839                 new Answer<MultivaluedMap<String,String>>() {
840                     @Override
841                     public MultivaluedMap<String, String> answer( final InvocationOnMock invocation ) {
842                         return paramMap;
843                     }
844                 } );
845
846         getDataWithInvalidDepthParameterTest( mockInfo );
847
848         paramMap.putSingle( "depth", "0" );
849         getDataWithInvalidDepthParameterTest( mockInfo );
850
851         paramMap.putSingle( "depth", "-1" );
852         getDataWithInvalidDepthParameterTest( mockInfo );
853     }
854
855     private void getDataWithInvalidDepthParameterTest( final UriInfo uriInfo ) {
856         try {
857             restconfImpl.readConfigurationData( "nested-module:depth1-cont", uriInfo );
858             fail( "Expected RestconfDocumentedException" );
859         }
860         catch( RestconfDocumentedException e ) {
861             assertTrue( "Unexpected error message: " + e.getErrors().get( 0 ).getErrorMessage(),
862                     e.getErrors().get( 0 ).getErrorMessage().contains( "depth" ) );
863         }
864     }
865
866     private void verifyXMLResponse( final Response response, final NodeData nodeData ) {
867
868         Document doc = TestUtils.loadDocumentFrom( (InputStream) response.getEntity() );
869         assertNotNull( "Could not parse XML document", doc );
870
871         //System.out.println(TestUtils.getDocumentInPrintableForm( doc ));
872
873         verifyContainerElement( doc.getDocumentElement(), nodeData );
874     }
875
876     @SuppressWarnings("unchecked")
877     private void verifyContainerElement( final Element element, final NodeData nodeData ) {
878
879         assertEquals( "Element local name", nodeData.key, element.getNodeName() );
880
881         NodeList childNodes = element.getChildNodes();
882         if( nodeData.data == null ) { // empty container
883             assertTrue( "Expected no child elements for \"" + element.getNodeName() + "\"",
884                     childNodes.getLength() == 0 );
885             return;
886         }
887
888         Map<String,NodeData> expChildMap = Maps.newHashMap();
889         for( NodeData expChild: (List<NodeData>)nodeData.data ) {
890             expChildMap.put( expChild.key.toString(), expChild );
891         }
892
893         for( int i = 0; i < childNodes.getLength(); i++ ) {
894             org.w3c.dom.Node actualChild = childNodes.item( i );
895             if( !( actualChild instanceof Element ) ) {
896                 continue;
897             }
898
899             Element actualElement = (Element)actualChild;
900             NodeData expChild = expChildMap.remove( actualElement.getNodeName() );
901             assertNotNull( "Unexpected child element for parent \"" + element.getNodeName() +
902                     "\": " + actualElement.getNodeName(), expChild );
903
904             if( expChild.data == null || expChild.data instanceof List ) {
905                 verifyContainerElement( actualElement, expChild );
906             }
907             else {
908                 assertEquals( "Text content for element: " + actualElement.getNodeName(),
909                         expChild.data, actualElement.getTextContent() );
910             }
911         }
912
913         if( !expChildMap.isEmpty() ) {
914             fail( "Missing elements for parent \"" + element.getNodeName() +
915                     "\": " + expChildMap.keySet() );
916         }
917     }
918
919     private NodeData expectContainer( final String name, final NodeData... childData ) {
920         return new NodeData( name, Lists.newArrayList( childData ) );
921     }
922
923     private NodeData expectEmptyContainer( final String name ) {
924         return new NodeData( name, null );
925     }
926
927     private NodeData expectLeaf( final String name, final Object value ) {
928         return new NodeData( name, value );
929     }
930
931     private QName toNestedQName( final String localName ) {
932         return QName.create( "urn:nested:module", "2014-06-3", localName );
933     }
934
935     @SuppressWarnings("unchecked")
936     private CompositeNode toCompositeNode( final NodeData nodeData ) {
937         CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
938         builder.setQName( (QName) nodeData.key );
939
940         for( NodeData child: (List<NodeData>)nodeData.data ) {
941             if( child.data instanceof List ) {
942                 builder.add( toCompositeNode( child ) );
943             }
944             else {
945                 builder.addLeaf( (QName) child.key, child.data );
946             }
947         }
948
949         return builder.toInstance();
950     }
951
952     private NodeData toCompositeNodeData( final QName key, final NodeData... childData ) {
953         return new NodeData( key, Lists.newArrayList( childData ) );
954     }
955
956     private NodeData toSimpleNodeData( final QName key, final Object value ) {
957         return new NodeData( key, value );
958     }
959 }