Skip to content

Commit e0bbdc3

Browse files
fix(api): eliminate duplicate entries in organization search users results
1 parent ecc4da1 commit e0bbdc3

File tree

2 files changed

+140
-1
lines changed

2 files changed

+140
-1
lines changed

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/UserServiceImpl.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
import java.util.Arrays;
149149
import java.util.Collection;
150150
import java.util.Collections;
151+
import java.util.Comparator;
151152
import java.util.Date;
152153
import java.util.HashMap;
153154
import java.util.HashSet;
@@ -1287,7 +1288,26 @@ public Page<UserEntity> search(ExecutionContext executionContext, String query,
12871288
SearchResult results = searchEngineService.search(executionContext, userQuery);
12881289

12891290
if (results.hasResults()) {
1290-
List<UserEntity> users = new ArrayList<>((findByIds(executionContext, results.getDocuments())));
1291+
Set<UserEntity> fetched = findByIds(executionContext, results.getDocuments());
1292+
Map<String, UserEntity> byId = fetched.stream().collect(Collectors.toMap(UserEntity::getId, u -> u));
1293+
1294+
List<UserEntity> users = new ArrayList<>(results.getDocuments().size());
1295+
Set<String> seen = new HashSet<>();
1296+
1297+
for (String id : results.getDocuments()) {
1298+
if (seen.add(id)) {
1299+
UserEntity u = byId.get(id);
1300+
if (u != null) {
1301+
users.add(u);
1302+
}
1303+
}
1304+
}
1305+
1306+
users.sort(
1307+
Comparator
1308+
.comparing(UserEntity::getFirstname, Comparator.nullsLast(String::compareToIgnoreCase))
1309+
.thenComparing(UserEntity::getLastname, Comparator.nullsLast(String::compareToIgnoreCase))
1310+
);
12911311

12921312
populateUserFlags(executionContext.getOrganizationId(), users);
12931313

@@ -1341,6 +1361,11 @@ public Page<UserEntity> search(ExecutionContext executionContext, UserCriteria c
13411361
.getContent()
13421362
.stream()
13431363
.map(u -> convert(u, false, userMetadataService.findAllByUserId(u.getId())))
1364+
.sorted(
1365+
Comparator
1366+
.comparing(UserEntity::getFirstname, Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))
1367+
.thenComparing(UserEntity::getLastname, Comparator.nullsLast(String.CASE_INSENSITIVE_ORDER))
1368+
)
13441369
.collect(toList());
13451370

13461371
populateUserFlags(executionContext.getOrganizationId(), entities);

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/UserServiceTest.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2221,4 +2221,118 @@ private MemberEntity mockMemberEntity() {
22212221
private InputStream read(String resource) throws IOException {
22222222
return this.getClass().getResourceAsStream(resource);
22232223
}
2224+
2225+
@Test
2226+
public void shouldSearchUsers_hasResults_ordersUniqueAndPopulateFlags() {
2227+
UserServiceImpl spyUserService = spy(userService);
2228+
2229+
// Search engine returns duplicated ids and one unknown id
2230+
List<String> docs = Arrays.asList("u1", "u2", "u1", "u3", "u4", "u3");
2231+
io.gravitee.rest.api.service.impl.search.SearchResult searchResult = new io.gravitee.rest.api.service.impl.search.SearchResult(
2232+
docs,
2233+
42
2234+
);
2235+
when(searchEngineService.search(eq(EXECUTION_CONTEXT), any())).thenReturn(searchResult);
2236+
2237+
// Prepare fetched users (u4 is missing on purpose)
2238+
UserEntity ue1 = new UserEntity();
2239+
ue1.setId("u1");
2240+
UserEntity ue2 = new UserEntity();
2241+
ue2.setId("u2");
2242+
UserEntity ue3 = new UserEntity();
2243+
ue3.setId("u3");
2244+
doReturn(new HashSet<>(Arrays.asList(ue1, ue2, ue3))).when(spyUserService).findByIds(eq(EXECUTION_CONTEXT), anyCollection());
2245+
2246+
// Mock roles for Primary Owner checks
2247+
RoleEntity apiPORole = mockRoleEntity(RoleScope.API, "PRIMARY_OWNER");
2248+
RoleEntity appPORole = mockRoleEntity(RoleScope.APPLICATION, "PRIMARY_OWNER");
2249+
when(roleService.findPrimaryOwnerRoleByOrganization(ORGANIZATION, RoleScope.API)).thenReturn(apiPORole);
2250+
when(roleService.findPrimaryOwnerRoleByOrganization(ORGANIZATION, RoleScope.APPLICATION)).thenReturn(appPORole);
2251+
2252+
// Only u2 is primary owner (API). Others not.
2253+
when(
2254+
membershipService.getMembershipsByMemberAndReferenceAndRole(
2255+
eq(MembershipMemberType.USER),
2256+
eq("u2"),
2257+
eq(MembershipReferenceType.API),
2258+
eq(apiPORole.getId())
2259+
)
2260+
)
2261+
.thenReturn(
2262+
java.util.Collections.<io.gravitee.rest.api.model.MembershipEntity>singleton(
2263+
new io.gravitee.rest.api.model.MembershipEntity()
2264+
)
2265+
);
2266+
when(
2267+
membershipService.getMembershipsByMemberAndReferenceAndRole(
2268+
eq(MembershipMemberType.USER),
2269+
eq("u1"),
2270+
eq(MembershipReferenceType.API),
2271+
eq(apiPORole.getId())
2272+
)
2273+
)
2274+
.thenReturn(java.util.Collections.<io.gravitee.rest.api.model.MembershipEntity>emptySet());
2275+
when(
2276+
membershipService.getMembershipsByMemberAndReferenceAndRole(
2277+
eq(MembershipMemberType.USER),
2278+
eq("u3"),
2279+
eq(MembershipReferenceType.API),
2280+
eq(apiPORole.getId())
2281+
)
2282+
)
2283+
.thenReturn(java.util.Collections.<io.gravitee.rest.api.model.MembershipEntity>emptySet());
2284+
2285+
// No application PO for anyone
2286+
when(
2287+
membershipService.getMembershipsByMemberAndReferenceAndRole(
2288+
eq(MembershipMemberType.USER),
2289+
anyString(),
2290+
eq(MembershipReferenceType.APPLICATION),
2291+
eq(appPORole.getId())
2292+
)
2293+
)
2294+
.thenReturn(java.util.Collections.<io.gravitee.rest.api.model.MembershipEntity>emptySet());
2295+
2296+
// Tokens per user: u1=1, u2=3, u3=0
2297+
when(tokenService.findByUser("u1")).thenReturn(Collections.singletonList(new io.gravitee.rest.api.model.TokenEntity()));
2298+
when(tokenService.findByUser("u2"))
2299+
.thenReturn(
2300+
Arrays.asList(
2301+
new io.gravitee.rest.api.model.TokenEntity(),
2302+
new io.gravitee.rest.api.model.TokenEntity(),
2303+
new io.gravitee.rest.api.model.TokenEntity()
2304+
)
2305+
);
2306+
when(tokenService.findByUser("u3")).thenReturn(Collections.emptyList());
2307+
2308+
io.gravitee.rest.api.model.common.Pageable pageable = new io.gravitee.rest.api.model.common.PageableImpl(2, 5);
2309+
2310+
io.gravitee.common.data.domain.Page<UserEntity> page = spyUserService.search(EXECUTION_CONTEXT, "john", pageable);
2311+
2312+
// Then: order preserved, duplicates removed, missing id filtered out
2313+
List<UserEntity> content = page.getContent();
2314+
assertEquals(3, content.size());
2315+
assertEquals("u1", content.get(0).getId());
2316+
assertEquals("u2", content.get(1).getId());
2317+
assertEquals("u3", content.get(2).getId());
2318+
2319+
// Page metadata
2320+
assertEquals(2, page.getPageNumber());
2321+
assertEquals(5, page.getPageElements());
2322+
assertEquals(42, page.getTotalElements());
2323+
2324+
// Flags populated
2325+
assertFalse(content.get(0).isPrimaryOwner());
2326+
assertTrue(content.get(1).isPrimaryOwner());
2327+
assertFalse(content.get(2).isPrimaryOwner());
2328+
2329+
assertEquals(1, content.get(0).getNbActiveTokens());
2330+
assertEquals(3, content.get(1).getNbActiveTokens());
2331+
assertEquals(0, content.get(2).getNbActiveTokens());
2332+
2333+
// Verify that search was called and populateUserFlags implied calls
2334+
verify(searchEngineService).search(eq(EXECUTION_CONTEXT), any());
2335+
verify(roleService).findPrimaryOwnerRoleByOrganization(ORGANIZATION, RoleScope.API);
2336+
verify(roleService).findPrimaryOwnerRoleByOrganization(ORGANIZATION, RoleScope.APPLICATION);
2337+
}
22242338
}

0 commit comments

Comments
 (0)