001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2016 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.connector;
015
016import static org.assertj.core.api.Assertions.assertThat;
017import static org.junit.jupiter.api.Assertions.assertThrows;
018import static org.shredzone.acme4j.toolbox.TestUtils.url;
019
020import java.io.IOException;
021import java.net.HttpURLConnection;
022import java.net.URL;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.Iterator;
027import java.util.List;
028import java.util.NoSuchElementException;
029
030import org.junit.jupiter.api.BeforeEach;
031import org.junit.jupiter.api.Test;
032import org.shredzone.acme4j.Authorization;
033import org.shredzone.acme4j.Login;
034import org.shredzone.acme4j.provider.TestableConnectionProvider;
035import org.shredzone.acme4j.toolbox.JSON;
036import org.shredzone.acme4j.toolbox.JSONBuilder;
037
038/**
039 * Unit test for {@link ResourceIterator}.
040 */
041public class ResourceIteratorTest {
042
043    private static final int PAGES = 4;
044    private static final int RESOURCES_PER_PAGE = 5;
045    private static final String TYPE = "authorizations";
046
047    private final List<URL> resourceURLs = new ArrayList<>(PAGES * RESOURCES_PER_PAGE);
048    private final List<URL> pageURLs = new ArrayList<>(PAGES);
049
050    @BeforeEach
051    public void setup() {
052        resourceURLs.clear();
053        for (var ix = 0; ix < RESOURCES_PER_PAGE * PAGES; ix++) {
054            resourceURLs.add(url("https://example.com/acme/auth/" + ix));
055        }
056
057        pageURLs.clear();
058        for (var ix = 0; ix < PAGES; ix++) {
059            pageURLs.add(url("https://example.com/acme/batch/" + ix));
060        }
061    }
062
063    /**
064     * Test if the {@link ResourceIterator} handles a {@code null} start URL.
065     */
066    @Test
067    public void nullTest() {
068        assertThrows(NoSuchElementException.class, () -> {
069            var it = createIterator(null);
070
071            assertThat(it).isNotNull();
072            assertThat(it.hasNext()).isFalse();
073            it.next(); // throws NoSuchElementException
074        });
075    }
076
077    /**
078     * Test if the {@link ResourceIterator} returns all objects in the correct order.
079     */
080    @Test
081    public void iteratorTest() throws IOException {
082        var result = new ArrayList<URL>();
083
084        var it = createIterator(pageURLs.get(0));
085        while (it.hasNext()) {
086            result.add(it.next().getLocation());
087        }
088
089        assertThat(result).isEqualTo(resourceURLs);
090    }
091
092    /**
093     * Test unusual {@link Iterator#next()} and {@link Iterator#hasNext()} usage.
094     */
095    @Test
096    public void nextHasNextTest() throws IOException {
097        var result = new ArrayList<URL>();
098
099        var it = createIterator(pageURLs.get(0));
100        assertThat(it.hasNext()).isTrue();
101        assertThat(it.hasNext()).isTrue();
102
103        // don't try this at home, kids...
104        try {
105            for (;;) {
106                result.add(it.next().getLocation());
107            }
108        } catch (NoSuchElementException ex) {
109            assertThat(it.hasNext()).isFalse();
110            assertThat(it.hasNext()).isFalse();
111        }
112
113        assertThat(result).isEqualTo(resourceURLs);
114    }
115
116    /**
117     * Test that {@link Iterator#remove()} fails.
118     */
119    @Test
120    public void removeTest() {
121        assertThrows(UnsupportedOperationException.class, () -> {
122            var it = createIterator(pageURLs.get(0));
123            it.next();
124            it.remove(); // throws UnsupportedOperationException
125        });
126    }
127
128    /**
129     * Creates a new {@link Iterator} of {@link Authorization} objects.
130     *
131     * @param first
132     *            URL of the first page
133     * @return Created {@link Iterator}
134     */
135    private Iterator<Authorization> createIterator(URL first) throws IOException {
136        var provider = new TestableConnectionProvider() {
137            private int ix;
138
139            @Override
140            public int sendSignedPostAsGetRequest(URL url, Login login) {
141                ix = pageURLs.indexOf(url);
142                assertThat(ix).isGreaterThanOrEqualTo(0);
143                return HttpURLConnection.HTTP_OK;
144            }
145
146            @Override
147            public JSON readJsonResponse() {
148                var start = ix * RESOURCES_PER_PAGE;
149                var end = (ix + 1) * RESOURCES_PER_PAGE;
150
151                var cb = new JSONBuilder();
152                cb.array(TYPE, resourceURLs.subList(start, end));
153
154                return JSON.parse(cb.toString());
155            }
156
157            @Override
158            public Collection<URL> getLinks(String relation) {
159                if ("next".equals(relation) && (ix + 1 < pageURLs.size())) {
160                    return Collections.singletonList(pageURLs.get(ix + 1));
161                }
162                return Collections.emptyList();
163            }
164        };
165
166        var login = provider.createLogin();
167
168        provider.close();
169
170        return new ResourceIterator<>(login, TYPE, first, Login::bindAuthorization);
171    }
172
173}