mirror of https://github.com/halo-dev/halo
pref: user list supports searching by username (#4451)
#### 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 用户列表搜索支持按用户名搜索 ```pull/4482/head
parent
81cafb14bb
commit
1d9186c1db
|
@ -33,8 +33,6 @@ import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springdoc.core.fn.builders.requestbody.Builder;
|
import org.springdoc.core.fn.builders.requestbody.Builder;
|
||||||
|
@ -556,37 +554,39 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
return SortResolver.defaultInstance.resolve(exchange);
|
return SortResolver.defaultInstance.resolve(exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts query parameters to user predicate.
|
||||||
|
*
|
||||||
|
* @return user predicate to filter users
|
||||||
|
*/
|
||||||
public Predicate<User> toPredicate() {
|
public Predicate<User> toPredicate() {
|
||||||
Predicate<User> displayNamePredicate = user -> {
|
Predicate<User> keywordPredicate = user -> {
|
||||||
var keyword = getKeyword();
|
var keyword = getKeyword();
|
||||||
if (!org.springframework.util.StringUtils.hasText(keyword)) {
|
if (StringUtils.isBlank(keyword)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
var username = user.getMetadata().getName();
|
||||||
var displayName = user.getSpec().getDisplayName();
|
var displayName = user.getSpec().getDisplayName();
|
||||||
if (!org.springframework.util.StringUtils.hasText(displayName)) {
|
return StringUtils.containsIgnoreCase(displayName, keyword)
|
||||||
return false;
|
|| keyword.equalsIgnoreCase(username);
|
||||||
}
|
|
||||||
return displayName.toLowerCase().contains(keyword.trim().toLowerCase());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Predicate<User> rolePredicate = user -> {
|
Predicate<User> rolePredicate = user -> {
|
||||||
var role = getRole();
|
var roleName = getRole();
|
||||||
if (role == null) {
|
if (StringUtils.isBlank(roleName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
var annotations = user.getMetadata().getAnnotations();
|
var roleNamesAnno = MetadataUtil.nullSafeAnnotations(user)
|
||||||
if (annotations == null || !annotations.containsKey(User.ROLE_NAMES_ANNO)) {
|
.get(User.ROLE_NAMES_ANNO);
|
||||||
|
if (StringUtils.isBlank(roleNamesAnno)) {
|
||||||
return false;
|
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(rolePredicate)
|
||||||
.and(labelAndFieldSelectorToPredicate(getLabelSelector(), getFieldSelector()));
|
.and(labelAndFieldSelectorToPredicate(getLabelSelector(), getFieldSelector()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,35 +125,6 @@ class UserEndpointTest {
|
||||||
.jsonPath("$.total").isEqualTo(3);
|
.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
|
@Test
|
||||||
void shouldFilterUsersWhenRoleProvided() {
|
void shouldFilterUsersWhenRoleProvided() {
|
||||||
var expectUser =
|
var expectUser =
|
||||||
|
|
Loading…
Reference in New Issue