5c1aa55c10ff5001eb8ff4dc572d2b12d6f24266
[yangtools.git] / codec / yang-data-codec-xml / src / test / java / org / opendaylight / yangtools / yang / data / codec / xml / YT1473Test.java
1 /*
2  * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.codec.xml;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
12 import static org.mockito.Mockito.doNothing;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.mock;
15
16 import com.google.common.collect.ImmutableSet;
17 import javax.xml.namespace.NamespaceContext;
18 import javax.xml.stream.XMLStreamWriter;
19 import org.junit.jupiter.api.AfterAll;
20 import org.junit.jupiter.api.BeforeAll;
21 import org.junit.jupiter.api.Test;
22 import org.junit.jupiter.api.extension.ExtendWith;
23 import org.mockito.ArgumentCaptor;
24 import org.mockito.junit.jupiter.MockitoExtension;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
27 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
28 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
31 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
32
33 @ExtendWith(MockitoExtension.class)
34 class YT1473Test {
35     private static final String FOO_NS = "foons"; // namespace for prefix 'foo'
36     private static final QName FOO_FOO = QName.create(FOO_NS, "foo"); // list with key 'str'
37     private static final QName FOO_BAR = QName.create(FOO_NS, "bar"); // list with key 'qname'
38     private static final QName FOO_BAZ = QName.create(FOO_NS, "baz"); // list with key 'id'
39     private static final QName FOO_BEE = QName.create(FOO_NS, "bee"); // list with key 'bts'
40     private static final QName FOO_ONE = QName.create(FOO_NS, "one"); // identity
41     private static final QName FOO_STR = QName.create(FOO_NS, "str"); // key of type 'string'
42     private static final QName FOO_QNAME = QName.create(FOO_NS, "qname"); // key of type 'one' based
43     private static final QName FOO_ID = QName.create(FOO_NS, "id"); // key of type 'instance-identifier'
44     private static final QName FOO_BTS = QName.create(FOO_NS, "bts"); // key of type 'bts' (bits)
45
46     private static final String BAR_NS = "barns"; // namespace for prefix 'bar'
47     private static final QName BAR_TWO = QName.create(BAR_NS, "two"); // identity inheriting 'foo:one'
48     private static final QName BAR_STR = QName.create(BAR_NS, "str"); // leaf-list of type 'string'
49     private static final QName BAR_FOO = QName.create(BAR_NS, "foo"); // leaf-list of type 'foo:one' based
50     private static final QName BAR_BAR = QName.create(BAR_NS, "bar"); // leaf-list of type 'instance-identifier'
51     private static final QName BAR_BEE = QName.create(BAR_NS, "bee"); // leaf-list of type 'foo:bts' (bits)
52
53     private static XmlCodec<YangInstanceIdentifier> CODEC;
54
55     @BeforeAll
56     static void beforeAll() {
57         final var modelContext = YangParserTestUtils.parseYang("""
58             module bar {
59               namespace barns;
60               prefix b;
61
62               import foo {
63                 prefix foo;
64               }
65
66               identity two {
67                 base foo:one;
68               }
69
70               leaf-list str {
71                 type string;
72               }
73
74               leaf-list foo {
75                 type identityref {
76                   base foo:one;
77                 }
78               }
79
80               leaf-list bar {
81                 type instance-identifier;
82               }
83
84               leaf-list bee {
85                 type foo:bitz;
86               }
87
88               leaf baz {
89                 type instance-identifier;
90               }
91             }""", """
92             module foo {
93               namespace foons;
94               prefix f;
95               identity one;
96
97               typedef bitz {
98                 type bits {
99                   bit one;
100                   bit two;
101                   bit three;
102                 }
103               }
104
105               list foo {
106                 key str;
107                 leaf str {
108                   type string;
109                 }
110               }
111
112               list bar {
113                 key qname;
114                 leaf qname {
115                   type identityref {
116                     base one;
117                   }
118                 }
119               }
120
121               list baz {
122                 key id;
123                 leaf id {
124                   type instance-identifier;
125                 }
126               }
127
128               list bee {
129                 key bts;
130                 leaf bts {
131                   type bitz;
132                 }
133               }
134             }""");
135         final var baz = assertInstanceOf(ListSchemaNode.class, modelContext.getDataChildByName(FOO_BAZ));
136         final var id = assertInstanceOf(LeafSchemaNode.class, baz.getDataChildByName(FOO_ID));
137         final var type = assertInstanceOf(InstanceIdentifierTypeDefinition.class, id.getType());
138         CODEC = XmlCodecFactory.create(modelContext).instanceIdentifierCodec(type);
139     }
140
141     @AfterAll
142     static void afterAll() {
143         CODEC = null;
144     }
145
146     @Test
147     void testSerializeSimple() throws Exception {
148         // No escaping needed, use single quotes
149         assertBar("/b:str[.='str\"']", createIdentifier(BAR_STR, "str\""));
150         assertBar("/b:str[.='str\\']", createIdentifier(BAR_STR, "str\\"));
151         assertBar("/b:str[.='str\r']", createIdentifier(BAR_STR, "str\r"));
152         assertBar("/b:str[.='str\n']", createIdentifier(BAR_STR, "str\n"));
153         assertBar("/b:str[.='str\t']", createIdentifier(BAR_STR, "str\t"));
154
155         assertFoo("/f:foo[f:str='str\"\\']", createIdentifier(FOO_FOO, FOO_STR, "str\"\\"));
156         assertFoo("/f:foo[f:str='str\r\n\t']", createIdentifier(FOO_FOO, FOO_STR, "str\r\n\t"));
157     }
158
159     @Test
160     void testSerializeEscaped() throws Exception {
161         // Escaping is needed, use double quotes and escape
162         assertBar("/b:str[.=\"str'\\\"\"]", createIdentifier(BAR_STR, "str'\""));
163         assertBar("/b:str[.=\"str'\\n\"]", createIdentifier(BAR_STR, "str'\n"));
164         assertBar("/b:str[.=\"str'\\t\"]", createIdentifier(BAR_STR, "str'\t"));
165         assertBar("/b:str[.=\"str'\r\"]", createIdentifier(BAR_STR, "str'\r"));
166
167         assertFoo("/f:foo[f:str=\"str'\\\"\\n\"]", createIdentifier(FOO_FOO, FOO_STR, "str'\"\n"));
168         assertFoo("/f:foo[f:str=\"str'\\t\r\"]", createIdentifier(FOO_FOO, FOO_STR, "str'\t\r"));
169     }
170
171     @Test
172     void testSerializeIdentity() throws Exception {
173         assertFoo("/f:bar[f:qname='f:one']", createIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE));
174         assertFooBar("/f:bar[f:qname='b:two']", createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO));
175     }
176
177     @Test
178     void testSerializeInstanceIdentifierRef() throws Exception {
179         assertFooBar("/f:baz[f:id=\"/f:bar[f:qname='b:two']\"]",
180             createIdentifier(FOO_BAZ, FOO_ID, createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
181     }
182
183     @Test
184     void testSerializeIdentityValue() throws Exception {
185         assertFooBar("/b:foo[.='f:one']", createIdentifier(BAR_FOO, FOO_ONE));
186         assertBar("/b:foo[.='b:two']", createIdentifier(BAR_FOO, BAR_TWO));
187     }
188
189     @Test
190     void testSerializeInstanceIdentifierValue() throws Exception {
191         assertFooBar("/b:bar[.=\"/f:bar[f:qname='f:one']\"]",
192             createIdentifier(BAR_BAR, createIdentifier(FOO_BAR, FOO_QNAME, FOO_ONE)));
193         assertFooBar("/b:bar[.=\"/f:bar[f:qname='b:two']\"]",
194             createIdentifier(BAR_BAR, createIdentifier(FOO_BAR, FOO_QNAME, BAR_TWO)));
195     }
196
197     @Test
198     void testSerializeBits() throws Exception {
199         assertFoo("/f:bee[f:bts='']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of()));
200         assertFoo("/f:bee[f:bts='one']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("one")));
201         assertFoo("/f:bee[f:bts='two three']", createIdentifier(FOO_BEE, FOO_BTS, ImmutableSet.of("two", "three")));
202     }
203
204     @Test
205     void testSerializeBitsValue() throws Exception {
206         assertBar("/b:bee[.='']", createIdentifier(BAR_BEE, ImmutableSet.of()));
207         assertBar("/b:bee[.='one']", createIdentifier(BAR_BEE, ImmutableSet.of("one")));
208         assertBar("/b:bee[.='two three']", createIdentifier(BAR_BEE, ImmutableSet.of("two", "three")));
209     }
210
211     private static void assertBar(final String expected, final YangInstanceIdentifier id) throws Exception {
212         final var writer = mockWriter();
213         doNothing().when(writer).writeNamespace("b", "barns");
214
215         final var reader = mock(NamespaceContext.class);
216         doReturn("barns").when(reader).getNamespaceURI("b");
217         assertSerdes(reader, writer, expected, id);
218     }
219
220     private static void assertFoo(final String expected, final YangInstanceIdentifier id) throws Exception {
221         final var writer = mockWriter();
222         doNothing().when(writer).writeNamespace("f", "foons");
223
224         final var reader = mock(NamespaceContext.class);
225         doReturn("foons").when(reader).getNamespaceURI("f");
226
227         assertSerdes(reader, writer, expected, id);
228     }
229
230     private static void assertFooBar(final String expected, final YangInstanceIdentifier id) throws Exception {
231         final var writer = mockWriter();
232         doNothing().when(writer).writeNamespace("f", "foons");
233         doNothing().when(writer).writeNamespace("b", "barns");
234
235         final var reader = mock(NamespaceContext.class);
236         doReturn("foons").when(reader).getNamespaceURI("f");
237         doReturn("barns").when(reader).getNamespaceURI("b");
238
239         assertSerdes(reader, writer, expected, id);
240     }
241
242     private static void assertSerdes(final NamespaceContext reader, final XMLStreamWriter writer,
243             final String expected, final YangInstanceIdentifier id) throws Exception {
244         assertEquals(id, CODEC.parseValue(reader, expected));
245
246         final var captor = ArgumentCaptor.forClass(String.class);
247         doNothing().when(writer).writeCharacters(captor.capture());
248         CODEC.writeValue(writer, id);
249         assertEquals(expected, captor.getValue());
250     }
251
252     private static XMLStreamWriter mockWriter() {
253         final var writer = mock(XMLStreamWriter.class);
254         doReturn(null).when(writer).getNamespaceContext();
255         return writer;
256     }
257
258     private static YangInstanceIdentifier createIdentifier(final QName node, final QName key, final Object value) {
259         return YangInstanceIdentifier.builder().node(node).nodeWithKey(node, key, value).build();
260     }
261
262     private static YangInstanceIdentifier createIdentifier(final QName node, final Object value) {
263         return YangInstanceIdentifier.builder().node(node).node(new NodeWithValue<>(node, value)).build();
264     }
265 }