Merge "Fixed for bug : 1171 - issue while creating subnet"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / RestconfDocumentedExceptionMapperTest.java
1 /*
2  * Copyright (c) 2014 Brocade Communications 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
9 package org.opendaylight.controller.sal.restconf.impl.test;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertNotNull;
14 import static org.junit.Assert.assertNull;
15 import static org.junit.Assert.assertTrue;
16 import static org.junit.Assert.fail;
17 import static org.mockito.Matchers.any;
18 import static org.mockito.Mockito.mock;
19 import static org.mockito.Mockito.reset;
20 import static org.mockito.Mockito.when;
21
22 import com.google.common.collect.ImmutableMap;
23 import com.google.common.collect.Maps;
24 import com.google.common.io.ByteStreams;
25 import com.google.gson.JsonArray;
26 import com.google.gson.JsonElement;
27 import com.google.gson.JsonParser;
28 import java.io.ByteArrayInputStream;
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.InputStreamReader;
33 import java.util.Arrays;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Map.Entry;
38 import java.util.Set;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41 import javax.ws.rs.core.Application;
42 import javax.ws.rs.core.MediaType;
43 import javax.ws.rs.core.Response;
44 import javax.ws.rs.core.Response.Status;
45 import javax.ws.rs.core.UriInfo;
46 import javax.xml.namespace.NamespaceContext;
47 import javax.xml.parsers.DocumentBuilderFactory;
48 import javax.xml.xpath.XPath;
49 import javax.xml.xpath.XPathConstants;
50 import javax.xml.xpath.XPathExpression;
51 import javax.xml.xpath.XPathFactory;
52 import org.glassfish.jersey.server.ResourceConfig;
53 import org.glassfish.jersey.test.JerseyTest;
54 import org.junit.Before;
55 import org.junit.BeforeClass;
56 import org.junit.Test;
57 import org.opendaylight.controller.sal.rest.api.Draft02;
58 import org.opendaylight.controller.sal.rest.api.RestconfService;
59 import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper;
60 import org.opendaylight.controller.sal.rest.impl.StructuredDataToJsonProvider;
61 import org.opendaylight.controller.sal.rest.impl.StructuredDataToXmlProvider;
62 import org.opendaylight.controller.sal.restconf.impl.ControllerContext;
63 import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
64 import org.opendaylight.controller.sal.restconf.impl.RestconfError;
65 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
66 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
67 import org.opendaylight.controller.sal.restconf.impl.StructuredData;
68 import org.w3c.dom.Document;
69 import org.w3c.dom.Element;
70 import org.w3c.dom.Node;
71 import org.w3c.dom.NodeList;
72
73 /**
74  * Unit tests for RestconfDocumentedExceptionMapper.
75  *
76  * @author Thomas Pantelis
77  */
78 public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
79
80     interface ErrorInfoVerifier {
81         void verifyXML(Node errorInfoNode);
82
83         void verifyJson(JsonElement errorInfoElement);
84     }
85
86     static class ComplexErrorInfoVerifier implements ErrorInfoVerifier {
87
88         Map<String, String> expErrorInfo;
89
90         public ComplexErrorInfoVerifier(final Map<String, String> expErrorInfo) {
91             this.expErrorInfo = expErrorInfo;
92         }
93
94         @Override
95         public void verifyXML(final Node errorInfoNode) {
96
97             Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
98             NodeList childNodes = errorInfoNode.getChildNodes();
99             for (int i = 0; i < childNodes.getLength(); i++) {
100                 Node child = childNodes.item(i);
101                 if (child instanceof Element) {
102                     String expValue = mutableExpMap.remove(child.getNodeName());
103                     assertNotNull("Found unexpected \"error-info\" child node: " + child.getNodeName(), expValue);
104                     assertEquals("Text content for \"error-info\" child node " + child.getNodeName(), expValue,
105                             child.getTextContent());
106                 }
107             }
108
109             if (!mutableExpMap.isEmpty()) {
110                 fail("Missing \"error-info\" child nodes: " + mutableExpMap);
111             }
112         }
113
114         @Override
115         public void verifyJson(final JsonElement errorInfoElement) {
116
117             assertTrue("\"error-info\" Json element is not an Object", errorInfoElement.isJsonObject());
118
119             Map<String, String> actualErrorInfo = Maps.newHashMap();
120             for (Entry<String, JsonElement> entry : errorInfoElement.getAsJsonObject().entrySet()) {
121                 String leafName = entry.getKey();
122                 JsonElement leafElement = entry.getValue();
123                 actualErrorInfo.put(leafName, leafElement.getAsString());
124             }
125
126             Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
127             for (Entry<String, String> actual : actualErrorInfo.entrySet()) {
128                 String expValue = mutableExpMap.remove(actual.getKey());
129                 assertNotNull("Found unexpected \"error-info\" child node: " + actual.getKey(), expValue);
130                 assertEquals("Text content for \"error-info\" child node " + actual.getKey(), expValue,
131                         actual.getValue());
132             }
133
134             if (!mutableExpMap.isEmpty()) {
135                 fail("Missing \"error-info\" child nodes: " + mutableExpMap);
136             }
137         }
138     }
139
140     static class SimpleErrorInfoVerifier implements ErrorInfoVerifier {
141
142         String expTextContent;
143
144         public SimpleErrorInfoVerifier(final String expErrorInfo) {
145             this.expTextContent = expErrorInfo;
146         }
147
148         void verifyContent(final String actualContent) {
149             assertNotNull("Actual \"error-info\" text content is null", actualContent);
150             assertTrue("", actualContent.contains(expTextContent));
151         }
152
153         @Override
154         public void verifyXML(final Node errorInfoNode) {
155             verifyContent(errorInfoNode.getTextContent());
156         }
157
158         @Override
159         public void verifyJson(final JsonElement errorInfoElement) {
160             verifyContent(errorInfoElement.getAsString());
161         }
162     }
163
164     static RestconfService mockRestConf = mock(RestconfService.class);
165
166     static XPath XPATH = XPathFactory.newInstance().newXPath();
167     static XPathExpression ERROR_LIST;
168     static XPathExpression ERROR_TYPE;
169     static XPathExpression ERROR_TAG;
170     static XPathExpression ERROR_MESSAGE;
171     static XPathExpression ERROR_APP_TAG;
172     static XPathExpression ERROR_INFO;
173
174     @BeforeClass
175     public static void init() throws Exception {
176         ControllerContext.getInstance().setGlobalSchema(TestUtils.loadSchemaContext("/modules"));
177
178         NamespaceContext nsContext = new NamespaceContext() {
179             @Override
180             public Iterator<?> getPrefixes(final String namespaceURI) {
181                 return null;
182             }
183
184             @Override
185             public String getPrefix(final String namespaceURI) {
186                 return null;
187             }
188
189             @Override
190             public String getNamespaceURI(final String prefix) {
191                 return "ietf-restconf".equals(prefix) ? Draft02.RestConfModule.NAMESPACE : null;
192             }
193         };
194
195         XPATH.setNamespaceContext(nsContext);
196         ERROR_LIST = XPATH.compile("ietf-restconf:errors/ietf-restconf:error");
197         ERROR_TYPE = XPATH.compile("ietf-restconf:error-type");
198         ERROR_TAG = XPATH.compile("ietf-restconf:error-tag");
199         ERROR_MESSAGE = XPATH.compile("ietf-restconf:error-message");
200         ERROR_APP_TAG = XPATH.compile("ietf-restconf:error-app-tag");
201         ERROR_INFO = XPATH.compile("ietf-restconf:error-info");
202     }
203
204     @Override
205     @Before
206     public void setUp() throws Exception {
207         reset(mockRestConf);
208         super.setUp();
209     }
210
211     @Override
212     protected Application configure() {
213         ResourceConfig resourceConfig = new ResourceConfig();
214         resourceConfig = resourceConfig.registerInstances(mockRestConf, StructuredDataToXmlProvider.INSTANCE,
215                 StructuredDataToJsonProvider.INSTANCE);
216         resourceConfig.registerClasses(RestconfDocumentedExceptionMapper.class);
217         return resourceConfig;
218     }
219
220     void stageMockEx(final RestconfDocumentedException ex) {
221         reset(mockRestConf);
222         when(mockRestConf.readOperationalData(any(String.class), any(UriInfo.class))).thenThrow(ex);
223     }
224
225     void testJsonResponse(final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType,
226             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
227             final ErrorInfoVerifier errorInfoVerifier) throws Exception {
228
229         stageMockEx(ex);
230
231         Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
232
233         InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, expStatus);
234
235         verifyJsonResponseBody(stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier);
236     }
237
238     @Test
239     public void testToJsonResponseWithMessageOnly() throws Exception {
240
241         testJsonResponse(new RestconfDocumentedException("mock error"), Status.INTERNAL_SERVER_ERROR,
242                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
243
244         // To test verification code
245         // String json =
246         // "{ errors: {" +
247         // "    error: [{" +
248         // "      error-tag : \"operation-failed\"" +
249         // "      ,error-type : \"application\"" +
250         // "      ,error-message : \"An error occurred\"" +
251         // "      ,error-info : {" +
252         // "        session-id: \"123\"" +
253         // "        ,address: \"1.2.3.4\"" +
254         // "      }" +
255         // "    }]" +
256         // "  }" +
257         // "}";
258         //
259         // verifyJsonResponseBody( new java.io.StringBufferInputStream(json ),
260         // ErrorType.APPLICATION,
261         // ErrorTag.OPERATION_FAILED, "An error occurred", null,
262         // com.google.common.collect.ImmutableMap.of( "session-id", "123",
263         // "address", "1.2.3.4" ) );
264     }
265
266     @Test
267     public void testToJsonResponseWithInUseErrorTag() throws Exception {
268
269         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE),
270                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null);
271     }
272
273     @Test
274     public void testToJsonResponseWithInvalidValueErrorTag() throws Exception {
275
276         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE),
277                 Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null);
278
279     }
280
281     @Test
282     public void testToJsonResponseWithTooBigErrorTag() throws Exception {
283
284         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG),
285                 Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null);
286
287     }
288
289     @Test
290     public void testToJsonResponseWithMissingAttributeErrorTag() throws Exception {
291
292         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE),
293                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null);
294     }
295
296     @Test
297     public void testToJsonResponseWithBadAttributeErrorTag() throws Exception {
298
299         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE),
300                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null);
301     }
302
303     @Test
304     public void testToJsonResponseWithUnknownAttributeErrorTag() throws Exception {
305
306         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE),
307                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null);
308     }
309
310     @Test
311     public void testToJsonResponseWithBadElementErrorTag() throws Exception {
312
313         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT),
314                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null);
315     }
316
317     @Test
318     public void testToJsonResponseWithUnknownElementErrorTag() throws Exception {
319
320         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT),
321                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null);
322     }
323
324     @Test
325     public void testToJsonResponseWithUnknownNamespaceErrorTag() throws Exception {
326
327         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE),
328                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null);
329     }
330
331     @Test
332     public void testToJsonResponseWithMalformedMessageErrorTag() throws Exception {
333
334         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE),
335                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null);
336     }
337
338     @Test
339     public void testToJsonResponseWithAccessDeniedErrorTag() throws Exception {
340
341         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED),
342                 Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null);
343     }
344
345     @Test
346     public void testToJsonResponseWithLockDeniedErrorTag() throws Exception {
347
348         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED),
349                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null);
350     }
351
352     @Test
353     public void testToJsonResponseWithResourceDeniedErrorTag() throws Exception {
354
355         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED),
356                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null);
357     }
358
359     @Test
360     public void testToJsonResponseWithRollbackFailedErrorTag() throws Exception {
361
362         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED),
363                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null);
364     }
365
366     @Test
367     public void testToJsonResponseWithDataExistsErrorTag() throws Exception {
368
369         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS),
370                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null);
371     }
372
373     @Test
374     public void testToJsonResponseWithDataMissingErrorTag() throws Exception {
375
376         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING),
377                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null);
378     }
379
380     @Test
381     public void testToJsonResponseWithOperationNotSupportedErrorTag() throws Exception {
382
383         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL,
384                 ErrorTag.OPERATION_NOT_SUPPORTED), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
385                 ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null);
386     }
387
388     @Test
389     public void testToJsonResponseWithOperationFailedErrorTag() throws Exception {
390
391         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED),
392                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null);
393     }
394
395     @Test
396     public void testToJsonResponseWithPartialOperationErrorTag() throws Exception {
397
398         testJsonResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION),
399                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null);
400     }
401
402     @Test
403     public void testToJsonResponseWithErrorAppTag() throws Exception {
404
405         testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
406                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag")), Status.BAD_REQUEST, ErrorType.APPLICATION,
407                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null);
408     }
409
410     @Test
411     public void testToJsonResponseWithMultipleErrors() throws Exception {
412
413         List<RestconfError> errorList = Arrays.asList(new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED,
414                 "mock error1"), new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2"));
415         stageMockEx(new RestconfDocumentedException(errorList));
416
417         Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
418
419         InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.CONFLICT);
420
421         JsonArray arrayElement = parseJsonErrorArrayElement(stream);
422
423         assertEquals("\"error\" Json array element length", 2, arrayElement.size());
424
425         verifyJsonErrorNode(arrayElement.get(0), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null);
426
427         verifyJsonErrorNode(arrayElement.get(1), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null);
428     }
429
430     @Test
431     public void testToJsonResponseWithErrorInfo() throws Exception {
432
433         String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
434         testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
435                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
436                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
437                 new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
438     }
439
440     @Test
441     public void testToJsonResponseWithExceptionCause() throws Exception {
442
443         Exception cause = new Exception("mock exception cause");
444         testJsonResponse(new RestconfDocumentedException("mock error", cause), Status.INTERNAL_SERVER_ERROR,
445                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null,
446                 new SimpleErrorInfoVerifier(cause.getMessage()));
447     }
448
449     void testXMLResponse(final RestconfDocumentedException ex, final Status expStatus, final ErrorType expErrorType,
450             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
451             final ErrorInfoVerifier errorInfoVerifier) throws Exception {
452         stageMockEx(ex);
453
454         Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get();
455
456         InputStream stream = verifyResponse(resp, MediaType.APPLICATION_XML, expStatus);
457
458         verifyXMLResponseBody(stream, expErrorType, expErrorTag, expErrorMessage, expErrorAppTag, errorInfoVerifier);
459     }
460
461     @Test
462     public void testToXMLResponseWithMessageOnly() throws Exception {
463
464         testXMLResponse(new RestconfDocumentedException("mock error"), Status.INTERNAL_SERVER_ERROR,
465                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
466
467         // To test verification code
468         // String xml =
469         // "<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\">"+
470         // "  <error>" +
471         // "    <error-type>application</error-type>"+
472         // "    <error-tag>operation-failed</error-tag>"+
473         // "    <error-message>An error occurred</error-message>"+
474         // "    <error-info>" +
475         // "      <session-id>123</session-id>" +
476         // "      <address>1.2.3.4</address>" +
477         // "    </error-info>" +
478         // "  </error>" +
479         // "</errors>";
480         //
481         // verifyXMLResponseBody( new java.io.StringBufferInputStream(xml),
482         // ErrorType.APPLICATION,
483         // ErrorTag.OPERATION_FAILED, "An error occurred", null,
484         // com.google.common.collect.ImmutableMap.of( "session-id", "123",
485         // "address", "1.2.3.4" ) );
486     }
487
488     @Test
489     public void testToXMLResponseWithInUseErrorTag() throws Exception {
490
491         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.IN_USE),
492                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.IN_USE, "mock error", null, null);
493     }
494
495     @Test
496     public void testToXMLResponseWithInvalidValueErrorTag() throws Exception {
497
498         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.RPC, ErrorTag.INVALID_VALUE),
499                 Status.BAD_REQUEST, ErrorType.RPC, ErrorTag.INVALID_VALUE, "mock error", null, null);
500     }
501
502     @Test
503     public void testToXMLResponseWithTooBigErrorTag() throws Exception {
504
505         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.TRANSPORT, ErrorTag.TOO_BIG),
506                 Status.REQUEST_ENTITY_TOO_LARGE, ErrorType.TRANSPORT, ErrorTag.TOO_BIG, "mock error", null, null);
507     }
508
509     @Test
510     public void testToXMLResponseWithMissingAttributeErrorTag() throws Exception {
511
512         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE),
513                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, "mock error", null, null);
514     }
515
516     @Test
517     public void testToXMLResponseWithBadAttributeErrorTag() throws Exception {
518
519         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE),
520                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ATTRIBUTE, "mock error", null, null);
521     }
522
523     @Test
524     public void testToXMLResponseWithUnknownAttributeErrorTag() throws Exception {
525
526         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE),
527                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ATTRIBUTE, "mock error", null, null);
528     }
529
530     @Test
531     public void testToXMLResponseWithBadElementErrorTag() throws Exception {
532
533         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT),
534                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, "mock error", null, null);
535     }
536
537     @Test
538     public void testToXMLResponseWithUnknownElementErrorTag() throws Exception {
539
540         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT),
541                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_ELEMENT, "mock error", null, null);
542     }
543
544     @Test
545     public void testToXMLResponseWithUnknownNamespaceErrorTag() throws Exception {
546
547         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE),
548                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.UNKNOWN_NAMESPACE, "mock error", null, null);
549     }
550
551     @Test
552     public void testToXMLResponseWithMalformedMessageErrorTag() throws Exception {
553
554         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE),
555                 Status.BAD_REQUEST, ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, "mock error", null, null);
556     }
557
558     @Test
559     public void testToXMLResponseWithAccessDeniedErrorTag() throws Exception {
560
561         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED),
562                 Status.FORBIDDEN, ErrorType.PROTOCOL, ErrorTag.ACCESS_DENIED, "mock error", null, null);
563     }
564
565     @Test
566     public void testToXMLResponseWithLockDeniedErrorTag() throws Exception {
567
568         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED),
569                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.LOCK_DENIED, "mock error", null, null);
570     }
571
572     @Test
573     public void testToXMLResponseWithResourceDeniedErrorTag() throws Exception {
574
575         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED),
576                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.RESOURCE_DENIED, "mock error", null, null);
577     }
578
579     @Test
580     public void testToXMLResponseWithRollbackFailedErrorTag() throws Exception {
581
582         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED),
583                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.ROLLBACK_FAILED, "mock error", null, null);
584     }
585
586     @Test
587     public void testToXMLResponseWithDataExistsErrorTag() throws Exception {
588
589         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS),
590                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, "mock error", null, null);
591     }
592
593     @Test
594     public void testToXMLResponseWithDataMissingErrorTag() throws Exception {
595
596         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING),
597                 Status.CONFLICT, ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, "mock error", null, null);
598     }
599
600     @Test
601     public void testToXMLResponseWithOperationNotSupportedErrorTag() throws Exception {
602
603         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL,
604                 ErrorTag.OPERATION_NOT_SUPPORTED), Status.NOT_IMPLEMENTED, ErrorType.PROTOCOL,
605                 ErrorTag.OPERATION_NOT_SUPPORTED, "mock error", null, null);
606     }
607
608     @Test
609     public void testToXMLResponseWithOperationFailedErrorTag() throws Exception {
610
611         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED),
612                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.OPERATION_FAILED, "mock error", null, null);
613     }
614
615     @Test
616     public void testToXMLResponseWithPartialOperationErrorTag() throws Exception {
617
618         testXMLResponse(new RestconfDocumentedException("mock error", ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION),
619                 Status.INTERNAL_SERVER_ERROR, ErrorType.PROTOCOL, ErrorTag.PARTIAL_OPERATION, "mock error", null, null);
620     }
621
622     @Test
623     public void testToXMLResponseWithErrorAppTag() throws Exception {
624
625         testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
626                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag")), Status.BAD_REQUEST, ErrorType.APPLICATION,
627                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", null);
628     }
629
630     @Test
631     public void testToXMLResponseWithErrorInfo() throws Exception {
632
633         String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
634         testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
635                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
636                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
637                 new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
638     }
639
640     @Test
641     public void testToXMLResponseWithExceptionCause() throws Exception {
642
643         Exception cause = new Exception("mock exception cause");
644         testXMLResponse(new RestconfDocumentedException("mock error", cause), Status.INTERNAL_SERVER_ERROR,
645                 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null,
646                 new SimpleErrorInfoVerifier(cause.getMessage()));
647     }
648
649     @Test
650     public void testToXMLResponseWithMultipleErrors() throws Exception {
651
652         List<RestconfError> errorList = Arrays.asList(new RestconfError(ErrorType.APPLICATION, ErrorTag.LOCK_DENIED,
653                 "mock error1"), new RestconfError(ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2"));
654         stageMockEx(new RestconfDocumentedException(errorList));
655
656         Response resp = target("/operational/foo").request(MediaType.APPLICATION_XML).get();
657
658         InputStream stream = verifyResponse(resp, MediaType.APPLICATION_XML, Status.CONFLICT);
659
660         Document doc = parseXMLDocument(stream);
661
662         NodeList children = getXMLErrorList(doc, 2);
663
664         verifyXMLErrorNode(children.item(0), ErrorType.APPLICATION, ErrorTag.LOCK_DENIED, "mock error1", null, null);
665
666         verifyXMLErrorNode(children.item(1), ErrorType.RPC, ErrorTag.ROLLBACK_FAILED, "mock error2", null, null);
667     }
668
669     @Test
670     public void testToResponseWithAcceptHeader() throws Exception {
671
672         stageMockEx(new RestconfDocumentedException("mock error"));
673
674         Response resp = target("/operational/foo").request().header("Accept", MediaType.APPLICATION_JSON).get();
675
676         InputStream stream = verifyResponse(resp, MediaType.APPLICATION_JSON, Status.INTERNAL_SERVER_ERROR);
677
678         verifyJsonResponseBody(stream, ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, "mock error", null, null);
679     }
680
681     @Test
682     public void testToResponseWithStatusOnly() throws Exception {
683
684         // The StructuredDataToJsonProvider should throw a
685         // RestconfDocumentedException with no data
686
687         when(mockRestConf.readOperationalData(any(String.class), any(UriInfo.class))).thenReturn(
688                 new StructuredData(null, null, null));
689
690         Response resp = target("/operational/foo").request(MediaType.APPLICATION_JSON).get();
691
692         verifyResponse(resp, MediaType.TEXT_PLAIN, Status.NOT_FOUND);
693     }
694
695     InputStream verifyResponse(final Response resp, final String expMediaType, final Status expStatus) {
696         assertEquals("getMediaType", MediaType.valueOf(expMediaType), resp.getMediaType());
697         assertEquals("getStatus", expStatus.getStatusCode(), resp.getStatus());
698
699         Object entity = resp.getEntity();
700         assertEquals("Response entity", true, entity instanceof InputStream);
701         InputStream stream = (InputStream) entity;
702         return stream;
703     }
704
705     void verifyJsonResponseBody(final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag,
706             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
707             throws Exception {
708
709         JsonArray arrayElement = parseJsonErrorArrayElement(stream);
710
711         assertEquals("\"error\" Json array element length", 1, arrayElement.size());
712
713         verifyJsonErrorNode(arrayElement.get(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag,
714                 errorInfoVerifier);
715     }
716
717     private JsonArray parseJsonErrorArrayElement(final InputStream stream) throws IOException {
718         ByteArrayOutputStream bos = new ByteArrayOutputStream();
719         ByteStreams.copy(stream, bos);
720
721         System.out.println("JSON: " + bos.toString());
722
723         JsonParser parser = new JsonParser();
724         JsonElement rootElement;
725
726         try {
727             rootElement = parser.parse(new InputStreamReader(new ByteArrayInputStream(bos.toByteArray())));
728         } catch (Exception e) {
729             throw new IllegalArgumentException("Invalid JSON response:\n" + bos.toString(), e);
730         }
731
732         assertTrue("Root element of Json is not an Object", rootElement.isJsonObject());
733
734         Set<Entry<String, JsonElement>> errorsEntrySet = rootElement.getAsJsonObject().entrySet();
735         assertEquals("Json Object element set count", 1, errorsEntrySet.size());
736
737         Entry<String, JsonElement> errorsEntry = errorsEntrySet.iterator().next();
738         JsonElement errorsElement = errorsEntry.getValue();
739         assertEquals("First Json element name", "errors", errorsEntry.getKey());
740         assertTrue("\"errors\" Json element is not an Object", errorsElement.isJsonObject());
741
742         Set<Entry<String, JsonElement>> errorListEntrySet = errorsElement.getAsJsonObject().entrySet();
743         assertEquals("Root \"errors\" element child count", 1, errorListEntrySet.size());
744
745         JsonElement errorListElement = errorListEntrySet.iterator().next().getValue();
746         assertEquals("\"errors\" child Json element name", "error", errorListEntrySet.iterator().next().getKey());
747         assertTrue("\"error\" Json element is not an Array", errorListElement.isJsonArray());
748
749         // As a final check, make sure there aren't multiple "error" array
750         // elements. Unfortunately,
751         // the call above to getAsJsonObject().entrySet() will out duplicate
752         // "error" elements. So
753         // we'll use regex on the json string to verify this.
754
755         Matcher matcher = Pattern.compile("\"error\"[ ]*:[ ]*\\[", Pattern.DOTALL).matcher(bos.toString());
756         assertTrue("Expected 1 \"error\" element", matcher.find());
757         assertFalse("Found multiple \"error\" elements", matcher.find());
758
759         return errorListElement.getAsJsonArray();
760     }
761
762     void verifyJsonErrorNode(final JsonElement errorEntryElement, final ErrorType expErrorType,
763             final ErrorTag expErrorTag, final String expErrorMessage, final String expErrorAppTag,
764             final ErrorInfoVerifier errorInfoVerifier) {
765
766         JsonElement errorInfoElement = null;
767         Map<String, String> leafMap = Maps.newHashMap();
768         for (Entry<String, JsonElement> entry : errorEntryElement.getAsJsonObject().entrySet()) {
769             String leafName = entry.getKey();
770             JsonElement leafElement = entry.getValue();
771
772             if ("error-info".equals(leafName)) {
773                 assertNotNull("Found unexpected \"error-info\" element", errorInfoVerifier);
774                 errorInfoElement = leafElement;
775             } else {
776                 assertTrue("\"error\" leaf Json element " + leafName + " is not a Primitive",
777                         leafElement.isJsonPrimitive());
778
779                 leafMap.put(leafName, leafElement.getAsString());
780             }
781         }
782
783         assertEquals("error-type", expErrorType.getErrorTypeTag(), leafMap.remove("error-type"));
784         assertEquals("error-tag", expErrorTag.getTagValue(), leafMap.remove("error-tag"));
785
786         verifyOptionalJsonLeaf(leafMap.remove("error-message"), expErrorMessage, "error-message");
787         verifyOptionalJsonLeaf(leafMap.remove("error-app-tag"), expErrorAppTag, "error-app-tag");
788
789         if (!leafMap.isEmpty()) {
790             fail("Found unexpected Json leaf elements for \"error\" element: " + leafMap);
791         }
792
793         if (errorInfoVerifier != null) {
794             assertNotNull("Missing \"error-info\" element", errorInfoElement);
795             errorInfoVerifier.verifyJson(errorInfoElement);
796         }
797     }
798
799     void verifyOptionalJsonLeaf(final String actualValue, final String expValue, final String tagName) {
800         if (expValue != null) {
801             assertEquals(tagName, expValue, actualValue);
802         } else {
803             assertNull("Found unexpected \"error\" leaf entry for: " + tagName, actualValue);
804         }
805     }
806
807     void verifyXMLResponseBody(final InputStream stream, final ErrorType expErrorType, final ErrorTag expErrorTag,
808             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
809             throws Exception {
810
811         Document doc = parseXMLDocument(stream);
812
813         NodeList children = getXMLErrorList(doc, 1);
814
815         verifyXMLErrorNode(children.item(0), expErrorType, expErrorTag, expErrorMessage, expErrorAppTag,
816                 errorInfoVerifier);
817     }
818
819     private Document parseXMLDocument(final InputStream stream) throws IOException {
820         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
821         factory.setNamespaceAware(true);
822         factory.setCoalescing(true);
823         factory.setIgnoringElementContentWhitespace(true);
824         factory.setIgnoringComments(true);
825
826         ByteArrayOutputStream bos = new ByteArrayOutputStream();
827         ByteStreams.copy(stream, bos);
828
829         System.out.println("XML: " + bos.toString());
830
831         Document doc = null;
832         try {
833             doc = factory.newDocumentBuilder().parse(new ByteArrayInputStream(bos.toByteArray()));
834         } catch (Exception e) {
835             throw new IllegalArgumentException("Invalid XML response:\n" + bos.toString(), e);
836         }
837         return doc;
838     }
839
840     void verifyXMLErrorNode(final Node errorNode, final ErrorType expErrorType, final ErrorTag expErrorTag,
841             final String expErrorMessage, final String expErrorAppTag, final ErrorInfoVerifier errorInfoVerifier)
842             throws Exception {
843
844         String errorType = (String) ERROR_TYPE.evaluate(errorNode, XPathConstants.STRING);
845         assertEquals("error-type", expErrorType.getErrorTypeTag(), errorType);
846
847         String errorTag = (String) ERROR_TAG.evaluate(errorNode, XPathConstants.STRING);
848         assertEquals("error-tag", expErrorTag.getTagValue(), errorTag);
849
850         verifyOptionalXMLLeaf(errorNode, ERROR_MESSAGE, expErrorMessage, "error-message");
851         verifyOptionalXMLLeaf(errorNode, ERROR_APP_TAG, expErrorAppTag, "error-app-tag");
852
853         Node errorInfoNode = (Node) ERROR_INFO.evaluate(errorNode, XPathConstants.NODE);
854         if (errorInfoVerifier != null) {
855             assertNotNull("Missing \"error-info\" node", errorInfoNode);
856
857             errorInfoVerifier.verifyXML(errorInfoNode);
858         } else {
859             assertNull("Found unexpected \"error-info\" node", errorInfoNode);
860         }
861     }
862
863     void verifyOptionalXMLLeaf(final Node fromNode, final XPathExpression xpath, final String expValue,
864             final String tagName) throws Exception {
865         if (expValue != null) {
866             String actual = (String) xpath.evaluate(fromNode, XPathConstants.STRING);
867             assertEquals(tagName, expValue, actual);
868         } else {
869             assertNull("Found unexpected \"error\" leaf entry for: " + tagName,
870                     xpath.evaluate(fromNode, XPathConstants.NODE));
871         }
872     }
873
874     NodeList getXMLErrorList(final Node fromNode, final int count) throws Exception {
875         NodeList errorList = (NodeList) ERROR_LIST.evaluate(fromNode, XPathConstants.NODESET);
876         assertNotNull("Root errors node is empty", errorList);
877         assertEquals("Root errors node child count", count, errorList.getLength());
878         return errorList;
879     }
880 }