diff --git a/src/main/java/cloud/dnation/hetznerclient/PagedResourceHelper.java b/src/main/java/cloud/dnation/hetznerclient/PagedResourceHelper.java index d9ecb8d..5731d53 100644 --- a/src/main/java/cloud/dnation/hetznerclient/PagedResourceHelper.java +++ b/src/main/java/cloud/dnation/hetznerclient/PagedResourceHelper.java @@ -33,7 +33,7 @@ public class PagedResourceHelper { * Consume all items from paginated REST endpoint. * * @param labelSelector label selector to restrict items only to those that match selector - * @param pageSupplier {@link BiFunction} that takes page index (zero based) and selector and produces {@link Response} + * @param pageSupplier {@link BiFunction} that takes page index (one based) and selector and produces {@link Response} * @param itemsGetter {@link Function} that takes response from pageSupplier and extracts list of items * @return all items combined to single list * @param item type @@ -43,7 +43,8 @@ public static String labelSelector, BiFunction> pageSupplier, Function> itemsGetter) throws IOException { final List result = new ArrayList<>(); - for (int i = 0; ; i++) { + // Hetzner API pages are 1-based. Page 0 is served as page 1, so starting at 0 returns the first page twice. + for (int i = 1; ; i++) { final Response page = pageSupplier.apply(i, labelSelector).execute(); assertValidResponse(page); final X body = page.body(); diff --git a/src/test/java/cloud/dnation/hetznerclient/BasicTest.java b/src/test/java/cloud/dnation/hetznerclient/BasicTest.java index b11bc3e..c510908 100644 --- a/src/test/java/cloud/dnation/hetznerclient/BasicTest.java +++ b/src/test/java/cloud/dnation/hetznerclient/BasicTest.java @@ -15,8 +15,11 @@ */ package cloud.dnation.hetznerclient; +import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.QueueDispatcher; +import okhttp3.mockwebserver.RecordedRequest; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -25,7 +28,10 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.stream.Collectors; import static cloud.dnation.hetznerclient.TestHelper.resourceAsString; import static org.junit.Assert.assertEquals; @@ -186,4 +192,39 @@ public void testPaginationHelper() throws IOException { assertEquals("1.2.3.29", items.get(25).getIp()); assertEquals("1.2.3.30", items.get(26).getIp()); } + + @Test + public void testPaginationHelperRequestsOneBasedPages() throws IOException { + final List requestedPages = new ArrayList<>(); + ws.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + final String page = request.getRequestUrl().queryParameter("page"); + requestedPages.add(page); + try { + switch (page) { + // Hetzner API pages are 1-based and treat page=0 as page=1 + case "0": + case "1": + return new MockResponse().setBody(resourceAsString("paging-primary-ips-1.json")); + case "2": + return new MockResponse().setBody(resourceAsString("paging-primary-ips-2.json")); + default: + return new MockResponse().setResponseCode(404); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + try { + List items = PagedResourceHelper.getAllPrimaryIps(api, ""); + assertEquals(Arrays.asList("1", "2"), requestedPages); + List ips = items.stream().map(PrimaryIpDetail::getIp).collect(Collectors.toList()); + assertEquals(27, ips.size()); + assertEquals(ips.size(), new HashSet<>(ips).size()); + } finally { + ws.setDispatcher(new QueueDispatcher()); + } + } }