package com.spotify.heroic.elasticsearch; import com.spotify.heroic.common.OptionalLimit; import com.spotify.heroic.elasticsearch.AbstractElasticsearchMetadataBackend.LimitedSet; import com.spotify.heroic.elasticsearch.AbstractElasticsearchMetadataBackend.ScrollTransform; import eu.toolchain.async.AsyncFramework; import eu.toolchain.async.AsyncFuture; import eu.toolchain.async.LazyTransform; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.mockito.stubbing.OngoingStubbing; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ScrollTransformTest { @Mock private AsyncFramework async; @Mock private AsyncFuture<LimitedSet> resolved; @Mock Supplier<AsyncFuture<SearchResponse>> scroller; @Mock AsyncFuture<SearchResponse> response; @Mock SearchResponse searchResponse; @Mock SearchHits searchHits; private final SearchHit[] searchHits1 = { mock(SearchHit.class), mock(SearchHit.class), mock(SearchHit.class), }; private final SearchHit[] searchHits2 = { mock(SearchHit.class), mock(SearchHit.class), mock(SearchHit.class), }; private final SearchHit[] emptySearchHits = {}; @Before public void setup() { doReturn(resolved).when(async).resolved(any(LimitedSet.class)); doReturn(searchHits).when(searchResponse).getHits(); doReturn(response).when(scroller).get(); doAnswer(new Answer<AsyncFuture<LimitedSet<Integer>>>() { public AsyncFuture<LimitedSet<Integer>> answer( InvocationOnMock invocation ) throws Exception { LazyTransform<SearchResponse, LimitedSet<Integer>> transform = (LazyTransform<SearchResponse, LimitedSet<Integer>>) invocation.getArguments()[0]; return transform.transform(searchResponse); } }).when(response).lazyTransform(any(LazyTransform.class)); } public void setSearchHitPages(SearchHit[]... pages) { OngoingStubbing<SearchHit[]> stub = when(searchHits.getHits()); for (final SearchHit[] page : pages) { stub = stub.thenReturn(page); } } public ScrollTransform<SearchHit> createScrollTransform( Integer limit, Supplier<AsyncFuture<SearchResponse>> scroller ){ final OptionalLimit optionalLimit = OptionalLimit.of(limit); return new ScrollTransform<>(async, optionalLimit, scroller, Function.identity()); } public LimitedSet<SearchHit> createLimitSet(Integer limit, SearchHit[]... pages){ Set<SearchHit> set = new HashSet<>(); Stream<SearchHit> stream = Arrays.stream(pages) .map(Arrays::stream) .reduce(Stream.empty(), Stream::concat); if (limit!=null){ stream = stream.limit(limit); } stream.map(Function.identity()) .forEach(set::add); return new LimitedSet<>(set, limit != null); } @Test public void aboveLimit() throws Exception { setSearchHitPages(searchHits1, emptySearchHits); final Integer limit = searchHits1.length - 1; final ScrollTransform<SearchHit> scrollTransform = createScrollTransform(limit, scroller); scroller.get().lazyTransform(scrollTransform); verify(async).resolved(createLimitSet(limit, searchHits1)); verify(scroller).get(); } @Test public void aboveLimitWithDuplicates() throws Exception { setSearchHitPages(searchHits1, searchHits1, searchHits2, emptySearchHits); final Integer limit = searchHits1.length + searchHits2.length - 1; final ScrollTransform<SearchHit> scrollTransform = createScrollTransform(limit, scroller); scroller.get().lazyTransform(scrollTransform); verify(async).resolved(createLimitSet(limit, searchHits1, searchHits2)); verify(scroller, times(3)).get(); } @Test public void belowLimit() throws Exception { setSearchHitPages(searchHits1, emptySearchHits); final Integer limit = searchHits1.length; final ScrollTransform<SearchHit> scrollTransform = createScrollTransform(limit, scroller); scroller.get().lazyTransform(scrollTransform); verify(async).resolved(createLimitSet(null, searchHits1)); verify(scroller, times(2)).get(); } @Test public void belowLimitWithDuplicates() throws Exception { setSearchHitPages(searchHits1, searchHits1, searchHits2, emptySearchHits); final Integer limit = searchHits1.length + searchHits2.length; final ScrollTransform<SearchHit> scrollTransform = createScrollTransform(limit, scroller); scroller.get().lazyTransform(scrollTransform); verify(async).resolved(createLimitSet(null, searchHits1, searchHits2)); verify(scroller, times(4)).get(); } }