Tests for YangInstanceIdentifier key value serialization 09/104909/3
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 6 Jan 2023 14:24:24 +0000 (15:24 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 15 Mar 2023 22:06:57 +0000 (23:06 +0100)
There are a number of corner cases which we do not handle. This patch
adds @Ignored tests which hightlight basic serialization problems here.

JIRA: YANGTOOLS-1473
Change-Id: I6226cc8ebe48acc03a62309efec2ab205549e0fb
Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 6fca49a9a8daff89d5304c15d3e5c734dfa3da11)

codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1473Test.java [new file with mode: 0644]
codec/yang-data-codec-gson/src/test/resources/yt1473/bar.yang [new file with mode: 0644]
codec/yang-data-codec-gson/src/test/resources/yt1473/foo.yang [new file with mode: 0644]
codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1473Test.java [new file with mode: 0644]
codec/yang-data-codec-xml/src/test/resources/yt1473/bar.yang [new file with mode: 0644]
codec/yang-data-codec-xml/src/test/resources/yt1473/foo.yang [new file with mode: 0644]

diff --git a/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1473Test.java b/codec/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/YT1473Test.java
new file mode 100644 (file)
index 0000000..7434a32
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import com.google.gson.stream.JsonWriter;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+@ExtendWith(MockitoExtension.class)
+public class YT1473Test {
+
+    private static final String FOO_NS = "foons"; // namespace for prefix 'foo'
+    private static final QName FOO_FOO = QName.create(FOO_NS, "foo"); // list with key 'str'
+    private static final QName FOO_BAR = QName.create(FOO_NS, "bar"); // list with key 'qname'
+    private static final QName FOO_BAZ = QName.create(FOO_NS, "baz"); // list with key 'id'
+    private static final QName FOO_ONE = QName.create(FOO_NS, "one"); // identity
+    private static final QName FOO_STR = QName.create(FOO_NS, "str"); // key of type 'string'
+    private static final QName FOO_QNAME = QName.create(FOO_NS, "qname"); // key of type 'one' based
+    private static final QName FOO_ID = QName.create(FOO_NS, "id"); // key of type 'instance-identifier'
+
+    private static final String BAR_NS = "barns"; // namespace for prefix 'bar'
+    private static final QName BAR_FOO = QName.create(BAR_NS, "foo"); // leaf of type 'foo:one' based
+    private static final QName BAR_BAR = QName.create(BAR_NS, "bar"); // leaf of type 'instance-identifier'
+    private static final QName BAR_TWO = QName.create(BAR_NS, "two"); // identity inheriting 'foo:one'
+
+    private static JSONCodec<YangInstanceIdentifier> CODEC;
+
+    @Mock
+    private JsonWriter writer;
+    @Captor
+    private ArgumentCaptor<String> captor;
+
+    @BeforeAll
+    public static void beforeAll() {
+        final var modelContext = YangParserTestUtils.parseYangResourceDirectory("/yt1473");
+        final var baz = modelContext.getDataChildByName(FOO_BAZ);
+        assertTrue(baz instanceof ListSchemaNode);
+        final var id = ((ListSchemaNode) baz).getDataChildByName(FOO_ID);
+        assertTrue(id instanceof LeafSchemaNode);
+        final var type = ((LeafSchemaNode) id).getType();
+        assertTrue(type instanceof InstanceIdentifierTypeDefinition);
+        CODEC = JSONCodecFactorySupplier.RFC7951.getShared(modelContext)
+                .instanceIdentifierCodec((InstanceIdentifierTypeDefinition) type);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        CODEC = null;
+    }
+
+    @Test
+    public void testSerializeSimple() throws Exception {
+        // No escaping needed, use single quotes
+        assertEquals("/foo:foo[str='str\"']", write(buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str\"")));
+    }
+
+    @Test
+    @Disabled("YT-1473: string escaping needs to work")
+    public void testSerializeEscaped() throws Exception {
+        // Escaping is needed, use double quotes and escape
+        assertEquals("/foo:foo[str=\"str'\\\"\"]", write(buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str'\"")));
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityRefSame() throws Exception {
+        // TODO: an improvement is to use just 'one' as the namespace is the same as the leaf (see RFC7951 section 6.8)
+        assertEquals("/foo:bar[foo:qname='foo:one']", write(buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE)));
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityRefOther() throws Exception {
+        // No escaping is needed, use double quotes and escape
+        assertEquals("/foo:bar[qname='bar:two']", write(buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
+    }
+
+    @Test
+    @Disabled("YT-1473: Instance-identifier values need to be recognized and properly encoded and escaped")
+    public void testSerializeInstanceIdentifierRef() throws Exception {
+        assertEquals("/foo:baz[id=\"/foo:bar[qname='bar:two']\"]", write(
+                buildYangInstanceIdentifier(FOO_BAZ, FOO_ID, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)))
+        );
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityValue() throws Exception {
+        assertEquals("/bar:foo[.='foo:one']", write(buildYangInstanceIdentifier(BAR_FOO, FOO_ONE)));
+    }
+
+    @Test
+    @Disabled("YT-1473: Instance-identifier values need to be recognized and properly encoded and escaped")
+    public void testSerializeInstanceIdentifierValue() throws Exception {
+        assertEquals("/bar:bar[.=\"/foo:bar/bar[qname='bar:two'\"]']",
+                write(buildYangInstanceIdentifier(BAR_BAR, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO))));
+    }
+
+    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName node, final QName key,
+            final Object value) {
+        return YangInstanceIdentifier.create(
+                new NodeIdentifier(node), NodeIdentifierWithPredicates.of(node, key, value));
+    }
+
+    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName nodeQName, final Object value) {
+        return YangInstanceIdentifier.create(new NodeWithValue<>(nodeQName, value));
+    }
+
+    private String write(final YangInstanceIdentifier yangInstanceIdentifier) throws Exception {
+        doReturn(writer).when(writer).value(anyString());
+        CODEC.writeValue(writer, yangInstanceIdentifier);
+        verify(writer).value(captor.capture());
+        return captor.getValue();
+    }
+}
diff --git a/codec/yang-data-codec-gson/src/test/resources/yt1473/bar.yang b/codec/yang-data-codec-gson/src/test/resources/yt1473/bar.yang
new file mode 100644 (file)
index 0000000..dceeaca
--- /dev/null
@@ -0,0 +1,20 @@
+module bar {
+  namespace barns;
+  prefix bar;
+
+  import foo { prefix foo; }
+
+  identity two {
+    base foo:one;
+  }
+
+  leaf foo {
+    type identityref {
+      base foo:one;
+    }
+  }
+
+  leaf bar {
+    type instance-identifier;
+  }
+}
diff --git a/codec/yang-data-codec-gson/src/test/resources/yt1473/foo.yang b/codec/yang-data-codec-gson/src/test/resources/yt1473/foo.yang
new file mode 100644 (file)
index 0000000..9690484
--- /dev/null
@@ -0,0 +1,29 @@
+module foo {
+  namespace foons;
+  prefix foo;
+
+  identity one;
+
+  list foo {
+    key str;
+    leaf str {
+      type string;
+    }
+  }
+
+  list bar {
+    key qname;
+    leaf qname {
+      type identityref {
+        base one;
+      }
+    }
+  }
+
+  list baz {
+    key id;
+    leaf id {
+      type instance-identifier;
+    }
+  }
+}
diff --git a/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1473Test.java b/codec/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/YT1473Test.java
new file mode 100644 (file)
index 0000000..d68424b
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.xml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.verify;
+
+import javax.xml.stream.XMLStreamWriter;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+@ExtendWith(MockitoExtension.class)
+public class YT1473Test {
+    public static final String FOO_NS = "foons"; // namespace for prefix 'foo'
+    public static final QName FOO_FOO = QName.create(FOO_NS, "foo"); // list with key 'str'
+    public static final QName FOO_BAR = QName.create(FOO_NS, "bar"); // list with key 'qname'
+    public static final QName FOO_BAZ = QName.create(FOO_NS, "baz"); // list with key 'id'
+    public static final QName FOO_ONE = QName.create(FOO_NS, "one"); // identity
+    public static final QName FOO_STR = QName.create(FOO_NS, "str"); // key of type 'string'
+    public static final QName FOO_QNAME = QName.create(FOO_NS, "qname"); // key of type 'one' based
+    public static final QName FOO_ID = QName.create(FOO_NS, "id"); // key of type 'instance-identifier'
+
+    public static final String BAR_NS = "barns"; // namespace for prefix 'bar'
+    public static final QName BAR_FOO = QName.create(BAR_NS, "foo"); // leaf of type 'foo:one' based
+    public static final QName BAR_BAR = QName.create(BAR_NS, "bar"); // leaf of type 'instance-identifier'
+    public static final QName BAR_TWO = QName.create(BAR_NS, "two"); // identity inheriting 'foo:one'
+
+    public static XmlCodec<YangInstanceIdentifier> CODEC;
+
+    @Mock
+    public XMLStreamWriter writer;
+    @Captor
+    public ArgumentCaptor<String> captor;
+
+    @BeforeAll
+    public static void beforeAll() {
+        final var modelContext = YangParserTestUtils.parseYangResourceDirectory("/yt1473");
+        final var baz = modelContext.getDataChildByName(FOO_BAZ);
+        assertTrue(baz instanceof ListSchemaNode);
+        final var id = ((ListSchemaNode) baz).getDataChildByName(FOO_ID);
+        assertTrue(id instanceof LeafSchemaNode);
+        final var type = ((LeafSchemaNode) id).getType();
+        assertTrue(type instanceof InstanceIdentifierTypeDefinition);
+        CODEC = (XmlStringInstanceIdentifierCodec) XmlCodecFactory.create(modelContext)
+                .instanceIdentifierCodec((InstanceIdentifierTypeDefinition) type);
+    }
+
+    @AfterAll
+    public static void afterAll() {
+        CODEC = null;
+    }
+
+    @Test
+    public void testSerializeSimple() throws Exception {
+        // No escaping needed, use single quotes
+        assertEquals("/foo:foo[foo:str='str\"']", write(buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str\"")));
+    }
+
+    @Test
+    @Disabled("YT-1473: string escaping needs to work")
+    public void testSerializeEscaped() throws Exception {
+        // Escaping is needed, use double quotes and escape
+        assertEquals("/foo:foo[foo:str=\"str'\\\"\"]", write(buildYangInstanceIdentifier(FOO_FOO, FOO_STR, "str'\"")));
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityRefSame() throws Exception {
+        // TODO: an improvement is to use just 'one' as the namespace is the same as the leaf (see RFC7951 section 6.8)
+        assertEquals("/foo:bar[qname='one']", write(buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE)));
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityRefOther() throws Exception {
+        // No escaping is needed, use double quotes and escape
+        assertEquals("/foo:bar[qname='bar:two']", write(buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
+    }
+
+    @Test
+    @Disabled("YT-1473: Instance-identifier values need to be recognized and properly encoded and escaped")
+    public void testSerializeInstanceIdentifierRef() throws Exception {
+        assertEquals("/foo:baz[id=\"/foo:bar[qname='bar:two']\"]", write(
+                buildYangInstanceIdentifier(FOO_BAZ, FOO_ID, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)))
+        );
+    }
+
+    @Test
+    @Disabled("YT-1473: QName values need to be recognized and properly encoded via identity codec")
+    public void testSerializeIdentityValue() throws Exception {
+        assertEquals("/bar:foo[.='foo:one']", write(buildYangInstanceIdentifier(BAR_FOO, FOO_ONE)));
+    }
+
+    @Test
+    @Disabled("YT-1473: Instance-identifier values need to be recognized and properly encoded and escaped")
+    public void testSerializeInstanceIdentifierValue() throws Exception {
+        assertEquals("/bar:bar[.=\"/foo:bar/bar[qname='bar:two'\"]']",
+                write(buildYangInstanceIdentifier(BAR_BAR, buildYangInstanceIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO))));
+    }
+
+    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName node, final QName key,
+            final Object value) {
+        return YangInstanceIdentifier.create(
+                new NodeIdentifier(node), NodeIdentifierWithPredicates.of(node, key, value));
+    }
+
+    private static YangInstanceIdentifier buildYangInstanceIdentifier(final QName node, final Object value) {
+        return YangInstanceIdentifier.create(new NodeWithValue<>(node, value));
+    }
+
+    private String write(final YangInstanceIdentifier yangInstanceIdentifier) throws Exception {
+        CODEC.writeValue(writer, yangInstanceIdentifier);
+        verify(writer).writeCharacters(captor.capture());
+        return captor.getValue();
+    }
+}
diff --git a/codec/yang-data-codec-xml/src/test/resources/yt1473/bar.yang b/codec/yang-data-codec-xml/src/test/resources/yt1473/bar.yang
new file mode 100644 (file)
index 0000000..dceeaca
--- /dev/null
@@ -0,0 +1,20 @@
+module bar {
+  namespace barns;
+  prefix bar;
+
+  import foo { prefix foo; }
+
+  identity two {
+    base foo:one;
+  }
+
+  leaf foo {
+    type identityref {
+      base foo:one;
+    }
+  }
+
+  leaf bar {
+    type instance-identifier;
+  }
+}
diff --git a/codec/yang-data-codec-xml/src/test/resources/yt1473/foo.yang b/codec/yang-data-codec-xml/src/test/resources/yt1473/foo.yang
new file mode 100644 (file)
index 0000000..9690484
--- /dev/null
@@ -0,0 +1,29 @@
+module foo {
+  namespace foons;
+  prefix foo;
+
+  identity one;
+
+  list foo {
+    key str;
+    leaf str {
+      type string;
+    }
+  }
+
+  list bar {
+    key qname;
+    leaf qname {
+      type identityref {
+        base one;
+      }
+    }
+  }
+
+  list baz {
+    key id;
+    leaf id {
+      type instance-identifier;
+    }
+  }
+}