This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 00a4a9c464 [SYNCOPE-1981] Search audit events by who (#1438)
00a4a9c464 is described below

commit 00a4a9c464d7547585532d8cb7643ebd53db0968
Author: Oleg Zimakov <[email protected]>
AuthorDate: Fri Jun 26 02:56:47 2026 -0700

    [SYNCOPE-1981] Search audit events by who (#1438)
---
 .../syncope/common/rest/api/beans/AuditQuery.java  | 22 +++++++
 .../org/apache/syncope/core/logic/AuditLogic.java  |  5 +-
 .../core/rest/cxf/service/AuditServiceImpl.java    |  1 +
 .../core/persistence/api/dao/AuditEventDAO.java    |  3 +
 .../core/persistence/jpa/dao/JPAAuditEventDAO.java | 17 +++++
 .../persistence/neo4j/dao/Neo4jAuditEventDAO.java  | 14 +++++
 .../dao/ElasticsearchAuditEventDAO.java            | 16 ++++-
 .../opensearch/dao/OpenSearchAuditEventDAO.java    | 16 ++++-
 .../org/apache/syncope/fit/core/AuditITCase.java   | 72 ++++++++++++++++++++++
 9 files changed, 160 insertions(+), 6 deletions(-)

diff --git 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
index cddcf3b616..9d82c912a0 100644
--- 
a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
+++ 
b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AuditQuery.java
@@ -19,8 +19,11 @@
 package org.apache.syncope.common.rest.api.beans;
 
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.ws.rs.QueryParam;
+import java.util.HashSet;
+import java.util.Set;
 import org.apache.syncope.common.lib.types.OpEvent;
 import org.apache.syncope.common.rest.api.service.JAXRSService;
 
@@ -40,6 +43,11 @@ public class AuditQuery extends AbstractTimeframeQuery {
             return this;
         }
 
+        public Builder who(final String who) {
+            getInstance().getWho().add(who);
+            return this;
+        }
+
         public Builder type(final OpEvent.CategoryType type) {
             getInstance().setType(type);
             return this;
@@ -68,6 +76,8 @@ public class AuditQuery extends AbstractTimeframeQuery {
 
     private String entityKey;
 
+    private Set<String> who = new HashSet<>();
+
     private OpEvent.CategoryType type;
 
     private String category;
@@ -89,6 +99,18 @@ public class AuditQuery extends AbstractTimeframeQuery {
         this.entityKey = entityKey;
     }
 
+    @Parameter(name = "who", description = "audit event author(s) (username) 
to match; "
+            + "may be repeated to match any of the given values", array =
+            @ArraySchema(schema = @Schema(implementation = String.class)))
+    public Set<String> getWho() {
+        return who;
+    }
+
+    @QueryParam("who")
+    public void setWho(final Set<String> who) {
+        this.who = who;
+    }
+
     @Parameter(name = "type", description = "audit type to match", schema =
             @Schema(implementation = OpEvent.CategoryType.class))
     public OpEvent.CategoryType getType() {
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
index 89c9718cb7..291c5291e6 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -275,6 +275,7 @@ public class AuditLogic extends 
AbstractTransactionalLogic<AuditConfTO> {
     @Transactional(readOnly = true)
     public Page<AuditEventTO> search(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -284,10 +285,10 @@ public class AuditLogic extends 
AbstractTransactionalLogic<AuditConfTO> {
             final OffsetDateTime after,
             final Pageable pageable) {
 
-        long count = auditEventDAO.count(entityKey, type, category, 
subcategory, op, result, before, after);
+        long count = auditEventDAO.count(entityKey, who, type, category, 
subcategory, op, result, before, after);
 
         List<AuditEventTO> matching = auditEventDAO.search(
-                entityKey, type, category, subcategory, op, result, before, 
after, pageable);
+                entityKey, who, type, category, subcategory, op, result, 
before, after, pageable);
 
         return new SyncopePage<>(matching, pageable, count);
     }
diff --git 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
index 4d4578d462..468830cf5e 100644
--- 
a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
+++ 
b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuditServiceImpl.java
@@ -70,6 +70,7 @@ public class AuditServiceImpl extends AbstractService 
implements AuditService {
     public PagedResult<AuditEventTO> search(final AuditQuery auditQuery) {
         Page<AuditEventTO> result = logic.search(
                 auditQuery.getEntityKey(),
+                auditQuery.getWho(),
                 auditQuery.getType(),
                 auditQuery.getCategory(),
                 auditQuery.getSubcategory(),
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
index 6c5822ae0c..a2f9e2a107 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AuditEventDAO.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.persistence.api.dao;
 
 import java.time.OffsetDateTime;
 import java.util.List;
+import java.util.Set;
 import org.apache.syncope.common.lib.to.AuditEventTO;
 import org.apache.syncope.common.lib.types.OpEvent;
 import org.apache.syncope.core.persistence.api.entity.AuditEvent;
@@ -31,6 +32,7 @@ public interface AuditEventDAO {
 
     long count(
             String entityKey,
+            Set<String> who,
             OpEvent.CategoryType type,
             String category,
             String subcategory,
@@ -54,6 +56,7 @@ public interface AuditEventDAO {
 
     List<AuditEventTO> search(
             String entityKey,
+            Set<String> who,
             OpEvent.CategoryType type,
             String category,
             String subcategory,
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
index 307dcc22eb..df621d77cd 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAuditEventDAO.java
@@ -24,6 +24,7 @@ import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.AuditEventTO;
 import org.apache.syncope.common.lib.types.OpEvent;
@@ -34,6 +35,7 @@ import 
org.apache.syncope.core.persistence.jpa.entity.JPAAuditEvent;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 public class JPAAuditEventDAO extends AbstractAuditEventDAO implements 
AuditEventDAO {
 
@@ -61,6 +63,17 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO 
implements AuditEven
             return this;
         }
 
+        public AuditEventCriteriaBuilder who(final Set<String> who, final 
List<Object> parameters) {
+            if (!CollectionUtils.isEmpty(who)) {
+                query.append(andIfNeeded()).append("who IN (").
+                        append(who.stream().
+                                map(value -> "?" + setParameter(parameters, 
value)).
+                                collect(Collectors.joining(", "))).
+                        append(")");
+            }
+            return this;
+        }
+
         public AuditEventCriteriaBuilder opEvent(
                 final OpEvent.CategoryType type,
                 final String category,
@@ -127,6 +140,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO 
implements AuditEven
     @Override
     public long count(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -139,6 +153,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO 
implements AuditEven
         String queryString = "SELECT COUNT(0)"
                 + " FROM " + JPAAuditEvent.TABLE
                 + " WHERE" + criteriaBuilder(entityKey).
+                        who(who, parameters).
                         opEvent(type, category, subcategory, op, outcome).
                         before(before, parameters).
                         after(after, parameters).
@@ -153,6 +168,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO 
implements AuditEven
     @Override
     public List<AuditEventTO> search(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -166,6 +182,7 @@ public class JPAAuditEventDAO extends AbstractAuditEventDAO 
implements AuditEven
         String queryString = "SELECT id"
                 + " FROM " + JPAAuditEvent.TABLE
                 + " WHERE" + criteriaBuilder(entityKey).
+                        who(who, parameters).
                         opEvent(type, category, subcategory, op, outcome).
                         before(before, parameters).
                         after(after, parameters).
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
index 6cc3d0903c..2c59bdbf0a 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
@@ -23,6 +23,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.AuditEventTO;
 import org.apache.syncope.common.lib.types.OpEvent;
@@ -36,6 +37,7 @@ import org.springframework.data.domain.Sort;
 import org.springframework.data.neo4j.core.Neo4jClient;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 public class Neo4jAuditEventDAO extends AbstractAuditEventDAO implements 
AuditEventDAO {
 
@@ -58,6 +60,14 @@ public class Neo4jAuditEventDAO extends 
AbstractAuditEventDAO implements AuditEv
             return this;
         }
 
+        public AuditEventCriteriaBuilder who(final Set<String> who, final 
Map<String, Object> parameters) {
+            if (!CollectionUtils.isEmpty(who)) {
+                parameters.put("who", List.copyOf(who));
+                query.append(andIfNeeded()).append("n.who IN $who");
+            }
+            return this;
+        }
+
         public AuditEventCriteriaBuilder opEvent(
                 final OpEvent.CategoryType type,
                 final String category,
@@ -125,6 +135,7 @@ public class Neo4jAuditEventDAO extends 
AbstractAuditEventDAO implements AuditEv
     @Override
     public long count(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -136,6 +147,7 @@ public class Neo4jAuditEventDAO extends 
AbstractAuditEventDAO implements AuditEv
         Map<String, Object> parameters = new HashMap<>();
         String query = "MATCH (n:" + Neo4jAuditEvent.NODE + ") "
                 + " WHERE " + criteriaBuilder(entityKey).
+                        who(who, parameters).
                         opEvent(type, category, subcategory, op, outcome).
                         before(before, parameters).
                         after(after, parameters).
@@ -148,6 +160,7 @@ public class Neo4jAuditEventDAO extends 
AbstractAuditEventDAO implements AuditEv
     @Override
     public List<AuditEventTO> search(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -161,6 +174,7 @@ public class Neo4jAuditEventDAO extends 
AbstractAuditEventDAO implements AuditEv
 
         StringBuilder query = new StringBuilder("MATCH (n:" + 
Neo4jAuditEvent.NODE + ") "
                 + "WHERE " + criteriaBuilder(entityKey).
+                        who(who, parameters).
                         opEvent(type, category, subcategory, op, outcome).
                         before(before, parameters).
                         after(after, parameters).
diff --git 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
index 4fe92c1fe7..2798489ee5 100644
--- 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
+++ 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAuditEventDAO.java
@@ -35,6 +35,7 @@ import java.io.IOException;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.AuditEventTO;
 import org.apache.syncope.common.lib.types.OpEvent;
@@ -81,6 +82,7 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
 
     protected Query getQuery(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -99,6 +101,14 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
                             query("\"key\":\"" + entityKey + 
"\"").build()).build());
         }
 
+        if (!CollectionUtils.isEmpty(who)) {
+            List<Query> whoQueries = who.stream().map(value -> new 
Query.Builder().
+                    
term(QueryBuilders.term().field("who").value(value).build()).build()).
+                    toList();
+            queries.add(new Query.Builder().
+                    
bool(QueryBuilders.bool().should(whoQueries).minimumShouldMatch("1").build()).build());
+        }
+
         queries.add(new Query.Builder().regexp(QueryBuilders.regexp().
                 field("opEvent").
                 value(OpEvent.toString(type, category, subcategory, op, 
outcome).
@@ -127,6 +137,7 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
     @Override
     public long count(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -137,7 +148,7 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
 
         CountRequest request = new CountRequest.Builder().
                 
index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
-                query(getQuery(entityKey, type, category, subcategory, op, 
outcome, before, after)).
+                query(getQuery(entityKey, who, type, category, subcategory, 
op, outcome, before, after)).
                 build();
         LOG.debug("Count request: {}", request);
 
@@ -161,6 +172,7 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
     @Override
     public List<AuditEventTO> search(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -173,7 +185,7 @@ public class ElasticsearchAuditEventDAO implements 
AuditEventDAO {
         SearchRequest request = new SearchRequest.Builder().
                 
index(ElasticsearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
                 searchType(SearchType.QueryThenFetch).
-                query(getQuery(entityKey, type, category, subcategory, op, 
outcome, before, after)).
+                query(getQuery(entityKey, who, type, category, subcategory, 
op, outcome, before, after)).
                 from(pageable.isUnpaged() ? 0 : pageable.getPageSize() * 
pageable.getPageNumber()).
                 size(pageable.isUnpaged() ? indexMaxResultWindow : 
pageable.getPageSize()).
                 sort(sortBuilders(pageable.getSort().get())).
diff --git 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
index 71833f4ce3..02bf3be892 100644
--- 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
+++ 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAuditEventDAO.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.time.OffsetDateTime;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.syncope.common.lib.to.AuditEventTO;
 import org.apache.syncope.common.lib.types.OpEvent;
@@ -80,6 +81,7 @@ public class OpenSearchAuditEventDAO implements AuditEventDAO 
{
 
     protected Query getQuery(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -98,6 +100,14 @@ public class OpenSearchAuditEventDAO implements 
AuditEventDAO {
                             query("\"key\":\"" + entityKey + 
"\"").build()).build());
         }
 
+        if (!CollectionUtils.isEmpty(who)) {
+            List<Query> whoQueries = who.stream().map(value -> new 
Query.Builder().
+                    term(QueryBuilders.term().field("who").value(v -> 
v.stringValue(value)).build()).build()).
+                    toList();
+            queries.add(new Query.Builder().
+                    
bool(QueryBuilders.bool().should(whoQueries).minimumShouldMatch("1").build()).build());
+        }
+
         queries.add(new Query.Builder().regexp(QueryBuilders.regexp().
                 field("opEvent").
                 value(OpEvent.toString(type, category, subcategory, op, 
outcome).
@@ -126,6 +136,7 @@ public class OpenSearchAuditEventDAO implements 
AuditEventDAO {
     @Override
     public long count(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -136,7 +147,7 @@ public class OpenSearchAuditEventDAO implements 
AuditEventDAO {
 
         CountRequest request = new CountRequest.Builder().
                 
index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
-                query(getQuery(entityKey, type, category, subcategory, op, 
outcome, before, after)).
+                query(getQuery(entityKey, who, type, category, subcategory, 
op, outcome, before, after)).
                 build();
         LOG.debug("Count request: {}", request);
 
@@ -160,6 +171,7 @@ public class OpenSearchAuditEventDAO implements 
AuditEventDAO {
     @Override
     public List<AuditEventTO> search(
             final String entityKey,
+            final Set<String> who,
             final OpEvent.CategoryType type,
             final String category,
             final String subcategory,
@@ -172,7 +184,7 @@ public class OpenSearchAuditEventDAO implements 
AuditEventDAO {
         SearchRequest request = new SearchRequest.Builder().
                 
index(OpenSearchUtils.getAuditIndex(AuthContextUtils.getDomain())).
                 searchType(SearchType.QueryThenFetch).
-                query(getQuery(entityKey, type, category, subcategory, op, 
outcome, before, after)).
+                query(getQuery(entityKey, who, type, category, subcategory, 
op, outcome, before, after)).
                 from(pageable.isUnpaged() ? 0 : pageable.getPageSize() * 
pageable.getPageNumber()).
                 size(pageable.isUnpaged() ? indexMaxResultWindow : 
pageable.getPageSize()).
                 sort(sortBuilders(pageable.getSort().get())).
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
index bbaedc394d..7d94db44a6 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -161,6 +161,78 @@ public class AuditITCase extends AbstractITCase {
         USER_SERVICE.delete(userTO.getKey());
     }
 
+    @Test
+    public void findByWho() {
+        UserTO userTO = 
createUser(UserITCase.getUniqueSample("[email protected]")).getEntity();
+        assertNotNull(userTO.getKey());
+
+        // exact match on the author of the just-created user's create event
+        AuditQuery exact = new AuditQuery.Builder().
+                entityKey(userTO.getKey()).
+                who(ADMIN_UNAME).
+                type(OpEvent.CategoryType.LOGIC).
+                category(UserLogic.class.getSimpleName()).
+                op("create").
+                outcome(OpEvent.Outcome.SUCCESS).
+                build();
+        assertFalse(query(exact, MAX_WAIT_SECONDS).isEmpty());
+
+        // multiple who values are OR'ed: admin still matches alongside an 
unrelated author
+        AuditQuery multiple = new AuditQuery.Builder().
+                entityKey(userTO.getKey()).
+                who(ADMIN_UNAME).
+                who("non-existent-" + UUID.randomUUID()).
+                type(OpEvent.CategoryType.LOGIC).
+                category(UserLogic.class.getSimpleName()).
+                op("create").
+                outcome(OpEvent.Outcome.SUCCESS).
+                build();
+        assertFalse(AUDIT_SERVICE.search(multiple).getResult().isEmpty());
+
+        // the who filter excludes non-matching authors
+        AuditQuery noMatch = new AuditQuery.Builder().
+                entityKey(userTO.getKey()).
+                who("non-existent-" + UUID.randomUUID()).
+                type(OpEvent.CategoryType.LOGIC).
+                category(UserLogic.class.getSimpleName()).
+                op("create").
+                outcome(OpEvent.Outcome.SUCCESS).
+                build();
+        assertTrue(AUDIT_SERVICE.search(noMatch).getResult().isEmpty());
+
+        USER_SERVICE.delete(userTO.getKey());
+    }
+
+    @Test
+    public void findByWhoIsExactMatch() {
+        UserTO userTO = 
createUser(UserITCase.getUniqueSample("[email protected]")).getEntity();
+        assertNotNull(userTO.getKey());
+
+        // make sure the create event is there (matched exactly by the admin 
author)
+        AuditQuery exact = new AuditQuery.Builder().
+                entityKey(userTO.getKey()).
+                who(ADMIN_UNAME).
+                type(OpEvent.CategoryType.LOGIC).
+                category(UserLogic.class.getSimpleName()).
+                op("create").
+                outcome(OpEvent.Outcome.SUCCESS).
+                build();
+        assertFalse(query(exact, MAX_WAIT_SECONDS).isEmpty());
+
+        // who matches exactly: a '*' is treated literally, not as a wildcard
+        AuditQuery wildcard = new AuditQuery.Builder().
+                entityKey(userTO.getKey()).
+                who(ADMIN_UNAME.substring(0, 3) + '*').
+                type(OpEvent.CategoryType.LOGIC).
+                category(UserLogic.class.getSimpleName()).
+                op("create").
+                outcome(OpEvent.Outcome.SUCCESS).
+                build();
+        assertTrue(AUDIT_SERVICE.search(wildcard).getResult().isEmpty());
+
+        USER_SERVICE.delete(userTO.getKey());
+    }
+
     @Test
     public void findByGroup() {
         GroupTO groupTO = 
createGroup(GroupITCase.getBasicSample("AuditGroup")).getEntity();

Reply via email to