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