diff --git a/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java b/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java index ab3ba4da..e805da6a 100644 --- a/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java +++ b/commafeed-server/src/test/java/com/commafeed/CommaFeedDropwizardAppExtension.java @@ -3,6 +3,8 @@ package com.commafeed; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; import javax.sql.DataSource; @@ -13,6 +15,9 @@ import io.dropwizard.testing.junit5.DropwizardAppExtension; public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension { + private static final List TABLES = Arrays.asList("FEEDENTRYSTATUSES", "FEEDENTRYTAGS", "FEEDENTRIES", "FEEDENTRYCONTENTS", + "FEEDSUBSCRIPTIONS", "FEEDS", "FEEDCATEGORIES"); + public CommaFeedDropwizardAppExtension() { super(CommaFeedApplication.class, ResourceHelpers.resourceFilePath("config.test.yml")); } @@ -23,9 +28,11 @@ public class CommaFeedDropwizardAppExtension extends DropwizardAppExtension existingUsers = getAllUsers(); + + User user = new User(); + user.setName("test"); + user.setPassword("test".getBytes()); + user.setEmail("test@test.com"); + + try (Response response = getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + + List newUsers = getAllUsers(); + Assertions.assertEquals(existingUsers.size() + 1, newUsers.size()); + + UserModel newUser = newUsers.stream().filter(u -> u.getName().equals("test")).findFirst().get(); + user.setId(newUser.getId()); + } + + IDRequest req = new IDRequest(); + req.setId(user.getId()); + try (Response response = getClient().target(getApiBaseUrl() + "admin/user/delete").request().post(Entity.json(req))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + + List newUsers = getAllUsers(); + Assertions.assertEquals(existingUsers.size(), newUsers.size()); + } + } + + @Test + void editExistingUser() { + List existingUsers = getAllUsers(); + UserModel user = existingUsers.stream().filter(u -> u.getName().equals("admin")).findFirst().get(); + user.setEmail("new-email@provider.com"); + + try (Response response = getClient().target(getApiBaseUrl() + "admin/user/save").request().post(Entity.json(user))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + + List newUsers = getAllUsers(); + Assertions.assertEquals(existingUsers.size(), newUsers.size()); + } + } + + private List getAllUsers() { + try (Response response = getClient().target(getApiBaseUrl() + "admin/user/getAll").request().get()) { + return Arrays.asList(response.readEntity(UserModel[].class)); + } + } + } + +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java new file mode 100644 index 00000000..20ec17b8 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/BaseIT.java @@ -0,0 +1,108 @@ +package com.commafeed.integration; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Response; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jetty.http.HttpStatus; +import org.glassfish.jersey.client.JerseyClientBuilder; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockserver.client.MockServerClient; +import org.mockserver.junit.jupiter.MockServerExtension; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; + +import com.commafeed.CommaFeedDropwizardAppExtension; +import com.commafeed.frontend.model.Entries; +import com.commafeed.frontend.model.Subscription; +import com.commafeed.frontend.model.request.LoginRequest; +import com.commafeed.frontend.model.request.SubscribeRequest; + +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import lombok.Getter; + +@Getter +@ExtendWith(DropwizardExtensionsSupport.class) +@ExtendWith(MockServerExtension.class) +abstract class BaseIT { + + private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() { + @Override + protected JerseyClientBuilder clientBuilder() { + return super.clientBuilder().register(HttpAuthenticationFeature.basic("admin", "admin")).register(MultiPartFeature.class); + } + }; + + private Client client; + + private String feedUrl; + + private String baseUrl; + + private String apiBaseUrl; + + private String webSocketUrl; + + @BeforeEach + void init(MockServerClient mockServerClient) throws IOException { + URL resource = Objects.requireNonNull(getClass().getResource("/feed/rss.xml")); + mockServerClient.when(HttpRequest.request().withMethod("GET").withPath("/")) + .respond(HttpResponse.response().withBody(IOUtils.toString(resource, StandardCharsets.UTF_8))); + + this.client = EXT.client(); + this.feedUrl = "http://localhost:" + mockServerClient.getPort() + "/"; + this.baseUrl = "http://localhost:" + EXT.getLocalPort() + "/"; + this.apiBaseUrl = this.baseUrl + "rest/"; + this.webSocketUrl = "ws://localhost:" + EXT.getLocalPort() + "/ws"; + } + + protected String login() { + LoginRequest req = new LoginRequest(); + req.setName("admin"); + req.setPassword("admin"); + try (Response response = client.target(apiBaseUrl + "user/login").request().post(Entity.json(req))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + return response.getCookies().get("JSESSIONID").getValue(); + } + } + + protected Long subscribe(String feedUrl) { + SubscribeRequest subscribeRequest = new SubscribeRequest(); + subscribeRequest.setUrl(feedUrl); + subscribeRequest.setTitle("my title for this feed"); + try (Response response = client.target(apiBaseUrl + "feed/subscribe").request().post(Entity.json(subscribeRequest))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + return response.readEntity(Long.class); + } + } + + protected Subscription getSubscription(Long subscriptionId) { + try (Response response = client.target(apiBaseUrl + "feed/get") + .path("{id}") + .resolveTemplate("id", subscriptionId) + .request() + .get()) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + return response.readEntity(Subscription.class); + } + } + + protected Entries getFeedEntries(long subscriptionId) { + Response response = client.target(apiBaseUrl + "feed/entries") + .queryParam("id", subscriptionId) + .queryParam("readType", "unread") + .request() + .get(); + return response.readEntity(Entries.class); + } +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java b/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java index f06adeed..877b0248 100644 --- a/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java +++ b/commafeed-server/src/test/java/com/commafeed/integration/FeedIT.java @@ -1,86 +1,237 @@ package com.commafeed.integration; import java.io.IOException; -import java.nio.charset.StandardCharsets; +import java.io.InputStream; import java.time.Duration; +import java.util.Date; +import java.util.Objects; -import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.awaitility.Awaitility; import org.eclipse.jetty.http.HttpStatus; -import org.glassfish.jersey.client.JerseyClientBuilder; -import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; +import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; 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.mockserver.client.MockServerClient; -import org.mockserver.junit.jupiter.MockServerExtension; -import org.mockserver.model.HttpRequest; -import org.mockserver.model.HttpResponse; -import com.commafeed.CommaFeedDropwizardAppExtension; import com.commafeed.frontend.model.Entries; -import com.commafeed.frontend.model.request.SubscribeRequest; +import com.commafeed.frontend.model.Entry; +import com.commafeed.frontend.model.FeedInfo; +import com.commafeed.frontend.model.Subscription; +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 io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +class FeedIT extends BaseIT { -@ExtendWith(DropwizardExtensionsSupport.class) -@ExtendWith(MockServerExtension.class) -class FeedIT { + @Nested + class Fetch { + @Test + void fetchFeed() { + FeedInfoRequest req = new FeedInfoRequest(); + req.setUrl(getFeedUrl()); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/fetch").request().post(Entity.json(req))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + + FeedInfo feedInfo = response.readEntity(FeedInfo.class); + Assertions.assertEquals("CommaFeed test feed", feedInfo.getTitle()); + Assertions.assertEquals(getFeedUrl(), feedInfo.getUrl()); + } - private static final CommaFeedDropwizardAppExtension EXT = new CommaFeedDropwizardAppExtension() { - @Override - protected JerseyClientBuilder clientBuilder() { - HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("admin", "admin"); - return super.clientBuilder().register(feature); } - }; - - private MockServerClient mockServerClient; - - @BeforeEach - void init(MockServerClient mockServerClient) throws IOException { - this.mockServerClient = mockServerClient; - this.mockServerClient.when(HttpRequest.request().withMethod("GET")) - .respond(HttpResponse.response() - .withBody(IOUtils.toString(getClass().getResource("/feed/rss.xml"), StandardCharsets.UTF_8))); } - @Test - void test() { - Client client = EXT.client(); + @Nested + class Subscribe { + @Test + void subscribeAndReadEntries() { + long subscriptionId = subscribe(getFeedUrl()); + Awaitility.await().atMost(Duration.ofSeconds(15)).until(() -> getFeedEntries(subscriptionId), e -> e.getEntries().size() == 2); + } - String feedUrl = "http://localhost:" + this.mockServerClient.getPort(); + @Test + void subscribeFromUrl() { + try (Response response = getClient().target(getApiBaseUrl() + "feed/subscribe") + .queryParam("url", getFeedUrl()) + .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE) + .request() + .get()) { + Assertions.assertEquals(HttpStatus.TEMPORARY_REDIRECT_307, response.getStatus()); + } + } - long subscriptionId = subscribe(client, feedUrl); - Awaitility.await() - .atMost(Duration.ofSeconds(15)) - .pollInterval(Duration.ofMillis(500)) - .until(() -> getFeedEntries(client, subscriptionId), e -> e.getEntries().size() == 2); + @Test + void unsubscribeFromUnknownFeed() { + Assertions.assertEquals(HttpStatus.NOT_FOUND_404, unsubsribe(1L)); + } + + @Test + void unsubscribeFromKnownFeed() { + long subscriptionId = subscribe(getFeedUrl()); + Assertions.assertEquals(HttpStatus.OK_200, unsubsribe(subscriptionId)); + } + + private int unsubsribe(long subscriptionId) { + IDRequest request = new IDRequest(); + request.setId(subscriptionId); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/unsubscribe").request().post(Entity.json(request))) { + return response.getStatus(); + } + } } - private Long subscribe(Client client, String feedUrl) { - SubscribeRequest subscribeRequest = new SubscribeRequest(); - subscribeRequest.setUrl(feedUrl); - subscribeRequest.setTitle("my title for this feed"); - Response response = client.target(String.format("http://localhost:%d/rest/feed/subscribe", EXT.getLocalPort())) - .request() - .post(Entity.json(subscribeRequest)); - Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); - return response.readEntity(Long.class); + @Nested + class Mark { + @Test + void mark() { + long subscriptionId = subscribe(getFeedUrl()); + Entries entries = Awaitility.await() + .atMost(Duration.ofSeconds(15)) + .until(() -> getFeedEntries(subscriptionId), e -> e.getEntries().size() == 2); + Assertions.assertTrue(entries.getEntries().stream().noneMatch(Entry::isRead)); + + markFeedEntries(subscriptionId); + Awaitility.await() + .atMost(Duration.ofSeconds(15)) + .until(() -> getFeedEntries(subscriptionId), e -> e.getEntries().stream().allMatch(Entry::isRead)); + } + + private void markFeedEntries(long subscriptionId) { + MarkRequest request = new MarkRequest(); + request.setId(String.valueOf(subscriptionId)); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/mark").request().post(Entity.json(request))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } } - private Entries getFeedEntries(Client client, long subscriptionId) { - Response response = client.target(String.format("http://localhost:%d/rest/feed/entries", EXT.getLocalPort())) - .queryParam("id", subscriptionId) - .queryParam("readType", "unread") - .request() - .get(); - return response.readEntity(Entries.class); + @Nested + class Refresh { + @Test + void refresh() { + Long subscriptionId = subscribe(getFeedUrl()); + + Date now = new Date(); + refreshFeed(subscriptionId); + + Awaitility.await() + .atMost(Duration.ofSeconds(15)) + .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().after(now)); + } + + @Test + void refreshAll() { + Long subscriptionId = subscribe(getFeedUrl()); + + Date now = new Date(); + refreshAllFeeds(); + + Awaitility.await() + .atMost(Duration.ofSeconds(15)) + .until(() -> getSubscription(subscriptionId), f -> f.getLastRefresh().after(now)); + } + + private void refreshFeed(Long subscriptionId) { + IDRequest request = new IDRequest(); + request.setId(subscriptionId); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/refresh").request().post(Entity.json(request))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } + + private void refreshAllFeeds() { + try (Response response = getClient().target(getApiBaseUrl() + "feed/refreshAll").request().get()) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } + + } + + @Nested + class Modify { + @Test + void modify() { + Long subscriptionId = subscribe(getFeedUrl()); + + Subscription subscription = getSubscription(subscriptionId); + + FeedModificationRequest req = new FeedModificationRequest(); + req.setId(subscriptionId); + req.setName("new name"); + req.setCategoryId(subscription.getCategoryId()); + try (Response response = getClient().target(getApiBaseUrl() + "feed/modify").request().post(Entity.json(req))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + subscription = getSubscription(subscriptionId); + Assertions.assertEquals("new name", subscription.getName()); + } + } + + @Nested + class Favicon { + @Test + void favicon() throws IOException { + Long subscriptionId = subscribe(getFeedUrl()); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/favicon") + .path("{id}") + .resolveTemplate("id", subscriptionId) + .request() + .get()) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + byte[] icon = response.readEntity(byte[].class); + + byte[] defaultFavicon = IOUtils.toByteArray(Objects.requireNonNull(getClass().getResource("/images/default_favicon.gif"))); + Assertions.assertArrayEquals(defaultFavicon, icon); + } + } + } + + @Nested + class Opml { + @Test + void importExportOpml() throws IOException { + importOpml(); + String opml = exportOpml(); + String expextedOpml = "\n" + "\n" + " \n" + + " admin subscriptions in CommaFeed\n" + " \n" + " \n" + + " \n" + + " \n" + + " \n" + " \n" + "\n"; + Assertions.assertEquals(StringUtils.normalizeSpace(expextedOpml), StringUtils.normalizeSpace(opml)); + } + + void importOpml() { + InputStream stream = Objects.requireNonNull(getClass().getResourceAsStream("/opml/opml_v2.0.xml")); + MultiPart multiPart = new MultiPart().bodyPart(new StreamDataBodyPart("file", stream)); + multiPart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE); + + try (Response response = getClient().target(getApiBaseUrl() + "feed/import") + .request() + .post(Entity.entity(multiPart, multiPart.getMediaType()))) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } + + String exportOpml() { + try (Response response = getClient().target(getApiBaseUrl() + "feed/export").request().get()) { + Assertions.assertEquals(HttpStatus.OK_200, response.getStatus()); + return response.readEntity(String.class); + } + } } } diff --git a/commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java b/commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java new file mode 100644 index 00000000..8d76f576 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/NextUnreadIT.java @@ -0,0 +1,31 @@ +package com.commafeed.integration; + +import java.util.concurrent.TimeUnit; + +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; + +class NextUnreadIT extends BaseIT { + + @Test + void test() { + Long subscriptionId = subscribe(getFeedUrl()); + Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> !getFeedEntries(subscriptionId).getEntries().isEmpty()); + + String cookie = login(); + Response response = getClient().target(getBaseUrl() + "next") + .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.FALSE) + .request() + .header(HttpHeaders.COOKIE, "JSESSIONID=" + cookie) + .get(); + Assertions.assertEquals(HttpStatus.FOUND_302, response.getStatus()); + Assertions.assertEquals("https://www.commafeed.com/2", response.getHeaderString(HttpHeaders.LOCATION)); + } + +} diff --git a/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java b/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java new file mode 100644 index 00000000..c0613814 --- /dev/null +++ b/commafeed-server/src/test/java/com/commafeed/integration/WebSocketIT.java @@ -0,0 +1,59 @@ +package com.commafeed.integration; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.ContainerProvider; +import javax.websocket.DeploymentException; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class WebSocketIT extends BaseIT { + + @Test + void subscribeAndGetsNotified() throws DeploymentException, IOException { + String sessionId = login(); + ClientEndpointConfig config = ClientEndpointConfig.Builder.create().configurator(new ClientEndpointConfig.Configurator() { + @Override + public void beforeRequest(Map> headers) { + headers.put("Cookie", Collections.singletonList("JSESSIONID=" + sessionId)); + } + }).build(); + + AtomicBoolean connected = new AtomicBoolean(); + AtomicReference messageRef = new AtomicReference<>(); + try (Session ignored = ContainerProvider.getWebSocketContainer().connectToServer(new Endpoint() { + @Override + public void onOpen(Session session, EndpointConfig config) { + session.addMessageHandler(new MessageHandler.Whole() { + @Override + public void onMessage(String message) { + messageRef.set(message); + } + }); + connected.set(true); + } + }, config, URI.create(getWebSocketUrl()))) { + Awaitility.await().atMost(15, TimeUnit.SECONDS).untilTrue(connected); + + Long subscriptionId = subscribe(getFeedUrl()); + + Awaitility.await().atMost(15, TimeUnit.SECONDS).until(() -> messageRef.get() != null); + Assertions.assertEquals("new-feed-entries:" + subscriptionId, messageRef.get()); + } + } + +}