2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.rests.transactions;
10 import static org.hamcrest.CoreMatchers.containsString;
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.assertFalse;
15 import static org.junit.Assert.assertNotNull;
16 import static org.junit.Assert.assertNull;
17 import static org.junit.Assert.assertThrows;
18 import static org.junit.Assert.assertTrue;
20 import com.google.common.collect.ImmutableList;
21 import com.google.common.util.concurrent.Futures;
22 import java.util.List;
23 import java.util.concurrent.ExecutionException;
24 import javax.ws.rs.core.UriInfo;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.mockito.Mock;
30 import org.opendaylight.restconf.api.ApiPath;
31 import org.opendaylight.restconf.api.query.ContentParam;
32 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
33 import org.opendaylight.restconf.common.patch.PatchContext;
34 import org.opendaylight.restconf.common.patch.PatchEntity;
35 import org.opendaylight.restconf.common.patch.PatchStatusContext;
36 import org.opendaylight.restconf.common.patch.PatchStatusEntity;
37 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
38 import org.opendaylight.restconf.server.api.DatabindContext;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.patch.rev170222.yang.patch.yang.patch.Edit.Operation;
40 import org.opendaylight.yangtools.yang.common.ErrorTag;
41 import org.opendaylight.yangtools.yang.common.ErrorType;
42 import org.opendaylight.yangtools.yang.common.QName;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
46 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
48 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
52 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
54 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
56 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
57 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
58 import org.w3c.dom.DOMException;
60 abstract class AbstractRestconfStrategyTest extends AbstractJukeboxTest {
61 static final ContainerNode JUKEBOX_WITH_BANDS = ImmutableNodes.newContainerBuilder()
62 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
63 .withChild(ImmutableNodes.newSystemMapBuilder()
64 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
65 .withChild(BAND_ENTRY)
66 .withChild(ImmutableNodes.newMapEntryBuilder()
67 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "name of band 2"))
68 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of band 2"))
69 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description 2"))
73 static final ContainerNode JUKEBOX_WITH_PLAYLIST = ImmutableNodes.newContainerBuilder()
74 .withNodeIdentifier(new NodeIdentifier(JUKEBOX_QNAME))
75 .withChild(ImmutableNodes.newSystemMapBuilder()
76 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
77 .withChild(ImmutableNodes.newMapEntryBuilder()
78 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-A"))
79 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-A"))
80 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description A"))
82 .withChild(ImmutableNodes.newMapEntryBuilder()
83 .withNodeIdentifier(NodeIdentifierWithPredicates.of(PLAYLIST_QNAME, NAME_QNAME, "MyFavoriteBand-B"))
84 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "MyFavoriteBand-B"))
85 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "band description B"))
89 static final MapNode PLAYLIST = ImmutableNodes.newSystemMapBuilder()
90 .withNodeIdentifier(new NodeIdentifier(PLAYLIST_QNAME))
91 .withChild(BAND_ENTRY)
93 // instance identifier for accessing container node "player"
94 static final YangInstanceIdentifier PLAYER_IID = YangInstanceIdentifier.of(JUKEBOX_QNAME, PLAYER_QNAME);
95 static final YangInstanceIdentifier ARTIST_IID = YangInstanceIdentifier.builder()
99 .nodeWithKey(ARTIST_QNAME, NAME_QNAME, "name of artist")
101 // FIXME: this looks weird
102 static final YangInstanceIdentifier CREATE_AND_DELETE_TARGET = GAP_IID.node(PLAYER_QNAME).node(GAP_QNAME);
105 static final QName BASE = QName.create("ns", "2016-02-28", "base");
106 private static final QName LIST_KEY_QNAME = QName.create(BASE, "list-key");
107 private static final QName LEAF_LIST_QNAME = QName.create(BASE, "leaf-list");
108 private static final QName LIST_QNAME = QName.create(BASE, "list");
109 static final QName CONT_QNAME = QName.create(BASE, "cont");
111 private static final NodeIdentifierWithPredicates NODE_WITH_KEY =
112 NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue");
113 private static final NodeIdentifierWithPredicates NODE_WITH_KEY_2 =
114 NodeIdentifierWithPredicates.of(LIST_QNAME, LIST_KEY_QNAME, "keyValue2");
116 private static final LeafNode<?> CONTENT = ImmutableNodes.leafNode(QName.create(BASE, "leaf-content"), "content");
117 private static final LeafNode<?> CONTENT_2 =
118 ImmutableNodes.leafNode(QName.create(BASE, "leaf-content-different"), "content-different");
119 static final YangInstanceIdentifier PATH = YangInstanceIdentifier.builder()
124 static final YangInstanceIdentifier PATH_2 = YangInstanceIdentifier.builder()
127 .node(NODE_WITH_KEY_2)
129 static final YangInstanceIdentifier PATH_3 = YangInstanceIdentifier.of(CONT_QNAME, LIST_QNAME);
130 private static final MapEntryNode DATA = ImmutableNodes.newMapEntryBuilder()
131 .withNodeIdentifier(NODE_WITH_KEY)
134 static final MapEntryNode DATA_2 = ImmutableNodes.newMapEntryBuilder()
135 .withNodeIdentifier(NODE_WITH_KEY)
136 .withChild(CONTENT_2)
138 private static final LeafNode<?> CONTENT_LEAF = ImmutableNodes.leafNode(QName.create(BASE, "content"), "test");
139 private static final LeafNode<?> CONTENT_LEAF_2 = ImmutableNodes.leafNode(QName.create(BASE, "content2"), "test2");
140 static final ContainerNode DATA_3 = ImmutableNodes.newContainerBuilder()
141 .withNodeIdentifier(new NodeIdentifier(QName.create(BASE, "container")))
142 .withChild(CONTENT_LEAF)
144 static final ContainerNode DATA_4 = ImmutableNodes.newContainerBuilder()
145 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(BASE, "container2")))
146 .withChild(CONTENT_LEAF_2)
148 static final MapNode LIST_DATA = ImmutableNodes.newSystemMapBuilder()
149 .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
152 static final MapNode LIST_DATA_2 = ImmutableNodes.newSystemMapBuilder()
153 .withNodeIdentifier(new NodeIdentifier(QName.create(LIST_QNAME, "list")))
157 static final UserMapNode ORDERED_MAP_NODE_1 = ImmutableNodes.newUserMapBuilder()
158 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
161 static final UserMapNode ORDERED_MAP_NODE_2 = ImmutableNodes.newUserMapBuilder()
162 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
166 private static final MapEntryNode CHECK_DATA = ImmutableNodes.newMapEntryBuilder()
167 .withNodeIdentifier(NODE_WITH_KEY)
168 .withChild(CONTENT_2)
171 static final LeafSetNode<String> LEAF_SET_NODE_1 = ImmutableNodes.<String>newSystemLeafSetBuilder()
172 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
173 .withChildValue("one")
174 .withChildValue("two")
176 static final LeafSetNode<String> LEAF_SET_NODE_2 = ImmutableNodes.<String>newSystemLeafSetBuilder()
177 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
178 .withChildValue("three")
180 static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_1 = ImmutableNodes.<String>newUserLeafSetBuilder()
181 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
182 .withChildValue("one")
183 .withChildValue("two")
185 static final LeafSetNode<String> ORDERED_LEAF_SET_NODE_2 = ImmutableNodes.<String>newUserLeafSetBuilder()
186 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
187 .withChildValue("three")
188 .withChildValue("four")
190 static final YangInstanceIdentifier LEAF_SET_NODE_PATH = YangInstanceIdentifier.builder()
192 .node(LEAF_LIST_QNAME)
194 private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_1 = ImmutableNodes.newUnkeyedListEntryBuilder()
195 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
198 private static final UnkeyedListEntryNode UNKEYED_LIST_ENTRY_NODE_2 = ImmutableNodes.newUnkeyedListEntryBuilder()
199 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
200 .withChild(CONTENT_2)
202 static final UnkeyedListNode UNKEYED_LIST_NODE_1 = ImmutableNodes.newUnkeyedListBuilder()
203 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
204 .withChild(UNKEYED_LIST_ENTRY_NODE_1)
206 static final UnkeyedListNode UNKEYED_LIST_NODE_2 = ImmutableNodes.newUnkeyedListBuilder()
207 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(LIST_QNAME))
208 .withChild(UNKEYED_LIST_ENTRY_NODE_2)
210 private static final NodeIdentifier NODE_IDENTIFIER =
211 new NodeIdentifier(QName.create("ns", "2016-02-28", "container"));
214 private EffectiveModelContext mockSchemaContext;
216 private UriInfo uriInfo;
218 private DatabindContext mockDatabind;
221 public void initMockDatabind() {
222 mockDatabind = DatabindContext.ofModel(mockSchemaContext);
225 abstract @NonNull RestconfStrategy newStrategy(DatabindContext databind);
227 final @NonNull RestconfStrategy jukeboxStrategy() {
228 return newStrategy(JUKEBOX_DATABIND);
231 final @NonNull RestconfStrategy mockStrategy() {
232 return newStrategy(mockDatabind);
236 * Test of successful DELETE operation.
239 public final void testDeleteData() throws Exception {
240 final var future = testDeleteDataStrategy().dataDELETE(ApiPath.empty());
241 assertNotNull(Futures.getDone(future));
244 abstract @NonNull RestconfStrategy testDeleteDataStrategy();
247 * Negative test for DELETE operation when data to delete does not exist. Error DATA_MISSING is expected.
250 public final void testNegativeDeleteData() {
251 final var future = testNegativeDeleteDataStrategy().dataDELETE(ApiPath.empty());
252 final var ex = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
253 assertThat(ex, instanceOf(RestconfDocumentedException.class));
254 final var errors = ((RestconfDocumentedException) ex).getErrors();
255 assertEquals(1, errors.size());
256 final var error = errors.get(0);
257 assertEquals(ErrorType.PROTOCOL, error.getErrorType());
258 assertEquals(ErrorTag.DATA_MISSING, error.getErrorTag());
261 abstract @NonNull RestconfStrategy testNegativeDeleteDataStrategy();
264 public final void testPostContainerData() {
265 testPostContainerDataStrategy().postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
268 abstract @NonNull RestconfStrategy testPostContainerDataStrategy();
271 public final void testPostListData() {
272 testPostListDataStrategy(BAND_ENTRY, PLAYLIST_IID.node(BAND_ENTRY.name())).postData(PLAYLIST_IID, PLAYLIST,
276 abstract @NonNull RestconfStrategy testPostListDataStrategy(MapEntryNode entryNode, YangInstanceIdentifier node);
279 public final void testPostDataFail() {
280 final var domException = new DOMException((short) 414, "Post request failed");
281 final var future = testPostDataFailStrategy(domException).postData(JUKEBOX_IID, EMPTY_JUKEBOX, null);
282 final var cause = assertThrows(ExecutionException.class, () -> Futures.getDone(future)).getCause();
283 assertThat(cause, instanceOf(RestconfDocumentedException.class));
284 final var errors = ((RestconfDocumentedException) cause).getErrors();
285 assertEquals(1, errors.size());
286 assertThat(errors.get(0).getErrorInfo(), containsString(domException.getMessage()));
289 abstract @NonNull RestconfStrategy testPostDataFailStrategy(DOMException domException);
292 public final void testPatchContainerData() {
293 testPatchContainerDataStrategy().merge(JUKEBOX_IID, EMPTY_JUKEBOX).getOrThrow();
296 abstract @NonNull RestconfStrategy testPatchContainerDataStrategy();
299 public final void testPatchLeafData() {
300 testPatchLeafDataStrategy().merge(GAP_IID, GAP_LEAF).getOrThrow();
303 abstract @NonNull RestconfStrategy testPatchLeafDataStrategy();
306 public final void testPatchListData() {
307 testPatchListDataStrategy().merge(JUKEBOX_IID, JUKEBOX_WITH_PLAYLIST).getOrThrow();
310 abstract @NonNull RestconfStrategy testPatchListDataStrategy();
313 public final void testPatchDataReplaceMergeAndRemove() {
314 final var buildArtistList = ImmutableNodes.newSystemMapBuilder()
315 .withNodeIdentifier(new NodeIdentifier(ARTIST_QNAME))
316 .withChild(ImmutableNodes.newMapEntryBuilder()
317 .withNodeIdentifier(NodeIdentifierWithPredicates.of(ARTIST_QNAME, NAME_QNAME, "name of artist"))
318 .withChild(ImmutableNodes.leafNode(NAME_QNAME, "name of artist"))
319 .withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, "description of artist"))
323 patch(new PatchContext("patchRMRm",
324 List.of(new PatchEntity("edit1", Operation.Replace, ARTIST_IID, buildArtistList),
325 new PatchEntity("edit2", Operation.Merge, ARTIST_IID, buildArtistList),
326 new PatchEntity("edit3", Operation.Remove, ARTIST_IID))),
327 testPatchDataReplaceMergeAndRemoveStrategy(), false);
330 abstract @NonNull RestconfStrategy testPatchDataReplaceMergeAndRemoveStrategy();
333 public final void testPatchDataCreateAndDelete() {
334 patch(new PatchContext("patchCD", List.of(
335 new PatchEntity("edit1", Operation.Create, PLAYER_IID, EMPTY_JUKEBOX),
336 new PatchEntity("edit2", Operation.Delete, CREATE_AND_DELETE_TARGET))),
337 testPatchDataCreateAndDeleteStrategy(), true);
340 abstract @NonNull RestconfStrategy testPatchDataCreateAndDeleteStrategy();
343 public final void testPatchMergePutContainer() {
344 patch(new PatchContext("patchM", List.of(new PatchEntity("edit1", Operation.Merge, PLAYER_IID, EMPTY_JUKEBOX))),
345 testPatchMergePutContainerStrategy(), false);
348 abstract @NonNull RestconfStrategy testPatchMergePutContainerStrategy();
351 public final void testDeleteNonexistentData() {
352 final var status = deleteNonexistentDataTestStrategy().patchData(new PatchContext("patchD",
353 List.of(new PatchEntity("edit", Operation.Delete, CREATE_AND_DELETE_TARGET))))
354 .getOrThrow().status();
355 assertEquals("patchD", status.patchId());
356 assertFalse(status.ok());
357 final var edits = status.editCollection();
358 assertEquals(1, edits.size());
359 final var edit = edits.get(0);
360 assertEquals("edit", edit.getEditId());
361 assertTestDeleteNonexistentData(status, edit);
364 abstract @NonNull RestconfStrategy deleteNonexistentDataTestStrategy();
366 abstract void assertTestDeleteNonexistentData(@NonNull PatchStatusContext status, @NonNull PatchStatusEntity edit);
369 public final void readDataConfigTest() {
370 assertEquals(DATA_3, readData(ContentParam.CONFIG, PATH, readDataConfigTestStrategy()));
373 abstract @NonNull RestconfStrategy readDataConfigTestStrategy();
376 public final void readAllHavingOnlyConfigTest() {
377 assertEquals(DATA_3, readData(ContentParam.ALL, PATH, readAllHavingOnlyConfigTestStrategy()));
380 abstract @NonNull RestconfStrategy readAllHavingOnlyConfigTestStrategy();
383 public final void readAllHavingOnlyNonConfigTest() {
384 assertEquals(DATA_2, readData(ContentParam.ALL, PATH_2, readAllHavingOnlyNonConfigTestStrategy()));
387 abstract @NonNull RestconfStrategy readAllHavingOnlyNonConfigTestStrategy();
390 public final void readDataNonConfigTest() {
391 assertEquals(DATA_2, readData(ContentParam.NONCONFIG, PATH_2, readDataNonConfigTestStrategy()));
394 abstract @NonNull RestconfStrategy readDataNonConfigTestStrategy();
397 public final void readContainerDataAllTest() {
398 assertEquals(ImmutableNodes.newContainerBuilder()
399 .withNodeIdentifier(NODE_IDENTIFIER)
400 .withChild(CONTENT_LEAF)
401 .withChild(CONTENT_LEAF_2)
402 .build(), readData(ContentParam.ALL, PATH, readContainerDataAllTestStrategy()));
405 abstract @NonNull RestconfStrategy readContainerDataAllTestStrategy();
408 public final void readContainerDataConfigNoValueOfContentTest() {
409 assertEquals(ImmutableNodes.newContainerBuilder()
410 .withNodeIdentifier(NODE_IDENTIFIER)
411 .withChild(CONTENT_LEAF)
412 .withChild(CONTENT_LEAF_2)
413 .build(), readData(ContentParam.ALL, PATH, readContainerDataConfigNoValueOfContentTestStrategy()));
416 abstract @NonNull RestconfStrategy readContainerDataConfigNoValueOfContentTestStrategy();
419 public final void readListDataAllTest() {
420 assertEquals(ImmutableNodes.newSystemMapBuilder()
421 .withNodeIdentifier(new NodeIdentifier(QName.create("ns", "2016-02-28", "list")))
422 .withChild(CHECK_DATA)
423 .build(), readData(ContentParam.ALL, PATH_3, readListDataAllTestStrategy()));
426 abstract @NonNull RestconfStrategy readListDataAllTestStrategy();
429 public final void readOrderedListDataAllTest() {
430 assertEquals(ImmutableNodes.newUserMapBuilder()
431 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
432 .withChild(CHECK_DATA)
433 .build(), readData(ContentParam.ALL, PATH_3, readOrderedListDataAllTestStrategy()));
436 abstract @NonNull RestconfStrategy readOrderedListDataAllTestStrategy();
439 public void readUnkeyedListDataAllTest() {
440 assertEquals(ImmutableNodes.newUnkeyedListBuilder()
441 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
442 .withChild(ImmutableNodes.newUnkeyedListEntryBuilder()
443 .withNodeIdentifier(new NodeIdentifier(LIST_QNAME))
444 .withChild(UNKEYED_LIST_ENTRY_NODE_1.body().iterator().next())
445 .withChild(UNKEYED_LIST_ENTRY_NODE_2.body().iterator().next())
447 .build(), readData(ContentParam.ALL, PATH_3, readUnkeyedListDataAllTestStrategy()));
450 abstract @NonNull RestconfStrategy readUnkeyedListDataAllTestStrategy();
453 public final void readLeafListDataAllTest() {
454 assertEquals(ImmutableNodes.<String>newSystemLeafSetBuilder()
455 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
456 .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
457 .addAll(LEAF_SET_NODE_1.body())
458 .addAll(LEAF_SET_NODE_2.body())
460 .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readLeafListDataAllTestStrategy()));
463 abstract @NonNull RestconfStrategy readLeafListDataAllTestStrategy();
466 public final void readOrderedLeafListDataAllTest() {
467 assertEquals(ImmutableNodes.<String>newUserLeafSetBuilder()
468 .withNodeIdentifier(new NodeIdentifier(LEAF_LIST_QNAME))
469 .withValue(ImmutableList.<LeafSetEntryNode<String>>builder()
470 .addAll(ORDERED_LEAF_SET_NODE_1.body())
471 .addAll(ORDERED_LEAF_SET_NODE_2.body())
473 .build(), readData(ContentParam.ALL, LEAF_SET_NODE_PATH, readOrderedLeafListDataAllTestStrategy()));
476 abstract @NonNull RestconfStrategy readOrderedLeafListDataAllTestStrategy();
479 public void readDataWrongPathOrNoContentTest() {
480 assertNull(readData(ContentParam.CONFIG, PATH_2, readDataWrongPathOrNoContentTestStrategy()));
483 abstract @NonNull RestconfStrategy readDataWrongPathOrNoContentTestStrategy();
486 * Read specific type of data from data store via transaction.
488 * @param content type of data to read (config, state, all)
489 * @param strategy {@link RestconfStrategy} - wrapper for variables
490 * @return {@link NormalizedNode}
492 private static @Nullable NormalizedNode readData(final @NonNull ContentParam content,
493 final YangInstanceIdentifier path, final @NonNull RestconfStrategy strategy) {
494 return strategy.readData(content, path, null);
497 private static void patch(final PatchContext patchContext, final RestconfStrategy strategy, final boolean failed) {
498 final var patchStatusContext = strategy.patchData(patchContext).getOrThrow().status();
499 for (var entity : patchStatusContext.editCollection()) {
501 assertTrue("Edit " + entity.getEditId() + " failed", entity.isOk());
503 assertTrue(entity.isOk());
506 assertTrue(patchStatusContext.ok());