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