mirror of
https://github.com/Athou/commafeed.git
synced 2026-03-21 21:37:29 +00:00
normalize line endings
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DigestsTest {
|
||||
|
||||
@Test
|
||||
void sha1Hex() {
|
||||
Assertions.assertEquals("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", Digests.sha1Hex("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void md5Hex() {
|
||||
Assertions.assertEquals("5d41402abc4b2a76b9719d911017c592", Digests.md5Hex("hello"));
|
||||
}
|
||||
|
||||
package com.commafeed.backend;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class DigestsTest {
|
||||
|
||||
@Test
|
||||
void sha1Hex() {
|
||||
Assertions.assertEquals("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d", Digests.sha1Hex("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void md5Hex() {
|
||||
Assertions.assertEquals("5d41402abc4b2a76b9719d911017c592", Digests.md5Hex("hello"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,402 +1,402 @@
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.hc.client5.http.ConnectTimeoutException;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
import org.mockserver.junit.jupiter.MockServerExtension;
|
||||
import org.mockserver.model.ConnectionOptions;
|
||||
import org.mockserver.model.Delay;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
import org.mockserver.model.MediaType;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.commafeed.backend.HttpGetter.HttpResponseException;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||
import com.commafeed.backend.HttpGetter.TooManyRequestsException;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import io.quarkus.runtime.configuration.MemorySize;
|
||||
|
||||
@ExtendWith(MockServerExtension.class)
|
||||
class HttpGetterTest {
|
||||
|
||||
private static final Instant NOW = Instant.now();
|
||||
|
||||
private MockServerClient mockServerClient;
|
||||
private String feedUrl;
|
||||
private byte[] feedContent;
|
||||
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
private HttpGetter getter;
|
||||
|
||||
@BeforeEach
|
||||
void init(MockServerClient mockServerClient) throws IOException {
|
||||
this.mockServerClient = mockServerClient;
|
||||
this.mockServerClient.reset();
|
||||
this.feedUrl = "http://localhost:" + this.mockServerClient.getPort() + "/";
|
||||
this.feedContent = IOUtils.toByteArray(Objects.requireNonNull(getClass().getResource("/feed/rss.xml")));
|
||||
|
||||
this.config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Mockito.when(config.httpClient().userAgent()).thenReturn(Optional.of("http-getter-test"));
|
||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().sslHandshakeTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().socketTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().connectionTimeToLive()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().maxResponseSize()).thenReturn(new MemorySize(new BigInteger("10000")));
|
||||
Mockito.when(config.httpClient().cache().enabled()).thenReturn(true);
|
||||
Mockito.when(config.httpClient().cache().maximumMemorySize()).thenReturn(new MemorySize(new BigInteger("100000")));
|
||||
Mockito.when(config.httpClient().cache().expiration()).thenReturn(Duration.ofMinutes(1));
|
||||
Mockito.when(config.feedRefresh().httpThreads()).thenReturn(3);
|
||||
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
ints = { HttpStatus.SC_UNAUTHORIZED, HttpStatus.SC_FORBIDDEN, HttpStatus.SC_NOT_FOUND, HttpStatus.SC_INTERNAL_SERVER_ERROR })
|
||||
void errorCodes(int code) {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withStatusCode(code));
|
||||
|
||||
HttpResponseException e = Assertions.assertThrows(HttpResponseException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(code, e.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validFeed() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(feedContent)
|
||||
.withContentType(MediaType.APPLICATION_ATOM_XML)
|
||||
.withHeader(HttpHeaders.LAST_MODIFIED, "123456")
|
||||
.withHeader(HttpHeaders.ETAG, "78910")
|
||||
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60, must-revalidate")
|
||||
.withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertArrayEquals(feedContent, result.getContent());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.getContentType());
|
||||
Assertions.assertEquals("123456", result.getLastModifiedSince());
|
||||
Assertions.assertEquals("78910", result.getETag());
|
||||
Assertions.assertEquals(Duration.ofSeconds(60), result.getValidFor());
|
||||
Assertions.assertEquals(this.feedUrl, result.getUrlAfterRedirect());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreInvalidCacheControlValue() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(feedContent)
|
||||
.withContentType(MediaType.APPLICATION_ATOM_XML)
|
||||
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60; must-revalidate"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals(Duration.ZERO, result.getValidFor());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooManyRequestsExceptionSeconds() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(
|
||||
HttpResponse.response().withStatusCode(HttpStatus.SC_TOO_MANY_REQUESTS).withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
||||
|
||||
TooManyRequestsException e = Assertions.assertThrows(TooManyRequestsException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(NOW.plusSeconds(120), e.getRetryAfter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooManyRequestsExceptionDate() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(HttpStatus.SC_TOO_MANY_REQUESTS)
|
||||
.withHeader(HttpHeaders.RETRY_AFTER, "Wed, 21 Oct 2015 07:28:00 GMT"));
|
||||
|
||||
TooManyRequestsException e = Assertions.assertThrows(TooManyRequestsException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(Instant.parse("2015-10-21T07:28:00Z"), e.getRetryAfter());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
ints = { HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_MOVED_TEMPORARILY, HttpStatus.SC_TEMPORARY_REDIRECT,
|
||||
HttpStatus.SC_PERMANENT_REDIRECT })
|
||||
void followRedirects(int code) throws Exception {
|
||||
// first redirect
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(code)
|
||||
.withHeader(HttpHeaders.LOCATION, "http://localhost:" + this.mockServerClient.getPort() + "/redirected"));
|
||||
|
||||
// second redirect
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/redirected"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(code)
|
||||
.withHeader(HttpHeaders.LOCATION, "http://localhost:" + this.mockServerClient.getPort() + "/redirected-2"));
|
||||
|
||||
// final destination
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/redirected-2"))
|
||||
.respond(HttpResponse.response().withBody(feedContent).withContentType(MediaType.APPLICATION_ATOM_XML));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals("http://localhost:" + this.mockServerClient.getPort() + "/redirected-2", result.getUrlAfterRedirect());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataTimeout() {
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
|
||||
|
||||
Assertions.assertThrows(SocketTimeoutException.class, () -> getter.get(this.feedUrl));
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectTimeout() {
|
||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
// try to connect to a non-routable address
|
||||
// https://stackoverflow.com/a/904609
|
||||
Assertions.assertThrows(ConnectTimeoutException.class, () -> getter.get("http://10.255.255.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userAgent() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.USER_AGENT, "http-getter-test"))
|
||||
.respond(HttpResponse.response().withBody("ok"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lastModifiedReturns304() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.IF_MODIFIED_SINCE, "123456"))
|
||||
.respond(HttpResponse.response().withStatusCode(HttpStatus.SC_NOT_MODIFIED));
|
||||
|
||||
Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> getter.get(HttpGetter.HttpRequest.builder(this.feedUrl).lastModified("123456").build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void eTagReturns304() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.IF_NONE_MATCH, "78910"))
|
||||
.respond(HttpResponse.response().withStatusCode(HttpStatus.SC_NOT_MODIFIED));
|
||||
|
||||
Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> getter.get(HttpGetter.HttpRequest.builder(this.feedUrl).eTag("78910").build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreCookie() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
|
||||
if (req.containsHeader(HttpHeaders.COOKIE)) {
|
||||
throw new Exception("cookie should not be sent by the client");
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody("ok").withHeader(HttpHeaders.SET_COOKIE, "foo=bar");
|
||||
});
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl));
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl + "?foo=bar"));
|
||||
Assertions.assertEquals(2, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheSubsequentCalls() throws Exception {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
return HttpResponse.response().withBody("ok");
|
||||
});
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals(result, getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void largeFeedWithContentLengthHeader() {
|
||||
byte[] bytes = new byte[100000];
|
||||
Arrays.fill(bytes, (byte) 1);
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody(bytes));
|
||||
|
||||
IOException e = Assertions.assertThrows(IOException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals("Response size (100000 bytes) exceeds the maximum allowed size (10000 bytes)", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void largeFeedWithoutContentLengthHeader() {
|
||||
byte[] bytes = new byte[100000];
|
||||
Arrays.fill(bytes, (byte) 1);
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(bytes)
|
||||
.withConnectionOptions(ConnectionOptions.connectionOptions().withSuppressContentLengthHeader(true)));
|
||||
|
||||
IOException e = Assertions.assertThrows(IOException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals("Response size exceeds the maximum allowed size (10000 bytes)", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreInvalidSsl() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody("ok"));
|
||||
|
||||
HttpResult result = getter.get("https://localhost:" + this.mockServerClient.getPort());
|
||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotUseUpgradeProtocolHeader() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
|
||||
if (req.containsHeader(HttpHeaders.UPGRADE)) {
|
||||
throw new Exception("upgrade header should not be sent by the client");
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody("ok");
|
||||
});
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Compression {
|
||||
|
||||
@Test
|
||||
void deflate() throws Exception {
|
||||
supportsCompression("deflate", DeflaterOutputStream::new);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gzip() throws Exception {
|
||||
supportsCompression("gzip", GZIPOutputStream::new);
|
||||
}
|
||||
|
||||
void supportsCompression(String encoding, CompressionOutputStreamFunction compressionOutputStreamFunction) throws Exception {
|
||||
String body = "my body";
|
||||
|
||||
HttpGetterTest.this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
String acceptEncodingHeader = req.getFirstHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||
if (!Set.of(acceptEncodingHeader.split(", ")).contains(encoding)) {
|
||||
throw new Exception(encoding + " should be in the Accept-Encoding header");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try (OutputStream compressionOutputStream = compressionOutputStreamFunction.apply(output)) {
|
||||
compressionOutputStream.write(body.getBytes());
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody(output.toByteArray()).withHeader(HttpHeaders.CONTENT_ENCODING, encoding);
|
||||
});
|
||||
|
||||
HttpResult result = getter.get(HttpGetterTest.this.feedUrl);
|
||||
Assertions.assertEquals(body, new String(result.getContent()));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompressionOutputStreamFunction {
|
||||
OutputStream apply(OutputStream input) throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SchemeNotAllowed {
|
||||
@Test
|
||||
void file() {
|
||||
Assertions.assertThrows(HttpGetter.SchemeNotAllowedException.class, () -> getter.get("file://localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ftp() {
|
||||
Assertions.assertThrows(HttpGetter.SchemeNotAllowedException.class, () -> getter.get("ftp://localhost"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class HostNotAllowed {
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
Mockito.when(config.httpClient().blockLocalAddresses()).thenReturn(true);
|
||||
getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localhost() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://localhost"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://127.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://2130706433"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void zero() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0.0.0.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkLocal() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.12.34"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.169.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multicast() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://224.2.3.4"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://239.255.255.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv4Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://10.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://172.16.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://192.168.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv6Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://fd12:3456:789a:1::1"));
|
||||
}
|
||||
}
|
||||
|
||||
package com.commafeed.backend;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.hc.client5.http.ConnectTimeoutException;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
import org.mockserver.junit.jupiter.MockServerExtension;
|
||||
import org.mockserver.model.ConnectionOptions;
|
||||
import org.mockserver.model.Delay;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.HttpResponse;
|
||||
import org.mockserver.model.MediaType;
|
||||
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedVersion;
|
||||
import com.commafeed.backend.HttpGetter.HttpResponseException;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||
import com.commafeed.backend.HttpGetter.TooManyRequestsException;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
|
||||
import io.quarkus.runtime.configuration.MemorySize;
|
||||
|
||||
@ExtendWith(MockServerExtension.class)
|
||||
class HttpGetterTest {
|
||||
|
||||
private static final Instant NOW = Instant.now();
|
||||
|
||||
private MockServerClient mockServerClient;
|
||||
private String feedUrl;
|
||||
private byte[] feedContent;
|
||||
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
private HttpGetter getter;
|
||||
|
||||
@BeforeEach
|
||||
void init(MockServerClient mockServerClient) throws IOException {
|
||||
this.mockServerClient = mockServerClient;
|
||||
this.mockServerClient.reset();
|
||||
this.feedUrl = "http://localhost:" + this.mockServerClient.getPort() + "/";
|
||||
this.feedContent = IOUtils.toByteArray(Objects.requireNonNull(getClass().getResource("/feed/rss.xml")));
|
||||
|
||||
this.config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Mockito.when(config.httpClient().userAgent()).thenReturn(Optional.of("http-getter-test"));
|
||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().sslHandshakeTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().socketTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().connectionTimeToLive()).thenReturn(Duration.ofSeconds(30));
|
||||
Mockito.when(config.httpClient().maxResponseSize()).thenReturn(new MemorySize(new BigInteger("10000")));
|
||||
Mockito.when(config.httpClient().cache().enabled()).thenReturn(true);
|
||||
Mockito.when(config.httpClient().cache().maximumMemorySize()).thenReturn(new MemorySize(new BigInteger("100000")));
|
||||
Mockito.when(config.httpClient().cache().expiration()).thenReturn(Duration.ofMinutes(1));
|
||||
Mockito.when(config.feedRefresh().httpThreads()).thenReturn(3);
|
||||
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
ints = { HttpStatus.SC_UNAUTHORIZED, HttpStatus.SC_FORBIDDEN, HttpStatus.SC_NOT_FOUND, HttpStatus.SC_INTERNAL_SERVER_ERROR })
|
||||
void errorCodes(int code) {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withStatusCode(code));
|
||||
|
||||
HttpResponseException e = Assertions.assertThrows(HttpResponseException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(code, e.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validFeed() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(feedContent)
|
||||
.withContentType(MediaType.APPLICATION_ATOM_XML)
|
||||
.withHeader(HttpHeaders.LAST_MODIFIED, "123456")
|
||||
.withHeader(HttpHeaders.ETAG, "78910")
|
||||
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60, must-revalidate")
|
||||
.withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertArrayEquals(feedContent, result.getContent());
|
||||
Assertions.assertEquals(MediaType.APPLICATION_ATOM_XML.toString(), result.getContentType());
|
||||
Assertions.assertEquals("123456", result.getLastModifiedSince());
|
||||
Assertions.assertEquals("78910", result.getETag());
|
||||
Assertions.assertEquals(Duration.ofSeconds(60), result.getValidFor());
|
||||
Assertions.assertEquals(this.feedUrl, result.getUrlAfterRedirect());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreInvalidCacheControlValue() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(feedContent)
|
||||
.withContentType(MediaType.APPLICATION_ATOM_XML)
|
||||
.withHeader(HttpHeaders.CACHE_CONTROL, "max-age=60; must-revalidate"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals(Duration.ZERO, result.getValidFor());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooManyRequestsExceptionSeconds() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(
|
||||
HttpResponse.response().withStatusCode(HttpStatus.SC_TOO_MANY_REQUESTS).withHeader(HttpHeaders.RETRY_AFTER, "120"));
|
||||
|
||||
TooManyRequestsException e = Assertions.assertThrows(TooManyRequestsException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(NOW.plusSeconds(120), e.getRetryAfter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooManyRequestsExceptionDate() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(HttpStatus.SC_TOO_MANY_REQUESTS)
|
||||
.withHeader(HttpHeaders.RETRY_AFTER, "Wed, 21 Oct 2015 07:28:00 GMT"));
|
||||
|
||||
TooManyRequestsException e = Assertions.assertThrows(TooManyRequestsException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(Instant.parse("2015-10-21T07:28:00Z"), e.getRetryAfter());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(
|
||||
ints = { HttpStatus.SC_MOVED_PERMANENTLY, HttpStatus.SC_MOVED_TEMPORARILY, HttpStatus.SC_TEMPORARY_REDIRECT,
|
||||
HttpStatus.SC_PERMANENT_REDIRECT })
|
||||
void followRedirects(int code) throws Exception {
|
||||
// first redirect
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(code)
|
||||
.withHeader(HttpHeaders.LOCATION, "http://localhost:" + this.mockServerClient.getPort() + "/redirected"));
|
||||
|
||||
// second redirect
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/redirected"))
|
||||
.respond(HttpResponse.response()
|
||||
.withStatusCode(code)
|
||||
.withHeader(HttpHeaders.LOCATION, "http://localhost:" + this.mockServerClient.getPort() + "/redirected-2"));
|
||||
|
||||
// final destination
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/redirected-2"))
|
||||
.respond(HttpResponse.response().withBody(feedContent).withContentType(MediaType.APPLICATION_ATOM_XML));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals("http://localhost:" + this.mockServerClient.getPort() + "/redirected-2", result.getUrlAfterRedirect());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dataTimeout() {
|
||||
Mockito.when(config.httpClient().responseTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response().withDelay(Delay.milliseconds(1000)));
|
||||
|
||||
Assertions.assertThrows(SocketTimeoutException.class, () -> getter.get(this.feedUrl));
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectTimeout() {
|
||||
Mockito.when(config.httpClient().connectTimeout()).thenReturn(Duration.ofMillis(500));
|
||||
this.getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
// try to connect to a non-routable address
|
||||
// https://stackoverflow.com/a/904609
|
||||
Assertions.assertThrows(ConnectTimeoutException.class, () -> getter.get("http://10.255.255.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userAgent() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.USER_AGENT, "http-getter-test"))
|
||||
.respond(HttpResponse.response().withBody("ok"));
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lastModifiedReturns304() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.IF_MODIFIED_SINCE, "123456"))
|
||||
.respond(HttpResponse.response().withStatusCode(HttpStatus.SC_NOT_MODIFIED));
|
||||
|
||||
Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> getter.get(HttpGetter.HttpRequest.builder(this.feedUrl).lastModified("123456").build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void eTagReturns304() {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET").withHeader(HttpHeaders.IF_NONE_MATCH, "78910"))
|
||||
.respond(HttpResponse.response().withStatusCode(HttpStatus.SC_NOT_MODIFIED));
|
||||
|
||||
Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> getter.get(HttpGetter.HttpRequest.builder(this.feedUrl).eTag("78910").build()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreCookie() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
|
||||
if (req.containsHeader(HttpHeaders.COOKIE)) {
|
||||
throw new Exception("cookie should not be sent by the client");
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody("ok").withHeader(HttpHeaders.SET_COOKIE, "foo=bar");
|
||||
});
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl));
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl + "?foo=bar"));
|
||||
Assertions.assertEquals(2, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheSubsequentCalls() throws Exception {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
return HttpResponse.response().withBody("ok");
|
||||
});
|
||||
|
||||
HttpResult result = getter.get(this.feedUrl);
|
||||
Assertions.assertEquals(result, getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void largeFeedWithContentLengthHeader() {
|
||||
byte[] bytes = new byte[100000];
|
||||
Arrays.fill(bytes, (byte) 1);
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody(bytes));
|
||||
|
||||
IOException e = Assertions.assertThrows(IOException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals("Response size (100000 bytes) exceeds the maximum allowed size (10000 bytes)", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void largeFeedWithoutContentLengthHeader() {
|
||||
byte[] bytes = new byte[100000];
|
||||
Arrays.fill(bytes, (byte) 1);
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET"))
|
||||
.respond(HttpResponse.response()
|
||||
.withBody(bytes)
|
||||
.withConnectionOptions(ConnectionOptions.connectionOptions().withSuppressContentLengthHeader(true)));
|
||||
|
||||
IOException e = Assertions.assertThrows(IOException.class, () -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals("Response size exceeds the maximum allowed size (10000 bytes)", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreInvalidSsl() throws Exception {
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(HttpResponse.response().withBody("ok"));
|
||||
|
||||
HttpResult result = getter.get("https://localhost:" + this.mockServerClient.getPort());
|
||||
Assertions.assertEquals("ok", new String(result.getContent()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotUseUpgradeProtocolHeader() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
|
||||
this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
calls.incrementAndGet();
|
||||
|
||||
if (req.containsHeader(HttpHeaders.UPGRADE)) {
|
||||
throw new Exception("upgrade header should not be sent by the client");
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody("ok");
|
||||
});
|
||||
|
||||
Assertions.assertDoesNotThrow(() -> getter.get(this.feedUrl));
|
||||
Assertions.assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Compression {
|
||||
|
||||
@Test
|
||||
void deflate() throws Exception {
|
||||
supportsCompression("deflate", DeflaterOutputStream::new);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gzip() throws Exception {
|
||||
supportsCompression("gzip", GZIPOutputStream::new);
|
||||
}
|
||||
|
||||
void supportsCompression(String encoding, CompressionOutputStreamFunction compressionOutputStreamFunction) throws Exception {
|
||||
String body = "my body";
|
||||
|
||||
HttpGetterTest.this.mockServerClient.when(HttpRequest.request().withMethod("GET")).respond(req -> {
|
||||
String acceptEncodingHeader = req.getFirstHeader(HttpHeaders.ACCEPT_ENCODING);
|
||||
if (!Set.of(acceptEncodingHeader.split(", ")).contains(encoding)) {
|
||||
throw new Exception(encoding + " should be in the Accept-Encoding header");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try (OutputStream compressionOutputStream = compressionOutputStreamFunction.apply(output)) {
|
||||
compressionOutputStream.write(body.getBytes());
|
||||
}
|
||||
|
||||
return HttpResponse.response().withBody(output.toByteArray()).withHeader(HttpHeaders.CONTENT_ENCODING, encoding);
|
||||
});
|
||||
|
||||
HttpResult result = getter.get(HttpGetterTest.this.feedUrl);
|
||||
Assertions.assertEquals(body, new String(result.getContent()));
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompressionOutputStreamFunction {
|
||||
OutputStream apply(OutputStream input) throws IOException;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class SchemeNotAllowed {
|
||||
@Test
|
||||
void file() {
|
||||
Assertions.assertThrows(HttpGetter.SchemeNotAllowedException.class, () -> getter.get("file://localhost"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ftp() {
|
||||
Assertions.assertThrows(HttpGetter.SchemeNotAllowedException.class, () -> getter.get("ftp://localhost"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class HostNotAllowed {
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
Mockito.when(config.httpClient().blockLocalAddresses()).thenReturn(true);
|
||||
getter = new HttpGetter(config, () -> NOW, Mockito.mock(CommaFeedVersion.class), Mockito.mock(MetricRegistry.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localhost() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://localhost"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://127.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://2130706433"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0x7F.0x00.0x00.0X01"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void zero() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://0.0.0.0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void linkLocal() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.12.34"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://169.254.169.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multicast() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://224.2.3.4"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://239.255.255.254"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv4Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://10.0.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://172.16.0.1"));
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://192.168.0.1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void privateIpv6Ranges() {
|
||||
Assertions.assertThrows(HttpGetter.HostNotAllowedException.class, () -> getter.get("http://fd12:3456:789a:1::1"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +1,60 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.backend.Digests;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||
import com.commafeed.backend.feed.parser.FeedParser;
|
||||
import com.commafeed.backend.urlprovider.FeedURLProvider;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeedFetcherTest {
|
||||
|
||||
@Mock
|
||||
private FeedParser parser;
|
||||
|
||||
@Mock
|
||||
private HttpGetter getter;
|
||||
|
||||
@Mock
|
||||
private List<FeedURLProvider> urlProviders;
|
||||
|
||||
private FeedFetcher fetcher;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
fetcher = new FeedFetcher(parser, getter, urlProviders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updatesHeaderWhenContentDitNotChange() throws Exception {
|
||||
String url = "https://aaa.com";
|
||||
String lastModified = "last-modified-1";
|
||||
String etag = "etag-1";
|
||||
byte[] content = "content".getBytes();
|
||||
String lastContentHash = Digests.sha1Hex(content);
|
||||
|
||||
Mockito.when(getter.get(HttpGetter.HttpRequest.builder(url).lastModified(lastModified).eTag(etag).build()))
|
||||
.thenReturn(new HttpResult(content, "content-type", "last-modified-2", "etag-2", null, Duration.ZERO));
|
||||
|
||||
NotModifiedException e = Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> fetcher.fetch(url, false, lastModified, etag, Instant.now(), lastContentHash));
|
||||
|
||||
Assertions.assertEquals("last-modified-2", e.getNewLastModifiedHeader());
|
||||
Assertions.assertEquals("etag-2", e.getNewEtagHeader());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.backend.Digests;
|
||||
import com.commafeed.backend.HttpGetter;
|
||||
import com.commafeed.backend.HttpGetter.HttpResult;
|
||||
import com.commafeed.backend.HttpGetter.NotModifiedException;
|
||||
import com.commafeed.backend.feed.parser.FeedParser;
|
||||
import com.commafeed.backend.urlprovider.FeedURLProvider;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeedFetcherTest {
|
||||
|
||||
@Mock
|
||||
private FeedParser parser;
|
||||
|
||||
@Mock
|
||||
private HttpGetter getter;
|
||||
|
||||
@Mock
|
||||
private List<FeedURLProvider> urlProviders;
|
||||
|
||||
private FeedFetcher fetcher;
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
fetcher = new FeedFetcher(parser, getter, urlProviders);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updatesHeaderWhenContentDitNotChange() throws Exception {
|
||||
String url = "https://aaa.com";
|
||||
String lastModified = "last-modified-1";
|
||||
String etag = "etag-1";
|
||||
byte[] content = "content".getBytes();
|
||||
String lastContentHash = Digests.sha1Hex(content);
|
||||
|
||||
Mockito.when(getter.get(HttpGetter.HttpRequest.builder(url).lastModified(lastModified).eTag(etag).build()))
|
||||
.thenReturn(new HttpResult(content, "content-type", "last-modified-2", "etag-2", null, Duration.ZERO));
|
||||
|
||||
NotModifiedException e = Assertions.assertThrows(NotModifiedException.class,
|
||||
() -> fetcher.fetch(url, false, lastModified, etag, Instant.now(), lastContentHash));
|
||||
|
||||
Assertions.assertEquals("last-modified-2", e.getNewLastModifiedHeader());
|
||||
Assertions.assertEquals("etag-2", e.getNewEtagHeader());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,278 +1,278 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.InstantSource;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeedRefreshIntervalCalculatorTest {
|
||||
|
||||
private static final Instant NOW = Instant.now();
|
||||
private static final Duration DEFAULT_INTERVAL = Duration.ofHours(1);
|
||||
private static final Duration MAX_INTERVAL = Duration.ofDays(1);
|
||||
|
||||
@Mock
|
||||
private InstantSource instantSource;
|
||||
|
||||
@Mock
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
@Mock
|
||||
private FeedRefreshErrorHandling errorHandling;
|
||||
|
||||
private FeedRefreshIntervalCalculator calculator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(instantSource.instant()).thenReturn(NOW);
|
||||
Mockito.when(config.feedRefresh()).thenReturn(Mockito.mock(CommaFeedConfiguration.FeedRefresh.class));
|
||||
Mockito.when(config.feedRefresh().interval()).thenReturn(DEFAULT_INTERVAL);
|
||||
Mockito.when(config.feedRefresh().maxInterval()).thenReturn(MAX_INTERVAL);
|
||||
Mockito.when(config.feedRefresh().errors()).thenReturn(errorHandling);
|
||||
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FetchSuccess {
|
||||
|
||||
@Nested
|
||||
class EmpiricalDisabled {
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = { 0, 1, 300, 86400000L })
|
||||
void withoutValidFor(long averageEntryInterval) {
|
||||
// averageEntryInterval is ignored when empirical is disabled
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), averageEntryInterval, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withValidForGreaterThanMaxInterval() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 1L, MAX_INTERVAL.plusDays(1));
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withValidForLowerThanMaxInterval() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 1L, MAX_INTERVAL.minusSeconds(1));
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL).minusSeconds(1), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EmpiricalEnabled {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().intervalEmpirical()).thenReturn(true);
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(null, 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with31DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(31)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with15DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(15)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with8DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(8)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FiveDaysOld {
|
||||
@Test
|
||||
void averageBetweenBounds() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), Duration.ofHours(4).toMillis(),
|
||||
Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageBelowMinimum() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 10L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageAboveMaximum() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), Long.MAX_VALUE, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noAverage() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), null, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FeedNotModified {
|
||||
|
||||
@Nested
|
||||
class EmpiricalDisabled {
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = { 0, 1, 300, 86400000L })
|
||||
void withoutValidFor(long averageEntryInterval) {
|
||||
// averageEntryInterval is ignored when empirical is disabled
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), averageEntryInterval);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EmpiricalEnabled {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().intervalEmpirical()).thenReturn(true);
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(null, 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with31DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(31)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with15DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(15)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with8DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(8)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FiveDaysOld {
|
||||
@Test
|
||||
void averageBetweenBounds() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), Duration.ofHours(4).toMillis());
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageBelowMinimum() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), 10L);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageAboveMaximum() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), Long.MAX_VALUE);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noAverage() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), null);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FetchError {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().errors().retriesBeforeBackoff()).thenReturn(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lowErrorCount() {
|
||||
Instant result = calculator.onFetchError(1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void highErrorCount() {
|
||||
Mockito.when(config.feedRefresh().errors().backoffInterval()).thenReturn(Duration.ofHours(1));
|
||||
|
||||
Instant result = calculator.onFetchError(5);
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(3)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void veryHighErrorCount() {
|
||||
Mockito.when(config.feedRefresh().errors().backoffInterval()).thenReturn(Duration.ofHours(1));
|
||||
|
||||
Instant result = calculator.onFetchError(100000);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TooManyRequests {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().errors().retriesBeforeBackoff()).thenReturn(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterZero() {
|
||||
Instant result = calculator.onTooManyRequests(NOW, 1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterLowerThanInterval() {
|
||||
Instant retryAfter = NOW.plus(DEFAULT_INTERVAL.minusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterBetweenBounds() {
|
||||
Instant retryAfter = NOW.plus(DEFAULT_INTERVAL.plusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(retryAfter, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterGreaterThanMaxInterval() {
|
||||
Instant retryAfter = NOW.plus(MAX_INTERVAL.plusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.InstantSource;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
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.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.CommaFeedConfiguration.FeedRefreshErrorHandling;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FeedRefreshIntervalCalculatorTest {
|
||||
|
||||
private static final Instant NOW = Instant.now();
|
||||
private static final Duration DEFAULT_INTERVAL = Duration.ofHours(1);
|
||||
private static final Duration MAX_INTERVAL = Duration.ofDays(1);
|
||||
|
||||
@Mock
|
||||
private InstantSource instantSource;
|
||||
|
||||
@Mock
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
@Mock
|
||||
private FeedRefreshErrorHandling errorHandling;
|
||||
|
||||
private FeedRefreshIntervalCalculator calculator;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(instantSource.instant()).thenReturn(NOW);
|
||||
Mockito.when(config.feedRefresh()).thenReturn(Mockito.mock(CommaFeedConfiguration.FeedRefresh.class));
|
||||
Mockito.when(config.feedRefresh().interval()).thenReturn(DEFAULT_INTERVAL);
|
||||
Mockito.when(config.feedRefresh().maxInterval()).thenReturn(MAX_INTERVAL);
|
||||
Mockito.when(config.feedRefresh().errors()).thenReturn(errorHandling);
|
||||
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FetchSuccess {
|
||||
|
||||
@Nested
|
||||
class EmpiricalDisabled {
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = { 0, 1, 300, 86400000L })
|
||||
void withoutValidFor(long averageEntryInterval) {
|
||||
// averageEntryInterval is ignored when empirical is disabled
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), averageEntryInterval, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withValidForGreaterThanMaxInterval() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 1L, MAX_INTERVAL.plusDays(1));
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withValidForLowerThanMaxInterval() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 1L, MAX_INTERVAL.minusSeconds(1));
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL).minusSeconds(1), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EmpiricalEnabled {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().intervalEmpirical()).thenReturn(true);
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(null, 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with31DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(31)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with15DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(15)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with8DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(8)), 1L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FiveDaysOld {
|
||||
@Test
|
||||
void averageBetweenBounds() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), Duration.ofHours(4).toMillis(),
|
||||
Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageBelowMinimum() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), 10L, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageAboveMaximum() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), Long.MAX_VALUE, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noAverage() {
|
||||
Instant result = calculator.onFetchSuccess(NOW.minus(Duration.ofDays(5)), null, Duration.ZERO);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FeedNotModified {
|
||||
|
||||
@Nested
|
||||
class EmpiricalDisabled {
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = { 0, 1, 300, 86400000L })
|
||||
void withoutValidFor(long averageEntryInterval) {
|
||||
// averageEntryInterval is ignored when empirical is disabled
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), averageEntryInterval);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EmpiricalEnabled {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().intervalEmpirical()).thenReturn(true);
|
||||
calculator = new FeedRefreshIntervalCalculator(config, instantSource);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withNullPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(null, 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with31DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(31)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with15DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(15)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void with8DaysOldPublishedDate() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(8)), 1L);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FiveDaysOld {
|
||||
@Test
|
||||
void averageBetweenBounds() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), Duration.ofHours(4).toMillis());
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(2)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageBelowMinimum() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), 10L);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageAboveMaximum() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), Long.MAX_VALUE);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL.dividedBy(4)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noAverage() {
|
||||
Instant result = calculator.onFeedNotModified(NOW.minus(Duration.ofDays(5)), null);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class FetchError {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().errors().retriesBeforeBackoff()).thenReturn(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lowErrorCount() {
|
||||
Instant result = calculator.onFetchError(1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void highErrorCount() {
|
||||
Mockito.when(config.feedRefresh().errors().backoffInterval()).thenReturn(Duration.ofHours(1));
|
||||
|
||||
Instant result = calculator.onFetchError(5);
|
||||
Assertions.assertEquals(NOW.plus(Duration.ofHours(3)), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void veryHighErrorCount() {
|
||||
Mockito.when(config.feedRefresh().errors().backoffInterval()).thenReturn(Duration.ofHours(1));
|
||||
|
||||
Instant result = calculator.onFetchError(100000);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class TooManyRequests {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
Mockito.when(config.feedRefresh().errors().retriesBeforeBackoff()).thenReturn(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterZero() {
|
||||
Instant result = calculator.onTooManyRequests(NOW, 1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterLowerThanInterval() {
|
||||
Instant retryAfter = NOW.plus(DEFAULT_INTERVAL.minusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(NOW.plus(DEFAULT_INTERVAL), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterBetweenBounds() {
|
||||
Instant retryAfter = NOW.plus(DEFAULT_INTERVAL.plusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(retryAfter, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void withRetryAfterGreaterThanMaxInterval() {
|
||||
Instant retryAfter = NOW.plus(MAX_INTERVAL.plusSeconds(10));
|
||||
Instant result = calculator.onTooManyRequests(retryAfter, 1);
|
||||
Assertions.assertEquals(NOW.plus(MAX_INTERVAL), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,81 @@
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedUtilsTest {
|
||||
|
||||
@Test
|
||||
void testNormalization() {
|
||||
String urla1 = "http://example.com/hello?a=1&b=2";
|
||||
String urla2 = "http://www.example.com/hello?a=1&b=2";
|
||||
String urla3 = "http://EXAmPLe.com/HELLo?a=1&b=2";
|
||||
String urla4 = "http://example.com/hello?b=2&a=1";
|
||||
String urla5 = "https://example.com/hello?a=1&b=2";
|
||||
|
||||
String urlb1 = "http://ftr.fivefilters.org/makefulltextfeed.php?url=http%3A%2F%2Ffeeds.howtogeek.com%2FHowToGeek&max=10&summary=1";
|
||||
String urlb2 = "http://ftr.fivefilters.org/makefulltextfeed.php?url=http://feeds.howtogeek.com/HowToGeek&max=10&summary=1";
|
||||
|
||||
String urlc1 = "http://feeds.feedburner.com/Frandroid";
|
||||
String urlc2 = "http://feeds2.feedburner.com/frandroid";
|
||||
String urlc3 = "http://feedproxy.google.com/frandroid";
|
||||
String urlc4 = "http://feeds.feedburner.com/Frandroid/";
|
||||
String urlc5 = "http://feeds.feedburner.com/Frandroid?format=rss";
|
||||
|
||||
String urld1 = "http://fivefilters.org/content-only/makefulltextfeed.php?url=http://feeds.feedburner.com/Frandroid";
|
||||
String urld2 = "http://fivefilters.org/content-only/makefulltextfeed.php?url=http://feeds2.feedburner.com/Frandroid";
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla2));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla3));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla4));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla5));
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlb1), FeedUtils.normalizeURL(urlb2));
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc2));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc3));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc4));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc5));
|
||||
|
||||
Assertions.assertNotEquals(FeedUtils.normalizeURL(urld1), FeedUtils.normalizeURL(urld2));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToAbsoluteUrl() {
|
||||
String expected = "http://a.com/blog/entry/1";
|
||||
|
||||
// usual cases
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed", "http://a.com/feed"));
|
||||
|
||||
// relative links
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "feed.xml", "http://a.com/feed/feed.xml"));
|
||||
|
||||
// root-relative links
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("/blog/entry/1", "/feed", "http://a.com/feed"));
|
||||
|
||||
// real cases
|
||||
Assertions.assertEquals("https://github.com/erusev/parsedown/releases/tag/1.3.0", FeedUtils.toAbsoluteUrl(
|
||||
"/erusev/parsedown/releases/tag/1.3.0", "/erusev/parsedown/releases", "https://github.com/erusev/parsedown/tags.atom"));
|
||||
Assertions.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html",
|
||||
FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTrailingSlash() {
|
||||
final String url = "http://localhost/";
|
||||
final String result = FeedUtils.removeTrailingSlash(url);
|
||||
Assertions.assertEquals("http://localhost", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTrailingSlashLastSlashOnly() {
|
||||
final String url = "http://localhost//";
|
||||
final String result = FeedUtils.removeTrailingSlash(url);
|
||||
Assertions.assertEquals("http://localhost/", result);
|
||||
}
|
||||
|
||||
}
|
||||
package com.commafeed.backend.feed;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedUtilsTest {
|
||||
|
||||
@Test
|
||||
void testNormalization() {
|
||||
String urla1 = "http://example.com/hello?a=1&b=2";
|
||||
String urla2 = "http://www.example.com/hello?a=1&b=2";
|
||||
String urla3 = "http://EXAmPLe.com/HELLo?a=1&b=2";
|
||||
String urla4 = "http://example.com/hello?b=2&a=1";
|
||||
String urla5 = "https://example.com/hello?a=1&b=2";
|
||||
|
||||
String urlb1 = "http://ftr.fivefilters.org/makefulltextfeed.php?url=http%3A%2F%2Ffeeds.howtogeek.com%2FHowToGeek&max=10&summary=1";
|
||||
String urlb2 = "http://ftr.fivefilters.org/makefulltextfeed.php?url=http://feeds.howtogeek.com/HowToGeek&max=10&summary=1";
|
||||
|
||||
String urlc1 = "http://feeds.feedburner.com/Frandroid";
|
||||
String urlc2 = "http://feeds2.feedburner.com/frandroid";
|
||||
String urlc3 = "http://feedproxy.google.com/frandroid";
|
||||
String urlc4 = "http://feeds.feedburner.com/Frandroid/";
|
||||
String urlc5 = "http://feeds.feedburner.com/Frandroid?format=rss";
|
||||
|
||||
String urld1 = "http://fivefilters.org/content-only/makefulltextfeed.php?url=http://feeds.feedburner.com/Frandroid";
|
||||
String urld2 = "http://fivefilters.org/content-only/makefulltextfeed.php?url=http://feeds2.feedburner.com/Frandroid";
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla2));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla3));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla4));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urla1), FeedUtils.normalizeURL(urla5));
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlb1), FeedUtils.normalizeURL(urlb2));
|
||||
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc2));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc3));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc4));
|
||||
Assertions.assertEquals(FeedUtils.normalizeURL(urlc1), FeedUtils.normalizeURL(urlc5));
|
||||
|
||||
Assertions.assertNotEquals(FeedUtils.normalizeURL(urld1), FeedUtils.normalizeURL(urld2));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToAbsoluteUrl() {
|
||||
String expected = "http://a.com/blog/entry/1";
|
||||
|
||||
// usual cases
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("http://a.com/blog/entry/1", "http://a.com/feed", "http://a.com/feed"));
|
||||
|
||||
// relative links
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "http://a.com/feed/", "http://a.com/feed/"));
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("../blog/entry/1", "feed.xml", "http://a.com/feed/feed.xml"));
|
||||
|
||||
// root-relative links
|
||||
Assertions.assertEquals(expected, FeedUtils.toAbsoluteUrl("/blog/entry/1", "/feed", "http://a.com/feed"));
|
||||
|
||||
// real cases
|
||||
Assertions.assertEquals("https://github.com/erusev/parsedown/releases/tag/1.3.0", FeedUtils.toAbsoluteUrl(
|
||||
"/erusev/parsedown/releases/tag/1.3.0", "/erusev/parsedown/releases", "https://github.com/erusev/parsedown/tags.atom"));
|
||||
Assertions.assertEquals("http://ergoemacs.org/emacs/elisp_all_about_lines.html",
|
||||
FeedUtils.toAbsoluteUrl("elisp_all_about_lines.html", "blog.xml", "http://ergoemacs.org/emacs/blog.xml"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTrailingSlash() {
|
||||
final String url = "http://localhost/";
|
||||
final String result = FeedUtils.removeTrailingSlash(url);
|
||||
Assertions.assertEquals("http://localhost", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveTrailingSlashLastSlashOnly() {
|
||||
final String url = "http://localhost//";
|
||||
final String result = FeedUtils.removeTrailingSlash(url);
|
||||
Assertions.assertEquals("http://localhost/", result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EncodingDetectorTest {
|
||||
|
||||
EncodingDetector encodingDetector = new EncodingDetector();
|
||||
|
||||
@Test
|
||||
void testExtractDeclaredEncoding() {
|
||||
Assertions.assertNull(encodingDetector.extractDeclaredEncoding("<?xml ?>".getBytes()));
|
||||
Assertions.assertNull(encodingDetector.extractDeclaredEncoding("<feed></feed>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding=\"UTF-8\" ?>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding='UTF-8' ?>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding='UTF-8'?>".getBytes()));
|
||||
}
|
||||
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EncodingDetectorTest {
|
||||
|
||||
EncodingDetector encodingDetector = new EncodingDetector();
|
||||
|
||||
@Test
|
||||
void testExtractDeclaredEncoding() {
|
||||
Assertions.assertNull(encodingDetector.extractDeclaredEncoding("<?xml ?>".getBytes()));
|
||||
Assertions.assertNull(encodingDetector.extractDeclaredEncoding("<feed></feed>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding=\"UTF-8\" ?>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding='UTF-8' ?>".getBytes()));
|
||||
Assertions.assertEquals("UTF-8", encodingDetector.extractDeclaredEncoding("<?xml encoding='UTF-8'?>".getBytes()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +1,34 @@
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedCleanerTest {
|
||||
|
||||
FeedCleaner feedCleaner = new FeedCleaner();
|
||||
|
||||
@Test
|
||||
void testReplaceHtmlEntitiesWithNumericEntities() {
|
||||
String source = "<source>T´l´phone ′</source>";
|
||||
Assertions.assertEquals("<source>T´l´phone ′</source>", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveDoctype() {
|
||||
String source = "<!DOCTYPE html><html><head></head><body></body></html>";
|
||||
Assertions.assertEquals("<html><head></head><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveMultilineDoctype() {
|
||||
String source = """
|
||||
<!DOCTYPE
|
||||
html
|
||||
>
|
||||
<html><head></head><body></body></html>""";
|
||||
Assertions.assertEquals("""
|
||||
|
||||
<html><head></head><body></body></html>""", feedCleaner.removeDoctypeDeclarations(source));
|
||||
}
|
||||
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedCleanerTest {
|
||||
|
||||
FeedCleaner feedCleaner = new FeedCleaner();
|
||||
|
||||
@Test
|
||||
void testReplaceHtmlEntitiesWithNumericEntities() {
|
||||
String source = "<source>T´l´phone ′</source>";
|
||||
Assertions.assertEquals("<source>T´l´phone ′</source>", feedCleaner.replaceHtmlEntitiesWithNumericEntities(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveDoctype() {
|
||||
String source = "<!DOCTYPE html><html><head></head><body></body></html>";
|
||||
Assertions.assertEquals("<html><head></head><body></body></html>", feedCleaner.removeDoctypeDeclarations(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveMultilineDoctype() {
|
||||
String source = """
|
||||
<!DOCTYPE
|
||||
html
|
||||
>
|
||||
<html><head></head><body></body></html>""";
|
||||
Assertions.assertEquals("""
|
||||
|
||||
<html><head></head><body></body></html>""", feedCleaner.removeDoctypeDeclarations(source));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +1,53 @@
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class TextDirectionDetectorTest {
|
||||
|
||||
@Test
|
||||
public void testEstimateDirection() {
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect(""));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect(" "));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("! (...)"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("Pure Ascii content"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("-17.0%"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("http://foo/bar/"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT,
|
||||
TextDirectionDetector.detect("http://foo/bar/?s=\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0"
|
||||
+ "\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0" + "\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector.detect("\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector.detect("\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("http://foo/bar/ \u05d0 http://foo2/bar2/ http://foo3/bar3/"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05d0\u05d9\u05df \u05de\u05de\u05e9 " + "\u05de\u05d4 \u05dc\u05e8\u05d0\u05d5\u05ea: "
|
||||
+ "\u05dc\u05d0 \u05e6\u05d9\u05dc\u05de\u05ea\u05d9 " + "\u05d4\u05e8\u05d1\u05d4 \u05d5\u05d2\u05dd \u05d0"
|
||||
+ "\u05dd \u05d4\u05d9\u05d9\u05ea\u05d9 \u05de\u05e6\u05dc" + "\u05dd, \u05d4\u05d9\u05d4 \u05e9\u05dd"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05db\u05d0\u05df - http://geek.co.il/gallery/v/2007-06"
|
||||
+ " - \u05d0\u05d9\u05df \u05de\u05de\u05e9 \u05de\u05d4 " + "\u05dc\u05e8\u05d0\u05d5\u05ea: \u05dc\u05d0 \u05e6"
|
||||
+ "\u05d9\u05dc\u05de\u05ea\u05d9 \u05d4\u05e8\u05d1\u05d4 "
|
||||
+ "\u05d5\u05d2\u05dd \u05d0\u05dd \u05d4\u05d9\u05d9\u05ea"
|
||||
+ "\u05d9 \u05de\u05e6\u05dc\u05dd, \u05d4\u05d9\u05d4 "
|
||||
+ "\u05e9\u05dd \u05d1\u05e2\u05d9\u05e7\u05e8 \u05d4\u05e8" + "\u05d1\u05d4 \u05d0\u05e0\u05e9\u05d9\u05dd. \u05de"
|
||||
+ "\u05d4 \u05e9\u05db\u05df - \u05d0\u05e4\u05e9\u05e8 " + "\u05dc\u05e0\u05e6\u05dc \u05d0\u05ea \u05d4\u05d4 "
|
||||
+ "\u05d3\u05d6\u05de\u05e0\u05d5\u05ea \u05dc\u05d4\u05e1" + "\u05ea\u05db\u05dc \u05e2\u05dc \u05db\u05de\u05d4 "
|
||||
+ "\u05ea\u05de\u05d5\u05e0\u05d5\u05ea \u05de\u05e9\u05e2"
|
||||
+ "\u05e9\u05e2\u05d5\u05ea \u05d9\u05e9\u05e0\u05d5\u05ea " + "\u05d9\u05d5\u05ea\u05e8 \u05e9\u05d9\u05e9 \u05dc"
|
||||
+ "\u05d9 \u05d1\u05d0\u05ea\u05e8"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("CAPTCHA \u05de\u05e9\u05d5\u05db\u05dc\u05dc " + "\u05de\u05d3\u05d9?"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("Yes Prime Minister \u05e2\u05d3\u05db\u05d5\u05df. "
|
||||
+ "\u05e9\u05d0\u05dc\u05d5 \u05d0\u05d5\u05ea\u05d9 " + "\u05de\u05d4 \u05d0\u05e0\u05d9 \u05e8\u05d5\u05e6"
|
||||
+ "\u05d4 \u05de\u05ea\u05e0\u05d4 \u05dc\u05d7\u05d2"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector
|
||||
.detect("17.4.02 \u05e9\u05e2\u05d4:13-20 .15-00 .\u05dc\u05d0 " + "\u05d4\u05d9\u05d9\u05ea\u05d9 \u05db\u05d0\u05df."));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("5710 5720 5730. \u05d4\u05d3\u05dc\u05ea. " + "\u05d4\u05e0\u05e9\u05d9\u05e7\u05d4"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05d4\u05d3\u05dc\u05ea http://www.google.com " + "http://www.gmail.com"));
|
||||
}
|
||||
|
||||
package com.commafeed.backend.feed.parser;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class TextDirectionDetectorTest {
|
||||
|
||||
@Test
|
||||
public void testEstimateDirection() {
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect(""));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect(" "));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("! (...)"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("Pure Ascii content"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("-17.0%"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT, TextDirectionDetector.detect("http://foo/bar/"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.LEFT_TO_RIGHT,
|
||||
TextDirectionDetector.detect("http://foo/bar/?s=\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0"
|
||||
+ "\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0" + "\u05d0\u05d0\u05d0\u05d0\u05d0\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector.detect("\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector.detect("\u05d0"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("http://foo/bar/ \u05d0 http://foo2/bar2/ http://foo3/bar3/"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05d0\u05d9\u05df \u05de\u05de\u05e9 " + "\u05de\u05d4 \u05dc\u05e8\u05d0\u05d5\u05ea: "
|
||||
+ "\u05dc\u05d0 \u05e6\u05d9\u05dc\u05de\u05ea\u05d9 " + "\u05d4\u05e8\u05d1\u05d4 \u05d5\u05d2\u05dd \u05d0"
|
||||
+ "\u05dd \u05d4\u05d9\u05d9\u05ea\u05d9 \u05de\u05e6\u05dc" + "\u05dd, \u05d4\u05d9\u05d4 \u05e9\u05dd"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05db\u05d0\u05df - http://geek.co.il/gallery/v/2007-06"
|
||||
+ " - \u05d0\u05d9\u05df \u05de\u05de\u05e9 \u05de\u05d4 " + "\u05dc\u05e8\u05d0\u05d5\u05ea: \u05dc\u05d0 \u05e6"
|
||||
+ "\u05d9\u05dc\u05de\u05ea\u05d9 \u05d4\u05e8\u05d1\u05d4 "
|
||||
+ "\u05d5\u05d2\u05dd \u05d0\u05dd \u05d4\u05d9\u05d9\u05ea"
|
||||
+ "\u05d9 \u05de\u05e6\u05dc\u05dd, \u05d4\u05d9\u05d4 "
|
||||
+ "\u05e9\u05dd \u05d1\u05e2\u05d9\u05e7\u05e8 \u05d4\u05e8" + "\u05d1\u05d4 \u05d0\u05e0\u05e9\u05d9\u05dd. \u05de"
|
||||
+ "\u05d4 \u05e9\u05db\u05df - \u05d0\u05e4\u05e9\u05e8 " + "\u05dc\u05e0\u05e6\u05dc \u05d0\u05ea \u05d4\u05d4 "
|
||||
+ "\u05d3\u05d6\u05de\u05e0\u05d5\u05ea \u05dc\u05d4\u05e1" + "\u05ea\u05db\u05dc \u05e2\u05dc \u05db\u05de\u05d4 "
|
||||
+ "\u05ea\u05de\u05d5\u05e0\u05d5\u05ea \u05de\u05e9\u05e2"
|
||||
+ "\u05e9\u05e2\u05d5\u05ea \u05d9\u05e9\u05e0\u05d5\u05ea " + "\u05d9\u05d5\u05ea\u05e8 \u05e9\u05d9\u05e9 \u05dc"
|
||||
+ "\u05d9 \u05d1\u05d0\u05ea\u05e8"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("CAPTCHA \u05de\u05e9\u05d5\u05db\u05dc\u05dc " + "\u05de\u05d3\u05d9?"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("Yes Prime Minister \u05e2\u05d3\u05db\u05d5\u05df. "
|
||||
+ "\u05e9\u05d0\u05dc\u05d5 \u05d0\u05d5\u05ea\u05d9 " + "\u05de\u05d4 \u05d0\u05e0\u05d9 \u05e8\u05d5\u05e6"
|
||||
+ "\u05d4 \u05de\u05ea\u05e0\u05d4 \u05dc\u05d7\u05d2"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT, TextDirectionDetector
|
||||
.detect("17.4.02 \u05e9\u05e2\u05d4:13-20 .15-00 .\u05dc\u05d0 " + "\u05d4\u05d9\u05d9\u05ea\u05d9 \u05db\u05d0\u05df."));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("5710 5720 5730. \u05d4\u05d3\u05dc\u05ea. " + "\u05d4\u05e0\u05e9\u05d9\u05e7\u05d4"));
|
||||
Assertions.assertEquals(TextDirectionDetector.Direction.RIGHT_TO_LEFT,
|
||||
TextDirectionDetector.detect("\u05d4\u05d3\u05dc\u05ea http://www.google.com " + "http://www.gmail.com"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,147 +1,147 @@
|
||||
package com.commafeed.backend.opml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.rometools.opml.feed.opml.Opml;
|
||||
import com.rometools.opml.feed.opml.Outline;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OPMLExporterTest {
|
||||
|
||||
@Mock
|
||||
private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
|
||||
private final User user = new User();
|
||||
|
||||
private final FeedCategory cat1 = new FeedCategory();
|
||||
private final FeedCategory cat2 = new FeedCategory();
|
||||
|
||||
private final FeedSubscription rootFeed = newFeedSubscription("rootFeed", "rootFeed.com");
|
||||
private final FeedSubscription cat1Feed = newFeedSubscription("cat1Feed", "cat1Feed.com");
|
||||
private final FeedSubscription cat2Feed = newFeedSubscription("cat2Feed", "cat2Feed.com");
|
||||
|
||||
private final List<FeedCategory> categories = new ArrayList<>();
|
||||
private final List<FeedSubscription> subscriptions = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
user.setName("John Doe");
|
||||
|
||||
cat1.setId(1L);
|
||||
cat1.setName("cat1");
|
||||
cat1.setParent(null);
|
||||
cat1.setChildren(new HashSet<>());
|
||||
cat1.setSubscriptions(new HashSet<>());
|
||||
|
||||
cat2.setId(2L);
|
||||
cat2.setName("cat2");
|
||||
cat2.setParent(cat1);
|
||||
cat2.setChildren(new HashSet<>());
|
||||
cat2.setSubscriptions(new HashSet<>());
|
||||
|
||||
cat1.getChildren().add(cat2);
|
||||
|
||||
rootFeed.setCategory(null);
|
||||
cat1Feed.setCategory(cat1);
|
||||
cat2Feed.setCategory(cat2);
|
||||
|
||||
cat1.getSubscriptions().add(cat1Feed);
|
||||
cat2.getSubscriptions().add(cat2Feed);
|
||||
|
||||
categories.add(cat1);
|
||||
categories.add(cat2);
|
||||
|
||||
subscriptions.add(rootFeed);
|
||||
subscriptions.add(cat1Feed);
|
||||
subscriptions.add(cat2Feed);
|
||||
}
|
||||
|
||||
private Feed newFeed(String url) {
|
||||
Feed feed = new Feed();
|
||||
feed.setUrl(url);
|
||||
return feed;
|
||||
}
|
||||
|
||||
private FeedSubscription newFeedSubscription(String title, String url) {
|
||||
FeedSubscription feedSubscription = new FeedSubscription();
|
||||
feedSubscription.setTitle(title);
|
||||
feedSubscription.setFeed(newFeed(url));
|
||||
return feedSubscription;
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatesOpmlCorrectly() {
|
||||
Mockito.when(feedCategoryDAO.findAll(user)).thenReturn(categories);
|
||||
Mockito.when(feedSubscriptionDAO.findAll(user)).thenReturn(subscriptions);
|
||||
|
||||
Opml opml = new OPMLExporter(feedCategoryDAO, feedSubscriptionDAO).export(user);
|
||||
|
||||
List<Outline> rootOutlines = opml.getOutlines();
|
||||
Assertions.assertEquals(2, rootOutlines.size());
|
||||
Assertions.assertTrue(containsCategory(rootOutlines, "cat1"));
|
||||
Assertions.assertTrue(containsFeed(rootOutlines, "rootFeed", "rootFeed.com"));
|
||||
|
||||
Outline cat1Outline = getCategoryOutline(rootOutlines, "cat1");
|
||||
List<Outline> cat1Children = cat1Outline.getChildren();
|
||||
Assertions.assertEquals(2, cat1Children.size());
|
||||
Assertions.assertTrue(containsCategory(cat1Children, "cat2"));
|
||||
Assertions.assertTrue(containsFeed(cat1Children, "cat1Feed", "cat1Feed.com"));
|
||||
|
||||
Outline cat2Outline = getCategoryOutline(cat1Children, "cat2");
|
||||
List<Outline> cat2Children = cat2Outline.getChildren();
|
||||
Assertions.assertEquals(1, cat2Children.size());
|
||||
Assertions.assertTrue(containsFeed(cat2Children, "cat2Feed", "cat2Feed.com"));
|
||||
}
|
||||
|
||||
private boolean containsCategory(List<Outline> outlines, String category) {
|
||||
for (Outline o : outlines) {
|
||||
if (!"rss".equals(o.getType())) {
|
||||
if (category.equals(o.getTitle())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsFeed(List<Outline> outlines, String title, String url) {
|
||||
for (Outline o : outlines) {
|
||||
if ("rss".equals(o.getType())) {
|
||||
if (title.equals(o.getTitle()) && o.getAttributeValue("xmlUrl").equals(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Outline getCategoryOutline(List<Outline> outlines, String title) {
|
||||
for (Outline o : outlines) {
|
||||
if (o.getTitle().equals(title)) {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
package com.commafeed.backend.opml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.model.Feed;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.rometools.opml.feed.opml.Opml;
|
||||
import com.rometools.opml.feed.opml.Outline;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class OPMLExporterTest {
|
||||
|
||||
@Mock
|
||||
private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
|
||||
private final User user = new User();
|
||||
|
||||
private final FeedCategory cat1 = new FeedCategory();
|
||||
private final FeedCategory cat2 = new FeedCategory();
|
||||
|
||||
private final FeedSubscription rootFeed = newFeedSubscription("rootFeed", "rootFeed.com");
|
||||
private final FeedSubscription cat1Feed = newFeedSubscription("cat1Feed", "cat1Feed.com");
|
||||
private final FeedSubscription cat2Feed = newFeedSubscription("cat2Feed", "cat2Feed.com");
|
||||
|
||||
private final List<FeedCategory> categories = new ArrayList<>();
|
||||
private final List<FeedSubscription> subscriptions = new ArrayList<>();
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
user.setName("John Doe");
|
||||
|
||||
cat1.setId(1L);
|
||||
cat1.setName("cat1");
|
||||
cat1.setParent(null);
|
||||
cat1.setChildren(new HashSet<>());
|
||||
cat1.setSubscriptions(new HashSet<>());
|
||||
|
||||
cat2.setId(2L);
|
||||
cat2.setName("cat2");
|
||||
cat2.setParent(cat1);
|
||||
cat2.setChildren(new HashSet<>());
|
||||
cat2.setSubscriptions(new HashSet<>());
|
||||
|
||||
cat1.getChildren().add(cat2);
|
||||
|
||||
rootFeed.setCategory(null);
|
||||
cat1Feed.setCategory(cat1);
|
||||
cat2Feed.setCategory(cat2);
|
||||
|
||||
cat1.getSubscriptions().add(cat1Feed);
|
||||
cat2.getSubscriptions().add(cat2Feed);
|
||||
|
||||
categories.add(cat1);
|
||||
categories.add(cat2);
|
||||
|
||||
subscriptions.add(rootFeed);
|
||||
subscriptions.add(cat1Feed);
|
||||
subscriptions.add(cat2Feed);
|
||||
}
|
||||
|
||||
private Feed newFeed(String url) {
|
||||
Feed feed = new Feed();
|
||||
feed.setUrl(url);
|
||||
return feed;
|
||||
}
|
||||
|
||||
private FeedSubscription newFeedSubscription(String title, String url) {
|
||||
FeedSubscription feedSubscription = new FeedSubscription();
|
||||
feedSubscription.setTitle(title);
|
||||
feedSubscription.setFeed(newFeed(url));
|
||||
return feedSubscription;
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatesOpmlCorrectly() {
|
||||
Mockito.when(feedCategoryDAO.findAll(user)).thenReturn(categories);
|
||||
Mockito.when(feedSubscriptionDAO.findAll(user)).thenReturn(subscriptions);
|
||||
|
||||
Opml opml = new OPMLExporter(feedCategoryDAO, feedSubscriptionDAO).export(user);
|
||||
|
||||
List<Outline> rootOutlines = opml.getOutlines();
|
||||
Assertions.assertEquals(2, rootOutlines.size());
|
||||
Assertions.assertTrue(containsCategory(rootOutlines, "cat1"));
|
||||
Assertions.assertTrue(containsFeed(rootOutlines, "rootFeed", "rootFeed.com"));
|
||||
|
||||
Outline cat1Outline = getCategoryOutline(rootOutlines, "cat1");
|
||||
List<Outline> cat1Children = cat1Outline.getChildren();
|
||||
Assertions.assertEquals(2, cat1Children.size());
|
||||
Assertions.assertTrue(containsCategory(cat1Children, "cat2"));
|
||||
Assertions.assertTrue(containsFeed(cat1Children, "cat1Feed", "cat1Feed.com"));
|
||||
|
||||
Outline cat2Outline = getCategoryOutline(cat1Children, "cat2");
|
||||
List<Outline> cat2Children = cat2Outline.getChildren();
|
||||
Assertions.assertEquals(1, cat2Children.size());
|
||||
Assertions.assertTrue(containsFeed(cat2Children, "cat2Feed", "cat2Feed.com"));
|
||||
}
|
||||
|
||||
private boolean containsCategory(List<Outline> outlines, String category) {
|
||||
for (Outline o : outlines) {
|
||||
if (!"rss".equals(o.getType())) {
|
||||
if (category.equals(o.getTitle())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsFeed(List<Outline> outlines, String title, String url) {
|
||||
for (Outline o : outlines) {
|
||||
if ("rss".equals(o.getType())) {
|
||||
if (title.equals(o.getTitle()) && o.getAttributeValue("xmlUrl").equals(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Outline getCategoryOutline(List<Outline> outlines, String title) {
|
||||
for (Outline o : outlines) {
|
||||
if (o.getTitle().equals(title)) {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,52 @@
|
||||
package com.commafeed.backend.opml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
|
||||
class OPMLImporterTest {
|
||||
|
||||
@Test
|
||||
void testOpmlV10() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v1.0.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlV11() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v1.1.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlV20() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v2.0.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlNoVersion() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_noversion.xml");
|
||||
}
|
||||
|
||||
private void testOpmlVersion(String fileName) throws IOException, IllegalArgumentException, FeedException {
|
||||
FeedCategoryDAO feedCategoryDAO = Mockito.mock(FeedCategoryDAO.class);
|
||||
FeedSubscriptionService feedSubscriptionService = Mockito.mock(FeedSubscriptionService.class);
|
||||
User user = Mockito.mock(User.class);
|
||||
|
||||
String xml = IOUtils.toString(getClass().getResourceAsStream(fileName), StandardCharsets.UTF_8);
|
||||
|
||||
OPMLImporter importer = new OPMLImporter(feedCategoryDAO, feedSubscriptionService);
|
||||
importer.importOpml(user, xml);
|
||||
|
||||
Mockito.verify(feedSubscriptionService)
|
||||
.subscribe(Mockito.eq(user), Mockito.anyString(), Mockito.anyString(), Mockito.any(FeedCategory.class), Mockito.anyInt());
|
||||
}
|
||||
|
||||
}
|
||||
package com.commafeed.backend.opml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.model.FeedCategory;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.FeedSubscriptionService;
|
||||
import com.rometools.rome.io.FeedException;
|
||||
|
||||
class OPMLImporterTest {
|
||||
|
||||
@Test
|
||||
void testOpmlV10() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v1.0.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlV11() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v1.1.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlV20() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_v2.0.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOpmlNoVersion() throws IOException, IllegalArgumentException, FeedException {
|
||||
testOpmlVersion("/opml/opml_noversion.xml");
|
||||
}
|
||||
|
||||
private void testOpmlVersion(String fileName) throws IOException, IllegalArgumentException, FeedException {
|
||||
FeedCategoryDAO feedCategoryDAO = Mockito.mock(FeedCategoryDAO.class);
|
||||
FeedSubscriptionService feedSubscriptionService = Mockito.mock(FeedSubscriptionService.class);
|
||||
User user = Mockito.mock(User.class);
|
||||
|
||||
String xml = IOUtils.toString(getClass().getResourceAsStream(fileName), StandardCharsets.UTF_8);
|
||||
|
||||
OPMLImporter importer = new OPMLImporter(feedCategoryDAO, feedSubscriptionService);
|
||||
importer.importOpml(user, xml);
|
||||
|
||||
Mockito.verify(feedSubscriptionService)
|
||||
.subscribe(Mockito.eq(user), Mockito.anyString(), Mockito.anyString(), Mockito.any(FeedCategory.class), Mockito.anyInt());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedEntryContentCleaningServiceTest {
|
||||
|
||||
private final FeedEntryContentCleaningService feedEntryContentCleaningService = new FeedEntryContentCleaningService();
|
||||
|
||||
@Test
|
||||
void testClean() {
|
||||
String content = """
|
||||
<p>
|
||||
Some text
|
||||
<img width="965" height="320" src="https://localhost/an-image.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="alt-desc" decoding="async" sizes="(max-width: 965px) 100vw, 965px" style="width: 100%; opacity: 0">
|
||||
<iframe src="url" style="width: 100%; opacity: 0"></iframe>
|
||||
<forbidden-element>aaa</forbidden-element>
|
||||
""";
|
||||
String result = feedEntryContentCleaningService.clean(content, "baseUri", false);
|
||||
|
||||
Assertions.assertLinesMatch("""
|
||||
<p>
|
||||
Some text
|
||||
<img width="965" height="320" src="https://localhost/an-image.png" alt="alt-desc" style="width:100%;">
|
||||
<iframe src="url" style="width:100%;"></iframe>
|
||||
aaa
|
||||
</p>
|
||||
""".lines(), result.lines());
|
||||
}
|
||||
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FeedEntryContentCleaningServiceTest {
|
||||
|
||||
private final FeedEntryContentCleaningService feedEntryContentCleaningService = new FeedEntryContentCleaningService();
|
||||
|
||||
@Test
|
||||
void testClean() {
|
||||
String content = """
|
||||
<p>
|
||||
Some text
|
||||
<img width="965" height="320" src="https://localhost/an-image.png" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" alt="alt-desc" decoding="async" sizes="(max-width: 965px) 100vw, 965px" style="width: 100%; opacity: 0">
|
||||
<iframe src="url" style="width: 100%; opacity: 0"></iframe>
|
||||
<forbidden-element>aaa</forbidden-element>
|
||||
""";
|
||||
String result = feedEntryContentCleaningService.clean(content, "baseUri", false);
|
||||
|
||||
Assertions.assertLinesMatch("""
|
||||
<p>
|
||||
Some text
|
||||
<img width="965" height="320" src="https://localhost/an-image.png" alt="alt-desc" style="width:100%;">
|
||||
<iframe src="url" style="width:100%;"></iframe>
|
||||
aaa
|
||||
</p>
|
||||
""".lines(), result.lines());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +1,96 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException;
|
||||
|
||||
class FeedEntryFilteringServiceTest {
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
private FeedEntryFilteringService service;
|
||||
|
||||
private FeedEntry entry;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Mockito.when(config.feedRefresh().filteringExpressionEvaluationTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
|
||||
service = new FeedEntryFilteringService(config);
|
||||
|
||||
entry = new FeedEntry();
|
||||
entry.setUrl("https://github.com/Athou/commafeed");
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setAuthor("Athou");
|
||||
content.setTitle("Merge pull request #662 from Athou/dw8");
|
||||
content.setContent("Merge pull request #662 from Athou/dw8");
|
||||
entry.setContent(content);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyFilterMatchesFilter() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry(null, entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankFilterMatchesFilter() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleExpression() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("author.toString() eq 'athou'", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void newIsDisabled() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class,
|
||||
() -> service.filterMatchesEntry("null eq new ('java.lang.String', 'athou')", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassMethodIsDisabled() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("null eq ''.getClass()", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dotClassIsDisabled() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("null eq ''.class", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cannotLoopForever() {
|
||||
Mockito.when(config.feedRefresh().filteringExpressionEvaluationTimeout()).thenReturn(Duration.ofMillis(200));
|
||||
service = new FeedEntryFilteringService(config);
|
||||
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("while(true) {}", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesNullCorrectly() {
|
||||
entry.setUrl(null);
|
||||
entry.setContent(new FeedEntryContent());
|
||||
Assertions.assertDoesNotThrow(() -> service.filterMatchesEntry("author eq 'athou'", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void incorrectScriptThrowsException() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("aa eqz bb", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void incorrectReturnTypeThrowsException() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("1", entry));
|
||||
}
|
||||
|
||||
}
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.model.FeedEntry;
|
||||
import com.commafeed.backend.model.FeedEntryContent;
|
||||
import com.commafeed.backend.service.FeedEntryFilteringService.FeedEntryFilterException;
|
||||
|
||||
class FeedEntryFilteringServiceTest {
|
||||
private CommaFeedConfiguration config;
|
||||
|
||||
private FeedEntryFilteringService service;
|
||||
|
||||
private FeedEntry entry;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
config = Mockito.mock(CommaFeedConfiguration.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
Mockito.when(config.feedRefresh().filteringExpressionEvaluationTimeout()).thenReturn(Duration.ofSeconds(30));
|
||||
|
||||
service = new FeedEntryFilteringService(config);
|
||||
|
||||
entry = new FeedEntry();
|
||||
entry.setUrl("https://github.com/Athou/commafeed");
|
||||
|
||||
FeedEntryContent content = new FeedEntryContent();
|
||||
content.setAuthor("Athou");
|
||||
content.setTitle("Merge pull request #662 from Athou/dw8");
|
||||
content.setContent("Merge pull request #662 from Athou/dw8");
|
||||
entry.setContent(content);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyFilterMatchesFilter() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry(null, entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankFilterMatchesFilter() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void simpleExpression() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("author.toString() eq 'athou'", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void newIsDisabled() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class,
|
||||
() -> service.filterMatchesEntry("null eq new ('java.lang.String', 'athou')", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getClassMethodIsDisabled() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("null eq ''.getClass()", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dotClassIsDisabled() throws FeedEntryFilterException {
|
||||
Assertions.assertTrue(service.filterMatchesEntry("null eq ''.class", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cannotLoopForever() {
|
||||
Mockito.when(config.feedRefresh().filteringExpressionEvaluationTimeout()).thenReturn(Duration.ofMillis(200));
|
||||
service = new FeedEntryFilteringService(config);
|
||||
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("while(true) {}", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesNullCorrectly() {
|
||||
entry.setUrl(null);
|
||||
entry.setContent(new FeedEntryContent());
|
||||
Assertions.assertDoesNotThrow(() -> service.filterMatchesEntry("author eq 'athou'", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void incorrectScriptThrowsException() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("aa eqz bb", entry));
|
||||
}
|
||||
|
||||
@Test
|
||||
void incorrectReturnTypeThrowsException() {
|
||||
Assertions.assertThrows(FeedEntryFilterException.class, () -> service.filterMatchesEntry("1", entry));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PasswordEncryptionServiceTest {
|
||||
|
||||
@Test
|
||||
void authenticate() {
|
||||
String password = "password";
|
||||
byte[] salt = "abcdefgh".getBytes();
|
||||
|
||||
PasswordEncryptionService passwordEncryptionService = new PasswordEncryptionService();
|
||||
byte[] encryptedPassword = passwordEncryptionService.getEncryptedPassword(password, salt);
|
||||
|
||||
// make sure the encrypted password is always the same for a fixed salt
|
||||
Assertions.assertEquals("8b4660158141d9f4f7865718b9a2b940a3e3cea9", HexFormat.of().formatHex(encryptedPassword));
|
||||
Assertions.assertTrue(passwordEncryptionService.authenticate(password, encryptedPassword, salt));
|
||||
}
|
||||
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.HexFormat;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PasswordEncryptionServiceTest {
|
||||
|
||||
@Test
|
||||
void authenticate() {
|
||||
String password = "password";
|
||||
byte[] salt = "abcdefgh".getBytes();
|
||||
|
||||
PasswordEncryptionService passwordEncryptionService = new PasswordEncryptionService();
|
||||
byte[] encryptedPassword = passwordEncryptionService.getEncryptedPassword(password, salt);
|
||||
|
||||
// make sure the encrypted password is always the same for a fixed salt
|
||||
Assertions.assertEquals("8b4660158141d9f4f7865718b9a2b940a3e3cea9", HexFormat.of().formatHex(encryptedPassword));
|
||||
Assertions.assertTrue(passwordEncryptionService.authenticate(password, encryptedPassword, salt));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,194 +1,194 @@
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.dao.UserDAO;
|
||||
import com.commafeed.backend.dao.UserRoleDAO;
|
||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.internal.PostLoginActivities;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
private static final byte[] SALT = new byte[] { 1, 2, 3 };
|
||||
private static final byte[] ENCRYPTED_PASSWORD = new byte[] { 5, 6, 7 };
|
||||
|
||||
@Mock
|
||||
private CommaFeedConfiguration commaFeedConfiguration;
|
||||
@Mock
|
||||
private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
@Mock
|
||||
private UserDAO userDAO;
|
||||
@Mock
|
||||
private UserSettingsDAO userSettingsDAO;
|
||||
@Mock
|
||||
private UserRoleDAO userRoleDAO;
|
||||
@Mock
|
||||
private PasswordEncryptionService passwordEncryptionService;
|
||||
@Mock
|
||||
private PostLoginActivities postLoginActivities;
|
||||
|
||||
private User disabledUser;
|
||||
private User normalUser;
|
||||
|
||||
private UserService userService;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
userService = new UserService(feedCategoryDAO, feedSubscriptionDAO, userDAO, userRoleDAO, userSettingsDAO,
|
||||
passwordEncryptionService, commaFeedConfiguration, postLoginActivities);
|
||||
|
||||
disabledUser = new User();
|
||||
disabledUser.setDisabled(true);
|
||||
|
||||
normalUser = new User();
|
||||
normalUser.setDisabled(false);
|
||||
normalUser.setSalt(SALT);
|
||||
normalUser.setPassword(ENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectWhenGivenNullNameOrEmail() {
|
||||
Optional<User> user = userService.login(null, "password");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectWhenGivenNullPassword() {
|
||||
Optional<User> user = userService.login("testusername", null);
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldLookupUserByName() {
|
||||
userService.login("test", "password");
|
||||
Mockito.verify(userDAO).findByName("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldLookupUserByEmailIfLookupByNameFailed() {
|
||||
Mockito.when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
userService.login("test@test.com", "password");
|
||||
Mockito.verify(userDAO).findByEmail("test@test.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectIfCouldNotFindUserByNameOrEmail() {
|
||||
Mockito.when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
Mockito.when(userDAO.findByEmail("test@test.com")).thenReturn(null);
|
||||
|
||||
Optional<User> user = userService.login("test@test.com", "password");
|
||||
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectIfUserIsDisabled() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("test", "password");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldTryToAuthenticateUserWhoIsNotDisabled() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(false);
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
Mockito.verify(passwordEncryptionService).authenticate("password", ENCRYPTED_PASSWORD, SALT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectOnUnsuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(false);
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
Assertions.assertFalse(authenticatedUser.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldExecutePostLoginActivitiesForUserOnSuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(true);
|
||||
Mockito.doNothing().when(postLoginActivities).executeFor(Mockito.any(User.class));
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
Mockito.verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldReturnUserObjectOnSuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(true);
|
||||
Mockito.doNothing().when(postLoginActivities).executeFor(Mockito.any(User.class));
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
Assertions.assertTrue(authenticatedUser.isPresent());
|
||||
Assertions.assertEquals(normalUser, authenticatedUser.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfApikeyNull() {
|
||||
Optional<User> user = userService.login(null);
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldLookupUserByApikey() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
userService.login("apikey");
|
||||
Mockito.verify(userDAO).findByApiKey("apikey");
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfUserNotFoundFromLookupByApikey() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfUserFoundFromApikeyLookupIsDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldPerformPostLoginActivitiesIfUserFoundFromApikeyLookupNotDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
userService.login("apikey");
|
||||
Mockito.verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldReturnUserIfUserFoundFromApikeyLookupNotDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
Optional<User> returnedUser = userService.login("apikey");
|
||||
Assertions.assertEquals(normalUser, returnedUser.get());
|
||||
}
|
||||
|
||||
}
|
||||
package com.commafeed.backend.service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.FeedCategoryDAO;
|
||||
import com.commafeed.backend.dao.FeedSubscriptionDAO;
|
||||
import com.commafeed.backend.dao.UserDAO;
|
||||
import com.commafeed.backend.dao.UserRoleDAO;
|
||||
import com.commafeed.backend.dao.UserSettingsDAO;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.service.internal.PostLoginActivities;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceTest {
|
||||
|
||||
private static final byte[] SALT = new byte[] { 1, 2, 3 };
|
||||
private static final byte[] ENCRYPTED_PASSWORD = new byte[] { 5, 6, 7 };
|
||||
|
||||
@Mock
|
||||
private CommaFeedConfiguration commaFeedConfiguration;
|
||||
@Mock
|
||||
private FeedCategoryDAO feedCategoryDAO;
|
||||
@Mock
|
||||
private FeedSubscriptionDAO feedSubscriptionDAO;
|
||||
@Mock
|
||||
private UserDAO userDAO;
|
||||
@Mock
|
||||
private UserSettingsDAO userSettingsDAO;
|
||||
@Mock
|
||||
private UserRoleDAO userRoleDAO;
|
||||
@Mock
|
||||
private PasswordEncryptionService passwordEncryptionService;
|
||||
@Mock
|
||||
private PostLoginActivities postLoginActivities;
|
||||
|
||||
private User disabledUser;
|
||||
private User normalUser;
|
||||
|
||||
private UserService userService;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
userService = new UserService(feedCategoryDAO, feedSubscriptionDAO, userDAO, userRoleDAO, userSettingsDAO,
|
||||
passwordEncryptionService, commaFeedConfiguration, postLoginActivities);
|
||||
|
||||
disabledUser = new User();
|
||||
disabledUser.setDisabled(true);
|
||||
|
||||
normalUser = new User();
|
||||
normalUser.setDisabled(false);
|
||||
normalUser.setSalt(SALT);
|
||||
normalUser.setPassword(ENCRYPTED_PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectWhenGivenNullNameOrEmail() {
|
||||
Optional<User> user = userService.login(null, "password");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectWhenGivenNullPassword() {
|
||||
Optional<User> user = userService.login("testusername", null);
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldLookupUserByName() {
|
||||
userService.login("test", "password");
|
||||
Mockito.verify(userDAO).findByName("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldLookupUserByEmailIfLookupByNameFailed() {
|
||||
Mockito.when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
userService.login("test@test.com", "password");
|
||||
Mockito.verify(userDAO).findByEmail("test@test.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectIfCouldNotFindUserByNameOrEmail() {
|
||||
Mockito.when(userDAO.findByName("test@test.com")).thenReturn(null);
|
||||
Mockito.when(userDAO.findByEmail("test@test.com")).thenReturn(null);
|
||||
|
||||
Optional<User> user = userService.login("test@test.com", "password");
|
||||
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectIfUserIsDisabled() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("test", "password");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldTryToAuthenticateUserWhoIsNotDisabled() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(false);
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
Mockito.verify(passwordEncryptionService).authenticate("password", ENCRYPTED_PASSWORD, SALT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldNotReturnUserObjectOnUnsuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(false);
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
Assertions.assertFalse(authenticatedUser.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldExecutePostLoginActivitiesForUserOnSuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(true);
|
||||
Mockito.doNothing().when(postLoginActivities).executeFor(Mockito.any(User.class));
|
||||
|
||||
userService.login("test", "password");
|
||||
|
||||
Mockito.verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void callingLoginShouldReturnUserObjectOnSuccessfulAuthentication() {
|
||||
Mockito.when(userDAO.findByName("test")).thenReturn(normalUser);
|
||||
Mockito.when(passwordEncryptionService.authenticate(Mockito.anyString(), Mockito.any(byte[].class), Mockito.any(byte[].class)))
|
||||
.thenReturn(true);
|
||||
Mockito.doNothing().when(postLoginActivities).executeFor(Mockito.any(User.class));
|
||||
|
||||
Optional<User> authenticatedUser = userService.login("test", "password");
|
||||
|
||||
Assertions.assertTrue(authenticatedUser.isPresent());
|
||||
Assertions.assertEquals(normalUser, authenticatedUser.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfApikeyNull() {
|
||||
Optional<User> user = userService.login(null);
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldLookupUserByApikey() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
userService.login("apikey");
|
||||
Mockito.verify(userDAO).findByApiKey("apikey");
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfUserNotFoundFromLookupByApikey() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(null);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldNotReturnUserIfUserFoundFromApikeyLookupIsDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(disabledUser);
|
||||
Optional<User> user = userService.login("apikey");
|
||||
Assertions.assertFalse(user.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldPerformPostLoginActivitiesIfUserFoundFromApikeyLookupNotDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
userService.login("apikey");
|
||||
Mockito.verify(postLoginActivities).executeFor(normalUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
void apiLoginShouldReturnUserIfUserFoundFromApikeyLookupNotDisabled() {
|
||||
Mockito.when(userDAO.findByApiKey("apikey")).thenReturn(normalUser);
|
||||
Optional<User> returnedUser = userService.login("apikey");
|
||||
Assertions.assertEquals(normalUser, returnedUser.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package com.commafeed.backend.urlprovider;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class YoutubeFeedURLProviderTest {
|
||||
|
||||
private final YoutubeFeedURLProvider provider = new YoutubeFeedURLProvider();
|
||||
|
||||
@Test
|
||||
void matchesYoutubeChannelURL() {
|
||||
Assertions.assertEquals("https://www.youtube.com/feeds/videos.xml?channel_id=abc",
|
||||
provider.get("https://www.youtube.com/channel/abc", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotmatchYoutubeChannelURL() {
|
||||
Assertions.assertNull(provider.get("https://www.anothersite.com/channel/abc", null));
|
||||
Assertions.assertNull(provider.get("https://www.youtube.com/user/abc", null));
|
||||
}
|
||||
|
||||
package com.commafeed.backend.urlprovider;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class YoutubeFeedURLProviderTest {
|
||||
|
||||
private final YoutubeFeedURLProvider provider = new YoutubeFeedURLProvider();
|
||||
|
||||
@Test
|
||||
void matchesYoutubeChannelURL() {
|
||||
Assertions.assertEquals("https://www.youtube.com/feeds/videos.xml?channel_id=abc",
|
||||
provider.get("https://www.youtube.com/channel/abc", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotmatchYoutubeChannelURL() {
|
||||
Assertions.assertNull(provider.get("https://www.anothersite.com/channel/abc", null));
|
||||
Assertions.assertNull(provider.get("https://www.youtube.com/user/abc", null));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user