2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
3 * Copyright (c) 2021 PANTHEON.tech, s.r.o.
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
9 package org.opendaylight.restconf.server.spi;
11 import static org.junit.jupiter.api.Assertions.assertEquals;
12 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
13 import static org.junit.jupiter.api.Assertions.assertNotNull;
14 import static org.junit.jupiter.api.Assertions.assertThrows;
16 import com.google.common.collect.ImmutableMap;
17 import java.text.ParseException;
18 import org.junit.jupiter.api.Test;
19 import org.opendaylight.restconf.api.ApiPath;
20 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
21 import org.opendaylight.restconf.common.errors.RestconfError;
22 import org.opendaylight.restconf.server.api.DatabindContext;
23 import org.opendaylight.restconf.server.spi.ApiPathNormalizer.Result;
24 import org.opendaylight.yangtools.yang.common.ErrorTag;
25 import org.opendaylight.yangtools.yang.common.ErrorType;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.Revision;
28 import org.opendaylight.yangtools.yang.common.Uint16;
29 import org.opendaylight.yangtools.yang.common.Uint8;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
33 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
34 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
37 * Unit tests for {@link ApiPathNormalizer}.
39 class ApiPathNormalizerTest {
40 private static final QName ACTIONS_INTERFACES =
41 QName.create("https://example.com/ns/example-actions", "2016-07-07", "interfaces");
43 private static final ApiPathNormalizer NORMALIZER = new ApiPathNormalizer(DatabindContext.ofModel(
44 YangParserTestUtils.parseYangResourceDirectory("/restconf/parser/deserializer")));
47 * Test of deserialization <code>String</code> URI with container to
48 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
51 void deserializeContainerTest() {
52 final var result = assertNormalizedPath("deserializer-test:contA").path.getPathArguments();
53 assertEquals(1, result.size());
54 assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
58 * Test of deserialization <code>String</code> URI with container containing leaf to
59 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
62 void deserializeContainerWithLeafTest() {
63 final var result = assertNormalizedPath("deserializer-test:contA/leaf-A").path.getPathArguments();
64 assertEquals(2, result.size());
65 assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
66 assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")), result.get(1));
70 * Test of deserialization <code>String</code> URI with container containing list with leaf list to
71 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
74 void deserializeContainerWithListWithLeafListTest() {
75 final var result = assertNormalizedPath("deserializer-test:contA/list-A=100/leaf-list-AA=instance").path
77 assertEquals(5, result.size());
80 assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")), result.get(0));
82 final var list = QName.create("deserializer:test", "2016-06-06", "list-A");
83 assertEquals(NodeIdentifier.create(list), result.get(1));
84 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint8.valueOf(100)),
87 final var leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-AA");
88 assertEquals(NodeIdentifier.create(leafList), result.get(3));
89 assertEquals(new NodeWithValue<>(leafList, "instance"), result.get(4));
93 * Test of deserialization <code>String</code> URI with container containing list with Action to
94 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
97 void deserializeContainerWithListWithActionTest() {
98 final var result = assertNormalizedPath("example-actions:interfaces/interface=eth0/reset").path
100 assertEquals(4, result.size());
102 assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
104 final var list = QName.create(ACTIONS_INTERFACES, "interface");
105 assertEquals(NodeIdentifier.create(list), result.get(1));
106 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(2));
108 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reset")), result.get(3));
112 * Test of deserialization <code>String</code> URI with container containing choice node with Action to
113 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
116 void deserializeContainerWithChoiceSchemaNodeWithActionTest() {
117 final var result = assertNormalizedPath("example-actions:interfaces/typeA-gigabyte/interface=eth0/reboot").path
119 assertEquals(6, result.size());
122 assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
124 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "interface-type")), result.get(1));
126 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "typeA-gigabyte")), result.get(2));
129 final var list = QName.create(ACTIONS_INTERFACES, "interface");
130 assertEquals(NodeIdentifier.create(list), result.get(3));
131 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "eth0"), result.get(4));
134 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(5));
138 * Test of deserialization <code>String</code> URI with container containing choice node with Action to
139 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
142 void deserializeContainerWithChoiceCaseSchemaNodeWithActionTest() {
143 final var result = assertNormalizedPath("example-actions:interfaces/udp/reboot").path.getPathArguments();
144 assertEquals(4, result.size());
146 assertEquals(NodeIdentifier.create(ACTIONS_INTERFACES), result.get(0));
148 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "protocol")), result.get(1));
150 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "udp")), result.get(2));
152 assertEquals(NodeIdentifier.create(QName.create(ACTIONS_INTERFACES, "reboot")), result.get(3));
156 * Test of deserialization <code>String</code> URI containing list with no keys to
157 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
160 void deserializeListWithNoKeysTest() {
161 final var result = assertNormalizedPath("deserializer-test:list-no-key").path.getPathArguments();
162 assertEquals(2, result.size());
163 final var list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
164 assertEquals(NodeIdentifier.create(list), result.get(0));
165 assertEquals(NodeIdentifier.create(list), result.get(1));
169 * Test of deserialization <code>String</code> URI containing list with one key to
170 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
173 void deserializeListWithOneKeyTest() {
174 final var result = assertNormalizedPath("deserializer-test:list-one-key=value").path.getPathArguments();
175 assertEquals(2, result.size());
176 final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
177 assertEquals(NodeIdentifier.create(list), result.get(0));
178 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "name"), "value"), result.get(1));
182 * Test of deserialization <code>String</code> URI containing list with multiple keys to
183 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
186 void deserializeListWithMultipleKeysTest() {
187 final var list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
188 final var values = ImmutableMap.<QName, Object>of(
189 QName.create(list, "name"), "value",
190 QName.create(list, "number"), Uint8.valueOf(100),
191 QName.create(list, "enabled"), false);
193 final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=value,100,false").path
195 assertEquals(2, result.size());
196 assertEquals(NodeIdentifier.create(list), result.get(0));
197 assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
201 * Test of deserialization <code>String</code> URI containing leaf list to
202 * {@code Iterable<YangInstanceIdentifier.PathArgument>}.
205 void deserializeLeafListTest() {
206 final var result = assertNormalizedPath("deserializer-test:leaf-list-0=true").path.getPathArguments();
207 assertEquals(2, result.size());
209 final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
210 assertEquals(new NodeIdentifier(leafList), result.get(0));
211 assertEquals(new NodeWithValue<>(leafList, true), result.get(1));
215 * Test when empty <code>String</code> is supplied as an input. Test is expected to return empty result.
218 void deserializeEmptyDataTest() {
219 assertEquals(YangInstanceIdentifier.of(), assertNormalizedPath("").path);
223 * Negative test when supplied <code>String</code> data to deserialize is null.
226 void nullDataNegativeNegativeTest() {
227 assertThrows(NullPointerException.class, () -> assertNormalizedPath(null));
231 * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
232 * expected to fail with <code>RestconfDocumentedException</code>.
235 void prepareQnameNotExistingPrefixNegativeTest() {
236 final var error = assertErrorPath("not-existing:contA");
237 assertEquals("Failed to lookup for module with name 'not-existing'.", error.getErrorMessage());
238 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
239 assertEquals(ErrorTag.UNKNOWN_ELEMENT, error.getErrorTag());
243 * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
244 * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
245 * type, error tag and error status code are compared to expected values.
248 public void prepareQnameNotValidContainerNameNegativeTest() {
249 final var error = assertErrorPath("deserializer-test:contA/leafB");
250 assertEquals("Schema for '(deserializer:test?revision=2016-06-06)leafB' not found", error.getErrorMessage());
251 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
252 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
256 * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
257 * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
258 * type, error tag and error status code are compared to expected values.
261 void prepareQnameNotValidListNameNegativeTest() {
262 final var error = assertErrorPath("deserializer-test:list-no-key/disabled=false");
263 assertEquals("Schema for '(deserializer:test?revision=2016-06-06)disabled' not found", error.getErrorMessage());
264 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
265 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
269 * Negative test of getting next identifier when current node is keyed entry. Test is expected to
270 * fail with <code>RestconfDocumentedException</code>.
273 void prepareIdentifierNotKeyedEntryNegativeTest() {
274 final var error = assertErrorPath("deserializer-test:list-one-key");
276 Entry '(deserializer:test?revision=2016-06-06)list-one-key' requires key or value predicate to be \
277 present.""", error.getErrorMessage());
278 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
279 assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.getErrorTag());
283 * Negative test when there is a comma also after the last key. Test is expected to fail with
284 * <code>RestconfDocumentedException</code>. Last comma indicates a fourth key, which is a mismatch with schema.
287 void deserializeKeysEndsWithCommaTooManyNegativeTest() {
288 final var error = assertErrorPath("deserializer-test:list-multiple-keys=value,100,false,");
290 Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 4 supplied""",
291 error.getErrorMessage());
292 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
293 assertEquals(ErrorTag.UNKNOWN_ATTRIBUTE, error.getErrorTag());
297 * Negative test when there is a comma also after the last key. Test is expected to fail with
298 * <code>RestconfDocumentedException</code>. Last comma indicates a third key, whose is a mismatch with schema.
301 void deserializeKeysEndsWithCommaIllegalNegativeTest() {
302 final var error = assertErrorPath("deserializer-test:list-multiple-keys=value,100,");
303 assertEquals("Invalid value '' for (deserializer:test?revision=2016-06-06)enabled", error.getErrorMessage());
304 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
305 assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
306 assertEquals("Invalid value '' for boolean type. Allowed values are 'true' and 'false'", error.getErrorInfo());
310 * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
311 * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
314 void notAllListKeysEncodedPositiveTest() {
315 final var list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
316 final var values = ImmutableMap.<QName, Object>of(
317 QName.create(list, "name"), ":foo",
318 QName.create(list, "number"), Uint8.ONE,
319 QName.create(list, "enabled"), false);
321 final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=%3Afoo,1,false/string-value")
322 .path.getPathArguments();
323 assertEquals(3, result.size());
325 assertEquals(NodeIdentifier.create(list), result.get(0));
326 assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
328 assertEquals(new NodeIdentifier(QName.create("deserializer:test", "2016-06-06", "string-value")),
333 * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
334 * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
335 * status code are compared to expected values.
338 void notAllListKeysEncodedNegativeTest() {
339 final var error = assertErrorPath("deserializer-test:list-multiple-keys=%3Afoo/string-value");
341 Schema for (deserializer:test?revision=2016-06-06)list-multiple-keys requires 3 key values, 1 supplied""",
342 error.getErrorMessage());
343 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
344 assertEquals(ErrorTag.MISSING_ATTRIBUTE, error.getErrorTag());
348 * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
349 * value should be complete also with not percent-encoded parts.
352 void percentEncodedKeyEndsWithNoPercentEncodedChars() {
353 final var URI = "deserializer-test:list-multiple-keys=%3Afoo,1,true";
354 final var result = assertNormalizedPath(URI).path;
356 final var resultListKeys = assertInstanceOf(NodeIdentifierWithPredicates.class, result.getLastPathArgument())
357 .entrySet().iterator();
358 assertEquals(":foo", resultListKeys.next().getValue());
359 assertEquals(Uint8.ONE, resultListKeys.next().getValue());
360 assertEquals(true, resultListKeys.next().getValue());
364 * Positive test when all keys of list can be considered to be empty <code>String</code>.
367 void deserializeAllKeysEmptyTest() {
368 final var list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
369 final var values = ImmutableMap.<QName, Object>of(
370 QName.create(list, "name"), "",
371 QName.create(list, "number"), Uint8.ZERO,
372 QName.create(list, "enabled"), true);
374 final var result = assertNormalizedPath("deserializer-test:list-multiple-keys=,0,true").path.getPathArguments();
375 assertEquals(2, result.size());
376 assertEquals(NodeIdentifier.create(list), result.get(0));
377 assertEquals(NodeIdentifierWithPredicates.of(list, values), result.get(1));
381 * Negative test of deserialization when for leaf list there is no specified instance value.
382 * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
383 * compared to expected values.
386 void leafListMissingKeyNegativeTest() {
387 final var error = assertErrorPath("deserializer-test:leaf-list-0=");
388 assertEquals("Invalid value '' for (deserializer:test?revision=2016-06-06)leaf-list-0",
389 error.getErrorMessage());
390 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
391 assertEquals(ErrorTag.INVALID_VALUE, error.getErrorTag());
395 * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
398 void deserializePartInOtherModuleTest() {
399 final var result = assertNormalizedPath(
400 "deserializer-test-included:augmented-list=100/deserializer-test:augmented-leaf").path.getPathArguments();
401 assertEquals(3, result.size());
404 final var list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
405 assertEquals(NodeIdentifier.create(list), result.get(0));
406 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint16.valueOf(100)),
410 assertEquals(NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "augmented-leaf")),
415 void deserializeListInOtherModuleTest() {
416 final var result = assertNormalizedPath(
417 "deserializer-test-included:augmented-list=100/deserializer-test:augmenting-list=0")
418 .path.getPathArguments();
419 assertEquals(4, result.size());
422 final var list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
423 assertEquals(NodeIdentifier.create(list), result.get(0));
424 assertEquals(NodeIdentifierWithPredicates.of(list, QName.create(list, "list-key"), Uint16.valueOf(100)),
428 final var augList = QName.create("deserializer:test", "2016-06-06", "augmenting-list");
429 assertEquals(NodeIdentifier.create(augList), result.get(2));
430 assertEquals(NodeIdentifierWithPredicates.of(augList, QName.create(augList, "id"), 0), result.get(3));
434 * Deserialization of path that contains list entry with key which value is described by leaflef to identityref.
437 void deserializePathWithIdentityrefKeyValueTest() {
438 assertIdentityrefKeyValue(
439 "deserializer-test-included:refs/list-with-identityref=deserializer-test%3Aderived-identity/foo");
443 * Identityref key value is not encoded correctly - ':' character must be encoded as '%3A'.
446 void deserializePathWithInvalidIdentityrefKeyValueTest() {
447 assertIdentityrefKeyValue(
448 "deserializer-test-included:refs/list-with-identityref=deserializer-test:derived-identity/foo");
451 private static RestconfError assertErrorPath(final String path) {
452 final var apiPath = assertApiPath(path);
453 final var ex = assertThrows(RestconfDocumentedException.class, () -> NORMALIZER.normalizePath(apiPath));
454 final var errors = ex.getErrors();
455 assertEquals(1, errors.size());
456 return errors.get(0);
459 private static Result assertNormalizedPath(final String path) {
460 final var result = NORMALIZER.normalizePath(assertApiPath(path));
461 assertNotNull(result);
465 private static ApiPath assertApiPath(final String path) {
467 return ApiPath.parse(path);
468 } catch (ParseException e) {
469 throw new AssertionError(e);
473 private static void assertIdentityrefKeyValue(final String path) {
474 final var pathArgs = assertNormalizedPath(path).path.getPathArguments();
475 assertEquals(4, pathArgs.size());
477 assertEquals("refs", pathArgs.get(0).getNodeType().getLocalName());
478 assertEquals("list-with-identityref", pathArgs.get(1).getNodeType().getLocalName());
480 final var listEntryArg = assertInstanceOf(NodeIdentifierWithPredicates.class, pathArgs.get(2));
481 assertEquals("list-with-identityref", listEntryArg.getNodeType().getLocalName());
482 final var keys = listEntryArg.keySet();
483 assertEquals(1, keys.size());
484 assertEquals("id", keys.iterator().next().getLocalName());
485 final var keyValue = listEntryArg.values().iterator().next();
486 assertEquals(QName.create("deserializer:test", "derived-identity", Revision.of("2016-06-06")), keyValue);
488 assertEquals("foo", pathArgs.get(3).getNodeType().getLocalName());