From e741ceb86b64b9cecbeb544c4df31b230322b18a Mon Sep 17 00:00:00 2001 From: Jordy Cantaert Date: Tue, 20 Aug 2024 16:23:24 +0200 Subject: [PATCH] feat(engine): like-queries for candidate groups - adds functionality to filter tasks on a candidate group using a like-statement related to #4540 --- .../lib/commons/task-query-params.ftl | 5 + .../rest/dto/runtime/task/TaskQueryDto.ftl | 5 + .../engine/rest/dto/task/TaskQueryDto.java | 16 ++- .../engine/rest/TaskRestServiceQueryTest.java | 2 + .../bpm/engine/impl/TaskQueryImpl.java | 29 ++++- .../impl/json/JsonTaskQueryConverter.java | 5 + .../camunda/bpm/engine/task/TaskQuery.java | 19 +++ .../bpm/engine/impl/mapping/entity/Task.xml | 14 ++- .../test/api/filter/FilterTaskQueryTest.java | 84 +++++++++++++ .../engine/test/api/task/TaskQueryTest.java | 117 ++++++++++++++++++ 10 files changed, 290 insertions(+), 6 deletions(-) diff --git a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl index 23fefe039ff..1b5bc1e646d 100644 --- a/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl +++ b/engine-rest/engine-rest-openapi/src/main/templates/lib/commons/task-query-params.ftl @@ -200,6 +200,11 @@ type = "string" desc = "Only include tasks that are offered to the given group." /> + <@lib.parameter name = "candidateGroupLike" + location = "query" + type = "string" + desc = "Only include tasks that are offered to groups that have the parameter value as a substring." /> + <@lib.parameter name = "candidateGroupExpression" location = "query" type = "string" diff --git a/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl b/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl index 49bc2365d85..ae8783555e6 100644 --- a/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl +++ b/engine-rest/engine-rest-openapi/src/main/templates/models/org/camunda/bpm/engine/rest/dto/runtime/task/TaskQueryDto.ftl @@ -208,6 +208,11 @@ name = "candidateGroup" type = "string" desc = "Only include tasks that are offered to the given group." /> + + <@lib.property + name = "candidateGroupLike" + type = "string" + desc = "Only include tasks that are offered to groups that have the parameter value as a substring." /> <@lib.property name = "candidateGroupExpression" diff --git a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java index ad5a5d319ab..bd0b367b947 100644 --- a/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java +++ b/engine-rest/engine-rest/src/main/java/org/camunda/bpm/engine/rest/dto/task/TaskQueryDto.java @@ -131,6 +131,7 @@ public class TaskQueryDto extends AbstractQueryDto { private String[] assigneeNotIn; private String candidateGroup; private String candidateGroupExpression; + private String candidateGroupLike; private String candidateUser; private String candidateUserExpression; private Boolean includeAssignedTasks; @@ -347,6 +348,11 @@ public void setCandidateGroupExpression(String candidateGroupExpression) { this.candidateGroupExpression = candidateGroupExpression; } + @CamundaQueryParam("candidateGroupLike") + public void setCandidateGroupLike(String candidateGroupLike) { + this.candidateGroupLike = candidateGroupLike; + } + @CamundaQueryParam(value = "withCandidateGroups", converter = BooleanConverter.class) public void setWithCandidateGroups(Boolean withCandidateGroups) { this.withCandidateGroups = withCandidateGroups; @@ -821,6 +827,10 @@ public String getCandidateGroupExpression() { return candidateGroupExpression; } + public String getCandidateGroupLike() { + return candidateGroupLike; + } + public String getCandidateUser() { return candidateUser; } @@ -1169,6 +1179,9 @@ protected void applyFilters(TaskQuery query) { if (candidateGroupExpression != null) { query.taskCandidateGroupExpression(candidateGroupExpression); } + if (candidateGroupLike != null) { + query.taskCandidateGroupLike(candidateGroupLike); + } if (withCandidateGroups != null && withCandidateGroups) { query.withCandidateGroups(); } @@ -1570,6 +1583,7 @@ public static TaskQueryDto fromQuery(Query query, boolean isOrQueryActive) dto.candidateUser = taskQuery.getCandidateUser(); dto.candidateGroup = taskQuery.getCandidateGroup(); + dto.candidateGroupLike = taskQuery.getCandidateGroupLike(); dto.candidateGroups = taskQuery.getCandidateGroupsInternal(); dto.includeAssignedTasks = taskQuery.isIncludeAssignedTasksInternal(); dto.withCandidateGroups = taskQuery.isWithCandidateGroups(); @@ -1876,4 +1890,4 @@ else if (VariableInstanceQueryProperty.VARIABLE_TYPE.equals(property)) { } return parameters; } -} \ No newline at end of file +} diff --git a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java index adff79c00a0..75035a87a8e 100644 --- a/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java +++ b/engine-rest/engine-rest/src/test/java/org/camunda/bpm/engine/rest/TaskRestServiceQueryTest.java @@ -508,6 +508,7 @@ private Map getCompleteStringQueryParameters() { parameters.put("assignee", "anAssignee"); parameters.put("assigneeLike", "anAssigneeLike"); parameters.put("candidateGroup", "aCandidateGroup"); + parameters.put("candidateGroupLike", "aCandidateGroupLike"); parameters.put("candidateUser", "aCandidate"); parameters.put("includeAssignedTasks", "false"); parameters.put("taskId", "aTaskId"); @@ -566,6 +567,7 @@ private void verifyStringParameterQueryInvocations() { verify(mockQuery).taskAssignee(stringQueryParameters.get("assignee")); verify(mockQuery).taskAssigneeLike(stringQueryParameters.get("assigneeLike")); verify(mockQuery).taskCandidateGroup(stringQueryParameters.get("candidateGroup")); + verify(mockQuery).taskCandidateGroupLike(stringQueryParameters.get("candidateGroupLike")); verify(mockQuery).taskCandidateUser(stringQueryParameters.get("candidateUser")); verify(mockQuery).taskDefinitionKey(stringQueryParameters.get("taskDefinitionKey")); verify(mockQuery).taskDefinitionKeyLike(stringQueryParameters.get("taskDefinitionKeyLike")); diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java b/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java index 41861956ff7..c23eeb05eb1 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/TaskQueryImpl.java @@ -114,6 +114,7 @@ public class TaskQueryImpl extends AbstractQuery implements Tas protected DelegationState delegationState; protected String candidateUser; protected String candidateGroup; + protected String candidateGroupLike; protected List candidateGroups; protected Boolean withCandidateGroups; protected Boolean withoutCandidateGroups; @@ -451,6 +452,18 @@ public TaskQuery taskCandidateGroupExpression(String candidateGroupExpression) { return this; } + @Override + public TaskQuery taskCandidateGroupLike(String candidateGroupLike) { + ensureNotNull("Candidate group like", candidateGroupLike); + + if (!isOrQueryActive && (candidateUser != null || expressions.containsKey("taskCandidateUser"))) { + throw new ProcessEngineException("Invalid query usage: cannot set both candidateGroupLike and candidateUser"); + } + + this.candidateGroupLike = candidateGroupLike; + return this; + } + @Override public TaskQuery taskCandidateGroupIn(List candidateGroups) { ensureNotEmpty("Candidate group list", candidateGroups); @@ -482,10 +495,11 @@ public TaskQuery taskCandidateGroupInExpression(String candidateGroupsExpression @Override public TaskQuery includeAssignedTasks() { - if (candidateUser == null && candidateGroup == null && candidateGroups == null && !isWithCandidateGroups() && !isWithoutCandidateGroups() && !isWithCandidateUsers() && !isWithoutCandidateUsers() + if (candidateUser == null && candidateGroup == null && candidateGroupLike == null && candidateGroups == null + && !isWithCandidateGroups() && !isWithoutCandidateGroups() && !isWithCandidateUsers() && !isWithoutCandidateUsers() && !expressions.containsKey("taskCandidateUser") && !expressions.containsKey("taskCandidateGroup") && !expressions.containsKey("taskCandidateGroupIn")) { - throw new ProcessEngineException("Invalid query usage: candidateUser, candidateGroup, candidateGroupIn, withCandidateGroups, withoutCandidateGroups, withCandidateUsers, withoutCandidateUsers has to be called before 'includeAssignedTasks'."); + throw new ProcessEngineException("Invalid query usage: candidateUser, candidateGroup, candidateGroupLike, candidateGroupIn, withCandidateGroups, withoutCandidateGroups, withCandidateUsers, withoutCandidateUsers has to be called before 'includeAssignedTasks'."); } includeAssignedTasks = true; @@ -1578,6 +1592,10 @@ public String getCandidateGroup() { return candidateGroup; } + public String getCandidateGroupLike() { + return candidateGroupLike; + } + public boolean isIncludeAssignedTasks() { return includeAssignedTasks != null ? includeAssignedTasks : false; } @@ -1929,6 +1947,13 @@ else if (this.getCandidateGroup() != null) { extendedQuery.taskCandidateGroup(this.getCandidateGroup()); } + if (extendingQuery.getCandidateGroupLike() != null) { + extendedQuery.taskCandidateGroupLike(extendingQuery.getCandidateGroupLike()); + } + else if (this.getCandidateGroupLike() != null) { + extendedQuery.taskCandidateGroupLike(this.getCandidateGroupLike()); + } + if (extendingQuery.isWithCandidateGroups() || this.isWithCandidateGroups()) { extendedQuery.withCandidateGroups(); } diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java b/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java index 6632da9eabe..707f37c7834 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/json/JsonTaskQueryConverter.java @@ -62,6 +62,7 @@ public class JsonTaskQueryConverter extends JsonObjectConverter { public static final String DELEGATION_STATE = "delegationState"; public static final String CANDIDATE_USER = "candidateUser"; public static final String CANDIDATE_GROUP = "candidateGroup"; + public static final String CANDIDATE_GROUP_LIKE = "candidateGroupLike"; public static final String CANDIDATE_GROUPS = "candidateGroups"; public static final String WITH_CANDIDATE_GROUPS = "withCandidateGroups"; public static final String WITHOUT_CANDIDATE_GROUPS = "withoutCandidateGroups"; @@ -166,6 +167,7 @@ public JsonObject toJsonObject(TaskQuery taskQuery, boolean isOrQueryActive) { JsonUtil.addField(json, DELEGATION_STATE, query.getDelegationStateString()); JsonUtil.addField(json, CANDIDATE_USER, query.getCandidateUser()); JsonUtil.addField(json, CANDIDATE_GROUP, query.getCandidateGroup()); + JsonUtil.addField(json, CANDIDATE_GROUP_LIKE, query.getCandidateGroupLike()); JsonUtil.addListField(json, CANDIDATE_GROUPS, query.getCandidateGroupsInternal()); JsonUtil.addDefaultField(json, WITH_CANDIDATE_GROUPS, false, query.isWithCandidateGroups()); JsonUtil.addDefaultField(json, WITHOUT_CANDIDATE_GROUPS, false, query.isWithoutCandidateGroups()); @@ -363,6 +365,9 @@ protected TaskQuery toObject(JsonObject json, boolean isOrQuery) { if (json.has(CANDIDATE_GROUP)) { query.taskCandidateGroup(JsonUtil.getString(json, CANDIDATE_GROUP)); } + if (json.has(CANDIDATE_GROUP_LIKE)) { + query.taskCandidateGroupLike(JsonUtil.getString(json, CANDIDATE_GROUP_LIKE)); + } if (json.has(CANDIDATE_GROUPS) && !json.has(CANDIDATE_USER) && !json.has(CANDIDATE_GROUP)) { query.taskCandidateGroupIn(getList(JsonUtil.getArray(json, CANDIDATE_GROUPS))); } diff --git a/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java b/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java index 28d47be2c02..4bcc1edd0aa 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java +++ b/engine/src/main/java/org/camunda/bpm/engine/task/TaskQuery.java @@ -314,6 +314,25 @@ public interface TaskQuery extends Query { */ TaskQuery taskCandidateGroupExpression(String candidateGroupExpression); + /** + * Only select tasks whose candidate users belong to groups matching the given parameter. + * The syntax is that of SQL: for example usage: nameLike(%camunda%) + * + *

