Reduce exception guard
[netconf.git] / restconf / restconf-nb-rfc8040 / src / test / java / org / opendaylight / restconf / nb / rfc8040 / utils / parser / YangInstanceIdentifierDeserializerTest.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  * Copyright (c) 2021 PANTHEON.tech, s.r.o.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9 package org.opendaylight.restconf.nb.rfc8040.utils.parser;
10
11 import static org.hamcrest.CoreMatchers.instanceOf;
12 import static org.hamcrest.MatcherAssert.assertThat;
13 import static org.junit.Assert.assertEquals;
14 import static org.junit.Assert.assertThrows;
15
16 import com.google.common.collect.ImmutableMap;
17 import java.io.FileNotFoundException;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
22 import java.util.Set;
23 import org.junit.AfterClass;
24 import org.junit.BeforeClass;
25 import org.junit.Test;
26 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
27 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
28 import org.opendaylight.yangtools.yang.common.ErrorTag;
29 import org.opendaylight.yangtools.yang.common.ErrorType;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.common.Revision;
32 import org.opendaylight.yangtools.yang.common.Uint16;
33 import org.opendaylight.yangtools.yang.common.Uint8;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
40 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
41 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
42
43 /**
44  * Unit tests for {@link YangInstanceIdentifierDeserializer}.
45  */
46 public class YangInstanceIdentifierDeserializerTest {
47     private static final QName ACTIONS_INTERFACES =
48         QName.create("https://example.com/ns/example-actions", "2016-07-07", "interfaces");
49
50     // schema context
51     private static EffectiveModelContext SCHEMA_CONTEXT;
52
53     @BeforeClass
54     public static void beforeClass() throws FileNotFoundException {
55         SCHEMA_CONTEXT =
56                 YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/restconf/parser/deserializer"));
57     }
58
59     @AfterClass
60     public static void afterClass() {
61         SCHEMA_CONTEXT = null;
62     }
63
64     /**
65      * Test of deserialization <code>String</code> URI with container to
66      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
67      */
68     @Test
69     public void deserializeContainerTest() {
70         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA")
71             .path.getPathArguments();
72         assertEquals(1, result.size());
73         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
74     }
75
76     /**
77      * Test of deserialization <code>String</code> URI with container containing leaf to
78      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
79      */
80     @Test
81     public void deserializeContainerWithLeafTest() {
82         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/leaf-A")
83             .path.getPathArguments();
84         assertEquals(2, result.size());
85         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
86         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")), result.get(1));
87     }
88
89     /**
90      * Test of deserialization <code>String</code> URI with container containing list with leaf list to
91      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
92      */
93     @Test
94     public void deserializeContainerWithListWithLeafListTest() {
95         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
96             "deserializer-test:contA/list-A=100/leaf-list-AA=instance").path.getPathArguments();
97         assertEquals(5, result.size());
98
99         // container
100         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
101         // list
102         final QName list = QName.create("deserializer:test", "2016-06-06", "list-A");
103         assertEquals(NodeIdentifier.create(list), result.get(1));
104         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint8.valueOf(100)),
105             result.get(2));
106         // leaf list
107         final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-AA");
108         assertEquals(NodeIdentifier.create(leafList), result.get(3));
109         assertEquals(new NodeWithValue<>(leafList, "instance"), result.get(4));
110     }
111
112     /**
113      * Test of deserialization <code>String</code> URI with container containing list with Action to
114      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
115      */
116     @Test
117     public void deserializeContainerWithListWithActionTest() {
118         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
119             "example-actions:interfaces/interface=eth0/reset").path.getPathArguments();
120         assertEquals(4, result.size());
121         // container
122         assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
123         // list
124         final QName list = QName.create(ACTIONS_INTERFACES, "interface");
125         assertEquals(NodeIdentifier.create(list), result.get(1));
126         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(2));
127         // action
128         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reset")), result.get(3));
129     }
130
131     /**
132      * Test of deserialization <code>String</code> URI with container containing choice node with Action to
133      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
134      */
135     @Test
136     public void deserializeContainerWithChoiceSchemaNodeWithActionTest() {
137         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
138             "example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot").path.getPathArguments();
139         assertEquals(6, result.size());
140
141         // container
142         assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
143         // choice
144         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "interface-type")), result.get(1));
145         // container
146         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte")), result.get(2));
147
148         // list
149         final QName list = QName.create(ACTIONS_INTERFACES, "interface");
150         assertEquals(NodeIdentifier.create(list), result.get(3));
151         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(4));
152
153         // action QName
154         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(5));
155     }
156
157     /**
158      * Test of deserialization <code>String</code> URI with container containing choice node with Action to
159      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
160      */
161     @Test
162     public void deserializeContainerWithChoiceCaseSchemaNodeWithActionTest() {
163         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
164             "example-actions:interfaces/udp/reboot").path.getPathArguments();
165         assertEquals(4, result.size());
166         // container
167         assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
168         // choice
169         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "protocol")), result.get(1));
170         // choice container
171         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "udp")), result.get(2));
172         // action QName
173         assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(3));
174     }
175
176     /**
177      * Test of deserialization <code>String</code> URI containing list with no keys to
178      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
179      */
180     @Test
181     public void deserializeListWithNoKeysTest() {
182         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-no-key")
183             .path.getPathArguments();
184         assertEquals(2, result.size());
185         final QName list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
186         assertEquals(NodeIdentifier.create(list), result.get(0));
187         assertEquals(NodeIdentifier.create(list), result.get(1));
188     }
189
190     /**
191      * Test of deserialization <code>String</code> URI containing list with one key to
192      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
193      */
194     @Test
195     public void deserializeListWithOneKeyTest() {
196         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
197             "deserializer-test:list-one-key=value").path.getPathArguments();
198         assertEquals(2, result.size());
199         final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
200         assertEquals(NodeIdentifier.create(list), result.get(0));
201         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "value"), result.get(1));
202     }
203
204     /**
205      * Test of deserialization <code>String</code> URI containing list with multiple keys to
206      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
207      */
208     @Test
209     public void deserializeListWithMultipleKeysTest() {
210         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
211         final Map<QName, Object> values = ImmutableMap.of(
212             QName.create(list, "name"), "value",
213             QName.create(list, "number"), Uint8.valueOf(100),
214             QName.create(list, "enabled"), false);
215
216         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
217             "deserializer-test:list-multiple-keys=value,100,false").path.getPathArguments();
218         assertEquals(2, result.size());
219         assertEquals(NodeIdentifier.create(list), result.get(0));
220         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
221     }
222
223     /**
224      * Test of deserialization <code>String</code> URI containing leaf list to
225      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
226      */
227     @Test
228     public void deserializeLeafListTest() {
229         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
230             "deserializer-test:leaf-list-0=true").path.getPathArguments();
231         assertEquals(2, result.size());
232
233         final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
234         assertEquals(new NodeIdentifier(leafList), result.get(0));
235         assertEquals(new NodeWithValue<>(leafList, true), result.get(1));
236     }
237
238     /**
239      * Test when empty <code>String</code> is supplied as an input. Test is expected to return empty result.
240      */
241     @Test
242     public void deserializeEmptyDataTest() {
243         assertEquals(List.of(), YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "").path.getPathArguments());
244     }
245
246     /**
247      * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
248      * <code>NullPointerException</code>.
249      */
250     @Test
251     public void deserializeNullSchemaContextNegativeTest() {
252         assertThrows(NullPointerException.class,
253             () -> YangInstanceIdentifierDeserializer.create(null, "deserializer-test:contA"));
254     }
255
256     /**
257      * Negative test when supplied <code>String</code> data to deserialize is null. Test is expected to fail with
258      * <code>NullPointerException</code>.
259      */
260     @Test
261     public void nullDataNegativeNegativeTest() {
262         assertThrows(NullPointerException.class,
263             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, (String) null));
264     }
265
266     /**
267      * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
268      * <code>RestconfDocumentedException</code>.
269      */
270     @Test
271     public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
272         final var ex = assertThrows(RestconfDocumentedException.class,
273             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:cont*leaf-A"));
274         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
275             + "error-message: Invalid path 'deserializer-test:cont*leaf-A' at offset 22, "
276             + "error-info: Expecting [a-zA-Z_.-], not '*']]", ex.getMessage());
277         final var errors = ex.getErrors();
278         assertEquals(1, errors.size());
279         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
280         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
281         assertEquals("Expecting [a-zA-Z_.-], not '*'", errors.get(0).getErrorInfo());
282     }
283
284     /**
285      * Negative test of validating identifier when there is a slash after container without next identifier. Test
286      * is expected to fail with <code>RestconfDocumentedException</code>.
287      */
288     @Test
289     public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
290         final var ex = assertThrows(RestconfDocumentedException.class,
291             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/"));
292         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
293             + "error-message: Invalid path 'deserializer-test:contA/' at offset 24, "
294             + "error-info: Identifier may not be empty]]", ex.getMessage());
295         final var errors = ex.getErrors();
296         assertEquals(1, errors.size());
297         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
298         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
299         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
300     }
301
302     /**
303      * Negative test of validating identifier when there are multiple slashes after container without next identifier.
304      * Test is expected to fail with <code>RestconfDocumentedException</code>.
305      */
306     @Test
307     public void validArgIdentifierContainerEndsWithMultipleSlashesNegativeTest() {
308         final var ex = assertThrows(RestconfDocumentedException.class,
309             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA///"));
310         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
311             + "error-message: Invalid path 'deserializer-test:contA///' at offset 24, "
312             + "error-info: Identifier may not be empty]]", ex.getMessage());
313         final var errors = ex.getErrors();
314         assertEquals(1, errors.size());
315         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
316         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
317         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
318     }
319
320     /**
321      * Negative test of validating identifier when there is a slash after list key values without next identifier. Test
322      * is expected to fail with <code>RestconfDocumentedException</code>.
323      */
324     @Test
325     public void validArgIdentifierListEndsWithSlashLNegativeTest() {
326         final var ex = assertThrows(RestconfDocumentedException.class,
327             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key=value/"));
328         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
329             + "error-message: Invalid path 'deserializer-test:list-one-key=value/' at offset 37, "
330             + "error-info: Identifier may not be empty]]", ex.getMessage());
331         final var errors = ex.getErrors();
332         assertEquals(1, errors.size());
333         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
334         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
335         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
336     }
337
338     /**
339      * Negative test of validating identifier when there are multiple slashes after list key values without next
340      * identifier. Test is expected to fail with <code>RestconfDocumentedException</code>.
341      */
342     @Test
343     public void validArgIdentifierListEndsWithSlashesNegativeTest() {
344         final var ex = assertThrows(RestconfDocumentedException.class,
345             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key=value//"));
346         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
347             + "error-message: Invalid path 'deserializer-test:list-one-key=value//' at offset 37, "
348             + "error-info: Identifier may not be empty]]", ex.getMessage());
349         final var errors = ex.getErrors();
350         assertEquals(1, errors.size());
351         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
352         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
353         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
354     }
355
356     /**
357      * Negative test of creating <code>QName</code> when identifier is empty (example: '/'). Test is expected to fail
358      * with <code>RestconfDocumentedException</code>.
359      */
360     @Test
361     public void prepareQnameEmptyIdentifierNegativeTest() {
362         final var ex = assertThrows(RestconfDocumentedException.class,
363             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "/"));
364         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
365             + "error-message: Invalid path '/' at offset 0, "
366             + "error-info: Identifier may not be empty]]", ex.getMessage());
367         final var errors = ex.getErrors();
368         assertEquals(1, errors.size());
369         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
370         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
371         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
372     }
373
374     /**
375      * Negative test of creating <code>QName</code> when in identifier there is another sign than colon or equals.
376      * Test is expected to fail with <code>RestconfDocumentedException</code>.
377      */
378     @Test
379     public void prepareQnameBuildPathNegativeTest() {
380         final var ex = assertThrows(RestconfDocumentedException.class,
381             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test*contA"));
382         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
383             + "error-message: Invalid path 'deserializer-test*contA' at offset 17, "
384             + "error-info: Expecting [a-zA-Z_.-], not '*']]", ex.getMessage());
385         final var errors = ex.getErrors();
386         assertEquals(1, errors.size());
387         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
388         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
389         assertEquals("Expecting [a-zA-Z_.-], not '*'", errors.get(0).getErrorInfo());
390     }
391
392     /**
393      * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
394      * expected to fail with <code>RestconfDocumentedException</code>.
395      */
396     @Test
397     public void prepareQnameNotExistingPrefixNegativeTest() {
398         final var ex = assertThrows(RestconfDocumentedException.class,
399             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "not-existing:contA"));
400         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: unknown-element, "
401             + "error-message: Failed to lookup for module with name 'not-existing'.]]", ex.getMessage());
402         final var errors = ex.getErrors();
403         assertEquals(1, errors.size());
404         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
405         assertEquals(ErrorTag.UNKNOWN_ELEMENT, errors.get(0).getErrorTag());
406     }
407
408     /**
409      * Negative test of creating <code>QName</code> when after prefix and colon there is not parsable identifier as
410      * local name. Test is expected to fail with <code>RestconfDocumentedException</code>.
411      */
412     @Test
413     public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
414         final var ex = assertThrows(RestconfDocumentedException.class, () ->
415             YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:*not-parsable-identifier"));
416         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
417             + "error-message: Invalid path 'deserializer-test:*not-parsable-identifier' at offset 18, "
418             + "error-info: Expecting [a-zA-Z_], not '*']]", ex.getMessage());
419         final var errors = ex.getErrors();
420         assertEquals(1, errors.size());
421         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
422         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
423         assertEquals("Expecting [a-zA-Z_], not '*'", errors.get(0).getErrorInfo());
424     }
425
426     /**
427      * Negative test of creating <code>QName</code> when data ends after prefix and colon. Test is expected to fail
428      * with <code>StringIndexOutOfBoundsException</code>.
429      */
430     @Test
431     public void prepareQnameErrorParsingNegativeTest() {
432         final var ex = assertThrows(RestconfDocumentedException.class,
433             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:"));
434         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
435             + "error-message: Invalid path 'deserializer-test:' at offset 18, "
436             + "error-info: Identifier may not be empty]]", ex.getMessage());
437         final var errors = ex.getErrors();
438         assertEquals(1, errors.size());
439         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
440         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
441         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
442     }
443
444     /**
445      * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
446      * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
447      * type, error tag and error status code are compared to expected values.
448      */
449     @Test
450     public void prepareQnameNotValidContainerNameNegativeTest() {
451         final var ex = assertThrows(RestconfDocumentedException.class,
452             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/leafB"));
453         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: data-missing, "
454             + "error-message: Schema for '(deserializer:test?revision=2016-06-06)leafB' not found]]", ex.getMessage());
455         final var errors = ex.getErrors();
456         assertEquals(1, errors.size());
457         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
458         assertEquals(ErrorTag.DATA_MISSING, errors.get(0).getErrorTag());
459     }
460
461     /**
462      * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
463      * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
464      * type, error tag and error status code are compared to expected values.
465      */
466     @Test
467     public void prepareQnameNotValidListNameNegativeTest() {
468         final var ex = assertThrows(RestconfDocumentedException.class,
469             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
470                 "deserializer-test:list-no-key/disabled=false"));
471         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
472         assertEquals(ErrorTag.DATA_MISSING, ex.getErrors().get(0).getErrorTag());
473     }
474
475     /**
476      * Negative test of getting next identifier when current node is keyed entry. Test is expected to
477      * fail with <code>RestconfDocumentedException</code>.
478      */
479     @Test
480     public void prepareIdentifierNotKeyedEntryNegativeTest() {
481         final var ex = assertThrows(RestconfDocumentedException.class,
482             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key"));
483         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: missing-attribute, "
484             + "error-message: Entry '(deserializer:test?revision=2016-06-06)list-one-key' requires key or value "
485             + "predicate to be present.]]", ex.getMessage());
486         final var errors = ex.getErrors();
487         assertEquals(1, errors.size());
488         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
489         assertEquals(ErrorTag.MISSING_ATTRIBUTE, errors.get(0).getErrorTag());
490     }
491
492     /**
493      * Negative test when there is a comma also after the last key. Test is expected to fail with
494      * <code>RestconfDocumentedException</code>. Last comma indicates a fourth key, which is a mismatch with schema.
495      */
496     @Test
497     public void deserializeKeysEndsWithCommaTooManyNegativeTest() {
498         final var ex = assertThrows(RestconfDocumentedException.class,
499             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
500             "deserializer-test:list-multiple-keys=value,100,false,"));
501         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: unknown-attribute, "
502             + "error-message: Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys "
503             + "requires 3 key values, 4 supplied]]", ex.getMessage());
504         final var errors = ex.getErrors();
505         assertEquals(1, errors.size());
506         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
507         assertEquals(ErrorTag.UNKNOWN_ATTRIBUTE, errors.get(0).getErrorTag());
508     }
509
510     /**
511      * Negative test when there is a comma also after the last key. Test is expected to fail with
512      * <code>RestconfDocumentedException</code>. Last comma indicates a third key, whose is a mismatch with schema.
513      */
514     @Test
515     public void deserializeKeysEndsWithCommaIllegalNegativeTest() {
516         final var ex = assertThrows(RestconfDocumentedException.class,
517             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
518             "deserializer-test:list-multiple-keys=value,100,"));
519         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: invalid-value, "
520             + "error-message: Invalid value '' for (deserializer:test?revision=2016-06-06)enabled, "
521             + "error-info: Invalid value '' for boolean type. Allowed values are 'true' and 'false']]",
522             ex.getMessage());
523         final var errors = ex.getErrors();
524         assertEquals(1, errors.size());
525         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
526         assertEquals(ErrorTag.INVALID_VALUE, errors.get(0).getErrorTag());
527         assertEquals("Invalid value '' for boolean type. Allowed values are 'true' and 'false'",
528             errors.get(0).getErrorInfo());
529     }
530
531     /**
532      * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
533      * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
534      */
535     @Test
536     public void notAllListKeysEncodedPositiveTest() {
537         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
538         final Map<QName, Object> values = ImmutableMap.of(
539             QName.create(list, "name"), ":foo",
540             QName.create(list, "number"), Uint8.ONE,
541             QName.create(list, "enabled"), false);
542
543         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
544             "deserializer-test:list-multiple-keys=%3Afoo,1,false/string-value").path.getPathArguments();
545         assertEquals(3, result.size());
546         // list
547         assertEquals(NodeIdentifier.create(list), result.get(0));
548         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
549         // leaf
550         assertEquals(new NodeIdentifier(QName.create("deserializer:test", "2016-06-06", "string-value")),
551             result.get(2));
552     }
553
554     /**
555      * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
556      * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
557      * status code are compared to expected values.
558      */
559     @Test
560     public void notAllListKeysEncodedNegativeTest() {
561         final var ex = assertThrows(RestconfDocumentedException.class,
562             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
563                     "deserializer-test:list-multiple-keys=%3Afoo/string-value"));
564         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
565         assertEquals(ErrorTag.MISSING_ATTRIBUTE, ex.getErrors().get(0).getErrorTag());
566     }
567
568     /**
569      * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
570      * value should be complete also with not percent-encoded parts.
571      */
572     @Test
573     public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
574         final String URI = "deserializer-test:list-multiple-keys=%3Afoo,1,true";
575         final YangInstanceIdentifier result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, URI).path;
576
577         final Iterator<Entry<QName, Object>> resultListKeys =
578                 ((NodeIdentifierWithPredicates)result.getLastPathArgument()).entrySet().iterator();
579
580         assertEquals(":foo", resultListKeys.next().getValue());
581         assertEquals(Uint8.ONE, resultListKeys.next().getValue());
582         assertEquals(true, resultListKeys.next().getValue());
583     }
584
585     /**
586      * Positive test when all keys of list can be considered to be empty <code>String</code>.
587      */
588     @Test
589     public void deserializeAllKeysEmptyTest() {
590         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
591         final Map<QName, Object> values = ImmutableMap.of(
592             QName.create(list, "name"), "",
593             QName.create(list, "number"), Uint8.ZERO,
594             QName.create(list, "enabled"), true);
595
596         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
597             "deserializer-test:list-multiple-keys=,0,true").path.getPathArguments();
598         assertEquals(2, result.size());
599         assertEquals(NodeIdentifier.create(list), result.get(0));
600         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
601     }
602
603     /**
604      * Negative test of deserialization when for leaf list there is no specified instance value.
605      * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
606      * compared to expected values.
607      */
608     @Test
609     public void leafListMissingKeyNegativeTest() {
610         final var ex = assertThrows(RestconfDocumentedException.class,
611             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:leaf-list-0="));
612         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
613         assertEquals(ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
614     }
615
616     /**
617      * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
618      */
619     @Test
620     public void deserializePartInOtherModuleTest() {
621         final List<PathArgument> result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
622             "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf")
623             .path.getPathArguments();
624         assertEquals(4, result.size());
625
626         // list
627         final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
628         assertEquals(NodeIdentifier.create(list), result.get(0));
629         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint16.valueOf(100)),
630             result.get(1));
631
632         // augmented leaf
633         final QName augLeaf = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
634         final QName augList = QName.create("deserializer:test", "2016-06-06", "augmenting-list");
635         assertEquals(new AugmentationIdentifier(Set.of(augLeaf, augList)), result.get(2));
636         assertEquals(NodeIdentifier.create(augLeaf), result.get(3));
637     }
638
639     @Test
640     public void deserializeListInOtherModuleTest() {
641         final List<PathArgument> result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
642             "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0")
643             .path.getPathArguments();
644         assertEquals(5, result.size());
645
646         // list
647         final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
648         assertEquals(NodeIdentifier.create(list), result.get(0));
649         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint16.valueOf(100)),
650             result.get(1));
651
652         // augmented list
653         final QName augLeaf = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
654         final QName augList = QName.create("deserializer:test", "2016-06-06", "augmenting-list");
655         assertEquals(new AugmentationIdentifier(Set.of(augLeaf, augList)), result.get(2));
656         assertEquals(NodeIdentifier.create(augList), result.get(3));
657         assertEquals(NodeIdentifierWithPredicates.of(augList, QName.create(augList, "id"), 0), result.get(4));
658     }
659
660     /**
661      * Deserialization of path that contains list entry with key which value is described by leaflef to identityref.
662      */
663     @Test
664     public void deserializePathWithIdentityrefKeyValueTest() {
665         assertIdentityrefKeyValue(
666             "deserializer-test-included:refs/list-with-identityref=deserializer-test%3Aderived-identity/foo");
667     }
668
669     /**
670      * Identityref key value is not encoded correctly - ':' character must be encoded as '%3A'.
671      */
672     @Test
673     public void deserializePathWithInvalidIdentityrefKeyValueTest() {
674         assertIdentityrefKeyValue(
675             "deserializer-test-included:refs/list-with-identityref=deserializer-test:derived-identity/foo");
676     }
677
678     private static void assertIdentityrefKeyValue(final String path) {
679         final var pathArgs = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, path).path.getPathArguments();
680         assertEquals(4, pathArgs.size());
681
682         assertEquals("refs", pathArgs.get(0).getNodeType().getLocalName());
683         assertEquals("list-with-identityref", pathArgs.get(1).getNodeType().getLocalName());
684
685         final PathArgument listEntryArg = pathArgs.get(2);
686         assertThat(listEntryArg, instanceOf(NodeIdentifierWithPredicates.class));
687         assertEquals("list-with-identityref", listEntryArg.getNodeType().getLocalName());
688         final Set<QName> keys = ((NodeIdentifierWithPredicates) listEntryArg).keySet();
689         assertEquals(1, keys.size());
690         assertEquals("id", keys.iterator().next().getLocalName());
691         final Object keyValue = ((NodeIdentifierWithPredicates) listEntryArg).values().iterator().next();
692         assertEquals(QName.create("deserializer:test", "derived-identity", Revision.of("2016-06-06")), keyValue);
693
694         assertEquals("foo", pathArgs.get(3).getNodeType().getLocalName());
695     }
696 }