package com.belladati.sdk.util.impl; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.apache.http.entity.StringEntity; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.belladati.sdk.impl.BellaDatiClient; import com.belladati.sdk.impl.BellaDatiServiceImpl; import com.belladati.sdk.impl.TokenHolder; import com.belladati.sdk.test.SDKTest; import com.belladati.sdk.test.TestRequestHandler; import com.belladati.sdk.util.PaginatedIdList; import com.belladati.sdk.util.PaginatedList; import com.belladati.sdk.util.impl.PaginatedIdListImpl; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * Tests behavior of the {@link PaginatedList}. * * @author Chris Hennigfeld */ @Test public class PaginatedListTest extends SDKTest { private PaginatedIdList<Item> list; private final String relativeUrl = "/list"; private final String field = "field"; @Test(expectedExceptions = IllegalArgumentException.class) public void loadSizeNegative() { list.load(-1); } @Test(expectedExceptions = IllegalArgumentException.class) public void loadSizeZero() { list.load(0); } @Test(expectedExceptions = IllegalArgumentException.class) public void loadBothSizeNegative() { list.load(0, -1); } @Test(expectedExceptions = IllegalArgumentException.class) public void loadBothSizeZero() { list.load(0, 0); } @Test(expectedExceptions = IllegalArgumentException.class) public void loadBothPageNegative() { list.load(-1, 1); } /** Verifies no parameters sent to server on default load. */ public void load() { server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), Collections.emptyMap()); holder.response.setEntity(new StringEntity(buildResponse(1, 0).toString())); } }); list.load(); } /** Verifies parameters sent to server when size is set. */ public void loadSize() { final int size = 3; server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { Map<String, String> expectedMap = new HashMap<String, String>(); expectedMap.put("size", "" + size); expectedMap.put("offset", "" + 0); assertEquals(holder.getUrlParameters(), expectedMap); holder.response.setEntity(new StringEntity(buildResponse(size, 0).toString())); } }); list.load(size); } /** Verifies parameters sent to server when page and size are set. */ public void loadSizePage() { final int size = 3; final int page = 5; server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), buildParamMap(size, size * page)); holder.response.setEntity(new StringEntity(buildResponse(size, 0).toString())); } }); list.load(page, size); } /** Ensures old elements are discarded when the list is reloaded. */ public void discardOnLoad() { final String id1 = "id1"; final String id2 = "id2"; server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), Collections.emptyMap()); holder.response.setEntity(new StringEntity(buildResponse(1, 0, id1).toString())); } }); list.load(); assertEquals(list.toList(), Arrays.asList(new Item(id1))); server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), Collections.emptyMap()); holder.response.setEntity(new StringEntity(buildResponse(1, 0, id2).toString())); } }); list.load(); assertEquals(list.toList(), Arrays.asList(new Item(id2))); assertEquals(list.toString(), Arrays.asList(new Item(id2)).toString()); } /** loadNext() calls regular load when not yet loaded. */ public void loadNextCallsLoad() { server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), Collections.emptyMap()); holder.response.setEntity(new StringEntity(buildResponse(1, 0).toString())); } }); list.loadNext(); } /** loadNext() does nothing if the last page wasn't full. */ public void loadNextEmpty() { list.load(); list.loadNext(); server.assertRequestUris(relativeUrl); } /** Next page is loaded correctly. */ public void loadNext() { final String id1 = "id1"; final String id2 = "id2"; registerResponse(1, 0, id1); list.load(); server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), buildParamMap(1, 1)); holder.response.setEntity(new StringEntity(buildResponse(1, 1, id2).toString())); } }); list.loadNext(); server.register(relativeUrl, new TestRequestHandler() { @Override protected void handle(HttpHolder holder) throws IOException { assertEquals(holder.getUrlParameters(), buildParamMap(1, 2)); holder.response.setEntity(new StringEntity(buildResponse(1, 2).toString())); } }); list.loadNext(); assertEquals(list.toList(), Arrays.asList(new Item(id1), new Item(id2))); } /** isLoaded() indicates whether the list has been loaded. */ public void isLoaded() { assertFalse(list.isLoaded()); list.load(); assertTrue(list.isLoaded()); } /** Initially we assume there's a next page. */ public void hasNextInitial() { assertTrue(list.hasNextPage()); } /** No next page if list is empty on load. */ public void hasNextLoadEmpty() { list.load(); assertFalse(list.hasNextPage()); } /** No next page if first page wasn't full. */ public void hasNextLoadPartial() { registerResponse(2, 0, "id"); list.load(); assertFalse(list.hasNextPage()); } /** Has next page if first page was full. */ public void hasNextLoadFull() { registerResponse(2, 0, "id1", "id2"); list.load(); assertTrue(list.hasNextPage()); } /** No next page if first page was full, second page empty. */ public void hasNextEmptyAfterFull() { registerResponse(2, 0, "id1", "id2"); list.load(); registerResponse(2, 2); list.loadNext(); assertFalse(list.hasNextPage()); } /** No next page if first page was full, second page partial. */ public void hasNextPartialAfterFull() { registerResponse(2, 0, "id1", "id2"); list.load(); registerResponse(2, 2, "id3"); list.loadNext(); assertFalse(list.hasNextPage()); } /** Has next page if first page was full, second page full. */ public void hasNextFullAfterFull() { registerResponse(2, 0, "id1", "id2"); list.load(); registerResponse(2, 2, "id3", "id4"); list.loadNext(); assertTrue(list.hasNextPage()); } /** First/last loaded page/index start out as -1. */ public void initialFirstLastLoaded() { assertEquals(list.getFirstLoadedIndex(), -1); assertEquals(list.getLastLoadedIndex(), -1); assertEquals(list.getFirstLoadedPage(), -1); assertEquals(list.getLastLoadedPage(), -1); } /** First loaded page is updated on load. */ public void firstLoadedPage() { list.load(); assertEquals(list.getFirstLoadedPage(), 0); registerResponse(2, 10, "id", "id2"); list.load(5, 2); assertEquals(list.getFirstLoadedPage(), 5); registerResponse(2, 12); list.loadNext(); assertEquals(list.getFirstLoadedPage(), 5); } /** Last loaded page is updated on load and loadNext. */ public void lastLoadedPage() { registerResponse(2, 10, "id", "id2"); list.load(5, 2); assertEquals(list.getLastLoadedPage(), 5); registerResponse(2, 12); list.loadNext(); assertEquals(list.getLastLoadedPage(), 6); registerResponse(1, 0, "id"); list.load(); assertEquals(list.getLastLoadedPage(), 0); } /** First loaded index is updated on load when items are found. */ public void firstLoadedIndex() { list.load(); assertEquals(list.getFirstLoadedIndex(), -1); registerResponse(2, 10, "id", "id2"); list.load(5, 2); assertEquals(list.getFirstLoadedIndex(), 10); registerResponse(2, 12); list.loadNext(); assertEquals(list.getFirstLoadedIndex(), 10); } /** Last loaded index is updated on load and loadNext when items are found. */ public void lastLoadedIndex() { registerResponse(2, 10, "id", "id2"); list.load(5, 2); assertEquals(list.getLastLoadedIndex(), 11); registerResponse(2, 12); list.loadNext(); assertEquals(list.getLastLoadedIndex(), 11); registerResponse(1, 0, "id"); list.load(); assertEquals(list.getLastLoadedIndex(), 0); registerResponse(1, 0); list.load(); assertEquals(list.getLastLoadedIndex(), -1); } /** Page size is updated on load. */ public void pageSize() { assertEquals(list.getPageSize(), -1); registerResponse(2, 0); list.load(); assertEquals(list.getPageSize(), 2); } /** Page size returned by the server overrides local page size. */ public void sizeMismatch() { registerResponse(2, 0); list.load(1); assertEquals(list.getPageSize(), 2); } /** Offset returned by the server overrides local page. */ public void pageMismatch() { registerResponse(2, 10); list.load(2, 3); assertEquals(list.getFirstLoadedPage(), 5); assertEquals(list.getLastLoadedPage(), 5); } /** contains method checks elements since last load. */ public void contains() { String id = "id"; assertFalse(list.contains(new Item(id))); registerResponse(3, 0, "a", id, "b"); list.load(); assertTrue(list.contains(new Item(id))); registerResponse(1, 0); list.load(); assertFalse(list.contains(new Item(id))); } /** contains(id) method checks elements since last load. */ public void containsId() { String id = "id"; assertFalse(list.contains(id)); registerResponse(3, 0, "a", id, "b"); list.load(); assertTrue(list.contains(id)); registerResponse(1, 0); list.load(); assertFalse(list.contains(id)); } /** get() before load throws exception. */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void getIndexBeforeLoad() { list.get(0); } /** get() returns the element at the index. */ public void getIndex() { String id = "id"; registerResponse(20, 40, id); list.load(2, 20); assertEquals(list.get(40), new Item(id)); } /** get() with an index before the first loaded element throws exception. */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void getIndexTooLow() { registerResponse(20, 40, "id"); list.load(2, 20); list.get(39); } /** get() with an index after the last loaded element throws exception. */ @Test(expectedExceptions = IndexOutOfBoundsException.class) public void getIndexTooHigh() { registerResponse(20, 40, "id", "id2"); list.load(2, 20); list.get(42); } public void getIndexAfterLoadNext() { final String id1 = "id1"; final String id2 = "id2"; registerResponse(1, 40, id1); list.load(); registerResponse(1, 41, id2); list.loadNext(); assertEquals(list.get(41), new Item(id2)); } /** indexOf method checks elements since last load. */ public void indexOf() { String id = "id"; assertEquals(list.indexOf(new Item(id)), -1); registerResponse(3, 0, "a", id, "b"); list.load(); assertEquals(list.indexOf(new Item(id)), 1); registerResponse(3, 9, "a", id, "b"); list.load(); assertEquals(list.indexOf(new Item(id)), 10); registerResponse(1, 0); list.load(); assertEquals(list.indexOf(new Item(id)), -1); } /** indexOf(id) method checks elements since last load. */ public void indexOfId() { String id = "id"; assertEquals(list.indexOf(id), -1); registerResponse(3, 0, "a", id, "b"); list.load(); assertEquals(list.indexOf(id), 1); registerResponse(3, 9, "a", id, "b"); list.load(); assertEquals(list.indexOf(id), 10); registerResponse(1, 0); list.load(); assertEquals(list.indexOf(id), -1); } /** List is empty if it hasn't been loaded or actually is empty. */ public void isEmpty() { assertTrue(list.isEmpty()); registerResponse(1, 0, "id"); list.load(); assertFalse(list.isEmpty()); registerResponse(1, 0); list.load(); assertTrue(list.isEmpty()); } /** size() returns number of currently loaded elements. */ public void size() { assertEquals(list.size(), 0); registerResponse(1, 0, "id"); list.load(); assertEquals(list.size(), 1); registerResponse(1, 1, "id2"); list.loadNext(); assertEquals(list.size(), 2); registerResponse(1, 0); list.load(); assertEquals(list.size(), 0); } /** iterator() allows iterating over the list. */ public void iterator() { final String id1 = "id1"; final String id2 = "id2"; assertFalse(list.iterator().hasNext()); registerResponse(1, 0, id1); list.load(); Iterator<Item> iterator = list.iterator(); assertEquals(iterator.next(), new Item(id1)); assertFalse(iterator.hasNext()); registerResponse(1, 0, id2); list.loadNext(); iterator = list.iterator(); assertEquals(iterator.next(), new Item(id1)); assertEquals(iterator.next(), new Item(id2)); assertFalse(iterator.hasNext()); } private Map<String, String> buildParamMap(int size, int offset) { Map<String, String> paramMap = new HashMap<String, String>(); paramMap.put("size", "" + size); paramMap.put("offset", "" + offset); return paramMap; } /** * Builds an API response with the given size and offset parameters, * containing elements with the given IDs. * * @param size size field in the JSON object * @param offset offset field in the JSON object * @param ids list of IDs to return (optional) * @return a JSON API response */ private JsonNode buildResponse(int size, int offset, String... ids) { ObjectMapper mapper = new ObjectMapper(); ObjectNode node = mapper.createObjectNode(); ArrayNode items = mapper.createArrayNode(); for (String id : ids) { items.add(mapper.createObjectNode().put("id", id)); } node.put("size", "" + size).put("offset", "" + offset).put(field, items); return node; } /** * Tells the server to respond to this test's URL with the given size, * offset and element IDs. * * @param size size field in the JSON response * @param offset offset field in the JSON response * @param ids list of IDs to return (optional) */ private void registerResponse(final int size, final int offset, final String... ids) { server.register(relativeUrl, buildResponse(size, offset, ids).toString()); } @BeforeMethod protected void setupList() { BellaDatiClient client = new BellaDatiClient(server.getHttpURL(), false); BellaDatiServiceImpl service = new BellaDatiServiceImpl(client, new TokenHolder("key", "secret")); list = new PaginatedIdListImpl<Item>(service, relativeUrl, field) { @Override protected Item parse(BellaDatiServiceImpl service, JsonNode node) { return new Item(node.get("id").asText()); } }; registerResponse(1, 0); } }