Skip to content

Commit

Permalink
pref: user list supports searching by username (#4451)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind improvement
/area core
/milestone 2.9.x

#### What this PR does / why we need it:

用户列表搜索支持按用户名搜索

#### Which issue(s) this PR fixes:

Fixes #4256 

#### Does this PR introduce a user-facing change?
```release-note
用户列表搜索支持按用户名搜索
```
  • Loading branch information
LIlGG committed Aug 25, 2023
1 parent 81cafb1 commit 1d9186c
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 50 deletions.
Expand Up @@ -33,8 +33,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.fn.builders.requestbody.Builder;
Expand Down Expand Up @@ -556,37 +554,39 @@ public Sort getSort() {
return SortResolver.defaultInstance.resolve(exchange);
}

/**
* Converts query parameters to user predicate.
*
* @return user predicate to filter users
*/
public Predicate<User> toPredicate() {
Predicate<User> displayNamePredicate = user -> {
Predicate<User> keywordPredicate = user -> {
var keyword = getKeyword();
if (!org.springframework.util.StringUtils.hasText(keyword)) {
if (StringUtils.isBlank(keyword)) {
return true;
}
var username = user.getMetadata().getName();
var displayName = user.getSpec().getDisplayName();
if (!org.springframework.util.StringUtils.hasText(displayName)) {
return false;
}
return displayName.toLowerCase().contains(keyword.trim().toLowerCase());
return StringUtils.containsIgnoreCase(displayName, keyword)
|| keyword.equalsIgnoreCase(username);
};

Predicate<User> rolePredicate = user -> {
var role = getRole();
if (role == null) {
var roleName = getRole();
if (StringUtils.isBlank(roleName)) {
return true;
}
var annotations = user.getMetadata().getAnnotations();
if (annotations == null || !annotations.containsKey(User.ROLE_NAMES_ANNO)) {
var roleNamesAnno = MetadataUtil.nullSafeAnnotations(user)
.get(User.ROLE_NAMES_ANNO);
if (StringUtils.isBlank(roleNamesAnno)) {
return false;
} else {
Pattern pattern = Pattern.compile("\\[\"([^\"]*)\"\\]");
Matcher matcher = pattern.matcher(annotations.get(User.ROLE_NAMES_ANNO));
if (matcher.find()) {
return matcher.group(1).equals(role);
} else {
return false;
}
}
Set<String> roleNames = JsonUtils.jsonToObject(roleNamesAnno,
new TypeReference<>() {
});
return roleNames.contains(roleName);
};
return displayNamePredicate
return keywordPredicate
.and(rolePredicate)
.and(labelAndFieldSelectorToPredicate(getLabelSelector(), getFieldSelector()));
}
Expand Down
@@ -0,0 +1,117 @@
package run.halo.app.core.extension.endpoint;

import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;

import java.time.Instant;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.User;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;

@SpringBootTest
@AutoConfigureWebTestClient
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@WithMockUser(username = "fake-user", password = "fake-password", roles = "fake-super-role")
public class UserEndpointIntegrationTest {
@Autowired
WebTestClient webClient;

@Autowired
ReactiveExtensionClient client;

@MockBean
RoleService roleService;

@BeforeEach
void setUp() {
var rule = new Role.PolicyRule.Builder()
.apiGroups("*")
.resources("*")
.verbs("*")
.build();
var role = new Role();
role.setMetadata(new Metadata());
role.getMetadata().setName("super-role");
role.setRules(List.of(rule));
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
webClient = webClient.mutateWith(csrf());
}

@Nested
class UserListTest {
@Test
void shouldFilterUsersWhenDisplayNameKeywordProvided() {
var expectUser =
createUser("fake-user-2", "expected display name");
var unexpectedUser1 =
createUser("fake-user-1", "first fake display name");
var unexpectedUser2 =
createUser("fake-user-3", "second fake display name");

client.create(expectUser).block();
client.create(unexpectedUser1).block();
client.create(unexpectedUser2).block();

when(roleService.list(anySet())).thenReturn(Flux.empty());

webClient.get().uri("/apis/api.console.halo.run/v1alpha1/users?keyword=Expected")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.items.length()").isEqualTo(1)
.jsonPath("$.items[0].user.metadata.name").isEqualTo("fake-user-2");

}

@Test
void shouldFilterUsersWhenUserNameKeywordProvided() {
var expectUser =
createUser("fake-user", "expected display name");
var unexpectedUser1 =
createUser("fake-user-1", "first fake display name");
var unexpectedUser2 =
createUser("fake-user-3", "second fake display name");

client.create(expectUser).block();
client.create(unexpectedUser1).block();
client.create(unexpectedUser2).block();

when(roleService.list(anySet())).thenReturn(Flux.empty());

webClient.get().uri("/apis/api.console.halo.run/v1alpha1/users?keyword=fake-user")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$.items.length()").isEqualTo(1)
.jsonPath("$.items[0].user.metadata.name").isEqualTo("fake-user");
}
}

User createUser(String name, String displayName) {
var metadata = new Metadata();
metadata.setName(name);
metadata.setCreationTimestamp(Instant.now());
var spec = new User.UserSpec();
spec.setEmail("fake-email");
spec.setDisplayName(displayName);
var user = new User();
user.setMetadata(metadata);
user.setSpec(spec);
return user;
}
}
Expand Up @@ -125,35 +125,6 @@ void shouldListUsersWhenUserPresent() {
.jsonPath("$.total").isEqualTo(3);
}

@Test
void shouldFilterUsersWhenKeywordProvided() {
var expectUser =
createUser("fake-user-2", "expected display name");
var unexpectedUser1 =
createUser("fake-user-1", "first fake display name");
var unexpectedUser2 =
createUser("fake-user-3", "second fake display name");
var users = List.of(
expectUser
);
var expectResult = new ListResult<>(users);
when(client.list(same(User.class), any(), any(), anyInt(), anyInt()))
.thenReturn(Mono.just(expectResult));
when(roleService.list(anySet())).thenReturn(Flux.empty());

bindToRouterFunction(endpoint.endpoint())
.build()
.get().uri("/users?keyword=Expected")
.exchange()
.expectStatus().isOk();

verify(client).list(same(User.class), argThat(
predicate -> predicate.test(expectUser)
&& !predicate.test(unexpectedUser1)
&& !predicate.test(unexpectedUser2)),
any(), anyInt(), anyInt());
}

@Test
void shouldFilterUsersWhenRoleProvided() {
var expectUser =
Expand Down

0 comments on commit 1d9186c

Please sign in to comment.