Add choice/case tests
[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.text.ParseException;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Map.Entry;
23 import java.util.Set;
24 import org.junit.AfterClass;
25 import org.junit.BeforeClass;
26 import org.junit.Test;
27 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
28 import org.opendaylight.restconf.nb.rfc8040.ApiPath;
29 import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
30 import org.opendaylight.yangtools.yang.common.ErrorTag;
31 import org.opendaylight.yangtools.yang.common.ErrorType;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.Revision;
34 import org.opendaylight.yangtools.yang.common.Uint16;
35 import org.opendaylight.yangtools.yang.common.Uint8;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
42 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
43 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
44
45 /**
46  * Unit tests for {@link YangInstanceIdentifierDeserializer}.
47  */
48 public class YangInstanceIdentifierDeserializerTest {
49     private static final QName ACTIONS_INTERFACES =
50         QName.create("https://example.com/ns/example-actions", "2016-07-07", "interfaces");
51
52     // schema context
53     private static EffectiveModelContext SCHEMA_CONTEXT;
54
55     @BeforeClass
56     public static void beforeClass() throws FileNotFoundException {
57         SCHEMA_CONTEXT =
58                 YangParserTestUtils.parseYangFiles(TestRestconfUtils.loadFiles("/restconf/parser/deserializer"));
59     }
60
61     @AfterClass
62     public static void afterClass() {
63         SCHEMA_CONTEXT = null;
64     }
65
66     /**
67      * Test of deserialization <code>String</code> URI with container to
68      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
69      */
70     @Test
71     public void deserializeContainerTest() {
72         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA");
73         assertEquals(1, result.size());
74         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
75     }
76
77     /**
78      * Test of deserialization <code>String</code> URI with container containing leaf to
79      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
80      */
81     @Test
82     public void deserializeContainerWithLeafTest() {
83         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/leaf-A");
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");
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");
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");
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");
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         assertEquals(2, result.size());
184         final QName list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
185         assertEquals(NodeIdentifier.create(list), result.get(0));
186         assertEquals(NodeIdentifier.create(list), result.get(1));
187     }
188
189     /**
190      * Test of deserialization <code>String</code> URI containing list with one key to
191      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
192      */
193     @Test
194     public void deserializeListWithOneKeyTest() {
195         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
196             "deserializer-test:list-one-key=value");
197         assertEquals(2, result.size());
198         final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
199         assertEquals(NodeIdentifier.create(list), result.get(0));
200         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "value"), result.get(1));
201     }
202
203     /**
204      * Test of deserialization <code>String</code> URI containing list with multiple keys to
205      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
206      */
207     @Test
208     public void deserializeListWithMultipleKeysTest() {
209         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
210         final Map<QName, Object> values = ImmutableMap.of(
211             QName.create(list, "name"), "value",
212             QName.create(list, "number"), Uint8.valueOf(100),
213             QName.create(list, "enabled"), false);
214
215         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
216             "deserializer-test:list-multiple-keys=value,100,false");
217         assertEquals(2, result.size());
218         assertEquals(NodeIdentifier.create(list), result.get(0));
219         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
220     }
221
222     /**
223      * Test of deserialization <code>String</code> URI containing leaf list to
224      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
225      */
226     @Test
227     public void deserializeLeafListTest() {
228         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
229             "deserializer-test:leaf-list-0=true");
230         assertEquals(2, result.size());
231
232         final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
233         assertEquals(new NodeIdentifier(leafList), result.get(0));
234         assertEquals(new NodeWithValue<>(leafList, true), result.get(1));
235     }
236
237     /**
238      * Test when empty <code>String</code> is supplied as an input. Test is expected to return empty result.
239      */
240     @Test
241     public void deserializeEmptyDataTest() {
242         assertEquals(List.of(), YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, ""));
243     }
244
245     /**
246      * Test of deserialization <code>String</code> URI with identifiers separated by multiple slashes to
247      * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
248      */
249     @Test
250     public void deserializeMultipleSlashesTest() throws ParseException {
251         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
252             ApiPath.parseUrl("deserializer-test:contA////list-A=40//list-key"));
253         assertEquals(4, result.size());
254
255         // container
256         assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
257         // list
258         final QName list = QName.create("deserializer:test", "2016-06-06", "list-A");
259         assertEquals(NodeIdentifier.create(list), result.get(1));
260         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint8.valueOf(40)),
261             result.get(2));
262         // leaf
263         assertEquals(new NodeIdentifier(QName.create("deserializer:test", "2016-06-06", "list-key")), result.get(3));
264     }
265
266     /**
267      * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
268      * <code>NullPointerException</code>.
269      */
270     @Test
271     public void deserializeNullSchemaContextNegativeTest() {
272         assertThrows(NullPointerException.class,
273             () -> YangInstanceIdentifierDeserializer.create(null, "deserializer-test:contA"));
274     }
275
276     /**
277      * Negative test when supplied <code>String</code> data to deserialize is null. Test is expected to fail with
278      * <code>NullPointerException</code>.
279      */
280     @Test
281     public void nullDataNegativeNegativeTest() {
282         assertThrows(NullPointerException.class,
283             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, (String) null));
284     }
285
286     /**
287      * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
288      * <code>RestconfDocumentedException</code>.
289      */
290     @Test
291     public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
292         final var ex = assertThrows(RestconfDocumentedException.class,
293             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:cont*leaf-A"));
294         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
295             + "error-message: Invalid path 'deserializer-test:cont*leaf-A' at offset 22, "
296             + "error-info: Expecting [a-zA-Z_.-], not '*']]", ex.getMessage());
297         final var errors = ex.getErrors();
298         assertEquals(1, errors.size());
299         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
300         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
301         assertEquals("Expecting [a-zA-Z_.-], not '*'", errors.get(0).getErrorInfo());
302     }
303
304     /**
305      * Negative test of validating identifier when there is a slash after container without next identifier. Test
306      * is expected to fail with <code>RestconfDocumentedException</code>.
307      */
308     @Test
309     public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
310         final var ex = assertThrows(RestconfDocumentedException.class,
311             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/"));
312         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
313             + "error-message: Invalid path 'deserializer-test:contA/' at offset 24, "
314             + "error-info: Identifier may not be empty]]", ex.getMessage());
315         final var errors = ex.getErrors();
316         assertEquals(1, errors.size());
317         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
318         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
319         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
320     }
321
322     /**
323      * Negative test of validating identifier when there are multiple slashes after container without next identifier.
324      * Test is expected to fail with <code>RestconfDocumentedException</code>.
325      */
326     @Test
327     public void validArgIdentifierContainerEndsWithMultipleSlashesNegativeTest() {
328         final var ex = assertThrows(RestconfDocumentedException.class,
329             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA///"));
330         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
331             + "error-message: Invalid path 'deserializer-test:contA///' at offset 24, "
332             + "error-info: Identifier may not be empty]]", ex.getMessage());
333         final var errors = ex.getErrors();
334         assertEquals(1, errors.size());
335         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
336         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
337         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
338     }
339
340     /**
341      * Negative test of validating identifier when there is a slash after list key values without next identifier. Test
342      * is expected to fail with <code>RestconfDocumentedException</code>.
343      */
344     @Test
345     public void validArgIdentifierListEndsWithSlashLNegativeTest() {
346         final var ex = assertThrows(RestconfDocumentedException.class,
347             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key=value/"));
348         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
349             + "error-message: Invalid path 'deserializer-test:list-one-key=value/' at offset 37, "
350             + "error-info: Identifier may not be empty]]", ex.getMessage());
351         final var errors = ex.getErrors();
352         assertEquals(1, errors.size());
353         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
354         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
355         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
356     }
357
358     /**
359      * Negative test of validating identifier when there are multiple slashes after list key values without next
360      * identifier. Test is expected to fail with <code>RestconfDocumentedException</code>.
361      */
362     @Test
363     public void validArgIdentifierListEndsWithSlashesNegativeTest() {
364         final var ex = assertThrows(RestconfDocumentedException.class,
365             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key=value//"));
366         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
367             + "error-message: Invalid path 'deserializer-test:list-one-key=value//' at offset 37, "
368             + "error-info: Identifier may not be empty]]", ex.getMessage());
369         final var errors = ex.getErrors();
370         assertEquals(1, errors.size());
371         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
372         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
373         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
374     }
375
376     /**
377      * Negative test of creating <code>QName</code> when identifier is empty (example: '/'). Test is expected to fail
378      * with <code>RestconfDocumentedException</code>.
379      */
380     @Test
381     public void prepareQnameEmptyIdentifierNegativeTest() {
382         final var ex = assertThrows(RestconfDocumentedException.class,
383             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "/"));
384         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
385             + "error-message: Invalid path '/' at offset 0, "
386             + "error-info: Identifier may not be empty]]", ex.getMessage());
387         final var errors = ex.getErrors();
388         assertEquals(1, errors.size());
389         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
390         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
391         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
392     }
393
394     /**
395      * Negative test of creating <code>QName</code> when in identifier there is another sign than colon or equals.
396      * Test is expected to fail with <code>RestconfDocumentedException</code>.
397      */
398     @Test
399     public void prepareQnameBuildPathNegativeTest() {
400         final var ex = assertThrows(RestconfDocumentedException.class,
401             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test*contA"));
402         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
403             + "error-message: Invalid path 'deserializer-test*contA' at offset 17, "
404             + "error-info: Expecting [a-zA-Z_.-], not '*']]", ex.getMessage());
405         final var errors = ex.getErrors();
406         assertEquals(1, errors.size());
407         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
408         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
409         assertEquals("Expecting [a-zA-Z_.-], not '*'", errors.get(0).getErrorInfo());
410     }
411
412     /**
413      * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
414      * expected to fail with <code>RestconfDocumentedException</code>.
415      */
416     @Test
417     public void prepareQnameNotExistingPrefixNegativeTest() {
418         final var ex = assertThrows(RestconfDocumentedException.class,
419             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "not-existing:contA"));
420         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: unknown-element, "
421             + "error-message: Failed to lookup for module with name 'not-existing'.]]", ex.getMessage());
422         final var errors = ex.getErrors();
423         assertEquals(1, errors.size());
424         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
425         assertEquals(ErrorTag.UNKNOWN_ELEMENT, errors.get(0).getErrorTag());
426     }
427
428     /**
429      * Negative test of creating <code>QName</code> when after prefix and colon there is not parsable identifier as
430      * local name. Test is expected to fail with <code>RestconfDocumentedException</code>.
431      */
432     @Test
433     public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
434         final var ex = assertThrows(RestconfDocumentedException.class, () ->
435             YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:*not-parsable-identifier"));
436         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
437             + "error-message: Invalid path 'deserializer-test:*not-parsable-identifier' at offset 18, "
438             + "error-info: Expecting [a-zA-Z_], not '*']]", ex.getMessage());
439         final var errors = ex.getErrors();
440         assertEquals(1, errors.size());
441         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
442         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
443         assertEquals("Expecting [a-zA-Z_], not '*'", errors.get(0).getErrorInfo());
444     }
445
446     /**
447      * Negative test of creating <code>QName</code> when data ends after prefix and colon. Test is expected to fail
448      * with <code>StringIndexOutOfBoundsException</code>.
449      */
450     @Test
451     public void prepareQnameErrorParsingNegativeTest() {
452         final var ex = assertThrows(RestconfDocumentedException.class,
453             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:"));
454         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: malformed-message, "
455             + "error-message: Invalid path 'deserializer-test:' at offset 18, "
456             + "error-info: Identifier may not be empty]]", ex.getMessage());
457         final var errors = ex.getErrors();
458         assertEquals(1, errors.size());
459         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
460         assertEquals(ErrorTag.MALFORMED_MESSAGE, errors.get(0).getErrorTag());
461         assertEquals("Identifier may not be empty", errors.get(0).getErrorInfo());
462     }
463
464     /**
465      * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
466      * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
467      * type, error tag and error status code are compared to expected values.
468      */
469     @Test
470     public void prepareQnameNotValidContainerNameNegativeTest() {
471         final var ex = assertThrows(RestconfDocumentedException.class,
472             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:contA/leafB"));
473         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: data-missing, "
474             + "error-message: Schema for '(deserializer:test?revision=2016-06-06)leafB' not found]]", ex.getMessage());
475         final var errors = ex.getErrors();
476         assertEquals(1, errors.size());
477         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
478         assertEquals(ErrorTag.DATA_MISSING, errors.get(0).getErrorTag());
479     }
480
481     /**
482      * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
483      * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
484      * type, error tag and error status code are compared to expected values.
485      */
486     @Test
487     public void prepareQnameNotValidListNameNegativeTest() {
488         final var ex = assertThrows(RestconfDocumentedException.class,
489             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
490                 "deserializer-test:list-no-key/disabled=false"));
491         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
492         assertEquals(ErrorTag.DATA_MISSING, ex.getErrors().get(0).getErrorTag());
493     }
494
495     /**
496      * Negative test of getting next identifier when current node is keyed entry. Test is expected to
497      * fail with <code>RestconfDocumentedException</code>.
498      */
499     @Test
500     public void prepareIdentifierNotKeyedEntryNegativeTest() {
501         final var ex = assertThrows(RestconfDocumentedException.class,
502             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:list-one-key"));
503         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: missing-attribute, "
504             + "error-message: Entry '(deserializer:test?revision=2016-06-06)list-one-key' requires key or value "
505             + "predicate to be present.]]", ex.getMessage());
506         final var errors = ex.getErrors();
507         assertEquals(1, errors.size());
508         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
509         assertEquals(ErrorTag.MISSING_ATTRIBUTE, errors.get(0).getErrorTag());
510     }
511
512     /**
513      * Negative test when there is a comma also after the last key. Test is expected to fail with
514      * <code>RestconfDocumentedException</code>. Last comma indicates a fourth key, which is a mismatch with schema.
515      */
516     @Test
517     public void deserializeKeysEndsWithCommaTooManyNegativeTest() {
518         final var ex = assertThrows(RestconfDocumentedException.class,
519             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
520             "deserializer-test:list-multiple-keys=value,100,false,"));
521         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: unknown-attribute, "
522             + "error-message: Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys "
523             + "requires 3 key values, 4 supplied]]", ex.getMessage());
524         final var errors = ex.getErrors();
525         assertEquals(1, errors.size());
526         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
527         assertEquals(ErrorTag.UNKNOWN_ATTRIBUTE, errors.get(0).getErrorTag());
528     }
529
530     /**
531      * Negative test when there is a comma also after the last key. Test is expected to fail with
532      * <code>RestconfDocumentedException</code>. Last comma indicates a third key, whose is a mismatch with schema.
533      */
534     @Test
535     public void deserializeKeysEndsWithCommaIllegalNegativeTest() {
536         final var ex = assertThrows(RestconfDocumentedException.class,
537             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
538             "deserializer-test:list-multiple-keys=value,100,"));
539         assertEquals("errors: [RestconfError [error-type: protocol, error-tag: invalid-value, "
540             + "error-message: Invalid value '' for (deserializer:test?revision=2016-06-06)enabled, "
541             + "error-info: Invalid value '' for boolean type. Allowed values are 'true' and 'false']]",
542             ex.getMessage());
543         final var errors = ex.getErrors();
544         assertEquals(1, errors.size());
545         assertEquals(ErrorType.PROTOCOL, errors.get(0).getErrorType());
546         assertEquals(ErrorTag.INVALID_VALUE, errors.get(0).getErrorTag());
547         assertEquals("Invalid value '' for boolean type. Allowed values are 'true' and 'false'",
548             errors.get(0).getErrorInfo());
549     }
550
551     /**
552      * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
553      * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
554      */
555     @Test
556     public void notAllListKeysEncodedPositiveTest() {
557         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
558         final Map<QName, Object> values = ImmutableMap.of(
559             QName.create(list, "name"), ":foo",
560             QName.create(list, "number"), Uint8.ONE,
561             QName.create(list, "enabled"), false);
562
563         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
564             "deserializer-test:list-multiple-keys=%3Afoo,1,false/string-value");
565         assertEquals(3, result.size());
566         // list
567         assertEquals(NodeIdentifier.create(list), result.get(0));
568         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
569         // leaf
570         assertEquals(new NodeIdentifier(QName.create("deserializer:test", "2016-06-06", "string-value")),
571             result.get(2));
572     }
573
574     /**
575      * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
576      * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
577      * status code are compared to expected values.
578      */
579     @Test
580     public void notAllListKeysEncodedNegativeTest() {
581         final var ex = assertThrows(RestconfDocumentedException.class,
582             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
583                     "deserializer-test:list-multiple-keys=%3Afoo/string-value"));
584         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
585         assertEquals(ErrorTag.MISSING_ATTRIBUTE, ex.getErrors().get(0).getErrorTag());
586     }
587
588     /**
589      * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
590      * value should be complete also with not percent-encoded parts.
591      */
592     @Test
593     public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
594         final String URI = "deserializer-test:list-multiple-keys=%3Afoo,1,true";
595         final YangInstanceIdentifier result = YangInstanceIdentifier.create(
596                 YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, URI));
597
598         final Iterator<Entry<QName, Object>> resultListKeys =
599                 ((NodeIdentifierWithPredicates)result.getLastPathArgument()).entrySet().iterator();
600
601         assertEquals(":foo", resultListKeys.next().getValue());
602         assertEquals(Uint8.ONE, resultListKeys.next().getValue());
603         assertEquals(true, resultListKeys.next().getValue());
604     }
605
606     /**
607      * Positive test when all keys of list can be considered to be empty <code>String</code>.
608      */
609     @Test
610     public void deserializeAllKeysEmptyTest() {
611         final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
612         final Map<QName, Object> values = ImmutableMap.of(
613             QName.create(list, "name"), "",
614             QName.create(list, "number"), Uint8.ZERO,
615             QName.create(list, "enabled"), true);
616
617         final var result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
618             "deserializer-test:list-multiple-keys=,0,true");
619         assertEquals(2, result.size());
620         assertEquals(NodeIdentifier.create(list), result.get(0));
621         assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
622     }
623
624     /**
625      * Negative test of deserialization when for leaf list there is no specified instance value.
626      * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
627      * compared to expected values.
628      */
629     @Test
630     public void leafListMissingKeyNegativeTest() {
631         final var ex = assertThrows(RestconfDocumentedException.class,
632             () -> YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT, "deserializer-test:leaf-list-0="));
633         assertEquals(ErrorType.PROTOCOL, ex.getErrors().get(0).getErrorType());
634         assertEquals(ErrorTag.INVALID_VALUE, ex.getErrors().get(0).getErrorTag());
635     }
636
637     /**
638      * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
639      */
640     @Test
641     public void deserializePartInOtherModuleTest() {
642         final List<PathArgument> result = YangInstanceIdentifierDeserializer.create(SCHEMA_CONTEXT,
643             "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf");
644
645         assertEquals(4, result.size());
646
647         final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
648         final QName child = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
649
650         // list
651         assertEquals(NodeIdentifier.create(list), result.get(0));
652         assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint16.valueOf(100)),
653             result.get(1));
654
655         // augmented leaf
656         assertEquals(new AugmentationIdentifier(Set.of(child)), result.get(2));
657         assertEquals(NodeIdentifier.create(child), result.get(3));
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);
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 }