Fix incorrect schema reference in root POST link
[netconf.git] / restconf / sal-rest-docgen / src / test / java / org / opendaylight / netconf / sal / rest / doc / impl / ApiDocGeneratorRFC8040Test.java
1 /*
2  * Copyright (c) 2021 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.netconf.sal.rest.doc.impl;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertFalse;
12 import static org.junit.Assert.assertNotNull;
13 import static org.junit.Assert.assertNull;
14 import static org.junit.Assert.assertTrue;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.CONTENT_KEY;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.REF_KEY;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.REQUEST_BODY_KEY;
18 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.RESPONSES_KEY;
19 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.SCHEMA_KEY;
20 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getAppropriateModelPrefix;
21
22 import com.fasterxml.jackson.databind.JsonNode;
23 import com.fasterxml.jackson.databind.node.ObjectNode;
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.Sets;
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 import java.util.stream.StreamSupport;
32 import org.junit.Test;
33 import org.opendaylight.netconf.sal.rest.doc.swagger.OpenApiObject;
34 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
35
36 public final class ApiDocGeneratorRFC8040Test extends AbstractApiDocTest {
37     private static final String NAME = "toaster2";
38     private static final String MY_YANG = "my-yang";
39     private static final String MY_YANG_REVISION = "2022-10-06";
40     private static final String REVISION_DATE = "2009-11-20";
41     private static final String NAME_2 = "toaster";
42     private static final String REVISION_DATE_2 = "2009-11-20";
43     private static final String PATH_PARAMS_TEST_MODULE = "path-params-test";
44     private static final String MANDATORY_TEST = "mandatory-test";
45     private static final String CONFIG_ROOT_CONTAINER = "mandatory-test_config_root-container";
46     private static final String ROOT_CONTAINER = "mandatory-test_root-container";
47     private static final String CONFIG_MANDATORY_CONTAINER = "mandatory-test_root-container_config_mandatory-container";
48     private static final String MANDATORY_CONTAINER = "mandatory-test_root-container_mandatory-container";
49     private static final String CONFIG_MANDATORY_LIST = "mandatory-test_root-container_config_mandatory-list";
50     private static final String CONFIG_MANDATORY_LIST_POST = "mandatory-test_root-container_config_mandatory-list_post";
51     private static final String MANDATORY_LIST = "mandatory-test_root-container_mandatory-list";
52     private static final String MANDATORY_TEST_MODULE = "mandatory-test_config_module";
53     private static final String CHOICE_TEST_MODULE = "choice-test";
54     private static final String PROPERTIES = "properties";
55     private static final String CONTAINER = "container";
56     private static final String LIST = "list";
57
58     private final ApiDocGeneratorRFC8040 generator = new ApiDocGeneratorRFC8040(SCHEMA_SERVICE);
59
60     /**
61      * Test that paths are generated according to the model.
62      */
63     @Test
64     public void testPaths() {
65         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
66             ApiDocServiceImpl.OAversion.V2_0);
67
68         assertEquals(List.of("/rests/data",
69             "/rests/data/toaster2:toaster",
70             "/rests/data/toaster2:toaster/toasterSlot={slotId}",
71             "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo",
72             "/rests/data/toaster2:lst",
73             "/rests/data/toaster2:lst/cont1",
74             "/rests/data/toaster2:lst/cont1/cont11",
75             "/rests/data/toaster2:lst/cont1/lst11",
76             "/rests/data/toaster2:lst/lst1={key1},{key2}",
77             "/rests/operations/toaster2:make-toast",
78             "/rests/operations/toaster2:cancel-toast",
79             "/rests/operations/toaster2:restock-toaster"),
80             ImmutableList.copyOf(doc.getPaths().fieldNames()));
81     }
82
83     /**
84      * Test that generated configuration paths allow to use operations: get, put, delete and post.
85      */
86     @Test
87     public void testConfigPaths() {
88         final List<String> configPaths = List.of("/rests/data/toaster2:lst",
89                 "/rests/data/toaster2:lst/cont1",
90                 "/rests/data/toaster2:lst/cont1/cont11",
91                 "/rests/data/toaster2:lst/cont1/lst11",
92                 "/rests/data/toaster2:lst/lst1={key1},{key2}");
93
94         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
95             ApiDocServiceImpl.OAversion.V2_0);
96
97         for (final String path : configPaths) {
98             final JsonNode node = doc.getPaths().get(path);
99             assertFalse(node.path("get").isMissingNode());
100             assertFalse(node.path("put").isMissingNode());
101             assertFalse(node.path("delete").isMissingNode());
102             assertFalse(node.path("post").isMissingNode());
103         }
104     }
105
106     /**
107      * Test that generated document contains the following definitions.
108      */
109     @Test
110     public void testDefinitions() {
111         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
112             ApiDocServiceImpl.OAversion.V2_0);
113
114         final ObjectNode definitions = doc.getDefinitions();
115         assertNotNull(definitions);
116
117         final JsonNode configLstTop = definitions.get("toaster2_config_lst_TOP");
118         assertNotNull(configLstTop);
119         DocGenTestHelper.containsReferences(configLstTop, "toaster2:lst", "#/definitions/toaster2_config_lst");
120
121         final JsonNode configLst = definitions.get("toaster2_config_lst");
122         assertNotNull(configLst);
123         DocGenTestHelper.containsReferences(configLst, "lst1", "#/definitions/toaster2_lst_config_lst1");
124         DocGenTestHelper.containsReferences(configLst, "cont1", "#/definitions/toaster2_lst_config_cont1");
125
126         final JsonNode configLst1Top = definitions.get("toaster2_lst_config_lst1_TOP");
127         assertNotNull(configLst1Top);
128         DocGenTestHelper.containsReferences(configLst1Top, "toaster2:lst1", "#/definitions/toaster2_lst_config_lst1");
129
130         final JsonNode configLst1 = definitions.get("toaster2_lst_config_lst1");
131         assertNotNull(configLst1);
132
133         final JsonNode configCont1Top = definitions.get("toaster2_lst_config_cont1_TOP");
134         assertNotNull(configCont1Top);
135         DocGenTestHelper.containsReferences(configCont1Top, "toaster2:cont1",
136             "#/definitions/toaster2_lst_config_cont1");
137
138         final JsonNode configCont1 = definitions.get("toaster2_lst_config_cont1");
139         assertNotNull(configCont1);
140         DocGenTestHelper.containsReferences(configCont1, "cont11", "#/definitions/toaster2_lst_cont1_config_cont11");
141         DocGenTestHelper.containsReferences(configCont1, "lst11", "#/definitions/toaster2_lst_cont1_config_lst11");
142
143         final JsonNode configCont11Top = definitions.get("toaster2_lst_cont1_config_cont11_TOP");
144         assertNotNull(configCont11Top);
145         DocGenTestHelper.containsReferences(configCont11Top, "toaster2:cont11",
146             "#/definitions/toaster2_lst_cont1_config_cont11");
147
148         final JsonNode configCont11 = definitions.get("toaster2_lst_cont1_config_cont11");
149         assertNotNull(configCont11);
150
151         final JsonNode configLst11Top = definitions.get("toaster2_lst_cont1_config_lst11_TOP");
152         assertNotNull(configLst11Top);
153         DocGenTestHelper.containsReferences(configLst11Top, "toaster2:lst11",
154             "#/definitions/toaster2_lst_cont1_config_lst11");
155
156         final JsonNode configLst11 = definitions.get("toaster2_lst_cont1_config_lst11");
157         assertNotNull(configLst11);
158     }
159
160     /**
161      * Test that generated document contains RPC definition for "make-toast" with correct input.
162      */
163     @Test
164     public void testRPC() {
165         final SwaggerObject doc = (SwaggerObject) generator.getApiDeclaration(NAME_2, REVISION_DATE_2, URI_INFO,
166             ApiDocServiceImpl.OAversion.V2_0);
167         assertNotNull(doc);
168
169         final ObjectNode definitions = doc.getDefinitions();
170         final JsonNode inputTop = definitions.get("toaster_make-toast_input_TOP");
171         assertNotNull(inputTop);
172         final String testString = "{\"toaster:input\":{\"$ref\":\"#/definitions/toaster_make-toast_input\"}}";
173         assertEquals(testString, inputTop.get("properties").toString());
174         final JsonNode input = definitions.get("toaster_make-toast_input");
175         final JsonNode properties = input.get("properties");
176         assertTrue(properties.has("toasterDoneness"));
177         assertTrue(properties.has("toasterToastType"));
178     }
179
180     @Test
181     public void testMandatory() {
182         final var doc = (OpenApiObject) generator.getApiDeclaration(MANDATORY_TEST, null, URI_INFO,
183             ApiDocServiceImpl.OAversion.V3_0);
184         assertNotNull(doc);
185         final var definitions = doc.getComponents().getSchemas();
186         final var containersWithRequired = new ArrayList<String>();
187
188         final var reqRootContainerElements = Set.of("mandatory-root-leaf", "mandatory-container",
189             "mandatory-first-choice", "mandatory-list");
190         verifyRequiredField(definitions.get(CONFIG_ROOT_CONTAINER), reqRootContainerElements);
191         containersWithRequired.add(CONFIG_ROOT_CONTAINER);
192         verifyRequiredField(definitions.get(ROOT_CONTAINER), reqRootContainerElements);
193         containersWithRequired.add(ROOT_CONTAINER);
194
195         final var reqMandatoryContainerElements = Set.of("mandatory-leaf", "leaf-list-with-min-elements");
196         verifyRequiredField(definitions.get(CONFIG_MANDATORY_CONTAINER), reqMandatoryContainerElements);
197         containersWithRequired.add(CONFIG_MANDATORY_CONTAINER);
198         verifyRequiredField(definitions.get(MANDATORY_CONTAINER), reqMandatoryContainerElements);
199         containersWithRequired.add(MANDATORY_CONTAINER);
200
201         final var reqMandatoryListElements = Set.of("mandatory-list-field");
202         verifyRequiredField(definitions.get(CONFIG_MANDATORY_LIST), reqMandatoryListElements);
203         containersWithRequired.add(CONFIG_MANDATORY_LIST);
204         verifyRequiredField(definitions.get(MANDATORY_LIST), reqMandatoryListElements);
205         containersWithRequired.add(MANDATORY_LIST);
206
207         final var testModuleMandatoryArray = Set.of("root-container", "root-mandatory-list");
208         verifyRequiredField(definitions.get(MANDATORY_TEST_MODULE), testModuleMandatoryArray);
209         containersWithRequired.add(MANDATORY_TEST_MODULE);
210
211         verifyThatPropertyDoesNotHaveRequired(containersWithRequired, definitions);
212     }
213
214     /**
215      * Test that request parameters are correctly numbered.
216      *
217      * <p>
218      * It means we should have name and name1, etc. when we have the same parameter in path multiple times.
219      */
220     @Test
221     public void testParametersNumbering() {
222         final var doc = (OpenApiObject) generator.getApiDeclaration(PATH_PARAMS_TEST_MODULE, null, URI_INFO,
223             ApiDocServiceImpl.OAversion.V3_0);
224
225         var pathToList1 = "/rests/data/path-params-test:cont/list1={name}";
226         assertTrue(doc.getPaths().has(pathToList1));
227         assertEquals(List.of("name"), getPathParameters(doc.getPaths(), pathToList1));
228
229         var pathToList2 = "/rests/data/path-params-test:cont/list1={name}/list2={name1}";
230         assertTrue(doc.getPaths().has(pathToList2));
231         assertEquals(List.of("name", "name1"), getPathParameters(doc.getPaths(), pathToList2));
232
233         var pathToList3 = "/rests/data/path-params-test:cont/list3={name}";
234         assertTrue(doc.getPaths().has(pathToList3));
235         assertEquals(List.of("name"), getPathParameters(doc.getPaths(), pathToList3));
236
237         var pathToList4 = "/rests/data/path-params-test:cont/list1={name}/list4={name1}";
238         assertTrue(doc.getPaths().has(pathToList4));
239         assertEquals(List.of("name", "name1"), getPathParameters(doc.getPaths(), pathToList4));
240
241         var pathToList5 = "/rests/data/path-params-test:cont/list1={name}/cont2";
242         assertTrue(doc.getPaths().has(pathToList4));
243         assertEquals(List.of("name"), getPathParameters(doc.getPaths(), pathToList5));
244     }
245
246     private static void verifyThatPropertyDoesNotHaveRequired(final List<String> expected,
247         final ObjectNode definitions) {
248         final var fields = definitions.fields();
249         while (fields.hasNext()) {
250             final var next = fields.next();
251             final var nodeName = next.getKey();
252             final var jsonNode = next.getValue();
253             if (expected.contains(nodeName)) {
254                 continue;
255             }
256             assertNull("Json node " + nodeName + " should not have 'required' field in body",
257                 jsonNode.get("required"));
258         }
259     }
260
261     private static void verifyRequiredField(final JsonNode rootContainer, final Set<String> expected) {
262         assertNotNull(rootContainer);
263         final var required = rootContainer.get("required");
264         assertNotNull(required);
265         assertTrue(required.isArray());
266         final var actualContainerArray = StreamSupport.stream(required.spliterator(), false)
267             .map(JsonNode::textValue)
268             .collect(Collectors.toSet());
269         assertEquals(expected, actualContainerArray);
270     }
271
272     /**
273      * Test that request for actions is correct and has parameters.
274      */
275     @Test
276     public void testActionPathsParams() {
277         final var doc = (OpenApiObject) generator.getApiDeclaration("action-types", null, URI_INFO,
278             ApiDocServiceImpl.OAversion.V3_0);
279
280         final var pathWithParameters = "/rests/operations/action-types:list={name}/list-action";
281         assertTrue(doc.getPaths().has(pathWithParameters));
282         assertEquals(List.of("name"), getPathParameters(doc.getPaths(), pathWithParameters));
283
284         final var pathWithoutParameters = "/rests/operations/action-types:multi-container/inner-container/action";
285         assertTrue(doc.getPaths().has(pathWithoutParameters));
286         assertEquals(List.of(), getPathParameters(doc.getPaths(), pathWithoutParameters));
287     }
288
289     @Test
290     public void testChoice() {
291         final var doc = (SwaggerObject) generator.getApiDeclaration(CHOICE_TEST_MODULE, null, URI_INFO,
292             ApiDocServiceImpl.OAversion.V2_0);
293         assertNotNull(doc);
294
295         final var definitions = doc.getDefinitions();
296         JsonNode firstContainer = definitions.get("choice-test_first-container");
297         assertEquals("default-value",
298                 firstContainer.get(PROPERTIES).get("leaf-default").get("default").asText());
299         assertFalse(firstContainer.get(PROPERTIES).has("leaf-non-default"));
300
301         JsonNode secondContainer = definitions.get("choice-test_second-container");
302         assertTrue(secondContainer.get(PROPERTIES).has("leaf-first-case"));
303         assertFalse(secondContainer.get(PROPERTIES).has("leaf-second-case"));
304     }
305
306     @Test
307     public void testSimpleOpenApiObjects() {
308         final var doc = (OpenApiObject) generator.getApiDeclaration(MY_YANG, MY_YANG_REVISION, URI_INFO,
309             ApiDocServiceImpl.OAversion.V3_0);
310         assertEquals(List.of("/rests/data", "/rests/data/my-yang:data"),
311                 ImmutableList.copyOf(doc.getPaths().fieldNames()));
312
313         final var JsonNodeMyYangData = doc.getPaths().get("/rests/data/my-yang:data");
314         verifyRequestRef(JsonNodeMyYangData.path("post"),
315                 "#/components/schemas/my-yang_config_data",
316                 "#/components/schemas/my-yang_config_data");
317         verifyRequestRef(JsonNodeMyYangData.path("put"), "#/components/schemas/my-yang_config_data_TOP",
318                 "#/components/schemas/my-yang_config_data");
319         verifyRequestRef(JsonNodeMyYangData.path("get"), "#/components/schemas/my-yang_data_TOP",
320                 "#/components/schemas/my-yang_data");
321
322         // Test `components/schemas` objects
323         final var definitions = doc.getComponents().getSchemas();
324         assertEquals(5, definitions.size());
325         assertTrue(definitions.has("my-yang_config_data"));
326         assertTrue(definitions.has("my-yang_config_data_TOP"));
327         assertTrue(definitions.has("my-yang_data"));
328         assertTrue(definitions.has("my-yang_data_TOP"));
329         assertTrue(definitions.has("my-yang_config_module"));
330     }
331
332     @Test
333     public void testToaster2OpenApiObjects() {
334         final var doc = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
335             ApiDocServiceImpl.OAversion.V3_0);
336         final var jsonNodeToaster = doc.getPaths().get("/rests/data/toaster2:toaster");
337         verifyPostRequestRef(jsonNodeToaster.path("post"),
338             "#/components/schemas/toaster2_toaster_config_toasterSlot",
339             "#/components/schemas/toaster2_toaster_config_toasterSlot", LIST);
340         verifyRequestRef(jsonNodeToaster.path("put"), "#/components/schemas/toaster2_config_toaster_TOP",
341                 "#/components/schemas/toaster2_config_toaster");
342         verifyRequestRef(jsonNodeToaster.path("get"), "#/components/schemas/toaster2_toaster_TOP",
343                 "#/components/schemas/toaster2_toaster");
344
345         final var jsonNodeToasterSlot = doc.getPaths().get("/rests/data/toaster2:toaster/toasterSlot={slotId}");
346         verifyPostRequestRef(jsonNodeToasterSlot.path("post"),
347                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo",
348                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo", CONTAINER);
349         verifyRequestRef(jsonNodeToasterSlot.path("put"),
350                 "#/components/schemas/toaster2_toaster_config_toasterSlot_TOP",
351                 "#/components/schemas/toaster2_toaster_config_toasterSlot");
352         verifyRequestRef(jsonNodeToasterSlot.path("get"), "#/components/schemas/toaster2_toaster_toasterSlot_TOP",
353                 "#/components/schemas/toaster2_toaster_toasterSlot");
354
355         final var jsonNodeSlotInfo = doc.getPaths().get(
356                 "/rests/data/toaster2:toaster/toasterSlot={slotId}/toaster-augmented:slotInfo");
357         verifyRequestRef(jsonNodeSlotInfo.path("post"),
358                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo",
359                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo");
360         verifyRequestRef(jsonNodeSlotInfo.path("put"),
361                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo_TOP",
362                 "#/components/schemas/toaster2_toaster_toasterSlot_config_slotInfo");
363         verifyRequestRef(jsonNodeSlotInfo.path("get"), "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo_TOP",
364                 "#/components/schemas/toaster2_toaster_toasterSlot_slotInfo");
365
366         final var jsonNodeLst = doc.getPaths().get("/rests/data/toaster2:lst");
367         verifyPostRequestRef(jsonNodeLst.path("post"), "#/components/schemas/toaster2_lst_config_cont1",
368                 "#/components/schemas/toaster2_lst_config_cont1", CONTAINER);
369         verifyRequestRef(jsonNodeLst.path("put"), "#/components/schemas/toaster2_config_lst_TOP",
370                 "#/components/schemas/toaster2_config_lst");
371         verifyRequestRef(jsonNodeLst.path("get"), "#/components/schemas/toaster2_lst_TOP",
372                 "#/components/schemas/toaster2_lst");
373
374         final var jsonNodeLst1 = doc.getPaths().get("/rests/data/toaster2:lst/lst1={key1},{key2}");
375         verifyRequestRef(jsonNodeLst1.path("post"), "#/components/schemas/toaster2_lst_config_lst1",
376                 "#/components/schemas/toaster2_lst_config_lst1");
377         verifyRequestRef(jsonNodeLst1.path("put"), "#/components/schemas/toaster2_lst_config_lst1_TOP",
378                 "#/components/schemas/toaster2_lst_config_lst1");
379         verifyRequestRef(jsonNodeLst1.path("get"), "#/components/schemas/toaster2_lst_lst1_TOP",
380                 "#/components/schemas/toaster2_lst_lst1");
381
382         final var jsonNodeMakeToast = doc.getPaths().get("/rests/operations/toaster2:make-toast");
383         assertTrue(jsonNodeMakeToast.path("get").isMissingNode());
384         verifyRequestRef(jsonNodeMakeToast.path("post"), "#/components/schemas/toaster2_make-toast_input_TOP",
385                 "#/components/schemas/toaster2_make-toast_input");
386
387         final var jsonNodeCancelToast = doc.getPaths().get("/rests/operations/toaster2:cancel-toast");
388         assertTrue(jsonNodeCancelToast.path("get").isMissingNode());
389         // Test RPC with empty input
390         final var postContent = jsonNodeCancelToast.path("post").get("requestBody").get("content");
391         final var jsonSchema = postContent.get("application/json").get("schema");
392         assertNull(jsonSchema.get("$ref"));
393         assertEquals(2, jsonSchema.size());
394         final var xmlSchema = postContent.get("application/xml").get("schema");
395         assertNull(xmlSchema.get("$ref"));
396         assertEquals(2, xmlSchema.size());
397         // Test `components/schemas` objects
398         final var definitions = doc.getComponents().getSchemas();
399         assertEquals(44, definitions.size());
400     }
401
402     /**
403      * Test that reference to schema in each path is valid (all referenced schemas exist).
404      */
405     @Test
406     public void testRootPostSchemaReference() {
407         final var document = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
408             ApiDocServiceImpl.OAversion.V3_0);
409         assertNotNull(document);
410         final var expectedSchema = "toaster2_config_module";
411         // verify schema reference itself
412         verifyRequestRef(document.getPaths().path("/rests/data").path("post"),
413                 getAppropriateModelPrefix(ApiDocServiceImpl.OAversion.V3_0) + expectedSchema,
414                 getAppropriateModelPrefix(ApiDocServiceImpl.OAversion.V3_0) + expectedSchema);
415         // verify existence of the schemas being referenced
416         assertTrue("The expected referenced schema (" + expectedSchema + ") is not created",
417                 document.getComponents().getSchemas().has(expectedSchema));
418     }
419
420     /**
421      * Test that reference to schema in each path is valid (all referenced schemas exist).
422      */
423     @Test
424     public void testSchemasExistenceSingleModule() {
425         final var document = (OpenApiObject) generator.getApiDeclaration(NAME, REVISION_DATE, URI_INFO,
426             ApiDocServiceImpl.OAversion.V3_0);
427         assertNotNull(document);
428         final var referencedSchemas = new HashSet<String>();
429         for (final var elements = document.getPaths().elements(); elements.hasNext(); ) {
430             final var path = elements.next();
431             referencedSchemas.addAll(extractSchemaRefFromPath(path, ApiDocServiceImpl.OAversion.V3_0));
432         }
433         final var schemaNamesIterator = document.getComponents().getSchemas().fieldNames();
434         final var schemaNames = Sets.newHashSet(schemaNamesIterator);
435         for (final var ref : referencedSchemas) {
436             assertTrue("Referenced schema " + ref + " does not exist", schemaNames.contains(ref));
437         }
438     }
439
440     /**
441      *  Test JSON and XML references for request operation.
442      */
443     private static void verifyRequestRef(final JsonNode path, final String expectedJsonRef,
444             final String expectedXmlRef) {
445         final JsonNode postContent;
446         if (path.get("requestBody") != null) {
447             postContent = path.get("requestBody").get("content");
448         } else {
449             postContent = path.get("responses").get("200").get("content");
450         }
451         assertNotNull(postContent);
452         final var postJsonRef = postContent.get("application/json").get("schema").get("$ref");
453         assertNotNull(postJsonRef);
454         assertEquals(expectedJsonRef, postJsonRef.textValue());
455         final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
456         assertNotNull(postXmlRef);
457         assertEquals(expectedXmlRef, postXmlRef.textValue());
458     }
459
460     private static void verifyPostRequestRef(final JsonNode path, final String expectedJsonRef,
461         final String expectedXmlRef, String nodeType) {
462         final JsonNode postContent;
463         if (path.get("requestBody") != null) {
464             postContent = path.get("requestBody").get("content");
465         } else {
466             postContent = path.get("responses").get("200").get("content");
467         }
468         assertNotNull(postContent);
469         final String postJsonRef;
470         if (nodeType.equals(CONTAINER)) {
471             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
472                 .path("$ref").textValue();
473         } else {
474             postJsonRef = postContent.path("application/json").path("schema").path("properties").elements().next()
475                 .path("items").path("$ref").textValue();
476         }
477         assertEquals(expectedJsonRef, postJsonRef);
478         final var postXmlRef = postContent.get("application/xml").get("schema").get("$ref");
479         assertNotNull(postXmlRef);
480         assertEquals(expectedXmlRef, postXmlRef.textValue());
481     }
482
483     private static Set<String> extractSchemaRefFromPath(final JsonNode path,
484             final ApiDocServiceImpl.OAversion oaversion) {
485         if (path == null || path.isMissingNode()) {
486             return Set.of();
487         }
488         final var references = new HashSet<String>();
489         final var get = path.path("get");
490         if (!get.isMissingNode()) {
491             references.addAll(
492                     schemaRefFromContent(get.path(RESPONSES_KEY).path("200").path(CONTENT_KEY), oaversion));
493         }
494         final var post = path.path("post");
495         if (!post.isMissingNode()) {
496             references.addAll(schemaRefFromContent(post.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
497         }
498         final var put = path.path("put");
499         if (!put.isMissingNode()) {
500             references.addAll(schemaRefFromContent(put.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
501         }
502         final var patch = path.path("patch");
503         if (!patch.isMissingNode()) {
504             references.addAll(schemaRefFromContent(patch.path(REQUEST_BODY_KEY).path(CONTENT_KEY), oaversion));
505         }
506         return references;
507     }
508
509     private static Set<String> schemaRefFromContent(final JsonNode content,
510             final ApiDocServiceImpl.OAversion oaversion) {
511         final HashSet<String> refs = new HashSet<>();
512         content.fieldNames().forEachRemaining(mediaType -> {
513             final JsonNode ref = content.path(mediaType).path(SCHEMA_KEY).path(REF_KEY);
514             if (ref != null && !ref.isMissingNode()) {
515                 refs.add(ref.asText().replaceFirst(getAppropriateModelPrefix(oaversion), ""));
516             }
517         });
518         return refs;
519     }
520 }