diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java index 89ebffa0..d31c9dd2 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/UserREST.java @@ -211,8 +211,8 @@ public class UserREST { if (u != null && !user.getId().equals(u.getId())) { throw new BadRequestException("email already taken"); } + user.setEmail(email); } - user.setEmail(email); if (StringUtils.isNotBlank(request.getNewPassword())) { byte[] password = encryptionService.getEncryptedPassword(request.getNewPassword(), user.getSalt()); diff --git a/commafeed-server/src/main/java/com/commafeed/frontend/resource/fever/FeverResponse.java b/commafeed-server/src/main/java/com/commafeed/frontend/resource/fever/FeverResponse.java index 5dd91136..73c6eaf0 100644 --- a/commafeed-server/src/main/java/com/commafeed/frontend/resource/fever/FeverResponse.java +++ b/commafeed-server/src/main/java/com/commafeed/frontend/resource/fever/FeverResponse.java @@ -3,6 +3,7 @@ package com.commafeed.frontend.resource.fever; import java.io.IOException; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat.Shape; @@ -10,8 +11,12 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; @@ -41,10 +46,12 @@ public class FeverResponse { @JsonProperty("unread_item_ids") @JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class) + @JsonDeserialize(using = CommaSeparatedStringToLongListDeserializer.class) private List unreadItemIds; @JsonProperty("saved_item_ids") @JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class) + @JsonDeserialize(using = CommaSeparatedStringToLongListDeserializer.class) private List savedItemIds; @JsonProperty("items") @@ -100,6 +107,7 @@ public class FeverResponse { @JsonProperty("feed_ids") @JsonSerialize(using = LongListToCommaSeparatedStringSerializer.class) + @JsonDeserialize(using = CommaSeparatedStringToLongListDeserializer.class) private List feedIds; } @@ -153,12 +161,18 @@ public class FeverResponse { } public static class LongListToCommaSeparatedStringSerializer extends JsonSerializer> { - @Override public void serialize(List input, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { String output = input.stream().map(String::valueOf).collect(Collectors.joining(",")); jsonGenerator.writeObject(output); } + } + public static class CommaSeparatedStringToLongListDeserializer extends JsonDeserializer> { + @Override + public List deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String value = ctxt.readValue(p, String.class); + return Stream.of(value.split(",")).map(Long::valueOf).collect(Collectors.toList()); + } } } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java index 20ec17b8..1f6f09d9 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java @@ -3,6 +3,7 @@ package com.commafeed.integration; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Objects; import javax.ws.rs.client.Client; @@ -10,6 +11,7 @@ import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response; import org.apache.commons.io.IOUtils; +import org.awaitility.Awaitility; import org.eclipse.jetty.http.HttpStatus; import org.glassfish.jersey.client.JerseyClientBuilder; import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; @@ -34,7 +36,7 @@ import lombok.Getter; @Getter @ExtendWith(DropwizardExtensionsSupport.class) @ExtendWith(MockServerExtension.class) -abstract class BaseIT { +public abstract class BaseIT { private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() { @Override @@ -86,12 +88,14 @@ abstract class BaseIT { } } + protected Long subscribeAndWaitForEntries(String feedUrl) { + Long subscriptionId = subscribe(feedUrl); + Awaitility.await().atMost(Duration.ofSeconds(15)).until(() -> getFeedEntries(subscriptionId), e -> e.getEntries().size() == 2); + return subscriptionId; + } + protected Subscription getSubscription(Long subscriptionId) { - try (Response response = client.target(apiBaseUrl + "feed/get") - .path("{id}") - .resolveTemplate("id", subscriptionId) - .request() - .get()) { + try (Response response = client.target(apiBaseUrl + "feed/get/{id}").resolveTemplate("id", subscriptionId).request().get()) { Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); return response.readEntity(Subscription.class); } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/AdminIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java similarity index 94% rename from commafeed-server/src/test/java/com/commafeed/integration/AdminIT.java rename to commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java index 7bbe3e04..5bd4658d 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/AdminIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/AdminIT.java @@ -1,4 +1,4 @@ -package com.commafeed.integration; +package com.commafeed.integration.rest; import java.util.Arrays; import java.util.List; @@ -15,6 +15,7 @@ import com.commafeed.CommaFeedConfiguration.ApplicationSettings; import com.commafeed.backend.model.User; import com.commafeed.frontend.model.UserModel; import com.commafeed.frontend.model.request.IDRequest; +import com.commafeed.integration.BaseIT; class AdminIT extends BaseIT { diff --git a/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java similarity index 94% rename from commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java rename to commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java index 5687b4d3..277aefaf 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeedIT.java @@ -1,4 +1,4 @@ -package com.commafeed.integration; +package com.commafeed.integration.rest; import java.io.IOException; import java.io.InputStream; @@ -29,6 +29,7 @@ import com.commafeed.frontend.model.request.FeedInfoRequest; import com.commafeed.frontend.model.request.FeedModificationRequest; import com.commafeed.frontend.model.request.IDRequest; import com.commafeed.frontend.model.request.MarkRequest; +import com.commafeed.integration.BaseIT; class FeedIT extends BaseIT { @@ -120,7 +121,7 @@ class FeedIT extends BaseIT { class Refresh { @Test void refresh() { - Long subscriptionId = subscribe(getFeedUrl()); + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); Date now = new Date(); refreshFeed(subscriptionId); @@ -132,7 +133,7 @@ class FeedIT extends BaseIT { @Test void refreshAll() { - Long subscriptionId = subscribe(getFeedUrl()); + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); Date now = new Date(); refreshAllFeeds(); @@ -186,8 +187,7 @@ class FeedIT extends BaseIT { void favicon() throws IOException { Long subscriptionId = subscribe(getFeedUrl()); - try (Response response = getClient().target(getApiBaseUrl() + "feed/favicon") - .path("{id}") + try (Response response = getClient().target(getApiBaseUrl() + "feed/favicon/{id}") .resolveTemplate("id", subscriptionId) .request() .get()) { diff --git a/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java new file mode 100644 index 00000000..5ad0b2a1 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/rest/FeverIT.java @@ -0,0 +1,92 @@ +package com.commafeed.integration.rest; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.Response; + +import org.apache.commons.codec.digest.DigestUtils; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.commafeed.frontend.model.UserModel; +import com.commafeed.frontend.model.request.ProfileModificationRequest; +import com.commafeed.frontend.resource.fever.FeverResponse; +import com.commafeed.integration.BaseIT; + +class FeverIT extends BaseIT { + + private Long userId; + private String apiKey; + + @BeforeEach + void init() { + // create api key + ProfileModificationRequest req = new ProfileModificationRequest(); + req.setCurrentPassword("admin"); + req.setNewApiKey(true); + try (Response response = getClient().target(getApiBaseUrl() + "user/profile").request().post(Entity.json(req))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + // retrieve api key + UserModel user = getClient().target(getApiBaseUrl() + "user/profile").request().get(UserModel.class); + this.apiKey = user.getApiKey(); + this.userId = user.getId(); + } + + @Test + void get() { + try (Response response = getClient().target(getApiBaseUrl() + "fever/user/${userId}") + .resolveTemplate("userId", 1) + .request() + .get()) { + Assertions.assertEquals("Welcome to the CommaFeed Fever API. Add this URL to your Fever-compatible reader.", + response.readEntity(String.class)); + } + } + + @Test + void invalidApiKey() { + FeverResponse response = fetch("feeds", "invalid-key"); + Assertions.assertFalse(response.isAuth()); + } + + @Test + void validApiKey() { + FeverResponse response = fetch("feeds", apiKey); + Assertions.assertTrue(response.isAuth()); + } + + @Test + void feeds() { + subscribe(getFeedUrl()); + FeverResponse feverResponse = fetch("feeds"); + Assertions.assertEquals(1, feverResponse.getFeeds().size()); + } + + @Test + void unreadEntries() { + subscribeAndWaitForEntries(getFeedUrl()); + FeverResponse feverResponse = fetch("unread_item_ids"); + Assertions.assertEquals(2, feverResponse.getUnreadItemIds().size()); + } + + private FeverResponse fetch(String what) { + return fetch(what, apiKey); + } + + private FeverResponse fetch(String what, String apiKey) { + Form form = new Form(); + form.param("api_key", DigestUtils.md5Hex("admin:" + apiKey)); + form.param(what, "1"); + try (Response response = getClient().target(getApiBaseUrl() + "fever/user/{userId}") + .resolveTemplate("userId", userId) + .request() + .post(Entity.form(form))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + return response.readEntity(FeverResponse.class); + } + } +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java b/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java new file mode 100644 index 00000000..9bfc31ca --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/servlet/CustomCodeIT.java @@ -0,0 +1,46 @@ +package com.commafeed.integration.servlet; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.commafeed.frontend.model.Settings; +import com.commafeed.integration.BaseIT; + +class CustomCodeIT extends BaseIT { + + @Test + void test() { + // get settings + Settings settings = null; + try (Response response = getClient().target(getApiBaseUrl() + "user/settings").request().get()) { + settings = response.readEntity(Settings.class); + } + + // update settings + settings.setCustomJs("custom-js"); + settings.setCustomCss("custom-css"); + try (Response response = getClient().target(getApiBaseUrl() + "user/settings").request().post(Entity.json(settings))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + // check custom code servlets + String cookie = login(); + try (Response response = getClient().target(getBaseUrl() + "custom_js.js") + .request() + .header(HttpHeaders.COOKIE, "JSESSIONID=" + cookie) + .get()) { + Assertions.assertEquals("custom-js", response.readEntity(String.class)); + } + try (Response response = getClient().target(getBaseUrl() + "custom_css.css") + .request() + .header(HttpHeaders.COOKIE, "JSESSIONID=" + cookie) + .get()) { + Assertions.assertEquals("custom-css", response.readEntity(String.class)); + } + } +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/servlet/LogoutIT.java b/commafeed-server/src/test/java/com/commafeed/integration/servlet/LogoutIT.java new file mode 100644 index 00000000..96298620 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/servlet/LogoutIT.java @@ -0,0 +1,26 @@ +package com.commafeed.integration.servlet; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.eclipse.jetty.http.HttpStatus; +import org.glassfish.jersey.client.ClientProperties; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.commafeed.integration.BaseIT; + +class LogoutIT extends BaseIT { + + @Test + void test() { + String cookie = login(); + try (Response response = getClient().target(getBaseUrl() + "logout") + .request() + .header(HttpHeaders.COOKIE, "JSESSIONID=" + cookie) + .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE) + .get()) { + Assertions.assertEquals(HttpStatus.FOUND_302, response.getStatus()); + } + } +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java b/commafeed-server/src/test/java/com/commafeed/integration/servlet/NextUnreadIT.java similarity index 70% rename from commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java rename to commafeed-server/src/test/java/com/commafeed/integration/servlet/NextUnreadIT.java index 1830879a..a9d50cf4 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/servlet/NextUnreadIT.java @@ -1,22 +1,20 @@ -package com.commafeed.integration; - -import java.util.concurrent.TimeUnit; +package com.commafeed.integration.servlet; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import org.awaitility.Awaitility; import org.eclipse.jetty.http.HttpStatus; import org.glassfish.jersey.client.ClientProperties; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import com.commafeed.integration.BaseIT; + class NextUnreadIT extends BaseIT { @Test void test() { - Long subscriptionId = subscribe(getFeedUrl()); - Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> !getFeedEntries(subscriptionId).getEntries().isEmpty()); + Long subscriptionId = subscribeAndWaitForEntries(getFeedUrl()); String cookie = login(); Response response = getClient().target(getBaseUrl() + "next") diff --git a/commafeed-server/src/test/java/com/commafeed/integration/servlet/RobotsTxtIT.java b/commafeed-server/src/test/java/com/commafeed/integration/servlet/RobotsTxtIT.java new file mode 100644 index 00000000..3ca95cfd --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/servlet/RobotsTxtIT.java @@ -0,0 +1,17 @@ +package com.commafeed.integration.servlet; + +import javax.ws.rs.core.Response; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.commafeed.integration.BaseIT; + +class RobotsTxtIT extends BaseIT { + @Test + void test() { + try (Response response = getClient().target(getBaseUrl() + "robots.txt").request().get()) { + Assertions.assertEquals("User-agent: *\nDisallow: /", response.readEntity(String.class)); + } + } +}