BUG 2412 - restconf mountpoint getOperations method migration
[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     public void getOperationsTest() throws FileNotFoundException, UnsupportedEncodingException {
326         setControllerContext(schemaContextModules);
327
328         final String uri = "/operations";
329
330         Response response = target(uri).request("application/yang.api+xml").get();
331         assertEquals(200, response.getStatus());
332         final Document responseDoc = response.readEntity(Document.class);
333         validateOperationsResponseXml(responseDoc, schemaContextModules);
334
335         response = target(uri).request("application/yang.api+json").get();
336         assertEquals(200, response.getStatus());
337         final String responseBody = response.readEntity(String.class);
338         assertTrue("Json response for /operations dummy-rpc1-module1 is incorrect",
339                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module1", "module1").find());
340         assertTrue("Json response for /operations dummy-rpc2-module1 is incorrect",
341                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module1", "module1").find());
342         assertTrue("Json response for /operations dummy-rpc1-module2 is incorrect",
343                 validateOperationsResponseJson(responseBody, "dummy-rpc1-module2", "module2").find());
344         assertTrue("Json response for /operations dummy-rpc2-module2 is incorrect",
345                 validateOperationsResponseJson(responseBody, "dummy-rpc2-module2", "module2").find());
346
347     }
348
349     private void validateOperationsResponseXml(final Document responseDoc, final SchemaContext schemaContext) {
350         final Element operationsElem = responseDoc.getDocumentElement();
351         assertEquals(RESTCONF_NS, operationsElem.getNamespaceURI());
352         assertEquals("operations", operationsElem.getLocalName());
353
354
355         final HashSet<QName> foundOperations = new HashSet<>();
356
357         final NodeList operationsList = operationsElem.getChildNodes();
358         for(int i = 0;i < operationsList.getLength();i++) {
359             final org.w3c.dom.Node operation = operationsList.item(i);
360
361             final String namespace = operation.getNamespaceURI();
362             final String name = operation.getLocalName();
363             final QName opQName = QName.create(URI.create(namespace), null, name);
364             foundOperations.add(opQName);
365         }
366
367         for(final RpcDefinition schemaOp : schemaContext.getOperations()) {
368             assertTrue(foundOperations.contains(schemaOp.getQName().withoutRevision()));
369         }
370
371     }
372
373     // /operations/pathToMountPoint/yang-ext:mount
374     @Test
375     @Ignore // FIXME fix the way to provide operations overview functionality asap
376     public void getOperationsBehindMountPointTest() throws FileNotFoundException, UnsupportedEncodingException {
377         setControllerContext(schemaContextModules);
378
379         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
380         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
381         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
382         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
383
384         ControllerContext.getInstance().setMountService(mockMountService);
385
386         final String uri = "/operations/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
387
388         Response response = target(uri).request("application/yang.api+xml").get();
389         assertEquals(200, response.getStatus());
390
391         final Document responseDoc = response.readEntity(Document.class);
392         validateOperationsResponseXml(responseDoc, schemaContextBehindMountPoint);
393
394         response = target(uri).request("application/yang.api+json").get();
395         assertEquals(200, response.getStatus());
396         final String responseBody = response.readEntity(String.class);
397         assertTrue("Json response for /operations/mount_point rpc-behind-module1 is incorrect",
398                 validateOperationsResponseJson(responseBody, "rpc-behind-module1", "module1-behind-mount-point").find());
399         assertTrue("Json response for /operations/mount_point rpc-behind-module2 is incorrect",
400                 validateOperationsResponseJson(responseBody, "rpc-behind-module2", "module2-behind-mount-point").find());
401
402     }
403
404     private Matcher validateOperationsResponseJson(final String searchIn, final String rpcName, final String moduleName) {
405         final StringBuilder regex = new StringBuilder();
406         regex.append("^");
407
408         regex.append(".*\\{");
409         regex.append(".*\"");
410
411         // operations prefix optional
412         regex.append("(");
413         regex.append("ietf-restconf:");
414         regex.append("|)");
415         // :operations prefix optional
416
417         regex.append("operations\"");
418         regex.append(".*:");
419         regex.append(".*\\{");
420
421         regex.append(".*\"" + moduleName);
422         regex.append(":");
423         regex.append(rpcName + "\"");
424         regex.append(".*\\[");
425         regex.append(".*null");
426         regex.append(".*\\]");
427
428         regex.append(".*\\}");
429         regex.append(".*\\}");
430
431         regex.append(".*");
432         regex.append("$");
433         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
434         return ptrn.matcher(searchIn);
435
436     }
437
438     private Matcher validateOperationsResponseXml(final String searchIn, final String rpcName, final String namespace) {
439         final StringBuilder regex = new StringBuilder();
440
441         regex.append("^");
442
443         regex.append(".*<operations");
444         regex.append(".*xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
445         regex.append(".*>");
446
447         regex.append(".*<");
448         regex.append(".*" + rpcName);
449         regex.append(".*" + namespace);
450         regex.append(".*/");
451         regex.append(".*>");
452
453         regex.append(".*</operations.*");
454         regex.append(".*>");
455
456         regex.append(".*");
457         regex.append("$");
458         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
459         return ptrn.matcher(searchIn);
460     }
461
462     // /restconf/modules/pathToMountPoint/yang-ext:mount
463     @Test
464     public void getModulesBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
465         setControllerContext(schemaContextModules);
466
467         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
468         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
469         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
470         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
471
472         ControllerContext.getInstance().setMountService(mockMountService);
473
474         final String uri = "/modules/ietf-interfaces:interfaces/interface/0/yang-ext:mount/";
475
476         Response response = target(uri).request("application/yang.api+json").get();
477         assertEquals(200, response.getStatus());
478         final String responseBody = response.readEntity(String.class);
479
480         assertTrue(
481                 "module1-behind-mount-point in json wasn't found",
482                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
483                         responseBody).find());
484         assertTrue(
485                 "module2-behind-mount-point in json wasn't found",
486                 prepareJsonRegex("module2-behind-mount-point", "2014-02-04", "module:2:behind:mount:point",
487                         responseBody).find());
488
489         response = target(uri).request("application/yang.api+xml").get();
490         assertEquals(200, response.getStatus());
491         validateModulesResponseXml(response, schemaContextBehindMountPoint);
492
493     }
494
495     // /restconf/modules/module/pathToMountPoint/yang-ext:mount/moduleName/revision
496     @Test
497     public void getModuleBehindMountPoint() throws FileNotFoundException, UnsupportedEncodingException {
498         setControllerContext(schemaContextModules);
499
500         final DOMMountPoint mountInstance = mock(DOMMountPoint.class);
501         when(mountInstance.getSchemaContext()).thenReturn(schemaContextBehindMountPoint);
502         final DOMMountPointService mockMountService = mock(DOMMountPointService.class);
503         when(mockMountService.getMountPoint(any(YangInstanceIdentifier.class))).thenReturn(Optional.of(mountInstance));
504
505         ControllerContext.getInstance().setMountService(mockMountService);
506
507         final String uri = "/modules/module/ietf-interfaces:interfaces/interface/0/yang-ext:mount/module1-behind-mount-point/2014-02-03";
508
509         Response response = target(uri).request("application/yang.api+json").get();
510         assertEquals(200, response.getStatus());
511         final String responseBody = response.readEntity(String.class);
512
513         assertTrue(
514                 "module1-behind-mount-point in json wasn't found",
515                 prepareJsonRegex("module1-behind-mount-point", "2014-02-03", "module:1:behind:mount:point",
516                         responseBody).find());
517         final String[] split = responseBody.split("\"module\"");
518         assertEquals("\"module\" element is returned more then once", 2, split.length);
519
520         response = target(uri).request("application/yang.api+xml").get();
521         assertEquals(200, response.getStatus());
522         final Document responseXml = response.readEntity(Document.class);
523
524         final QName module = assertedModuleXmlToModuleQName(responseXml.getDocumentElement());
525
526         assertEquals("module1-behind-mount-point", module.getLocalName());
527         assertEquals("2014-02-03", module.getFormattedRevision());
528         assertEquals("module:1:behind:mount:point", module.getNamespace().toString());
529
530
531     }
532
533     private void validateModulesResponseXml(final Response response, final SchemaContext schemaContext) {
534         assertEquals(200, response.getStatus());
535         final Document responseBody = response.readEntity(Document.class);
536         final NodeList moduleNodes = responseBody.getDocumentElement().getElementsByTagNameNS(RESTCONF_NS, "module");
537
538         assertTrue(moduleNodes.getLength() > 0);
539
540         final HashSet<QName> foundModules = new HashSet<>();
541
542         for(int i=0;i < moduleNodes.getLength();i++) {
543             final org.w3c.dom.Node module = moduleNodes.item(i);
544
545             final QName name = assertedModuleXmlToModuleQName(module);
546             foundModules.add(name);
547         }
548
549         assertAllModules(foundModules,schemaContext);
550     }
551
552     private void assertAllModules(final Set<QName> foundModules, final SchemaContext schemaContext) {
553         for(final Module module : schemaContext.getModules()) {
554             final QName current = QName.create(module.getQNameModule(),module.getName());
555             assertTrue("Module not found in response.",foundModules.contains(current));
556         }
557
558     }
559
560     private QName assertedModuleXmlToModuleQName(final org.w3c.dom.Node module) {
561         assertEquals("module", module.getLocalName());
562         assertEquals(RESTCONF_NS, module.getNamespaceURI());
563         String revision = null;
564         String namespace = null;
565         String name = null;
566
567
568         final NodeList childNodes = module.getChildNodes();
569
570         for(int i =0;i < childNodes.getLength(); i++) {
571             final org.w3c.dom.Node child = childNodes.item(i);
572             assertEquals(RESTCONF_NS, child.getNamespaceURI());
573
574             switch(child.getLocalName()) {
575                 case "name":
576                     assertNull("Name element appeared multiple times",name);
577                     name = child.getTextContent().trim();
578                     break;
579                 case "revision":
580                     assertNull("Revision element appeared multiple times",revision);
581                     revision = child.getTextContent().trim();
582                     break;
583
584                 case "namespace":
585                     assertNull("Namespace element appeared multiple times",namespace);
586                     namespace = child.getTextContent().trim();
587                     break;
588             }
589         }
590
591         assertNotNull("Revision was not part of xml",revision);
592         assertNotNull("Module namespace was not part of xml",namespace);
593         assertNotNull("Module identiffier was not part of xml",name);
594
595
596         // TODO Auto-generated method stub
597
598         return QName.create(namespace,revision,name);
599     }
600
601     private void validateModulesResponseJson(final Response response) {
602         assertEquals(200, response.getStatus());
603         final String responseBody = response.readEntity(String.class);
604
605         assertTrue("Module1 in json wasn't found", prepareJsonRegex("module1", "2014-01-01", "module:1", responseBody)
606                 .find());
607         assertTrue("Module2 in json wasn't found", prepareJsonRegex("module2", "2014-01-02", "module:2", responseBody)
608                 .find());
609         assertTrue("Module3 in json wasn't found", prepareJsonRegex("module3", "2014-01-03", "module:3", responseBody)
610                 .find());
611     }
612
613     private Matcher prepareJsonRegex(final String module, final String revision, final String namespace,
614             final String searchIn) {
615         final StringBuilder regex = new StringBuilder();
616         regex.append("^");
617
618         regex.append(".*\\{");
619         regex.append(".*\"name\"");
620         regex.append(".*:");
621         regex.append(".*\"" + module + "\",");
622
623         regex.append(".*\"revision\"");
624         regex.append(".*:");
625         regex.append(".*\"" + revision + "\",");
626
627         regex.append(".*\"namespace\"");
628         regex.append(".*:");
629         regex.append(".*\"" + namespace + "\"");
630
631         regex.append(".*\\}");
632
633         regex.append(".*");
634         regex.append("$");
635         final Pattern ptrn = Pattern.compile(regex.toString(), Pattern.DOTALL);
636         return ptrn.matcher(searchIn);
637
638     }
639
640
641     private void prepareMockForModulesTest(final ControllerContext mockedControllerContext)
642             throws FileNotFoundException {
643         final SchemaContext schemaContext = TestUtils.loadSchemaContext("/modules");
644         mockedControllerContext.setGlobalSchema(schemaContext);
645         // when(mockedControllerContext.getGlobalSchema()).thenReturn(schemaContext);
646     }
647
648     private int get(final String uri, final String mediaType) {
649         return target(uri).request(mediaType).get().getStatus();
650     }
651
652     /**
653     container cont {
654         container cont1 {
655             leaf lf11 {
656                 type string;
657             }
658     */
659     private NormalizedNode prepareCnDataForMountPointTest(final boolean wrapToCont) throws URISyntaxException, ParseException {
660         final String testModuleDate = "2014-01-09";
661         final ContainerNode contChild = Builders
662                 .containerBuilder()
663                 .withNodeIdentifier(TestUtils.getNodeIdentifier("cont1", "test:module", testModuleDate))
664                 .withChild(
665                         Builders.leafBuilder()
666                                 .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", testModuleDate))
667                                 .withValue("lf11 value").build()).build();
668
669         if (wrapToCont) {
670             return Builders.containerBuilder()
671                     .withNodeIdentifier(TestUtils.getNodeIdentifier("cont", "test:module", testModuleDate))
672                     .withChild(contChild).build();
673         }
674         return contChild;
675
676     }
677
678     private void mockReadOperationalDataMethod() {
679         when(brokerFacade.readOperationalData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet);
680     }
681
682     private void mockReadConfigurationDataMethod() {
683         when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class))).thenReturn(answerFromGet);
684     }
685
686     private NormalizedNode prepareCnDataForSlashesBehindMountPointTest() throws ParseException {
687         return ImmutableMapEntryNodeBuilder
688                 .create()
689                 .withNodeIdentifier(
690                         TestUtils.getNodeIdentifierPredicate("lst1", "test:module", "2014-01-09", "lf11",
691                                 "GigabitEthernet0/0/0/0"))
692                 .withChild(
693                         ImmutableLeafNodeBuilder.create()
694                                 .withNodeIdentifier(TestUtils.getNodeIdentifier("lf11", "test:module", "2014-01-09"))
695                                 .withValue("GigabitEthernet0/0/0/0").build()).build();
696
697     }
698
699     /**
700      * If includeWhiteChars URI parameter is set to false then no white characters can be included in returned output
701      *
702      * @throws UnsupportedEncodingException
703      */
704     @Test
705     public void getDataWithUriIncludeWhiteCharsParameterTest() throws UnsupportedEncodingException {
706         getDataWithUriIncludeWhiteCharsParameter("config");
707         getDataWithUriIncludeWhiteCharsParameter("operational");
708     }
709
710     private void getDataWithUriIncludeWhiteCharsParameter(final String target) throws UnsupportedEncodingException {
711         mockReadConfigurationDataMethod();
712         mockReadOperationalDataMethod();
713         final String uri = "/" + target + "/ietf-interfaces:interfaces/interface/eth0";
714         Response response = target(uri).queryParam("prettyPrint", "false").request("application/xml").get();
715         final String xmlData = response.readEntity(String.class);
716
717         Pattern pattern = Pattern.compile(".*(>\\s+|\\s+<).*", Pattern.DOTALL);
718         Matcher matcher = pattern.matcher(xmlData);
719         // XML element can't surrounded with white character (e.g ">    " or
720         // "    <")
721         assertFalse(matcher.matches());
722
723         response = target(uri).queryParam("prettyPrint", "false").request("application/json").get();
724         final String jsonData = response.readEntity(String.class);
725         pattern = Pattern.compile(".*(\\}\\s+|\\s+\\{|\\]\\s+|\\s+\\[|\\s+:|:\\s+).*", Pattern.DOTALL);
726         matcher = pattern.matcher(jsonData);
727         // JSON element can't surrounded with white character (e.g "} ", " {",
728         // "] ", " [", " :" or ": ")
729         assertFalse(matcher.matches());
730     }
731
732     @Test
733     @Ignore
734     public void getDataWithUriDepthParameterTest() throws UnsupportedEncodingException {
735         setControllerContext(schemaContextModules);
736
737         final CompositeNode depth1Cont = toCompositeNode(toCompositeNodeData(
738                 toNestedQName("depth1-cont"),
739                 toCompositeNodeData(
740                         toNestedQName("depth2-cont1"),
741                         toCompositeNodeData(
742                                 toNestedQName("depth3-cont1"),
743                                 toCompositeNodeData(toNestedQName("depth4-cont1"),
744                                         toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
745                                 toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
746                         toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")),
747                 toCompositeNodeData(
748                         toNestedQName("depth2-cont2"),
749                         toCompositeNodeData(
750                                 toNestedQName("depth3-cont2"),
751                                 toCompositeNodeData(toNestedQName("depth4-cont2"),
752                                         toSimpleNodeData(toNestedQName("depth5-leaf2"), "depth5-leaf2-value")),
753                                 toSimpleNodeData(toNestedQName("depth4-leaf2"), "depth4-leaf2-value")),
754                         toSimpleNodeData(toNestedQName("depth3-leaf2"), "depth3-leaf2-value")),
755                 toSimpleNodeData(toNestedQName("depth2-leaf1"), "depth2-leaf1-value")));
756
757         final Module module = TestUtils.findModule(schemaContextModules.getModules(), "nested-module");
758         assertNotNull(module);
759
760         final DataSchemaNode dataSchemaNode = TestUtils.resolveDataSchemaNode("depth1-cont", module);
761         assertNotNull(dataSchemaNode);
762
763         when(brokerFacade.readConfigurationData(any(YangInstanceIdentifier.class))).thenReturn(
764                 TestUtils.compositeNodeToDatastoreNormalizedNode(depth1Cont, dataSchemaNode));
765
766         // Test config with depth 1
767
768         Response response = target("/config/nested-module:depth1-cont").queryParam("depth", "1")
769                 .request("application/xml").get();
770
771         verifyXMLResponse(response, expectEmptyContainer("depth1-cont"));
772
773         // Test config with depth 2
774
775         response = target("/config/nested-module:depth1-cont").queryParam("depth", "2").request("application/xml")
776                 .get();
777
778         // String
779         // xml="<depth1-cont><depth2-cont1/><depth2-cont2/><depth2-leaf1>depth2-leaf1-value</depth2-leaf1></depth1-cont>";
780         // Response mr=mock(Response.class);
781         // when(mr.getEntity()).thenReturn( new
782         // java.io.StringBufferInputStream(xml) );
783
784         verifyXMLResponse(
785                 response,
786                 expectContainer("depth1-cont", expectEmptyContainer("depth2-cont1"),
787                         expectEmptyContainer("depth2-cont2"), expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
788
789         // Test config with depth 3
790
791         response = target("/config/nested-module:depth1-cont").queryParam("depth", "3").request("application/xml")
792                 .get();
793
794         verifyXMLResponse(
795                 response,
796                 expectContainer(
797                         "depth1-cont",
798                         expectContainer("depth2-cont1", expectEmptyContainer("depth3-cont1"),
799                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
800                         expectContainer("depth2-cont2", expectEmptyContainer("depth3-cont2"),
801                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
802                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
803
804         // Test config with depth 4
805
806         response = target("/config/nested-module:depth1-cont").queryParam("depth", "4").request("application/xml")
807                 .get();
808
809         verifyXMLResponse(
810                 response,
811                 expectContainer(
812                         "depth1-cont",
813                         expectContainer(
814                                 "depth2-cont1",
815                                 expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
816                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
817                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
818                         expectContainer(
819                                 "depth2-cont2",
820                                 expectContainer("depth3-cont2", expectEmptyContainer("depth4-cont2"),
821                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
822                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
823                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
824
825         // Test config with depth 5
826
827         response = target("/config/nested-module:depth1-cont").queryParam("depth", "5").request("application/xml")
828                 .get();
829
830         verifyXMLResponse(
831                 response,
832                 expectContainer(
833                         "depth1-cont",
834                         expectContainer(
835                                 "depth2-cont1",
836                                 expectContainer(
837                                         "depth3-cont1",
838                                         expectContainer("depth4-cont1",
839                                                 expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
840                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
841                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
842                         expectContainer(
843                                 "depth2-cont2",
844                                 expectContainer(
845                                         "depth3-cont2",
846                                         expectContainer("depth4-cont2",
847                                                 expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
848                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
849                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
850                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
851
852         // Test config with depth unbounded
853
854         response = target("/config/nested-module:depth1-cont").queryParam("depth", "unbounded")
855                 .request("application/xml").get();
856
857         verifyXMLResponse(
858                 response,
859                 expectContainer(
860                         "depth1-cont",
861                         expectContainer(
862                                 "depth2-cont1",
863                                 expectContainer(
864                                         "depth3-cont1",
865                                         expectContainer("depth4-cont1",
866                                                 expectLeaf("depth5-leaf1", "depth5-leaf1-value")),
867                                         expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
868                                 expectLeaf("depth3-leaf1", "depth3-leaf1-value")),
869                         expectContainer(
870                                 "depth2-cont2",
871                                 expectContainer(
872                                         "depth3-cont2",
873                                         expectContainer("depth4-cont2",
874                                                 expectLeaf("depth5-leaf2", "depth5-leaf2-value")),
875                                         expectLeaf("depth4-leaf2", "depth4-leaf2-value")),
876                                 expectLeaf("depth3-leaf2", "depth3-leaf2-value")),
877                         expectLeaf("depth2-leaf1", "depth2-leaf1-value")));
878
879         // Test operational
880
881         final CompositeNode depth2Cont1 = toCompositeNode(toCompositeNodeData(
882                 toNestedQName("depth2-cont1"),
883                 toCompositeNodeData(
884                         toNestedQName("depth3-cont1"),
885                         toCompositeNodeData(toNestedQName("depth4-cont1"),
886                                 toSimpleNodeData(toNestedQName("depth5-leaf1"), "depth5-leaf1-value")),
887                         toSimpleNodeData(toNestedQName("depth4-leaf1"), "depth4-leaf1-value")),
888                 toSimpleNodeData(toNestedQName("depth3-leaf1"), "depth3-leaf1-value")));
889
890         assertTrue(dataSchemaNode instanceof DataNodeContainer);
891         DataSchemaNode depth2cont1Schema = null;
892         for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
893             if (childNode.getQName().getLocalName().equals("depth2-cont1")) {
894                 depth2cont1Schema = childNode;
895                 break;
896             }
897         }
898         assertNotNull(depth2Cont1);
899
900         when(brokerFacade.readOperationalData(any(YangInstanceIdentifier.class))).thenReturn(
901                 TestUtils.compositeNodeToDatastoreNormalizedNode(depth2Cont1, depth2cont1Schema));
902
903         response = target("/operational/nested-module:depth1-cont/depth2-cont1").queryParam("depth", "3")
904                 .request("application/xml").get();
905
906         verifyXMLResponse(
907                 response,
908                 expectContainer(
909                         "depth2-cont1",
910                         expectContainer("depth3-cont1", expectEmptyContainer("depth4-cont1"),
911                                 expectLeaf("depth4-leaf1", "depth4-leaf1-value")),
912                         expectLeaf("depth3-leaf1", "depth3-leaf1-value")));
913     }
914
915     /**
916      * Tests behavior when invalid value of depth URI parameter
917      */
918     @Test
919     @Ignore
920     public void getDataWithInvalidDepthParameterTest() {
921         setControllerContext(schemaContextModules);
922
923         final MultivaluedMap<String, String> paramMap = new MultivaluedHashMap<>();
924         paramMap.putSingle("depth", "1o");
925         final UriInfo mockInfo = mock(UriInfo.class);
926         when(mockInfo.getQueryParameters(false)).thenAnswer(new Answer<MultivaluedMap<String, String>>() {
927             @Override
928             public MultivaluedMap<String, String> answer(final InvocationOnMock invocation) {
929                 return paramMap;
930             }
931         });
932
933         getDataWithInvalidDepthParameterTest(mockInfo);
934
935         paramMap.putSingle("depth", "0");
936         getDataWithInvalidDepthParameterTest(mockInfo);
937
938         paramMap.putSingle("depth", "-1");
939         getDataWithInvalidDepthParameterTest(mockInfo);
940     }
941
942     private void getDataWithInvalidDepthParameterTest(final UriInfo uriInfo) {
943         try {
944             final QName qNameDepth1Cont = QName.create("urn:nested:module", "2014-06-3", "depth1-cont");
945             final YangInstanceIdentifier ii = YangInstanceIdentifier.builder().node(qNameDepth1Cont).build();
946             final NormalizedNode value = (Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(qNameDepth1Cont)).build());
947             when(brokerFacade.readConfigurationData(eq(ii))).thenReturn(value);
948             restconfImpl.readConfigurationData("nested-module:depth1-cont", uriInfo);
949             fail("Expected RestconfDocumentedException");
950         } catch (final RestconfDocumentedException e) {
951             assertTrue("Unexpected error message: " + e.getErrors().get(0).getErrorMessage(), e.getErrors().get(0)
952                     .getErrorMessage().contains("depth"));
953         }
954     }
955
956     private void verifyXMLResponse(final Response response, final NodeData nodeData) {
957         final Document doc = response.readEntity(Document.class);
958 //        Document doc = TestUtils.loadDocumentFrom((InputStream) response.getEntity());
959 //        System.out.println();
960         assertNotNull("Could not parse XML document", doc);
961
962         // System.out.println(TestUtils.getDocumentInPrintableForm( doc ));
963
964         verifyContainerElement(doc.getDocumentElement(), nodeData);
965     }
966
967     @SuppressWarnings("unchecked")
968     private void verifyContainerElement(final Element element, final NodeData nodeData) {
969
970         assertEquals("Element local name", nodeData.key, element.getLocalName());
971
972         final NodeList childNodes = element.getChildNodes();
973         if (nodeData.data == null) { // empty container
974             assertTrue("Expected no child elements for \"" + element.getLocalName() + "\"", childNodes.getLength() == 0);
975             return;
976         }
977
978         final Map<String, NodeData> expChildMap = Maps.newHashMap();
979         for (final NodeData expChild : (List<NodeData>) nodeData.data) {
980             expChildMap.put(expChild.key.toString(), expChild);
981         }
982
983         for (int i = 0; i < childNodes.getLength(); i++) {
984             final org.w3c.dom.Node actualChild = childNodes.item(i);
985             if (!(actualChild instanceof Element)) {
986                 continue;
987             }
988
989             final Element actualElement = (Element) actualChild;
990             final NodeData expChild = expChildMap.remove(actualElement.getLocalName());
991             assertNotNull(
992                     "Unexpected child element for parent \"" + element.getLocalName() + "\": "
993                             + actualElement.getLocalName(), expChild);
994
995             if (expChild.data == null || expChild.data instanceof List) {
996                 verifyContainerElement(actualElement, expChild);
997             } else {
998                 assertEquals("Text content for element: " + actualElement.getLocalName(), expChild.data,
999                         actualElement.getTextContent());
1000             }
1001         }
1002
1003         if (!expChildMap.isEmpty()) {
1004             fail("Missing elements for parent \"" + element.getLocalName() + "\": " + expChildMap.keySet());
1005         }
1006     }
1007
1008     private NodeData expectContainer(final String name, final NodeData... childData) {
1009         return new NodeData(name, Lists.newArrayList(childData));
1010     }
1011
1012     private NodeData expectEmptyContainer(final String name) {
1013         return new NodeData(name, null);
1014     }
1015
1016     private NodeData expectLeaf(final String name, final Object value) {
1017         return new NodeData(name, value);
1018     }
1019
1020     private QName toNestedQName(final String localName) {
1021         return QName.create("urn:nested:module", "2014-06-3", localName);
1022     }
1023
1024     @SuppressWarnings("unchecked")
1025     private CompositeNode toCompositeNode(final NodeData nodeData) {
1026         final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
1027         builder.setQName((QName) nodeData.key);
1028
1029         for (final NodeData child : (List<NodeData>) nodeData.data) {
1030             if (child.data instanceof List) {
1031                 builder.add(toCompositeNode(child));
1032             } else {
1033                 builder.addLeaf((QName) child.key, child.data);
1034             }
1035         }
1036
1037         return builder.toInstance();
1038     }
1039
1040     private NodeData toCompositeNodeData(final QName key, final NodeData... childData) {
1041         return new NodeData(key, Lists.newArrayList(childData));
1042     }
1043
1044     private NodeData toSimpleNodeData(final QName key, final Object value) {
1045         return new NodeData(key, value);
1046     }
1047
1048 }