Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pref: user list supports searching by username #4451

Merged
merged 6 commits into from Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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