mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-04 05:28:40 +02:00
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:
@@ -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 {
|
||||
|
||||
125
scm-webapp/src/test/java/sonia/scm/group/GroupIndexerTest.java
Normal file
125
scm-webapp/src/test/java/sonia/scm/group/GroupIndexerTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
125
scm-webapp/src/test/java/sonia/scm/user/UserIndexerTest.java
Normal file
125
scm-webapp/src/test/java/sonia/scm/user/UserIndexerTest.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user