+ * Per default it only selects tasks which are not already assigned + * to a user. To also include assigned task in the result specify + * {@link #includeAssignedTasks()} in your query. + *

+ * + * @throws ProcessEngineException
  • When query is executed and {@link #taskCandidateUser(String)} or + * {@link #taskCandidateUserExpression(String)} (List)} has been executed on the + * "and query" instance.
    + * No exception is thrown when query is executed and {@link #taskCandidateUser(String)} or + * {@link #taskCandidateUserExpression(String)} has been executed on the "or query" instance.
  • + *
  • When passed group is null.
+ */ + TaskQuery taskCandidateGroupLike(String candidateGroupLike); + /** * Only select tasks for which the 'candidateGroup' is one of the given groups. * diff --git a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml index 50f8bf39802..db05924fd35 100644 --- a/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml +++ b/engine/src/main/resources/org/camunda/bpm/engine/impl/mapping/entity/Task.xml @@ -250,7 +250,7 @@ - + + + or + + + I.GROUP_ID_ LIKE #{candidateGroupLike} + ) diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java index 93ec9dec53a..ddcbe46388a 100644 --- a/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java +++ b/engine/src/test/java/org/camunda/bpm/engine/test/api/filter/FilterTaskQueryTest.java @@ -185,6 +185,7 @@ public void testTaskQuery() { query.taskUnassigned(); query.taskAssigned(); query.taskDelegationState(testDelegationState); + query.taskCandidateGroupLike(testString); query.taskCandidateGroupIn(testCandidateGroups); query.taskCandidateGroupInExpression(testString); query.withCandidateGroups(); @@ -290,6 +291,7 @@ public void testTaskQuery() { assertTrue(query.isUnassigned()); assertTrue(query.isAssigned()); assertEquals(testDelegationState, query.getDelegationState()); + assertEquals(testString, query.getCandidateGroupLike()); assertEquals(testCandidateGroups, query.getCandidateGroups()); assertTrue(query.isWithCandidateGroups()); assertTrue(query.isWithoutCandidateGroups()); @@ -657,6 +659,21 @@ public void testTaskQueryCandidateGroup() { assertEquals(testGroup.getId(), query.getExpressions().get("taskCandidateGroup")); } + @Test + public void testTaskQueryCandidateGroupLike() { + // given + TaskQueryImpl query = new TaskQueryImpl(); + query.taskCandidateGroupLike(testGroup.getId()); + + saveQuery(query); + + // when + query = filter.getQuery(); + + // then + assertEquals(testGroup.getId(), query.getCandidateGroupLike()); + } + @Test public void testTaskQueryCandidateUserIncludeAssignedTasks() { TaskQueryImpl query = new TaskQueryImpl(); @@ -709,6 +726,23 @@ public void testTaskQueryCandidateGroupExpressionIncludeAssignedTasks() { assertTrue(query.isIncludeAssignedTasks()); } + @Test + public void testTaskQueryCandidateGroupLikeIncludeAssignedTasks() { + // given + TaskQueryImpl query = new TaskQueryImpl(); + query.taskCandidateGroupLike(testGroup.getId()); + query.includeAssignedTasks(); + + saveQuery(query); + + // when + query = filter.getQuery(); + + // then + assertEquals(testGroup.getId(), query.getCandidateGroupLike()); + assertTrue(query.isIncludeAssignedTasks()); + } + @Test public void testTaskQueryCandidateGroupsIncludeAssignedTasks() { TaskQueryImpl query = new TaskQueryImpl(); @@ -823,6 +857,56 @@ public void testExtendingTaskQueryWithAssigneeNotIn() { assertEquals(0, extendingQueryTasks.size()); } + @Test + public void testExtendingEmptyTaskQueryWithCandidateGroupLike() { + // given 3 test tasks created during setup + TaskQuery query = taskService.createTaskQuery(); + saveQuery(query); + List tasks = filterService.list(filter.getId()); + assertEquals(3, tasks.size()); + + // when extending the query with a "candidate group like" + TaskQuery extendingQuery = taskService.createTaskQuery(); + extendingQuery.taskCandidateGroupLike("%count%"); + + // then there is 1 unassigned task with the candidate group "accounting" + tasks = filterService.list(filter.getId(), extendingQuery); + assertEquals(1, tasks.size()); + } + + @Test + public void testExtendingCandidateGroupLikeTaskQueryWithEmpty() { + // given 3 existing tasks but only 1 unassigned task that matches the initial filter + TaskQuery query = taskService.createTaskQuery().taskCandidateGroupLike("%count%"); + saveQuery(query); + List tasks = filterService.list(filter.getId()); + assertEquals(1, tasks.size()); + + // when extending the query with an empty query + TaskQuery extendingQuery = taskService.createTaskQuery(); + + // then the empty query should be ignored in favor of the existing value for "candidate group like" + tasks = filterService.list(filter.getId(), extendingQuery); + assertEquals(1, tasks.size()); + } + + @Test + public void testExtendingCandidateGroupLikeTaskQueryWithCandidateGroupLike() { + // given 3 existing tasks but zero match the initial filter + TaskQuery query = taskService.createTaskQuery().taskCandidateGroupLike("HR"); + saveQuery(query); + List tasks = filterService.list(filter.getId()); + assertTrue(tasks.isEmpty()); + + // when extending the query with a "candidate groups like" query + TaskQuery extendingQuery = taskService.createTaskQuery(); + extendingQuery.taskCandidateGroupLike("acc%"); + + // then the query should be return result of task matching the new filter + tasks = filterService.list(filter.getId(), extendingQuery); + assertEquals(1, tasks.size()); + } + @Test public void testExtendingTaskQueryListWithCandidateGroups() { TaskQuery query = taskService.createTaskQuery(); diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java index 688d7ab5d84..f3b64ed8427 100644 --- a/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java +++ b/engine/src/test/java/org/camunda/bpm/engine/test/api/task/TaskQueryTest.java @@ -36,6 +36,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -613,6 +614,70 @@ public void testQueryByIncludeAssignedTasksWithMissingCandidateUserOrGroup() { } } + @Test + public void testQueryByIncludeAssignedTasksWithoutMissingCandidateUserOrGroup() { + // We expect no exceptions when the there is at least 1 candidate user or group present + try { + taskService.createTaskQuery().taskCandidateUser("user").includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a taskCandidateUser is present"); + } + + try { + taskService.createTaskQuery().taskCandidateGroupLike("%group%").includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a candidateGroupLike is present"); + } + + try { + taskService.createTaskQuery().taskCandidateGroupIn(List.of("group")).includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a taskCandidateGroupIn is present"); + } + + try { + taskService.createTaskQuery().withCandidateGroups().includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a withCandidateGroups is present"); + } + + try { + taskService.createTaskQuery().withoutCandidateGroups().includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a withoutCandidateGroups is present"); + } + + try { + taskService.createTaskQuery().withCandidateUsers().includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a withCandidateUsers is present"); + } + + try { + taskService.createTaskQuery().withoutCandidateUsers().includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a withoutCandidateUsers is present"); + } + + try { + taskService.createTaskQuery().taskCandidateUserExpression("expression").includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a taskCandidateUserExpression is present"); + } + + try { + taskService.createTaskQuery().taskCandidateGroupExpression("expression").includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a taskCandidateGroupExpression is present"); + } + + try { + taskService.createTaskQuery().taskCandidateGroupInExpression("expression").includeAssignedTasks(); + } catch (ProcessEngineException e) { + fail("We expect no exceptions when a taskCandidateGroupInExpression is present"); + } + } + @Test public void testQueryByCandidateGroup() { // management group is candidate for 3 tasks, one of them is already assigned @@ -653,6 +718,58 @@ public void testQueryByCandidateGroup() { assertEquals(0, query.list().size()); } + @Test + public void testQueryByCandidateGroupLike() { + // management group is candidate for 3 tasks, one of them is already assigned + TaskQuery query = taskService.createTaskQuery().taskCandidateGroupLike("management"); + assertEquals(2, query.count()); + assertEquals(2, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test with "shortened" group name for like query + query = taskService.createTaskQuery().taskCandidateGroupLike("mana%"); + assertEquals(2, query.count()); + assertEquals(2, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test with "shortened" group name for like query (different part) + query = taskService.createTaskQuery().taskCandidateGroupLike("%ment"); + assertEquals(2, query.count()); + assertEquals(2, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test management candidates group with assigned tasks included + query = taskService.createTaskQuery().taskCandidateGroupLike("management").includeAssignedTasks(); + assertEquals(3, query.count()); + assertEquals(3, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test with "shortened" group name for like query (assigned tasks included) + query = taskService.createTaskQuery().taskCandidateGroupLike("mana%").includeAssignedTasks(); + assertEquals(3, query.count()); + assertEquals(3, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test with "shortened" group name for like query (different part, assigned tasks included) + query = taskService.createTaskQuery().taskCandidateGroupLike("%ment").includeAssignedTasks(); + assertEquals(3, query.count()); + assertEquals(3, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test query that matches tasks with the "management" the "accountancy" candidate groups + // accountancy group is candidate for 3 tasks, one of them is already assigned + query = taskService.createTaskQuery().taskCandidateGroupLike("%an%"); + assertEquals(4, query.count()); + assertEquals(4, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + + // test query that matches tasks with the "management" the "accountancy" candidate groups (assigned tasks included) + query = taskService.createTaskQuery().taskCandidateGroupLike("%an%").includeAssignedTasks(); + assertEquals(5, query.count()); + assertEquals(5, query.list().size()); + assertThrows(ProcessEngineException.class, query::singleResult); + } + @Test public void testQueryWithCandidateGroups() { // test withCandidateGroups