Add detailed search result ui (#1738)

Add a dedicated search page with more results and different types.
Users and groups are now indexed along with repositories.

Co-authored-by: René Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
Sebastian Sdorra
2021-07-28 11:19:00 +02:00
committed by GitHub
parent ad6000722d
commit 91fec0f478
60 changed files with 2665 additions and 517 deletions

View File

@@ -39,6 +39,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.search.Hit;
import sonia.scm.search.IndexNames;
import sonia.scm.search.QueryCountResult;
import sonia.scm.search.QueryResult;
import sonia.scm.search.SearchEngine;
import sonia.scm.web.JsonMockHttpResponse;
@@ -182,6 +183,25 @@ class SearchResourceTest {
assertThat(highlightedField.get("fragments").get(0).asText()).isEqualTo("Hello");
}
@Test
void shouldReturnCountOnly() throws URISyntaxException {
when(
searchEngine.search(IndexNames.DEFAULT)
.count("string", "Hello")
).thenReturn(new QueryCountResult(String.class, 2L));
MockHttpRequest request = MockHttpRequest.get("/v2/search/query/string?q=Hello&countOnly=true");
JsonMockHttpResponse response = new JsonMockHttpResponse();
dispatcher.invoke(request, response);
JsonNode node = response.getContentAsJson();
assertThat(node.get("totalHits").asLong()).isEqualTo(2L);
JsonNode hits = node.get("_embedded").get("hits");
assertThat(hits.size()).isZero();
}
}
private void assertLink(JsonNode links, String self, String s) {
@@ -245,6 +265,7 @@ class SearchResourceTest {
return response;
}
@Getter
@Setter
public static class SampleEmbedded extends HalRepresentation {

View File

@@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.group;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.HandlerEventType;
import sonia.scm.search.Id;
import sonia.scm.search.Index;
import sonia.scm.search.IndexQueue;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class GroupIndexerTest {
@Mock
private GroupManager groupManager;
@Mock
private IndexQueue indexQueue;
@InjectMocks
private GroupIndexer indexer;
@Test
void shouldReturnRepositoryClass() {
assertThat(indexer.getType()).isEqualTo(Group.class);
}
@Test
void shouldReturnIndexName() {
assertThat(indexer.getIndex()).isEqualTo(GroupIndexer.INDEX);
}
@Test
void shouldReturnVersion() {
assertThat(indexer.getVersion()).isEqualTo(GroupIndexer.VERSION);
}
@Nested
class UpdaterTests {
@Mock
private Index index;
private final Group group = new Group("xml", "astronauts");
@BeforeEach
void open() {
when(indexQueue.getQueuedIndex(GroupIndexer.INDEX)).thenReturn(index);
}
@Test
void shouldStoreRepository() {
indexer.open().store(group);
verify(index).store(Id.of(group), "group:read:astronauts", group);
}
@Test
void shouldDeleteByRepository() {
indexer.open().delete(group);
verify(index).delete(Id.of(group), Group.class);
}
@Test
void shouldReIndexAll() {
when(groupManager.getAll()).thenReturn(singletonList(group));
indexer.open().reIndexAll();
verify(index).deleteByType(Group.class);
verify(index).store(Id.of(group), "group:read:astronauts", group);
}
@Test
void shouldHandleEvent() {
GroupEvent event = new GroupEvent(HandlerEventType.DELETE, group);
indexer.handleEvent(event);
verify(index).delete(Id.of(group), Group.class);
}
@Test
void shouldCloseIndex() {
indexer.open().close();
verify(index).close();
}
}
}

View File

@@ -1,147 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.HandlerEventType;
import sonia.scm.search.Id;
import sonia.scm.search.Index;
import sonia.scm.search.IndexLog;
import sonia.scm.search.IndexLogStore;
import sonia.scm.search.IndexNames;
import sonia.scm.search.IndexQueue;
import sonia.scm.web.security.AdministrationContext;
import java.util.List;
import java.util.Optional;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class IndexUpdateListenerTest {
@Mock
private RepositoryManager repositoryManager;
@Mock
private AdministrationContext administrationContext;
@Mock
private IndexQueue indexQueue;
@Mock
private Index index;
@Mock
private IndexLogStore indexLogStore;
@InjectMocks
private IndexUpdateListener updateListener;
@Test
@SuppressWarnings("java:S6068")
void shouldIndexAllRepositories() {
when(indexLogStore.get(IndexNames.DEFAULT, Repository.class)).thenReturn(Optional.empty());
doAnswer(ic -> {
IndexUpdateListener.ReIndexAll reIndexAll = new IndexUpdateListener.ReIndexAll(repositoryManager, indexQueue);
reIndexAll.run();
return null;
})
.when(administrationContext)
.runAsAdmin(IndexUpdateListener.ReIndexAll.class);
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
Repository puzzle42 = RepositoryTestData.create42Puzzle();
List<Repository> repositories = ImmutableList.of(heartOfGold, puzzle42);
when(repositoryManager.getAll()).thenReturn(repositories);
when(indexQueue.getQueuedIndex(IndexNames.DEFAULT)).thenReturn(index);
updateListener.contextInitialized(null);
verify(index).store(Id.of(heartOfGold), RepositoryPermissions.read(heartOfGold).asShiroString(), heartOfGold);
verify(index).store(Id.of(puzzle42), RepositoryPermissions.read(puzzle42).asShiroString(), puzzle42);
verify(index).close();
verify(indexLogStore).log(IndexNames.DEFAULT, Repository.class, IndexUpdateListener.INDEX_VERSION);
}
@Test
void shouldSkipReIndex() {
IndexLog log = new IndexLog(1);
when(indexLogStore.get(IndexNames.DEFAULT, Repository.class)).thenReturn(Optional.of(log));
updateListener.contextInitialized(null);
verifyNoInteractions(indexQueue);
}
@ParameterizedTest
@EnumSource(value = HandlerEventType.class, mode = EnumSource.Mode.MATCH_ANY, names = "BEFORE_.*")
void shouldIgnoreBeforeEvents(HandlerEventType type) {
RepositoryEvent event = new RepositoryEvent(type, RepositoryTestData.create42Puzzle());
updateListener.handleEvent(event);
verifyNoInteractions(indexQueue);
}
@ParameterizedTest
@EnumSource(value = HandlerEventType.class, mode = EnumSource.Mode.INCLUDE, names = {"CREATE", "MODIFY"})
void shouldStore(HandlerEventType type) {
when(indexQueue.getQueuedIndex(IndexNames.DEFAULT)).thenReturn(index);
Repository puzzle = RepositoryTestData.create42Puzzle();
RepositoryEvent event = new RepositoryEvent(type, puzzle);
updateListener.handleEvent(event);
verify(index).store(Id.of(puzzle), RepositoryPermissions.read(puzzle).asShiroString(), puzzle);
verify(index).close();
}
@Test
void shouldDelete() {
when(indexQueue.getQueuedIndex(IndexNames.DEFAULT)).thenReturn(index);
Repository puzzle = RepositoryTestData.create42Puzzle();
RepositoryEvent event = new RepositoryEvent(HandlerEventType.DELETE, puzzle);
updateListener.handleEvent(event);
verify(index).deleteByRepository(puzzle.getId());
verify(index).close();
}
}

View File

@@ -0,0 +1,128 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.HandlerEventType;
import sonia.scm.search.Id;
import sonia.scm.search.Index;
import sonia.scm.search.IndexQueue;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class RepositoryIndexerTest {
@Mock
private RepositoryManager repositoryManager;
@Mock
private IndexQueue indexQueue;
@InjectMocks
private RepositoryIndexer indexer;
@Test
void shouldReturnRepositoryClass() {
assertThat(indexer.getType()).isEqualTo(Repository.class);
}
@Test
void shouldReturnIndexName() {
assertThat(indexer.getIndex()).isEqualTo(RepositoryIndexer.INDEX);
}
@Test
void shouldReturnVersion() {
assertThat(indexer.getVersion()).isEqualTo(RepositoryIndexer.VERSION);
}
@Nested
class UpdaterTests {
@Mock
private Index index;
private Repository repository;
@BeforeEach
void open() {
when(indexQueue.getQueuedIndex(RepositoryIndexer.INDEX)).thenReturn(index);
repository = new Repository();
repository.setId("42");
}
@Test
void shouldStoreRepository() {
indexer.open().store(repository);
verify(index).store(Id.of(repository), "repository:read:42", repository);
}
@Test
void shouldDeleteByRepository() {
indexer.open().delete(repository);
verify(index).deleteByRepository("42");
}
@Test
void shouldReIndexAll() {
when(repositoryManager.getAll()).thenReturn(singletonList(repository));
indexer.open().reIndexAll();
verify(index).deleteByTypeName(Repository.class.getName());
verify(index).deleteByType(Repository.class);
verify(index).store(Id.of(repository), "repository:read:42", repository);
}
@Test
void shouldHandleEvent() {
RepositoryEvent event = new RepositoryEvent(HandlerEventType.DELETE, repository);
indexer.handleEvent(event);
verify(index).deleteByRepository("42");
}
@Test
void shouldCloseIndex() {
indexer.open().close();
verify(index).close();
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.search;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.HandlerEventType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryTestData;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class HandlerEventIndexSyncerTest {
@Mock
private Indexer<Repository> indexer;
@Mock
private Indexer.Updater<Repository> updater;
@ParameterizedTest
@EnumSource(value = HandlerEventType.class, mode = EnumSource.Mode.MATCH_ANY, names = "BEFORE_.*")
void shouldIgnoreBeforeEvents(HandlerEventType type) {
RepositoryEvent event = new RepositoryEvent(type, RepositoryTestData.create42Puzzle());
new HandlerEventIndexSyncer<>(indexer).handleEvent(event);
verifyNoInteractions(indexer);
}
@ParameterizedTest
@EnumSource(value = HandlerEventType.class, mode = EnumSource.Mode.INCLUDE, names = {"CREATE", "MODIFY"})
void shouldStore(HandlerEventType type) {
when(indexer.open()).thenReturn(updater);
Repository puzzle = RepositoryTestData.create42Puzzle();
RepositoryEvent event = new RepositoryEvent(type, puzzle);
new HandlerEventIndexSyncer<>(indexer).handleEvent(event);
verify(updater).store(puzzle);
verify(updater).close();
}
@Test
void shouldDelete() {
when(indexer.open()).thenReturn(updater);
Repository puzzle = RepositoryTestData.create42Puzzle();
RepositoryEvent event = new RepositoryEvent(HandlerEventType.DELETE, puzzle);
new HandlerEventIndexSyncer<>(indexer).handleEvent(event);
verify(updater).delete(puzzle);
verify(updater).close();
}
}

View File

@@ -0,0 +1,145 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.search;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.group.Group;
import sonia.scm.repository.Repository;
import sonia.scm.user.User;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.PrivilegedAction;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class IndexBootstrapListenerTest {
@Mock
private AdministrationContext administrationContext;
@Mock
private IndexLogStore indexLogStore;
@Test
void shouldReIndexWithoutLog() {
mockAdminContext();
Indexer<Repository> indexer = indexer(Repository.class, 1);
Indexer.Updater<Repository> updater = updater(indexer);
mockEmptyIndexLog(Repository.class);
doInitialization(indexer);
verify(updater).reIndexAll();
verify(updater).close();
verify(indexLogStore).log(IndexNames.DEFAULT, Repository.class, 1);
}
@Test
void shouldReIndexIfVersionWasUpdated() {
mockAdminContext();
Indexer<User> indexer = indexer(User.class, 2);
Indexer.Updater<User> updater = updater(indexer);
mockIndexLog(User.class, 1);
doInitialization(indexer);
verify(updater).reIndexAll();
verify(updater).close();
verify(indexLogStore).log(IndexNames.DEFAULT, User.class, 2);
}
@Test
void shouldSkipReIndexIfVersionIsEqual() {
Indexer<Group> indexer = indexer(Group.class, 3);
mockIndexLog(Group.class, 3);
doInitialization(indexer);
verify(indexer, never()).open();
}
private <T> void mockIndexLog(Class<T> type, int version) {
mockIndexLog(type, new IndexLog(version));
}
private <T> void mockEmptyIndexLog(Class<T> type) {
mockIndexLog(type, null);
}
private <T> void mockIndexLog(Class<T> type, @Nullable IndexLog indexLog) {
when(indexLogStore.get(IndexNames.DEFAULT, type)).thenReturn(Optional.ofNullable(indexLog));
}
private void mockAdminContext() {
doAnswer(ic -> {
PrivilegedAction action = ic.getArgument(0);
action.run();
return null;
}).when(administrationContext).runAsAdmin(any(PrivilegedAction.class));
}
@SuppressWarnings("rawtypes")
private void doInitialization(Indexer... indexers) {
IndexBootstrapListener listener = listener(indexers);
listener.contextInitialized(null);
}
@Nonnull
@SuppressWarnings("rawtypes")
private IndexBootstrapListener listener(Indexer... indexers) {
return new IndexBootstrapListener(
administrationContext, indexLogStore, new HashSet<>(Arrays.asList(indexers))
);
}
private <T> Indexer<T> indexer(Class<T> type, int version) {
Indexer<T> indexer = mock(Indexer.class);
when(indexer.getType()).thenReturn(type);
when(indexer.getVersion()).thenReturn(version);
when(indexer.getIndex()).thenReturn(IndexNames.DEFAULT);
return indexer;
}
private <T> Indexer.Updater<T> updater(Indexer<T> indexer) {
Indexer.Updater<T> updater = mock(Indexer.Updater.class);
when(indexer.open()).thenReturn(updater);
return updater;
}
}

View File

@@ -87,6 +87,16 @@ class LuceneQueryBuilderTest {
assertThat(result.getTotalHits()).isOne();
}
@Test
void shouldReturnCount() throws IOException {
try (IndexWriter writer = writer()) {
writer.addDocument(inetOrgPersonDoc("Arthur", "Dent", "Arthur Dent", "4211"));
}
long hits = count(InetOrgPerson.class, "Arthur");
assertThat(hits).isOne();
}
@Test
void shouldMatchPartial() throws IOException {
try (IndexWriter writer = writer()) {
@@ -239,6 +249,18 @@ class LuceneQueryBuilderTest {
});
}
@Test
void shouldCountOnlyPermittedHits() throws IOException {
try (IndexWriter writer = writer()) {
writer.addDocument(permissionDoc("Awesome content one", "abc"));
writer.addDocument(permissionDoc("Awesome content two", "cde"));
writer.addDocument(permissionDoc("Awesome content three", "fgh"));
}
long hits = count(Simple.class, "content:awesome");
assertThat(hits).isOne();
}
@Test
void shouldFilterByRepository() throws IOException {
try (IndexWriter writer = writer()) {
@@ -499,6 +521,17 @@ class LuceneQueryBuilderTest {
return query(type, queryString, null, null);
}
private long count(Class<?> type, String queryString) throws IOException {
try (DirectoryReader reader = DirectoryReader.open(directory)) {
lenient().when(opener.openForRead("default")).thenReturn(reader);
SearchableTypeResolver resolver = new SearchableTypeResolver(type);
LuceneQueryBuilder builder = new LuceneQueryBuilder(
opener, resolver, "default", new StandardAnalyzer()
);
return builder.count(type, queryString).getTotalHits();
}
}
private QueryResult query(Class<?> type, String queryString, Integer start, Integer limit) throws IOException {
try (DirectoryReader reader = DirectoryReader.open(directory)) {
lenient().when(opener.openForRead("default")).thenReturn(reader);

View File

@@ -0,0 +1,125 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.user;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.HandlerEventType;
import sonia.scm.search.Id;
import sonia.scm.search.Index;
import sonia.scm.search.IndexQueue;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class UserIndexerTest {
@Mock
private UserManager userManager;
@Mock
private IndexQueue indexQueue;
@InjectMocks
private UserIndexer indexer;
@Test
void shouldReturnRepositoryClass() {
assertThat(indexer.getType()).isEqualTo(User.class);
}
@Test
void shouldReturnIndexName() {
assertThat(indexer.getIndex()).isEqualTo(UserIndexer.INDEX);
}
@Test
void shouldReturnVersion() {
assertThat(indexer.getVersion()).isEqualTo(UserIndexer.VERSION);
}
@Nested
class UpdaterTests {
@Mock
private Index index;
private final User user = UserTestData.createTrillian();
@BeforeEach
void open() {
when(indexQueue.getQueuedIndex(UserIndexer.INDEX)).thenReturn(index);
}
@Test
void shouldStoreRepository() {
indexer.open().store(user);
verify(index).store(Id.of(user), "user:read:trillian", user);
}
@Test
void shouldDeleteByRepository() {
indexer.open().delete(user);
verify(index).delete(Id.of(user), User.class);
}
@Test
void shouldReIndexAll() {
when(userManager.getAll()).thenReturn(singletonList(user));
indexer.open().reIndexAll();
verify(index).deleteByType(User.class);
verify(index).store(Id.of(user), "user:read:trillian", user);
}
@Test
void shouldHandleEvent() {
UserEvent event = new UserEvent(HandlerEventType.DELETE, user);
indexer.handleEvent(event);
verify(index).delete(Id.of(user), User.class);
}
@Test
void shouldCloseIndex() {
indexer.open().close();
verify(index).close();
}
}
}