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