This is an automated email from the ASF dual-hosted git repository.
tiagobento pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git
The following commit(s) were added to refs/heads/main by this push:
new 9458a87dfe2 [NO-ISSUE] Restore 6 kogito-runtimes sync commits lost in
merge (PR #6804) (#6856)
9458a87dfe2 is described below
commit 9458a87dfe20a816770d6aea8b3a1ebcdc9e394b
Author: Kbowers <[email protected]>
AuthorDate: Thu Jul 30 16:35:25 2026 +0100
[NO-ISSUE] Restore 6 kogito-runtimes sync commits lost in merge (PR #6804)
(#6856)
The merge commit 6bcfcfd10ca ('Merge incubator-kie-kogito-runtimes
repository into drools') was constructed from a point-in-time snapshot
of kogito-runtimes-rewrite-new as of Jul 9 (cb2fb63c7d6). Six commits
were later synced to the branch tip (2c2cc9a8600) but their file
changes were never reflected in the merge tree. This commit restores
the correct content for all 22 affected files.
Lost commits (in order):
- 79c906871241 Upgrade SpringFramework to 7.0.8 for CVE fix (#4340)
- d91ba08b269 Workflow Engine: null work_item_id audit event (#4326)
- 00dc560b43d PATCH API ignores explicit JSON null values (#4335)
- fa7ff2f686f Add @Column mappings to unmapped JPA entity fields (#4344)
- b658c2316b9 fix CWE-470 validate rule unit class name (#4341)
- 2c2cc9a8600 Migrate testMultiInstanceLoopCharacteristicsProcess2 (#4343)
---
.../jbpm/usertask/jpa/model/AttachmentEntity.java | 3 +
.../org/jbpm/usertask/jpa/model/CommentEntity.java | 2 +
.../usertask/jpa/model/TaskTimerConfigEntity.java | 2 +
.../usertask/jpa/model/UserTaskInstanceEntity.java | 2 +
.../src/main/java/org/kie/kogito/Model.java | 13 +-
.../src/main/java/org/kie/kogito/Models.java | 8 +-
.../uow/events/UnitOfWorkProcessEventListener.java | 4 +-
.../kie/kogito/codegen/tests/PublishEventIT.java | 50 +++++++
.../RestResourceQuarkusTemplate.java | 4 +-
.../RestResourceSpringTemplate.java | 4 +-
.../org/jbpm/compiler/canonical/ModelMetaData.java | 25 +++-
.../canonical/ProcessToExecModelGenerator.java | 2 +
.../class-templates/ModelNoIDTemplate.java | 21 ++-
.../resources/class-templates/ModelTemplate.java | 35 ++++-
.../src/test/java/org/jbpm/bpmn2/FlowTest.java | 34 +++--
.../quarkus/support/RuleUnitServiceImpl.java | 12 +-
.../support/StatefulRuleUnitServiceImpl.java | 12 +-
.../integrationtests/quarkus/BasicRestIT.java | 86 +++++++++++
.../kie/kogito/integrationtests/quarkus/OASIT.java | 21 +++
kogito-springboot/bom/pom.xml | 2 +-
.../src/main/resources/AdHocFragments.bpmn | 2 +
.../integrationtests/springboot/BasicRestTest.java | 159 +++++++++++++++++++++
22 files changed, 468 insertions(+), 35 deletions(-)
diff --git
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
index 0ab9751bf95..42d1c45040a 100644
---
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
+++
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
@@ -28,10 +28,13 @@ import jakarta.persistence.*;
public class AttachmentEntity {
@Id
+ @Column(name = "id")
private String id;
+ @Column(name = "name")
private String name;
+ @Column(name = "url")
private String url;
@Column(name = "updated_by")
diff --git
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
index 83132a3c35a..4dd5fb15681 100644
---
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
+++
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
@@ -28,8 +28,10 @@ import jakarta.persistence.*;
public class CommentEntity {
@Id
+ @Column(name = "id")
private String id;
+ @Column(name = "comment")
private String comment;
@Column(name = "updated_by")
diff --git
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
index 1f59855a808..4aa514837d4 100644
---
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
+++
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskTimerConfigEntity.java
@@ -21,6 +21,7 @@ package org.jbpm.usertask.jpa.model;
import java.util.Objects;
+import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@@ -33,6 +34,7 @@ public abstract class TaskTimerConfigEntity<T> extends
AbstractTaskEntity<T> {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
+ @Column(name = "id")
protected Integer id;
@ManyToOne(optional = false)
diff --git
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
index fdd0ff06c07..276a65ccab2 100644
---
a/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
+++
b/kogito-addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
@@ -54,6 +54,7 @@ public class UserTaskInstanceEntity {
public static final String STATUS_FILTER_CLAUSE = " and userTask.status in
(:statusFilter)";
@Id
+ @Column(name = "id")
private String id;
@Column(name = "user_task_id")
@@ -68,6 +69,7 @@ public class UserTaskInstanceEntity {
@Column(name = "task_priority")
private String taskPriority;
+ @Column(name = "status")
private String status;
@Column(name = "termination_type")
diff --git a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
index fbe9acb8ded..a4583aa74f5 100644
--- a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
+++ b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Model.java
@@ -19,8 +19,7 @@
package org.kie.kogito;
import java.util.Map;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
+import java.util.Set;
/**
* Represents data model type of objects that are usually descriptor of data
holders.
@@ -33,8 +32,16 @@ public interface Model extends MapInput, MapOutput {
}
default Map<String, Object> updatePartially(Map<String, Object> params) {
- params = params.entrySet().stream().filter(e -> e.getValue() !=
null).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
update(params);
return params;
}
+
+ /**
+ * Returns the set of field names that were explicitly set (e.g. during a
PATCH request).
+ * Returns null when tracking is not active — all fields are included in
toMap().
+ * Returns a non-null set when tracking is active — only those fields are
included.
+ */
+ default Set<String> getModifiedFields() {
+ return null;
+ }
}
diff --git a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
index c1e57052d69..c91ca522fe2 100644
--- a/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
+++ b/kogito-api/kogito-api/src/main/java/org/kie/kogito/Models.java
@@ -21,6 +21,7 @@ package org.kie.kogito;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Set;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -30,12 +31,17 @@ public class Models {
}
@SuppressWarnings("squid:S3011")
- public static Map<String, Object> toMap(Object m) {
+ public static Map<String, Object> toMap(MapOutput m) {
+ Set<String> modifiedFields = (m instanceof Model model) ?
model.getModifiedFields() : null;
Map<String, Object> map = new LinkedHashMap<>();
for (Field field : m.getClass().getDeclaredFields()) {
JsonProperty jsonAnnotation =
field.getAnnotation(JsonProperty.class);
if (jsonAnnotation != null) {
String name = jsonAnnotation.value();
+ // null = not tracking (include all); non-null = include only
modified fields
+ if (modifiedFields != null && !modifiedFields.contains(name)) {
+ continue;
+ }
field.setAccessible(true);
try {
map.put(name, field.get(m));
diff --git
a/kogito-api/kogito-api/src/main/java/org/kie/kogito/uow/events/UnitOfWorkProcessEventListener.java
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/uow/events/UnitOfWorkProcessEventListener.java
index 7d33accfe7f..cee3039b41b 100644
---
a/kogito-api/kogito-api/src/main/java/org/kie/kogito/uow/events/UnitOfWorkProcessEventListener.java
+++
b/kogito-api/kogito-api/src/main/java/org/kie/kogito/uow/events/UnitOfWorkProcessEventListener.java
@@ -68,12 +68,12 @@ public class UnitOfWorkProcessEventListener extends
DefaultKogitoProcessEventLis
@Override
public void beforeNodeTriggered(ProcessNodeTriggeredEvent event) {
- intercept(event);
+
}
@Override
public void afterNodeTriggered(ProcessNodeTriggeredEvent event) {
-
+ intercept(event);
}
@Override
diff --git
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
index 6f75bfb6382..414676ef7dc 100644
---
a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
+++
b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java
@@ -529,6 +529,56 @@ public class PublishEventIT extends AbstractCodegenIT {
.isNotNull().containsEntry("s", "Goodbye Hello john!!");
}
+ @Test
+ public void testUserTaskNodeEnterEventContainsWorkItemId() throws
Exception {
+ Application app =
generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2");
+ assertThat(app).isNotNull();
+
+ Process<? extends Model> p =
app.get(Processes.class).processById("UserTasksProcess");
+
+ Model model = p.createModel();
+ model.fromMap(Collections.emptyMap());
+
+ TestEventPublisher publisher = new TestEventPublisher();
+ app.unitOfWorkManager().eventManager().setService("http://myhost");
+ app.unitOfWorkManager().eventManager().addPublisher(publisher);
+
+ UnitOfWork uow = app.unitOfWorkManager().newUnitOfWork();
+ uow.start();
+
+ ProcessInstance<?> processInstance = p.createInstance(model);
+ processInstance.start();
+
+ uow.end();
+
+ assertThat(processInstance.status())
+ .isEqualTo(ProcessInstance.STATE_ACTIVE);
+
+ List<WorkItem> workItems = processInstance.workItems(securityPolicy);
+
+ assertThat(workItems).hasSize(1);
+ assertThat(workItems.get(0).getName()).isEqualTo("FirstTask");
+ assertThat(workItems.get(0).getId()).isNotNull();
+
+ List<DataEvent<?>> events = publisher.extract();
+
+ List<ProcessInstanceNodeEventBody> nodeEnterEvents =
findNodeInstanceEvents(
+ events,
+ ProcessInstanceNodeEventBody.EVENT_TYPE_ENTER);
+
+ ProcessInstanceNodeEventBody humanTaskNodeEvent =
nodeEnterEvents.stream()
+ .filter(event -> "HumanTaskNode".equals(event.getNodeType()))
+ .filter(event -> "First Task".equals(event.getNodeName()))
+ .findFirst()
+ .orElseThrow(() -> new AssertionError(
+ "HumanTaskNode ENTER event was not published"));
+
+ assertThat(humanTaskNodeEvent.getWorkItemId())
+ .as("HumanTaskNode ENTER event must contain the created work
item ID")
+ .isNotNull()
+ .isEqualTo(workItems.get(0).getId());
+ }
+
/*
* Helper methods
*/
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceQuarkusTemplate.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceQuarkusTemplate.java
index 6202d1032e0..18d5e778b29 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceQuarkusTemplate.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceQuarkusTemplate.java
@@ -160,7 +160,9 @@ public class $Type$Resource {
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "updateProcessInstance_$name$", summary =
"$documentation$", description = "$processInstanceDescription$")
public $Type$Output updateModel_$name$(@PathParam("id") String id,
$Type$Input resource) {
- return processService.update(process, id,
resource.toModel()).orElseThrow(NotFoundException::new);
+ $Type$ model = resource.toModel();
+ model.clearModifiedFields();
+ return processService.update(process, id,
model).orElseThrow(NotFoundException::new);
}
@PATCH
diff --git
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceSpringTemplate.java
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceSpringTemplate.java
index fe227d7538c..5328f80d171 100644
---
a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceSpringTemplate.java
+++
b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceSpringTemplate.java
@@ -112,7 +112,9 @@ public class $Type$Resource {
consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(operationId = "updateProcessInstance_$name$", summary =
"$documentation$", description = "$processInstanceDescription$")
public $Type$Output updateModel_$name$(@PathVariable("id") String id,
@RequestBody(required = false) $Type$Input resource) {
- return processService.update(process, id,
resource.toModel()).orElseThrow(() -> new
ResponseStatusException(HttpStatus.NOT_FOUND));
+ $Type$ model = resource.toModel();
+ model.clearModifiedFields();
+ return processService.update(process, id, model).orElseThrow(() -> new
ResponseStatusException(HttpStatus.NOT_FOUND));
}
@PatchMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE,
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
index 62f35f59cdb..bd3d64d21af 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ModelMetaData.java
@@ -190,6 +190,18 @@ public class ModelMetaData {
}
modelClass.setName(modelClassSimpleName);
+ // The modified-fields tracking field/getter must stay out of the
generated OpenAPI
+ if (supportsOpenApiGeneration) {
+ NormalAnnotationExpr hiddenSchema = new NormalAnnotationExpr(new
Name(Schema.class.getCanonicalName()),
+ NodeList.nodeList(new MemberValuePair("hidden", new
BooleanLiteralExpr(true))));
+ modelClass.findFirst(FieldDeclaration.class,
+ fd -> fd.getVariables().stream().anyMatch(v ->
v.getNameAsString().equals("__modifiedFields")))
+ .ifPresent(fd -> fd.addAnnotation(hiddenSchema.clone()));
+ modelClass.findFirst(MethodDeclaration.class, md ->
md.getNameAsString().equals("getModifiedFields"))
+ .ifPresent(md -> md.addAnnotation(hiddenSchema.clone()));
+ compilationUnit.addImport(Schema.class.getCanonicalName());
+ }
+
// setup of the toMap method body
BlockStmt toMapBody = new BlockStmt();
ClassOrInterfaceType toMap = new ClassOrInterfaceType(null, new
SimpleName(Map.class.getSimpleName()),
@@ -225,7 +237,14 @@ public class ModelMetaData {
applyOpenApiSchemaAnnotation(fd);
fd.createGetter();
- fd.createSetter();
+ MethodDeclaration setter = fd.createSetter();
+ if (isInputModel()) {
+ // Only Input setters run from request deserialization -
Model/Output setters
+ // are also called by generated copy loops, which would
wrongly count as "set".
+ setter.getBody().ifPresent(setterBody ->
setterBody.addStatement(
+ new MethodCallExpr(null, "markModified",
+ NodeList.nodeList(new
StringLiteralExpr(varName)))));
+ }
}
@@ -265,6 +284,10 @@ public class ModelMetaData {
.addModifier(Modifier.Keyword.PRIVATE);
}
+ private boolean isInputModel() {
+ return "/class-templates/ModelNoIDTemplate.java".equals(templateName);
+ }
+
public String getModelClassSimpleName() {
return modelClassSimpleName;
}
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
index b78ed1741f2..1692b928f53 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java
@@ -222,6 +222,8 @@ public class ProcessToExecModelGenerator {
"get" +
fieldName)));
}
+ body.addStatement(new MethodCallExpr(returnName,
"addModifiedFields",
+ NodeList.nodeList(new MethodCallExpr(null,
"getModifiedFields"))));
body.addStatement(new ReturnStmt(returnName));
method.setBody(body);
}
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
index c5d6fdd0498..f66bd652d5a 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelNoIDTemplate.java
@@ -24,11 +24,30 @@ import org.kie.kogito.MapOutput;
import java.util.Map;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
import org.kie.kogito.MappableToModel;
import org.kie.kogito.Model;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
public class XXXModel implements Model, MapInput, MapInputId, MapOutput,
MappableToModel<$modelClass$> {
-}
\ No newline at end of file
+ // Ephemeral REST request-body carrier - always tracks modifications from
construction.
+ @JsonIgnore
+ private transient final Set<String> __modifiedFields = new HashSet<>();
+
+ @JsonIgnore
+ @Override
+ public Set<String> getModifiedFields() {
+ return __modifiedFields;
+ }
+
+ // Marks a field as explicitly set, so PATCH can tell a null apart from an
omitted field.
+ void markModified(String field) {
+ __modifiedFields.add(field);
+ }
+
+}
diff --git
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
index b33106e9e81..b74cb6e7b38 100644
---
a/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
+++
b/kogito-jbpm/jbpm-flow-builder/src/main/resources/class-templates/ModelTemplate.java
@@ -24,20 +24,51 @@ import org.kie.kogito.MapOutput;
import java.util.Map;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
import org.kie.kogito.MappableToModel;
import org.kie.kogito.Model;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
public class XXXModel implements org.kie.kogito.Model, MapInput, MapInputId,
MapOutput, MappableToModel<$modelClass$> {
private String id;
+ @JsonIgnore
+ private transient Set<String> __modifiedFields;
+
public void setId(String id) {
this.id = id;
}
-
+
public String getId() {
return this.id;
}
-}
\ No newline at end of file
+ @JsonIgnore
+ @Override
+ public Set<String> getModifiedFields() {
+ return __modifiedFields;
+ }
+
+ // Codegen-internal: carries the modified-fields set from a PATCH Input
onto
+ // its toModel() target. Left null otherwise, so toMap() includes every
field.
+ void addModifiedFields(Set<String> fields) {
+ if (fields == null) {
+ return;
+ }
+ if (__modifiedFields == null) {
+ __modifiedFields = new HashSet<>();
+ }
+ __modifiedFields.addAll(fields);
+ }
+
+ // Called by the generated PUT handler before a full-replace update, so
every
+ // field counts as significant - unlike PATCH, which only touches tracked
fields.
+ void clearModifiedFields() {
+ __modifiedFields = null;
+ }
+
+}
diff --git a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
index 8c08ed637e1..52a99b74982 100755
--- a/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
+++ b/kogito-jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/FlowTest.java
@@ -94,6 +94,8 @@ import
org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithOutputMode
import
org.jbpm.bpmn2.flow.MultiInstanceLoopCharacteristicsProcessWithOutputProcess;
import org.jbpm.bpmn2.flow.MultiInstanceLoopNumberingModel;
import org.jbpm.bpmn2.flow.MultiInstanceLoopNumberingProcess;
+import org.jbpm.bpmn2.flow.MultiInstanceProcessWithOutputOnTaskModel;
+import org.jbpm.bpmn2.flow.MultiInstanceProcessWithOutputOnTaskProcess;
import org.jbpm.bpmn2.flow.MultipleGatewaysProcessModel;
import org.jbpm.bpmn2.flow.MultipleGatewaysProcessProcess;
import
org.jbpm.bpmn2.loop.MultiInstanceLoopCharacteristicsProcessWithOutputAndScriptsModel;
@@ -1056,40 +1058,36 @@ public class FlowTest extends JbpmBpmn2TestCase {
@Test
public void testMultiInstanceLoopCharacteristicsProcess2() throws
Exception {
- kruntime =
createKogitoProcessRuntime("org/jbpm/bpmn2/flow/BPMN2-MultiInstanceProcessWithOutputOnTask.bpmn2");
-
+ Application app = ProcessTestHelper.newApplication();
TestWorkItemHandler handler = new TestWorkItemHandler();
- kruntime.getKogitoWorkItemManager().registerWorkItemHandler("Human
Task", handler);
- Map<String, Object> params = new HashMap<>();
+ ProcessTestHelper.registerHandler(app, "Human Task", handler);
+
org.kie.kogito.process.Process<MultiInstanceProcessWithOutputOnTaskModel>
processDefinition = MultiInstanceProcessWithOutputOnTaskProcess.newProcess(app);
+ MultiInstanceProcessWithOutputOnTaskModel model =
processDefinition.createModel();
List<String> myList = new ArrayList<>();
- List<String> myOutList = null;
myList.add("John");
myList.add("Mary");
- params.put("miinput", myList);
+ model.setMiinput(myList);
- KogitoProcessInstance processInstance =
kruntime.startProcess("MultiInstanceProcessWithOutputOnTask", params);
+ ProcessInstance<MultiInstanceProcessWithOutputOnTaskModel>
processInstance = processDefinition.createInstance(model);
+ processInstance.start();
List<KogitoWorkItem> workItems = handler.getWorkItems();
assertThat(workItems).isNotNull().hasSize(2);
- myOutList = (List<String>) kruntime.getKieSession().execute(new
GetProcessVariableCommand(processInstance.getStringId(), "mioutput"));
- assertThat(myOutList).isNull();
+ assertThat(processInstance.variables().getMioutput()).isNull();
Map<String, Object> results = new HashMap<>();
results.put("reply", "Hello John");
-
kruntime.getKogitoWorkItemManager().completeWorkItem(workItems.get(0).getStringId(),
results);
- myOutList = (List<String>) kruntime.getKieSession().execute(new
GetProcessVariableCommand(processInstance.getStringId(), "mioutput"));
- assertThat(myOutList).isNull();
+ processInstance.completeWorkItem(workItems.get(0).getStringId(),
results);
+ assertThat(processInstance.variables().getMioutput()).isNull();
results = new HashMap<>();
results.put("reply", "Hello Mary");
-
kruntime.getKogitoWorkItemManager().completeWorkItem(workItems.get(1).getStringId(),
results);
+ processInstance.completeWorkItem(workItems.get(1).getStringId(),
results);
- myOutList = (List<String>) kruntime.getKieSession().execute(new
GetProcessVariableCommand(processInstance.getStringId(), "mioutput"));
- assertThat(myOutList).isNotNull().hasSize(2).contains("Hello John",
"Hello Mary");
-
-
kruntime.getKogitoWorkItemManager().completeWorkItem(handler.getWorkItem().getStringId(),
null);
- assertProcessInstanceFinished(processInstance, kruntime);
+
assertThat(processInstance.variables().getMioutput()).isNotNull().hasSize(2).contains("Hello
John", "Hello Mary");
+ processInstance.completeWorkItem(handler.getWorkItem().getStringId(),
null);
+
assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED);
}
@Test
diff --git
a/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/RuleUnitServiceImpl.java
b/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/RuleUnitServiceImpl.java
index 5c4e6cd9843..ec1a04f0cd5 100644
---
a/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/RuleUnitServiceImpl.java
+++
b/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/RuleUnitServiceImpl.java
@@ -20,6 +20,7 @@ package org.kie.kogito.core.rules.incubation.quarkus.support;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.drools.ruleunits.api.RuleUnit;
@@ -36,6 +37,9 @@ import
org.kie.kogito.incubation.rules.services.RuleUnitService;
class RuleUnitServiceImpl implements RuleUnitService {
+ // CWE-470: validate class name is a legal Java FQCN before passing to
loadClass
+ private static final Pattern SAFE_CLASS_NAME =
Pattern.compile("[\\w$]+(\\.[\\w$]+)*");
+
private final RuleUnits ruleUnits;
public RuleUnitServiceImpl(RuleUnits ruleUnits) {
@@ -64,12 +68,16 @@ class RuleUnitServiceImpl implements RuleUnitService {
}
private RuleUnitData convertValue(Map<String, Object> payload, RuleUnitId
ruleUnitId) {
+ String className = ruleUnitId.ruleUnitId();
+ if (!SAFE_CLASS_NAME.matcher(className).matches()) {
+ throw new IllegalArgumentException("Invalid rule unit class name:
" + className);
+ }
try {
// converts the identifier into a Class object for conversion
- Class<RuleUnitData> type = (Class<RuleUnitData>)
Thread.currentThread().getContextClassLoader().loadClass(ruleUnitId.ruleUnitId());
+ Class<RuleUnitData> type = (Class<RuleUnitData>)
Thread.currentThread().getContextClassLoader().loadClass(className);
return InternalObjectMapper.objectMapper().convertValue(payload,
type);
} catch (ClassNotFoundException e) {
- throw new IllegalArgumentException("Cannot load class " +
ruleUnitId.ruleUnitId(), e);
+ throw new IllegalArgumentException("Cannot load class " +
className, e);
}
}
diff --git
a/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/StatefulRuleUnitServiceImpl.java
b/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/StatefulRuleUnitServiceImpl.java
index 59588caba9e..46ccdf5e1f6 100644
---
a/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/StatefulRuleUnitServiceImpl.java
+++
b/kogito-quarkus/extensions/kogito-quarkus-rules-extension/kogito-quarkus-rules/src/main/java/org/kie/kogito/core/rules/incubation/quarkus/support/StatefulRuleUnitServiceImpl.java
@@ -21,6 +21,7 @@ package org.kie.kogito.core.rules.incubation.quarkus.support;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.drools.ruleunits.api.RuleUnit;
@@ -41,6 +42,9 @@ import
org.kie.kogito.incubation.rules.services.StatefulRuleUnitService;
class StatefulRuleUnitServiceImpl implements StatefulRuleUnitService {
+ // CWE-470: validate class name is a legal Java FQCN before passing to
loadClass
+ private static final Pattern SAFE_CLASS_NAME =
Pattern.compile("[\\w$]+(\\.[\\w$]+)*");
+
private final RuleUnits ruleUnits;
public StatefulRuleUnitServiceImpl(RuleUnits ruleUnits) {
@@ -71,10 +75,14 @@ class StatefulRuleUnitServiceImpl implements
StatefulRuleUnitService {
}
private Class<RuleUnitData> toClass(RuleUnitId ruleUnitId) {
+ String className = ruleUnitId.ruleUnitId();
+ if (!SAFE_CLASS_NAME.matcher(className).matches()) {
+ throw new IllegalArgumentException("Invalid rule unit class name:
" + className);
+ }
try {
- return (Class<RuleUnitData>)
Thread.currentThread().getContextClassLoader().loadClass(ruleUnitId.ruleUnitId());
+ return (Class<RuleUnitData>)
Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
- throw new IllegalArgumentException(e);
+ throw new IllegalArgumentException("Cannot load class " +
className, e);
}
}
diff --git
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/BasicRestIT.java
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/BasicRestIT.java
index 48fc895af4d..39f5268b841 100644
---
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/BasicRestIT.java
+++
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/BasicRestIT.java
@@ -252,6 +252,92 @@ class BasicRestIT {
assertExpectedUnitOfWorkEvents(2);
}
+ @Test
+ void testPatchWithNullValue() {
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(getParams())
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .body("var2", equalTo(34))
+ .extract()
+ .path("id");
+
+ // var1 set to null must be cleared, unlike an omitted field
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": null}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", nullValue())
+ .body("var2", is(34));
+
+ assertExpectedUnitOfWorkEvents(2);
+ }
+
+ @Test
+ void testPatchAndPutSequence() {
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(getParams())
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .body("var2", equalTo(34))
+ .extract()
+ .path("id");
+
+ // PATCH: var1 explicitly nulled, var2 untouched
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": null}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", nullValue())
+ .body("var2", is(34));
+
+ // PUT right after a PATCH on the same resource: var2 omitted -> must
still be nulled out
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": \"Gonzo\"}")
+ .put("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Gonzo"))
+ .body("var2", nullValue());
+
+ // PATCH right after a PUT: empty body must leave both fields untouched
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Gonzo"))
+ .body("var2", nullValue());
+
+ assertExpectedUnitOfWorkEvents(4);
+ }
+
@Test
void testDelete() {
Map<String, String> params = new HashMap<>();
diff --git
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/OASIT.java
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/OASIT.java
index 50575452aef..9db03fe82fb 100644
---
a/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/OASIT.java
+++
b/kogito-quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/OASIT.java
@@ -27,6 +27,7 @@ import io.quarkus.test.junit.QuarkusIntegrationTest;
import io.restassured.RestAssured;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.ParseOptions;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
@@ -60,4 +61,24 @@ class OASIT {
assertThat(p1.getPost()).isNotNull();
}
+ @Test
+ public void testApprovalsSchemaProperties() {
+ String url = rootUrl.toString() + "/q/openapi";
+ ParseOptions parseOptions = new ParseOptions();
+ parseOptions.setResolve(true);
+ SwaggerParseResult result = new OpenAPIV3Parser().readLocation(url,
null, parseOptions);
+
+ OpenAPI openAPI = result.getOpenAPI();
+ PathItem p1 = openAPI.getPaths().get("/approvals");
+ assertThat(p1).isNotNull();
+
+ Schema requestSchema =
p1.getPost().getRequestBody().getContent().get("application/json").getSchema();
+ String schemaName =
requestSchema.get$ref().substring(requestSchema.get$ref().lastIndexOf('/') + 1);
+ Schema resolvedSchema =
openAPI.getComponents().getSchemas().get(schemaName);
+ assertThat(resolvedSchema).isNotNull();
+
+ assertThat(resolvedSchema.getProperties().keySet())
+ .containsExactlyInAnyOrder("approver", "traveller",
"firstLineApproval", "secondLineApproval");
+ }
+
}
diff --git a/kogito-springboot/bom/pom.xml b/kogito-springboot/bom/pom.xml
index 20b87c5cb60..3b913a1158b 100644
--- a/kogito-springboot/bom/pom.xml
+++ b/kogito-springboot/bom/pom.xml
@@ -41,7 +41,7 @@
<!-- This is needed to override the default version inherited from
kie-parent-drools -->
<version.mongodb-driver-sync>5.6.4</version.mongodb-driver-sync>
<version.org.springdoc>2.8.13</version.org.springdoc>
- <version.org.springframework>7.0.7</version.org.springframework>
+ <version.org.springframework>7.0.8</version.org.springframework>
<version.org.springframework.boot>4.0.7</version.org.springframework.boot>
<!-- Spring Boot Cloud aligned with Spring Boot Framework version. See:
https://spring.io/projects/spring-cloud -->
<!-- 2025.1.x (Oakwood) is the first Spring Cloud train compatible with
Spring Boot 4.0.x;
diff --git
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/AdHocFragments.bpmn
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/AdHocFragments.bpmn
index a9bdf9430c1..02675c1d611 100644
---
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/AdHocFragments.bpmn
+++
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/main/resources/AdHocFragments.bpmn
@@ -19,6 +19,7 @@
-->
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:bpsim="http://www.bpsim.org/schemas/1.0"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:drools="http://www.jboss.org/drools" id="_WL3jkCdwED2KYMa5towL6A"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd
http://www. [...]
<bpmn2:itemDefinition id="_var1Item" structureRef="String"/>
+ <bpmn2:itemDefinition id="_var2Item" structureRef="Integer"/>
<bpmn2:itemDefinition
id="__731518E6-88A2-4CCC-A2C0-97C45189A7F3_ConditionInputXItem"
structureRef="String"/>
<bpmn2:itemDefinition
id="__17762652-67B8-46FF-ADFE-D7AF3EA2ADAB_SkippableInputXItem"
structureRef="Object"/>
<bpmn2:itemDefinition
id="__17762652-67B8-46FF-ADFE-D7AF3EA2ADAB_PriorityInputXItem"
structureRef="Object"/>
@@ -64,6 +65,7 @@
</bpmn2:collaboration>
<bpmn2:process id="TestCase.AdHocFragments"
drools:packageName="org.kie.kogito" drools:version="1.0" drools:adHoc="true"
name="AdHocFragments" isExecutable="true" processType="Public">
<bpmn2:property id="var1" itemSubjectRef="_var1Item" name="var1"/>
+ <bpmn2:property id="var2" itemSubjectRef="_var2Item" name="var2"/>
<bpmn2:sequenceFlow id="_830D53F8-A344-45AE-A1A1-2173DD983A43"
sourceRef="_89FFDFF9-8D8A-4B86-B48C-76F30299D8D3"
targetRef="_319B612D-CD70-4FB0-8390-2F825115847E">
<bpmn2:extensionElements>
<drools:metaData name="isAutoConnection.source">
diff --git
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/BasicRestTest.java
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/BasicRestTest.java
index db13274bf8a..c4655363d4c 100644
---
a/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/BasicRestTest.java
+++
b/kogito-springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/BasicRestTest.java
@@ -38,6 +38,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.emptyOrNullString;
+import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = KogitoSpringbootApplication.class)
@@ -207,6 +208,164 @@ class BasicRestTest extends BaseRestTest {
assertExpectedUnitOfWorkEvents(2);
}
+ @Test
+ void testUpdateNullsOmittedField() {
+ Map<String, Object> params = new HashMap<>();
+ params.put("var1", "Kermit");
+ params.put("var2", 34);
+
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(params)
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .body("var2", equalTo(34))
+ .extract()
+ .path("id");
+
+ // var2 omitted from PUT body -> full replace must null it
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": \"Gonzo\"}")
+ .put("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Gonzo"))
+ .body("var2", nullValue());
+
+ assertExpectedUnitOfWorkEvents(2);
+ }
+
+ @Test
+ void testPatch() {
+ Map<String, String> params = new HashMap<>();
+ params.put("var1", "Kermit");
+
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(params)
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .extract()
+ .path("id");
+
+ // An empty PATCH body must leave existing variables untouched.
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Kermit"));
+
+ assertExpectedUnitOfWorkEvents(2);
+ }
+
+ @Test
+ void testPatchWithNullValue() {
+ Map<String, String> params = new HashMap<>();
+ params.put("var1", "Kermit");
+
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(params)
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .extract()
+ .path("id");
+
+ // var1 set to null must be cleared, unlike an omitted field
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": null}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", nullValue());
+
+ assertExpectedUnitOfWorkEvents(2);
+ }
+
+ @Test
+ void testPatchAndPutSequence() {
+ Map<String, Object> params = new HashMap<>();
+ params.put("var1", "Kermit");
+ params.put("var2", 34);
+
+ String id = given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(params)
+ .post("/AdHocFragments")
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .body("var1", equalTo("Kermit"))
+ .body("var2", equalTo(34))
+ .extract()
+ .path("id");
+
+ // PATCH: var1 explicitly nulled, var2 untouched
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": null}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", nullValue())
+ .body("var2", equalTo(34));
+
+ // PUT right after a PATCH on the same resource: var2 omitted -> must
still be nulled out
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{\"var1\": \"Gonzo\"}")
+ .put("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Gonzo"))
+ .body("var2", nullValue());
+
+ // PATCH right after a PUT: empty body must leave both fields untouched
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body("{}")
+ .patch("/AdHocFragments/{customId}", id)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(id))
+ .body("var1", equalTo("Gonzo"))
+ .body("var2", nullValue());
+
+ assertExpectedUnitOfWorkEvents(4);
+ }
+
@Test
void testDelete() {
Map<String, String> params = new HashMap<>();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]