diff --git a/.gitignore b/.gitignore index b38f8e7..9425845 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,10 @@ target */log *logs *data +misc +*allure-results +.allure docker-dir/tomcat/resources/*.war .DS_Store -kindle/kindlegen.exe \ No newline at end of file +kindle/kindlegen.exe +kindle/kindlegen \ No newline at end of file diff --git a/pom.xml b/pom.xml index 76c4377..b1eafc9 100755 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 4.11 2.1.0-RC.1 6.9.8 + 0.17.2 3.0.2 3.6.1 @@ -43,6 +44,7 @@ rss-2-kindle-rest-api rss-2-kindle-web docker-dir + rss-2-kindle-web-test @@ -105,6 +107,11 @@ ${mockito.version} test + + com.github.javafaker + javafaker + ${javafaker.version} + @@ -128,6 +135,11 @@ UTF-8 + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + org.apache.maven.plugins maven-checkstyle-plugin diff --git a/rss-2-kindle-camel/src/test/resources/log4j.properties b/rss-2-kindle-camel/src/test/resources/log4j.properties index 77d0561..15ccb46 100755 --- a/rss-2-kindle-camel/src/test/resources/log4j.properties +++ b/rss-2-kindle-camel/src/test/resources/log4j.properties @@ -6,10 +6,9 @@ log4j.rootLogger=DEBUG, out # CONSOLE appender not used by default log4j.appender.out=org.apache.log4j.ConsoleAppender log4j.appender.out.layout=org.apache.log4j.PatternLayout -log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n -#log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n +log4j.appender.out.layout.ConversionPattern=[%p][%t] <%c{1}> - %m%n -log4j.logger.org.apache.camel.component.file.remote=TRACE +log4j.logger.org.apache.camel.component.file.remote=INFO log4j.logger.org.springframework=WARN, SPRING log4j.appender.SPRING=org.apache.log4j.RollingFileAppender diff --git a/rss-2-kindle-datasource-mongo/pom.xml b/rss-2-kindle-datasource-mongo/pom.xml index a1d3f39..116455a 100755 --- a/rss-2-kindle-datasource-mongo/pom.xml +++ b/rss-2-kindle-datasource-mongo/pom.xml @@ -17,7 +17,6 @@ org.apache.maven.plugins maven-surefire-plugin - 2.20 true diff --git a/rss-2-kindle-rest-api/pom.xml b/rss-2-kindle-rest-api/pom.xml index 92ccd1c..5ea70f9 100755 --- a/rss-2-kindle-rest-api/pom.xml +++ b/rss-2-kindle-rest-api/pom.xml @@ -97,7 +97,12 @@ 2.26-b09 test - + + com.github.javafaker + javafaker + ${javafaker.version} + test + diff --git a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ProfileManager.java b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ProfileManager.java index 620e19f..1579dd3 100644 --- a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ProfileManager.java +++ b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ProfileManager.java @@ -15,13 +15,15 @@ import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** * Created by eurohlam on 30.08.17. */ @Service -@Path("profile/{username: [a-zA-Z][a-zA-Z_0-9]*}") +@Path("profile/{username: [a-zA-Z_.0-9]*}") public class ProfileManager { private final Logger logger = LoggerFactory.getLogger(ProfileManager.class); @@ -34,6 +36,8 @@ public class ProfileManager { private ModelFactory modelFactory = new ModelFactory(); + private List validationErrors = Collections.EMPTY_LIST; + @GET @Produces(MediaType.APPLICATION_JSON) public Response getUserDetails(@PathParam("username") String username) { @@ -62,7 +66,7 @@ public Response getAllSubscribers(@PathParam("username") String username) { } @GET - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}") @Produces(MediaType.APPLICATION_JSON) public Response getSubscriber(@PathParam("username") String username, @PathParam("email") String subscriberId) { logger.debug("Fetch subscriber {} for user {}", subscriberId, username); @@ -76,7 +80,7 @@ public Response getSubscriber(@PathParam("username") String username, @PathParam } @GET - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/suspend") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/suspend") @Produces(MediaType.APPLICATION_JSON) public Response suspendSubscriber(@PathParam("username") String username, @PathParam("email") String subscriberId) { logger.warn("Suspend subscriber {} for user {}", subscriberId, username); @@ -94,7 +98,7 @@ public Response suspendSubscriber(@PathParam("username") String username, @PathP } @GET - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/resume") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/resume") @Produces(MediaType.APPLICATION_JSON) public Response resumeSubscriber(@PathParam("username") String username, @PathParam("email") String subscriberId) { logger.warn("Resume subscriber {} for user {}", subscriberId, username); @@ -121,7 +125,11 @@ public Response addSubscriber(@PathParam("username") String username, @FormParam("rss") String rss) { logger.info("Add new subscriber {} for user {}", email, username); try { - OperationResult result = subscriberRepository.addSubscriber(username, modelFactory.newSubscriber(email, name, rss)); + Subscriber subscriber = modelFactory.newSubscriber(email, name, rss); + if (!isValid(subscriber)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } + OperationResult result = subscriberRepository.addSubscriber(username, subscriber); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); } else { @@ -142,6 +150,9 @@ public Response addSubscriber(@PathParam("username") String username, logger.info("Requested to add a new subscriber for user {} with data {}", username, message); try { Subscriber subscriber = modelFactory.json2Pojo(Subscriber.class, message); + if (!isValid(subscriber)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } OperationResult result = subscriberRepository.addSubscriber(username, subscriber); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); @@ -168,7 +179,11 @@ public Response updateSubscriber(@PathParam("username") String username, @FormParam("rss") String rss) { logger.warn("Update existing subscriber {} for user {}", email, username); try { - OperationResult result = subscriberRepository.updateSubscriber(username, modelFactory.newSubscriber(email, name, rss)); + Subscriber subscriber = modelFactory.newSubscriber(email, name, rss); + if (!isValid(subscriber)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } + OperationResult result = subscriberRepository.updateSubscriber(username, subscriber); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); } else { @@ -188,6 +203,9 @@ public Response updateSubscriber(@PathParam("username") String username, String logger.warn("Requested to update existing subscriber for user {} with data {}", username, message); try { Subscriber subscriber = modelFactory.json2Pojo(Subscriber.class, message); + if (!isValid(subscriber)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } OperationResult result = subscriberRepository.updateSubscriber(username, subscriber); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); @@ -201,7 +219,7 @@ public Response updateSubscriber(@PathParam("username") String username, String } @DELETE - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/remove") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/remove") @Produces(MediaType.APPLICATION_JSON) public Response removeSubscriber(@PathParam("username") String username, @PathParam("email") String subscriberId) { logger.warn("Remove subscriber {} for user {}", subscriberId, username); @@ -219,7 +237,7 @@ public Response removeSubscriber(@PathParam("username") String username, @PathPa } @GET - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/subscriptions") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/subscriptions") @Produces(MediaType.APPLICATION_JSON) public Response getAllSubscriptions(@PathParam("username") String username, @PathParam("email") String subscriberId) { logger.debug("Fetch all subscriptions for subscriber {} by user {}", subscriberId, username); @@ -234,7 +252,7 @@ public Response getAllSubscriptions(@PathParam("username") String username, @Pat } @POST - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/subscribe") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/subscribe") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) public Response addSubscription(@PathParam("username") String username, @@ -245,7 +263,7 @@ public Response addSubscription(@PathParam("username") String username, Subscriber subscriber = subscriberRepository.getSubscriber(username, subscriberId); for (Rss r : subscriber.getRsslist()) { if (r.getRss().equals(rss)) { - return Response.status(Response.Status.CONFLICT).build(); + return Response.status(Response.Status.CONFLICT.getStatusCode(), "Duplicated subscription").build(); } } @@ -266,7 +284,7 @@ public Response addSubscription(@PathParam("username") String username, } @POST - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}/unsubscribe") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}/unsubscribe") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) public Response removeSubscription(@PathParam("username") String username, @@ -295,4 +313,42 @@ public Response removeSubscription(@PathParam("username") String username, return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } } + + private boolean isValid(Subscriber subscriber) { + List errors = new ArrayList<>(); + boolean isValid = true; + if (subscriber.getName().isEmpty()) { + errors.add("subscriber's name must not be empty"); + isValid = false; + } + if (subscriber.getEmail().isEmpty()) { + errors.add("subscriber's email must not be empty"); + isValid = false; + } + if (subscriber.getRsslist().size() < 1) { + errors.add("subscriber must have at least 1 subscription"); + isValid = false; + } + if (!subscriber.getEmail().matches("[\\w.]+@[\\w.]+")) { + errors.add("email contains unexpected symbols. It must satisfy the mask [\\w.]+@[\\w.]+"); + isValid = false; + } + for (Rss rss: subscriber.getRsslist()) { + if (rss.getRss().isEmpty()) { + errors.add("subscription must not be empty"); + isValid = false; + } + String pattern = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)"; + if (!rss.getRss().matches(pattern)) { + errors.add("incorrect URL for subscription: " + rss.getRss()); + isValid = false; + } + } + if (isValid) { + validationErrors = Collections.EMPTY_LIST; + } else { + validationErrors = errors; + } + return isValid; + } } diff --git a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ServiceManager.java b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ServiceManager.java index 85791a2..399072f 100644 --- a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ServiceManager.java +++ b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/ServiceManager.java @@ -26,7 +26,7 @@ public class ServiceManager { private SubscriberRepository subscriberRepository; @GET - @Path("/{email: \\w+@\\w+\\.[a-zA-Z]{2,}}") + @Path("/{email: [\\w\\.]+@[\\w\\.]+}") @Produces(MediaType.APPLICATION_JSON) public Response runRssPollingForSubscriber(@PathParam("username") String username, @PathParam("email") String id) { logger.info("Run RSS polling by demand: user {}, subscriber {}", username, id); diff --git a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/UserManager.java b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/UserManager.java index 4257e6f..631ac3a 100755 --- a/rss-2-kindle-rest-api/src/main/java/org/roag/rest/UserManager.java +++ b/rss-2-kindle-rest-api/src/main/java/org/roag/rest/UserManager.java @@ -11,6 +11,9 @@ import javax.ws.rs.*; import javax.ws.rs.core.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * Created by eurohlam on 09.11.16. @@ -26,6 +29,8 @@ public class UserManager { private ModelFactory modelFactory; + private List validationErrors = Collections.EMPTY_LIST; + public UserManager() { super(); this.modelFactory = new ModelFactory(); @@ -45,7 +50,7 @@ public Response getAllUsers() { } @GET - @Path("/{username: [a-zA-Z][a-zA-Z_0-9]*}") + @Path("/{username: [a-zA-Z_.0-9]*}") @Produces(MediaType.APPLICATION_JSON) public Response getUser(@PathParam("username") String id) { logger.debug("Fetch user {} from repository", id); @@ -59,7 +64,7 @@ public Response getUser(@PathParam("username") String id) { } @GET - @Path("/{username: [a-zA-Z][a-zA-Z_0-9]*}/lock") + @Path("/{username: [a-zA-Z_.0-9]*}/lock") @Produces(MediaType.APPLICATION_JSON) public Response lockUser(@PathParam("username") String id) { logger.warn("Lock user {}", id); @@ -77,7 +82,7 @@ public Response lockUser(@PathParam("username") String id) { } @GET - @Path("/{username: [a-zA-Z][a-zA-Z_0-9]*}/unlock") + @Path("/{username: [a-zA-Z_.0-9]*}/unlock") @Produces(MediaType.APPLICATION_JSON) public Response unlockUser(@PathParam("username") String id) { logger.warn("Unlock user {}", id); @@ -102,7 +107,11 @@ public Response addUser(@FormParam("username") String username, @FormParam("password") String password) { logger.info("Add new user {}", username); try { - OperationResult result = userRepository.addUser(modelFactory.newUser(username, email, password)); + User user = modelFactory.newUser(username, email, password); + if (!isValid(user)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } + OperationResult result = userRepository.addUser(user); logger.info(result.toString()); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); @@ -123,6 +132,9 @@ public Response addUser(String message) { logger.info("Requested to add new user from data {}", message); try { User user = modelFactory.json2Pojo(User.class, message); + if (!isValid(user)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } OperationResult result = userRepository.addUser(user); logger.info(result.toString()); if (result == OperationResult.SUCCESS) { @@ -145,6 +157,9 @@ public Response updateUser(@FormParam("username") String username, logger.warn("Update existing user {}", username); try { User user = userRepository.getUser(username); + if (!isValid(user)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } user.setPassword(password); OperationResult result = userRepository.updateUser(user); if (result == OperationResult.SUCCESS) { @@ -166,6 +181,9 @@ public Response updateUser(String message) { logger.warn("Requested to update existing user with data {}", message); try { User user = modelFactory.json2Pojo(User.class, message); + if (!isValid(user)) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode(), validationErrors.toString()).build(); + } OperationResult result = userRepository.updateUser(user); if (result == OperationResult.SUCCESS) { return Response.ok(result.toJson(), MediaType.APPLICATION_JSON_TYPE).build(); @@ -179,7 +197,7 @@ public Response updateUser(String message) { } @DELETE - @Path("/{username: [a-zA-Z][a-zA-Z_0-9]*}/remove") + @Path("/{username: [a-zA-Z_.0-9]*}/remove") @Produces(MediaType.APPLICATION_JSON) public Response removeUser(@PathParam("username") String id) { logger.warn("Remove user {}", id); @@ -196,4 +214,39 @@ public Response removeUser(@PathParam("username") String id) { } } + private boolean isValid(User user) { + List errors = new ArrayList<>(); + boolean isValid = true; + if (user.getUsername().isEmpty()) { + errors.add("username must not be empty"); + isValid = false; + } + if (user.getPassword().isEmpty()) { + errors.add("password must not be empty"); + isValid = false; + } + if (user.getEmail().isEmpty()) { + errors.add("email must not be empty"); + isValid = false; + } + if (user.getPassword().length() < 6) { + errors.add("password must not be less then 6 symbols"); + isValid = false; + } + if (!user.getUsername().matches("[a-zA-Z_.0-9]*")) { + errors.add("username contains unexpected symbols. It must satisfy the mask [a-zA-Z_.0-9]*"); + isValid = false; + } + if (!user.getEmail().matches("[\\w.]+@[\\w.]+")) { + errors.add("email contains unexpected symbols. It must satisfy the mask [\\w.]+@[\\w.]+"); + isValid = false; + } + if (isValid) { + validationErrors = Collections.EMPTY_LIST; + } else { + validationErrors = errors; + } + return isValid; + } + } diff --git a/rss-2-kindle-rest-api/src/main/resources/log4j.properties b/rss-2-kindle-rest-api/src/main/resources/log4j.properties index 2377aa2..575f253 100755 --- a/rss-2-kindle-rest-api/src/main/resources/log4j.properties +++ b/rss-2-kindle-rest-api/src/main/resources/log4j.properties @@ -6,7 +6,7 @@ log4j.rootLogger=INFO, out # CONSOLE appender not used by default log4j.appender.out=org.apache.log4j.ConsoleAppender log4j.appender.out.layout=org.apache.log4j.PatternLayout -log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n +log4j.appender.out.layout.ConversionPattern=[%p][%t] <%c{1}> - %m%n log4j.logger.org.springframework=INFO, SPRING log4j.appender.SPRING=org.apache.log4j.RollingFileAppender diff --git a/rss-2-kindle-rest-api/src/test/java/org/roag/rest/ProfileManagerTest.java b/rss-2-kindle-rest-api/src/test/java/org/roag/rest/ProfileManagerTest.java index 48b5d94..19a1454 100644 --- a/rss-2-kindle-rest-api/src/test/java/org/roag/rest/ProfileManagerTest.java +++ b/rss-2-kindle-rest-api/src/test/java/org/roag/rest/ProfileManagerTest.java @@ -1,5 +1,6 @@ package org.roag.rest; +import com.github.javafaker.Faker; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTestNg; import org.roag.model.Rss; @@ -51,9 +52,10 @@ public void getSubscriberOperationsTest() { @Test(groups = {"Subscribers:CRUD"}) public void crudSubscriberTestHtmlForm() { - final String newEmail = "test2@mail.com"; - final String newName = "test_name"; - final String newRss = "http://test.com/rss"; + final Faker faker = new Faker(); + final String newEmail = faker.internet().emailAddress(); + final String newName = faker.funnyName().name(); + final String newRss = "http://" + faker.internet().url(); final ModelFactory factory = new ModelFactory(); //create @@ -95,9 +97,10 @@ public void crudSubscriberTestHtmlForm() { @Test(groups = {"Subscribers:CRUD"}) public void crudSubscriberTestJson() { - String newEmail = "test2@mail.com"; - String newName = "test_name"; - String newRss = "http://test.com/rss"; + final Faker faker = new Faker(); + final String newEmail = faker.internet().emailAddress(); + final String newName = faker.funnyName().name(); + final String newRss = "http://" + faker.internet().url(); ModelFactory factory = new ModelFactory(); //create diff --git a/rss-2-kindle-rest-api/src/test/java/org/roag/rest/UserManagerTest.java b/rss-2-kindle-rest-api/src/test/java/org/roag/rest/UserManagerTest.java index c850445..12c926d 100644 --- a/rss-2-kindle-rest-api/src/test/java/org/roag/rest/UserManagerTest.java +++ b/rss-2-kindle-rest-api/src/test/java/org/roag/rest/UserManagerTest.java @@ -1,5 +1,6 @@ package org.roag.rest; +import com.github.javafaker.Faker; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTestNg; import org.roag.model.User; @@ -49,16 +50,17 @@ public void getUserOperationsTest() { @Test(groups = {"UserManager:CRUD"}) public void crudTestHtmlForm() { - final String newUser = "formUser"; - final String newEmail = "html@mail.com"; - final String newPassword = "htmlforever"; + final Faker faker = new Faker(); + final String newEmail = faker.internet().emailAddress(); + final String newUser = faker.name().username(); + final String newPassword = faker.internet().password(6,8); final ModelFactory factory = new ModelFactory(); //create from html form Form formNew = new Form(); formNew.param("username", newUser); formNew.param("email", newEmail); - formNew.param("password", "12345"); + formNew.param("password", faker.internet().password(6,8)); Response response = target(PATH + "new").request().post(Entity.form(formNew), Response.class); assertEquals("Creating new User failed", 200, response.getStatus()); @@ -92,15 +94,16 @@ public void crudTestHtmlForm() { @Test(groups = {"UserManager:CRUD"}) public void crudTestJson() { - final String newUser = "jsonUser"; - final String newEmail = "json@mail.com"; - final String newPassword = "jsonforever"; + final Faker faker = new Faker(); + final String newEmail = faker.internet().emailAddress(); + final String newUser = faker.name().username(); + final String newPassword = faker.internet().password(6,8); final ModelFactory factory = new ModelFactory(); //create from json - User u = factory.newUser(newUser, newEmail, "12345"); + User u = factory.newUser(newUser, newEmail, faker.internet().password(6,8)); Response response = target(PATH + "new").request().post(Entity.json(factory.pojo2Json(u)), Response.class); - assertEquals("Creating new User failed", 200, response.getStatus()); + assertEquals("Creating new User failed: " + response.getStatusInfo().getReasonPhrase(), 200, response.getStatus()); //read response = target(PATH + newUser).request().accept(MediaType.APPLICATION_JSON_TYPE).get(); diff --git a/rss-2-kindle-rest-api/src/test/resources/test-context.properties b/rss-2-kindle-rest-api/src/test/resources/test-context.properties index 63ef590..37a016b 100644 --- a/rss-2-kindle-rest-api/src/test/resources/test-context.properties +++ b/rss-2-kindle-rest-api/src/test/resources/test-context.properties @@ -5,7 +5,7 @@ test.user.password=password #Test subscriber test.subscriber.email=test@mail.com test.subscriber.name=testSubscriber -test.subscriber.rss=file:src/test/resources/testrss.xml +test.subscriber.rss=http://localhost:1080/1/feed #Test local storage storage.path.root=test/data/ diff --git a/rss-2-kindle-web-test/README.md b/rss-2-kindle-web-test/README.md new file mode 100644 index 0000000..058cddd --- /dev/null +++ b/rss-2-kindle-web-test/README.md @@ -0,0 +1,46 @@ +Module for automated testing of Web UI +====================================== + +This is a module for automated testing of service functionality via web interface. +It is an autonomous module that has no dependencies from other `rss-2-kindle` modules. +The module uses `Page Objects` pattern as the main design pattern. +On technical level it is based on `Selenide`, `Allure` and `JUnit5` frameworks. + +## How to run automated tests + +By default, all tests for `rss-2-kindle-web-test` are disabled. +Before running tests we need to bring up environment for testing. The easiest way is to run the project via docker. +* go to docker directory + + cd docker-dir + +* run docker: + + docker-compose build + docker-compose up + +After that we can run automated tests +* go to testing module directory + + cd rss-2-kindle-web-test + +* run maven for docker-env profile + + mvn clean install -P docker-env + +By default tests run in headless mode. If you want to see the browser window then use enable-head profile + + mvn test -P docker-env -P enable-head + + ## How to run test report + + As soon as tests completed we can generate `Allure` test report. + * go to testing module directory + + cd rss-2-kindle-web-test + + * run allure via maven + + mvn allure:serve + + \ No newline at end of file diff --git a/rss-2-kindle-web-test/pom.xml b/rss-2-kindle-web-test/pom.xml new file mode 100644 index 0000000..2787a57 --- /dev/null +++ b/rss-2-kindle-web-test/pom.xml @@ -0,0 +1,162 @@ + + + + rss-2-kindle + org.roag + 3.0-SNAPSHOT + + 4.0.0 + + rss-2-kindle-web-test + + + true + + 5.2.8 + 2.12.1 + 1.8.10 + 5.5.1 + + http://localhost:8080/r2kweb + true + true + false + false + chrome + + testuser + testuser + test@test.com + + + + + com.codeborne + selenide + ${selenide.version} + + + io.qameta.allure + allure-selenide + ${allure.version} + + + io.qameta.allure + allure-junit5 + ${allure.version} + test + + + com.github.javafaker + javafaker + ${javafaker.version} + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + + + org.junit.platform + junit-platform-launcher + 1.5.1 + + + + + + + maven-surefire-plugin + + ${maven.test.skip} + false + + + -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" + + + true + 5 + fixed + true + concurrent + + ${selenide.baseUrl} + ${selenide.browser} + ${selenide.headless} + ${selenide.holdBrowserOpen} + ${project.build.directory}/reports/tests + ${selenide.screenshots} + ${selenide.proxyEnabled} + 1920x1080 + + ${project.build.directory}/allure-results + + ${r2k.username} + ${r2k.password} + ${r2k.email} + + + + + + org.junit.platform + junit-platform-surefire-provider + 1.3.2 + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + + io.qameta.allure + allure-maven + 2.10.0 + + + + + + docker-env + + false + http://localhost:8080/r2kweb + true + true + false + false + chrome + testuser + testuser + test@test.com + + + + vagrant-env + + false + http://10.100.100.200:8080/r2kweb + true + true + false + false + firefox + testuser + testuser + test@test.com + + + + enable-head + + false + true + + + + \ No newline at end of file diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/config/Config.java b/rss-2-kindle-web-test/src/main/java/org/roag/config/Config.java new file mode 100644 index 0000000..50d9a79 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/config/Config.java @@ -0,0 +1,14 @@ +package org.roag.config; + +/** + * Created by eurohlam on 17/08/19. + */ +public final class Config { + + public static Credentials credentials() { + return new Credentials(System.getProperty("r2k.username"), + System.getProperty("r2k.password"), + System.getProperty("r2k.email")); + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/config/Credentials.java b/rss-2-kindle-web-test/src/main/java/org/roag/config/Credentials.java new file mode 100644 index 0000000..5c42caf --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/config/Credentials.java @@ -0,0 +1,34 @@ +package org.roag.config; + +/** + * Created by eurohlam on 18/08/19. + */ +public final class Credentials { + + private final String username; + private final String password; + private final String email; + + public Credentials(final String username, final String password, final String email) { + this.username = username; + this.password = password; + this.email = email; + } + + public String username() { + return username; + } + + public String password() { + return password; + } + + public String email() { + return email; + } + + @Override + public String toString() { + return "username: " + username + " email: " + email; + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/exceptions/UnexpectedPageError.java b/rss-2-kindle-web-test/src/main/java/org/roag/exceptions/UnexpectedPageError.java new file mode 100644 index 0000000..e3b6277 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/exceptions/UnexpectedPageError.java @@ -0,0 +1,26 @@ +package org.roag.exceptions; + +import com.codeborne.selenide.WebDriverRunner; +import com.codeborne.selenide.ex.ErrorMessages; +import org.roag.pages.Page; + +/** + * Created by eurohlam on 18/08/19. + */ +public class UnexpectedPageError extends AssertionError { + private static final long serialVersionUID = 1L; + private final String detail; + + public UnexpectedPageError(final String expectedPageName, final Page expectedPage) { + super("Unexpected Page: current page should be " + expectedPageName); + this.detail = ErrorMessages.screenshot(WebDriverRunner.driver()) + + System.lineSeparator() + + "Page url: " + expectedPage.getUrl() + System.lineSeparator() + + "Page path: " + expectedPage.getPath(); + } + + @Override + public String toString() { + return this.getLocalizedMessage() + this.detail; + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/junit/AllureSelenideListener.java b/rss-2-kindle-web-test/src/main/java/org/roag/junit/AllureSelenideListener.java new file mode 100644 index 0000000..313eb04 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/junit/AllureSelenideListener.java @@ -0,0 +1,19 @@ +package org.roag.junit; + +import com.codeborne.selenide.logevents.SelenideLogger; +import io.qameta.allure.selenide.AllureSelenide; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +public class AllureSelenideListener implements TestExecutionListener { + + @Override + public void executionStarted(TestIdentifier testIdentifier) { + if (!SelenideLogger.hasListener("AllureSelenide")) { + SelenideLogger.addListener("AllureSelenide", + new AllureSelenide() + .screenshots(true) + .savePageSource(true)); + } + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/junit/LifecycleTestExtension.java b/rss-2-kindle-web-test/src/main/java/org/roag/junit/LifecycleTestExtension.java new file mode 100644 index 0000000..1afb6fa --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/junit/LifecycleTestExtension.java @@ -0,0 +1,37 @@ +package org.roag.junit; + +import org.junit.jupiter.api.extension.*; +import org.roag.config.Config; +import org.roag.pages.LoginPage; +import org.roag.pages.ProfilePage; +import org.roag.pages.SignUpPage; + +import static org.roag.pages.PageUtils.*; + +/** + * Created by eurohlam on 18/08/19. + */ +public class LifecycleTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, AfterAllCallback { + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + //we ignore error message here that such user already exists + to(SignUpPage.class).signUpWith(Config.credentials()); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + to(LoginPage.class).loginWith(Config.credentials()); + at(ProfilePage.class); + } + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + clearBrowserCache(); + } + + @Override + public void afterAll(ExtensionContext extensionContext) throws Exception { + closeWebDriver(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/AbstractPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/AbstractPage.java new file mode 100644 index 0000000..8fb1405 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/AbstractPage.java @@ -0,0 +1,37 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import io.qameta.allure.Step; +import org.roag.pages.modules.AlertPanel; +import org.roag.pages.modules.SideBar; +import org.roag.pages.modules.MenuBar; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Created by eurohlam on 17/08/19. + */ +abstract class AbstractPage implements Page { + + private final MenuBar menuBar = new MenuBar($("nav#mainNav")); + private final SideBar sideBar = new SideBar($("aside#sidebar-wrapper")); + private AlertPanel alertPanel = new AlertPanel($("#alerts_panel")); + + @Step("Get menu bar") + public MenuBar menubar() { + menuBar.shouldBe(Condition.visible); + return menuBar; + } + + @Step("Get side bar") + public SideBar sidebar() { + sideBar.shouldBe(Condition.visible); + return sideBar; + } + + @Step("Get alert panel") + public AlertPanel alertPanel() { + alertPanel.shouldBe(Condition.visible); + return alertPanel; + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/LandingPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/LandingPage.java new file mode 100644 index 0000000..f092c05 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/LandingPage.java @@ -0,0 +1,108 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; +import org.roag.pages.modules.ContactForm; + +import java.util.function.Consumer; + +import static com.codeborne.selenide.Selenide.$; +import static org.roag.pages.PageUtils.at; + +public class LandingPage extends AbstractPage { + + private ContactForm contactForm = new ContactForm($("form#contactForm")); + private SelenideElement whyBtn = $("a.portfolio-item[href='#howto-modal-1']"); + private SelenideElement howBtn = $("a.portfolio-item[href='#howto-modal-2']"); + private SelenideElement psBtn = $("a.portfolio-item[href='#howto-modal-3']"); + private SelenideElement signUpBtn = $("a.btn[href='view/register']"); + private SelenideElement whyModalForm = $("div#howto-modal-1"); + private SelenideElement howModalForm = $("div#howto-modal-2"); + private SelenideElement psModalForm = $("div#howto-modal-3"); + + + @Override + public String getPath() { + return ""; + } + + @Override + public boolean isDisplayed() { + return contactForm.isDisplayed(); + } + + @Step("Send a message with name: {name} and email: {email}") + public LandingPage sendMessage(String name, String email, String phone, String message) { + return sendMessage( + form -> form + .setName(name) + .setEmail(email) + .setPhone(phone) + .setMessage(message) + .clickSend() + ); + } + + @Step("Send a message via Contact form") + public LandingPage sendMessage(Consumer consumer) { + this.contactForm.shouldBe(Condition.visible); + consumer.accept(contactForm); + return this; + } + + @Step("Click Why button in HowTo section") + public LandingPage clickWhy() { + whyBtn.shouldBe(Condition.visible); + whyBtn.click(); + if (!whyModalForm.isDisplayed()) { + whyBtn.pressEnter(); + } + whyModalForm.shouldBe(Condition.visible); + return this; + } + + @Step("Click P&S button in HowTo section") + public LandingPage clickPrivacyAndSecurity() { + psBtn.shouldBe(Condition.visible); + psBtn.click(); + if (!psModalForm.isDisplayed()) { + psBtn.pressEnter(); + } + psModalForm.shouldBe(Condition.visible); + return this; + } + + @Step("Click How button in HowTo section") + public LandingPage clickHow() { + howBtn.shouldBe(Condition.visible, Condition.enabled); + howBtn.click(); + if (!howModalForm.isDisplayed()) { + howBtn.pressEnter(); + } + howModalForm.shouldBe(Condition.visible); + return this; + } + + @Step("Close modal form") + public LandingPage closeModalForm() { + if (whyModalForm.isDisplayed()) { + whyModalForm.$("a.portfolio-modal-dismiss.btn").click(); + } + if (howModalForm.isDisplayed()) { + howModalForm.$("a.portfolio-modal-dismiss.btn").click(); + } + if (psModalForm.isDisplayed()) { + psModalForm.$("a.portfolio-modal-dismiss.btn").click(); + } + return this; + } + + @Step("Click Sign Up button") + public SignUpPage clickSignUp() { + signUpBtn.shouldBe(Condition.visible, Condition.enabled); + signUpBtn.click(); + return at(SignUpPage.class); + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/LoginPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/LoginPage.java new file mode 100644 index 0000000..9bd14af --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/LoginPage.java @@ -0,0 +1,41 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; +import org.roag.config.Credentials; + +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.page; + +/** + * Created by eurohlam on 17/08/19. + */ +public class LoginPage extends AbstractPage { + + private SelenideElement username = $("input#username"); + private SelenideElement password = $("input#password"); + private SelenideElement signInBtn = $("button.btn"); + + + @Override + public String getPath() { + return "/login.jsp"; + } + + + @Step("Login as user: {username}") + public ProfilePage loginWith(String username, String password) { + this.username.shouldBe(Condition.visible); + this.password.shouldBe(Condition.visible); + this.signInBtn.shouldBe(Condition.visible); + this.username.setValue(username); + this.password.setValue(password); + this.signInBtn.click(); + return page(ProfilePage.class); + } + + public ProfilePage loginWith(Credentials credentials) { + return loginWith(credentials.username(), credentials.password()); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/Page.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/Page.java new file mode 100644 index 0000000..e4fa820 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/Page.java @@ -0,0 +1,29 @@ +package org.roag.pages; + +import com.codeborne.selenide.Configuration; +import com.codeborne.selenide.WebDriverRunner; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; + +/** + * Created by eurohlam on 17/08/19. + */ +public interface Page { + + String getPath(); + + default String getUrl() { + try { + URL base = new URL(Configuration.baseUrl + getPath()); + return base.toString(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + default boolean isDisplayed() { + return Objects.equals(getUrl(), WebDriverRunner.url()); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/PageUtils.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/PageUtils.java new file mode 100644 index 0000000..60a183b --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/PageUtils.java @@ -0,0 +1,130 @@ +package org.roag.pages; + +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.WebDriverRunner; +import io.qameta.allure.Allure; +import io.qameta.allure.Step; +import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.TakesScreenshot; +import org.roag.exceptions.UnexpectedPageError; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import static com.codeborne.selenide.Selenide.sleep; + +/** + * Created by eurohlam on 18/08/19. + */ +public final class PageUtils { + + private PageUtils() { + // static methods only + } + + @Step("Close WebDriver") + public static void closeWebDriver() { + Selenide.close(); + } + + @Step("Clear browser cache") + public static void clearBrowserCache() { + Selenide.clearBrowserCookies(); + Selenide.clearBrowserLocalStorage(); + } + + @Step("Navigate To {pageName}") + private static T navigateToPage(String pageName, Class pageClass) { + T page = instantiatePage(pageName, pageClass); + browseTo(page.getUrl()); + return page; + } + + @Step("Open URL {url}") + public static void browseTo(String url) { + Selenide.open(url); + } + + public static T to(Class pageClazz) { + String pageName = extractPageName(pageClazz); + + if (pageClazz == LoginPage.class) { + return navigateToPage(pageName, pageClazz); + } else { + // navigate to requested page + navigateToPage(pageName, pageClazz); + + // then make sure we actually arrived by checking with 'at' + return at(pageClazz); + } + } + + public static T at(Class pageClazz) { + return verifyAtPage(extractPageName(pageClazz), pageClazz); + } + + private static String extractPageName(Class pageClazz) { + String name = pageClazz.getSimpleName(); + String[] nameParts = StringUtils.splitByCharacterTypeCamelCase(name); + return StringUtils.join(nameParts, " "); + } + + @Step("Verify That {pageName} Is Displayed") + private static T verifyAtPage(String pageName, Class pageClass) { + T page = instantiatePage(pageName, pageClass); + if (!isPageDisplayed(pageName, page)) { + attachAll(); + throw new UnexpectedPageError(pageName, page); + } + return page; + } + + @Step("Create {pageName} Object") + @SuppressWarnings("PMD.UnusedFormalParameter") + private static T instantiatePage(String pageName, Class pageClass) { + try { + return pageClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Step("Evaluate {pageName} isDisplayed Method") + @SuppressWarnings("PMD.UnusedFormalParameter") + private static boolean isPageDisplayed(String pageName, Page page) { + for (int i = 0; i < 10; i++) { + boolean isDisplayed = page.isDisplayed(); + + if (isDisplayed) { + return true; + } + + sleep(1000); + } + + return false; + } + + public static void attachAll() { + attachScreenshot(); + attachUrl(); + attachPageSource(); + } + + public static void attachScreenshot() { + InputStream screenShot = new ByteArrayInputStream( + ((TakesScreenshot) WebDriverRunner.getWebDriver()).getScreenshotAs(OutputType.BYTES)); + Allure.addAttachment("Screenshot", "image/png", screenShot, ".png"); + } + + public static void attachPageSource() { + String pageSource = WebDriverRunner.getWebDriver().getPageSource(); + Allure.addAttachment("Page source", "text/html", pageSource, ".html"); + } + + public static void attachUrl() { + Allure.addAttachment("Current url", "text/plain", WebDriverRunner.url()); + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/ProfilePage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/ProfilePage.java new file mode 100644 index 0000000..93aafff --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/ProfilePage.java @@ -0,0 +1,52 @@ +package org.roag.pages; + + +import com.codeborne.selenide.Condition; +import io.qameta.allure.Step; +import org.junit.jupiter.api.extension.ExtendWith; +import org.roag.junit.LifecycleTestExtension; +import org.roag.pages.modules.PageModuleCollection; +import org.roag.pages.modules.SubscriberRecord; + +import static com.codeborne.selenide.Selenide.$$x; + +/** + * Created by eurohlam on 17/08/19. + */ +@ExtendWith(LifecycleTestExtension.class) +public class ProfilePage extends AbstractPage { + + private PageModuleCollection subscriberList = new PageModuleCollection<>( + $$x("div[id='subscribers_view']/table/tbody/tr"), SubscriberRecord::new); + + @Override + public String getPath() { + return "/view/profile"; + } + + + private SubscriberRecord findSubscriberBy(Condition condition) { + return subscriberList.findBy(condition); + } + + @Step("Get subscriber by name {name}") + public SubscriberRecord getSubscriberByName(String name) { + return findSubscriberBy(Condition.text(name)); + } + + @Step("Get subscriber by email {email}") + public SubscriberRecord getSubscriberByEmail(String email) { + return findSubscriberBy(Condition.text(email)); + } + + @Step("Get first subscriber") + public SubscriberRecord getFirstSubscriber() { + return subscriberList.first(); + } + + @Step("Get last subscriber") + public SubscriberRecord getLastSubscriber() { + return subscriberList.last(); + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/ServicePage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/ServicePage.java new file mode 100644 index 0000000..510d866 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/ServicePage.java @@ -0,0 +1,28 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Created by eurohlam on 17/08/19. + */ +public class ServicePage extends AbstractPage { + + private SelenideElement pollBtn = $("button#run_all"); + + @Override + public String getPath() { + return "/view/service"; + } + + @Step("Click poll my subscriptions immediately") + public ServicePage clickPollSubscriptionsImmediately() { + pollBtn.shouldBe(Condition.visible); + pollBtn.click(); + return this; + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/SignUpPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SignUpPage.java new file mode 100644 index 0000000..f81152f --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SignUpPage.java @@ -0,0 +1,96 @@ +package org.roag.pages; + + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; +import org.roag.config.Credentials; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Created by eurohlam on 17/08/19. + */ +public class SignUpPage extends AbstractPage { + + + private SelenideElement username = $("input#username"); + private SelenideElement email = $("input#email"); + private SelenideElement password = $("input#password"); + private SelenideElement confirmPassword = $("input#confirmPassword"); + private SelenideElement checkEmail = $("input#check_email"); + private SelenideElement signUpBtn = $("button.btn"); + private SelenideElement errorMessage = $("span.error"); + + @Override + public String getPath() { + return "/view/register"; + } + + @Override + public boolean isDisplayed() { + return $("form#newUserForm").isDisplayed(); + } + + @Step("Sign Up with: username={username} and email={email}") + public SignUpPage signUpWith(String username, String password, String email) { + setUsername(username); + setEmail(email); + setPassword(password); + setConfirmPassword(password); + clickSignUp(); + return this; + } + + @Step("Sign Up with credentials {credentials}") + public SignUpPage signUpWith(Credentials credentials) { + return signUpWith(credentials.username(), credentials.password(), credentials.email()); + } + + @Step("Set username for Singing Up {username}") + public SignUpPage setUsername(String username) { + this.username.shouldBe(Condition.visible); + this.username.setValue(username); + return this; + } + + @Step("Set email for Singing Up {email}") + public SignUpPage setEmail(String email) { + this.email.shouldBe(Condition.visible); + this.email.setValue(email); + return this; + } + + @Step("Set password for Singing Up {password}") + public SignUpPage setPassword(String password) { + this.password.shouldBe(Condition.visible); + this.password.setValue(password); + return this; + } + + @Step("Confirm password for Singing Up {confirmPassword}") + public SignUpPage setConfirmPassword(String confirmPassword) { + this.confirmPassword.shouldBe(Condition.visible); + this.confirmPassword.setValue(confirmPassword); + return this; + } + + @Step("Set email into fake field for Singing Up {email}") + public SignUpPage setCheckEmail(String email) { + this.checkEmail.setValue(email); + return this; + } + + @Step("Click Sign Up button") + public SignUpPage clickSignUp() { + this.signUpBtn.shouldBe(Condition.visible); + this.signUpBtn.click(); + return this; + } + + @Step("Get error message") + public String errorMessage() { + errorMessage.shouldBe(Condition.visible); + return errorMessage.getText(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscriberDetailsPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscriberDetailsPage.java new file mode 100644 index 0000000..a3c437a --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscriberDetailsPage.java @@ -0,0 +1,166 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; +import org.roag.pages.modules.*; + +import java.util.Arrays; +import java.util.function.Supplier; + +import static com.codeborne.selenide.Selenide.*; + +public class SubscriberDetailsPage extends AbstractPage { + + private SelenideElement addBtn = $("button#add_btn"); + private SelenideElement activateBtn = $("button#activate_btn"); + private SelenideElement deactivateBtn = $("button#deactivate_btn"); + private SelenideElement removeBtn = $("button#remove_btn"); + + private ModalForm activateModalForm = new ModalForm($("div#activateModal")); + private ModalForm deactivateModalForm = new ModalForm($("div#deactivateModal")); + private ModalForm removeModalForm = new ModalForm($("div#removeModal")); + private NewSubscriptionsForm newSubscriptionsForm = new NewSubscriptionsForm($("div#addModal")); + + + private PageModuleCollection subscriptionList = new PageModuleCollection<>( + $$x("//div[@id='details']/table/tbody/tr"), SubscriptionRecord::new); + + @Override + public String getPath() { + return "/view/subscriberDetails"; + } + + @Override + public boolean isDisplayed() { + return $x("//h5[contains(text(), 'Details of subscriptions for subscriber:')]").isDisplayed(); + } + + @Step("Add new subscription {url}") + public SubscriberDetailsPage addSubscriptions(String... url) { + addBtn.click(); + newSubscriptionsForm.shouldBe(Condition.visible); + Arrays.stream(url).forEach(newSubscriptionsForm::addRss); + newSubscriptionsForm.clickSubmit(); + return this; + } + + @Step("Activate subscription {subscriptionRecord}") + public SubscriberDetailsPage activateSubscription(SubscriptionRecord subscriptionRecord) { + subscriptionRecord.check(); + activateBtn.click(); + activateModalForm.shouldBe(Condition.visible); + activateModalForm.submit(); + return this; + } + + @Step("Activate subscription {url}") + public SubscriberDetailsPage activateSubscriptionByUrl(String url) { + return activateSubscription(subscriptionList.findBy(Condition.text(url))); + } + + @Step("Activate first subscription") + public SubscriberDetailsPage activateFirst() { + return activateSubscription(subscriptionList.first()); + } + + @Step("Activate last subscription") + public SubscriberDetailsPage activateLast() { + return activateSubscription(subscriptionList.last()); + } + + @Step("Deactivate subscription {subscriptionRecord}") + public SubscriberDetailsPage deactivateSubscription(SubscriptionRecord subscriptionRecord) { + subscriptionRecord.check(); + deactivateBtn.click(); + deactivateModalForm.shouldBe(Condition.visible); + deactivateModalForm.submit(); + return this; + } + + @Step("Deactivate subscription with URL: {url}") + public SubscriberDetailsPage deactivateSubscriptionByUrl(String url) { + return deactivateSubscription(subscriptionList.findBy(Condition.text(url))); + } + + @Step("Deactivate first subscription") + public SubscriberDetailsPage deactivateFirst() { + return deactivateSubscription(subscriptionList.first()); + } + + @Step("Deactivate last subscription") + public SubscriberDetailsPage deactivateLast() { + return deactivateSubscription(subscriptionList.last()); + } + + @Step("Remove subscription {url}") + public SubscriberDetailsPage removeSubscription(SubscriptionRecord subscriptionRecord) { + subscriptionRecord.check(); + removeBtn.click(); + removeModalForm.shouldBe(Condition.visible); + removeModalForm.submit(); + return this; + } + + @Step("Remove subscription with URL: {url}") + public SubscriberDetailsPage removeSubscriptionByUrl(String url) { + return removeSubscription(subscriptionList.findBy(Condition.text(url))); + } + + @Step("Remove first subscription") + public SubscriberDetailsPage removeFirst() { + return removeSubscription(subscriptionList.first()); + } + + @Step("Remove last subscription") + public SubscriberDetailsPage removeLast() { + return removeSubscription(subscriptionList.last()); + } + + + private static class NewSubscriptionsForm extends AbstractPageModule { + + private SelenideElement rssList = selenideElement().$("select#rss_list"); + private SelenideElement newRss = selenideElement().$("input#new_rss"); + private SelenideElement addRssBtn = selenideElement().$("button#btn_addrss"); + private SelenideElement deleteRssBtn = selenideElement().$("button#btn_deleterss"); + private SelenideElement submitBtn = selenideElement().$("button[type='submit']"); + + public NewSubscriptionsForm(SelenideElement selector) { + super(selector); + } + + public NewSubscriptionsForm(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Add subscription {rss}") + public NewSubscriptionsForm addRss(String rss) { + rssList.shouldBe(Condition.visible); + newRss.setValue(rss); + addRssBtn.click(); + return this; + } + + @Step("Delete subscription {rss}") + public NewSubscriptionsForm deleteRss(String rss) { + //TODO: implement to delete RSS + deleteRssBtn.click(); + return this; + } + + @Step("Click submit") + public NewSubscriptionsForm clickSubmit() { + submitBtn.shouldBe(Condition.visible); + submitBtn.shouldBe(Condition.enabled); + submitBtn.click(); + return this; + } + + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscribersPage.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscribersPage.java new file mode 100644 index 0000000..e274f85 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/SubscribersPage.java @@ -0,0 +1,45 @@ +package org.roag.pages; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; +import org.roag.pages.modules.EditSubscribersForm; +import org.roag.pages.modules.NewSubscriberForm; + +import java.util.function.Consumer; + +import static com.codeborne.selenide.Selenide.$; + +/** + * Created by eurohlam on 17/08/19. + */ +public class SubscribersPage extends AbstractPage { + + private SelenideElement newSubscriberLink = $("a#new-tab"); + private SelenideElement editSubscriberLink = $("a#edit-tab"); + + private NewSubscriberForm newSubscriberForm = new NewSubscriberForm($("div#new")); + private EditSubscribersForm editSubscribersForm = new EditSubscribersForm($("div#edit")); + + @Override + public String getPath() { + return "/view/subscribers"; + } + + @Step("Add new subscriber") + public SubscribersPage addNewSubscriber(Consumer consumer) { + newSubscriberLink.click(); + newSubscriberForm.shouldBe(Condition.visible); + consumer.accept(newSubscriberForm); + return this; + } + + @Step("Edit subscriber") + public SubscribersPage editSubscriber(Consumer consumer) { + editSubscriberLink.click(); + editSubscribersForm.shouldBe(Condition.visible); + consumer.accept(editSubscribersForm); + return this; + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractNavigationBar.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractNavigationBar.java new file mode 100644 index 0000000..47bf9b9 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractNavigationBar.java @@ -0,0 +1,27 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.SelenideElement; + +import java.util.function.Supplier; + +/** + * Created by eurohlam on 20/08/2019. + */ +public abstract class AbstractNavigationBar extends AbstractPageModule { + + + AbstractNavigationBar(SelenideElement selector) { + super(selector); + } + + AbstractNavigationBar(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + public abstract void navigateTo(NavigationItem item); +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractPageModule.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractPageModule.java new file mode 100644 index 0000000..b2e4c75 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AbstractPageModule.java @@ -0,0 +1,26 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.SelenideElement; + +import java.util.function.Supplier; + +/** + * Created by eurohlam on 19/08/2019. + */ +public abstract class AbstractPageModule implements PageModule { + + private final Supplier selector; + + public AbstractPageModule(final SelenideElement selector) { + this(() -> selector); + } + + public AbstractPageModule(final Supplier selector) { + this.selector = selector; + } + + @Override + public Supplier selector() { + return this.selector; + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AlertPanel.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AlertPanel.java new file mode 100644 index 0000000..8d40a55 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/AlertPanel.java @@ -0,0 +1,36 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +public class AlertPanel extends AbstractPageModule { + + private SelenideElement closeBtn = selenideElement().$("button.close"); + + public AlertPanel(SelenideElement selector) { + super(selector); + } + + public AlertPanel(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Close alert panel") + public void close() { + closeBtn.shouldBe(Condition.visible); + closeBtn.click(); + } + + @Step("Get alert text") + public String getText() { + return selenideElement().getText(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ContactForm.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ContactForm.java new file mode 100644 index 0000000..c34f784 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ContactForm.java @@ -0,0 +1,68 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +/** + * Created eurohlam on 19/08/2019. + */ +public class ContactForm extends AbstractPageModule { + + private SelenideElement name = selenideElement().$("input#name"); + private SelenideElement email = selenideElement().$("input#email"); + private SelenideElement phone = selenideElement().$("input#phone"); + private SelenideElement message = selenideElement().$("textarea#message"); + private SelenideElement sendBtn = selenideElement().$("button#sendMessageButton"); + + public ContactForm(SelenideElement selector) { + super(selector); + } + + public ContactForm(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Set contact name {name}") + public ContactForm setName(String name) { + this.name.shouldBe(Condition.visible); + this.name.setValue(name); + return this; + } + + @Step("Set contact email {email}") + public ContactForm setEmail(String email) { + this.email.shouldBe(Condition.visible); + this.email.setValue(email); + return this; + } + + @Step("Set contact phone {phone}") + public ContactForm setPhone(String phone) { + this.phone.shouldBe(Condition.visible); + this.phone.setValue(phone); + return this; + } + + @Step("Set message {message}") + public ContactForm setMessage(String message) { + this.message.shouldBe(Condition.visible); + this.message.setValue(message); + return this; + } + + @Step("Click send button") + public ContactForm clickSend() { + this.sendBtn.shouldBe(Condition.visible); + this.sendBtn.shouldBe(Condition.enabled); + this.sendBtn.click(); + return this; + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/EditSubscribersForm.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/EditSubscribersForm.java new file mode 100644 index 0000000..0532ece --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/EditSubscribersForm.java @@ -0,0 +1,81 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static com.codeborne.selenide.Selenide.$; + + +public class EditSubscribersForm extends AbstractPageModule { + + private PageModuleCollection subscriberList = new PageModuleCollection<>( + selenideElement().$$x("//table/tbody/tr"), SubscriberRecord::new); + + private ModalForm suspendModalForm = new ModalForm($("div#suspendModal")); + private ModalForm resumeModalForm = new ModalForm($("div#resumeModal")); + private ModalForm removeModalForm = new ModalForm($("div#removeModal")); + private UpdateSubscriberModalForm updateModalForm = new UpdateSubscriberModalForm($("div#updateModal")); + + public EditSubscribersForm(final SelenideElement selector) { + super(selector); + } + + public EditSubscribersForm(final Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Get subscribers list") + public PageModuleCollection getSubscriberList() { + return this.subscriberList; + } + + @Step("Suspend subscriber {subscriber}") + public EditSubscribersForm suspendSubscriber(String subscriber) { + subscriberList.findBy(Condition.text(subscriber)).clickSuspend(); + suspendModalForm.shouldBe(Condition.visible); + suspendModalForm.shouldHave(Condition.text(subscriber)); + suspendModalForm.submit(); + return this; + } + + @Step("Resume subscriber {subscriber}") + public EditSubscribersForm resumeSubscriber(String subscriber) { + subscriberList.findBy(Condition.text(subscriber)).clickResume(); + resumeModalForm.shouldBe(Condition.visible); + resumeModalForm.shouldHave(Condition.text(subscriber)); + resumeModalForm.submit(); + return this; + } + + @Step("Remove subscriber {subscriber}") + public EditSubscribersForm removeSubscriber(String subscriber) { + subscriberList.findBy(Condition.text(subscriber)).clickRemove(); + removeModalForm.shouldBe(Condition.visible); + removeModalForm.shouldHave(Condition.text(subscriber)); + removeModalForm.submit(); + return this; + } + + @Step("Update subscriber {subscriber}") + public EditSubscribersForm updateSubscriber(String subscriber, Consumer consumer) { + subscriberList.findBy(Condition.text(subscriber)).clickUpdate(); + updateModalForm.shouldBe(Condition.visible); + updateModalForm.shouldHave(Condition.text(subscriber)); + consumer.accept(updateModalForm); + return this; + } + + @Step("Navigate to details page for subscriber: {subscriber}") + public void navigateToSubscriberDetails(String subscriber) { + subscriberList.findBy(Condition.text(subscriber)).navigateToSubscriberDetails(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/MenuBar.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/MenuBar.java new file mode 100644 index 0000000..cdf4e3e --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/MenuBar.java @@ -0,0 +1,62 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +/** + * Created by eurohlam on 19/08/2019. + */ +public class MenuBar extends AbstractNavigationBar { + + private SelenideElement signInLink = selenideElement().$x("//a[contains(text(),'Sign In')]"); + private SelenideElement signUpLink = selenideElement().$x("//a[contains(text(),'Sign Up')]"); + private SelenideElement howToLink = selenideElement().$x("//a[contains(text(),'Howto')]"); + private SelenideElement aboutLink = selenideElement().$x("//a[contains(text(),'About')]"); + private SelenideElement contactLink = selenideElement().$x("//a[contains(text(),'Contact')]"); + private SelenideElement logoutLink = selenideElement().$x("//a[contains(text(),'Log out')]"); + + public MenuBar(SelenideElement selector) { + super(selector); + } + + public MenuBar(Supplier selector) { + super(selector); + } + + @Override + @Step("Navigate to {item}") + public void navigateTo(NavigationItem item) { + switch (item) { + case SIGN_IN: + signInLink.shouldBe(Condition.visible); + signInLink.click(); + break; + case SIGN_UP: + signUpLink.shouldBe(Condition.visible); + signUpLink.click(); + break; + case HOWTO: + howToLink.shouldBe(Condition.visible); + howToLink.click(); + break; + case ABOUT: + aboutLink.shouldBe(Condition.visible); + aboutLink.click(); + break; + case CONTACT: + contactLink.shouldBe(Condition.visible); + contactLink.click(); + break; + case LOGOUT: + logoutLink.shouldBe(Condition.visible); + logoutLink.click(); + break; + default: + throw new IllegalArgumentException("Incorrect menu item " + item); + } + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ModalForm.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ModalForm.java new file mode 100644 index 0000000..f7d4f26 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/ModalForm.java @@ -0,0 +1,56 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +public class ModalForm extends AbstractPageModule { + + private SelenideElement submitBtn = selenideElement().$("button[type='submit']"); + private SelenideElement cancelBtn = selenideElement().$("button[type='button'].btn-default"); + private SelenideElement closeBtn = selenideElement().$("button.close"); + + public ModalForm(SelenideElement selector) { + super(selector); + } + + public ModalForm(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Click submit") + public void submit() { + submitBtn.shouldBe(Condition.visible, Condition.enabled); + submitBtn.click(); + //TODO: something wrong with closing modal form after clicking + if (submitBtn.isDisplayed() && closeBtn.isDisplayed()) { + //if a modal form is still open we want just to close the modal form + closeBtn.click(); + } + } + + @Step("Click cancel") + public void cancel() { + cancelBtn.shouldBe(Condition.visible, Condition.enabled); + cancelBtn.click(); + if (cancelBtn.isDisplayed()) { + cancelBtn.click(); + } + } + + @Step("Click close") + public void close() { + closeBtn.shouldBe(Condition.visible, Condition.enabled); + closeBtn.click(); + if (closeBtn.isDisplayed()) { + closeBtn.click(); + } + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NavigationItem.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NavigationItem.java new file mode 100644 index 0000000..a8ad64e --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NavigationItem.java @@ -0,0 +1,13 @@ +package org.roag.pages.modules; + +public enum NavigationItem { + SIGN_IN, + SIGN_UP, + HOWTO, + ABOUT, + CONTACT, + PROFILE, + SUBSCRIBERS, + SERVICES, + LOGOUT +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NewSubscriberForm.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NewSubscriberForm.java new file mode 100644 index 0000000..e51ad8d --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/NewSubscriberForm.java @@ -0,0 +1,69 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +public class NewSubscriberForm extends AbstractPageModule { + + private SelenideElement subscriberEmail = selenideElement().$("input#new_subscriber_email"); + private SelenideElement subscriberName = selenideElement().$("input#new_subscriber_name"); + private SelenideElement subscriberRssList = selenideElement().$("select#new_subscriber_rsslist"); + private SelenideElement newRss = selenideElement().$("input#new_subscriber_addrss"); + private SelenideElement addRssBtn = selenideElement().$("button#btn_new_subscriber_addrss"); + private SelenideElement deleteRssBtn = selenideElement().$("button#btn_new_subscriber_deleterss"); + private SelenideElement submitBtn = selenideElement().$("button[type='submit']"); + + public NewSubscriberForm(SelenideElement selector) { + super(selector); + } + + public NewSubscriberForm(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Set subscriber's email {email}") + public NewSubscriberForm setEmail(String email) { + subscriberEmail.shouldBe(Condition.visible); + subscriberEmail.setValue(email); + return this; + } + + @Step("Set subscriber's name {name}") + public NewSubscriberForm setName(String name) { + subscriberName.shouldBe(Condition.visible); + subscriberName.setValue(name); + return this; + } + + @Step("Add subscription {rss}") + public NewSubscriberForm addRss(String rss) { + subscriberRssList.shouldBe(Condition.visible); + newRss.setValue(rss); + addRssBtn.click(); + return this; + } + + @Step("Delete subscription {rss}") + public NewSubscriberForm deleteRss(String rss) { + //TODO: implement to delete RSS + deleteRssBtn.click(); + return this; + } + + @Step("Click submit") + public NewSubscriberForm clickSubmit() { + submitBtn.shouldBe(Condition.visible); + submitBtn.shouldBe(Condition.enabled); + submitBtn.click(); + return this; + } + +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModule.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModule.java new file mode 100644 index 0000000..ca715ff --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModule.java @@ -0,0 +1,37 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import java.util.function.Supplier; + +public interface PageModule { + + boolean isDisplayed(); + + Supplier selector(); + + default SelenideElement selenideElement() { + return selector().get(); + } + + default void should(Condition... conditions) { + selenideElement().should(conditions); + } + + default void shouldBe(Condition... conditions) { + selenideElement().shouldBe(conditions); + } + + default void shouldNotBe(Condition... conditions) { + selenideElement().shouldNotBe(conditions); + } + + default void shouldHave(Condition... conditions) { + selenideElement().shouldHave(conditions); + } + + default void shouldNotHave(Condition... conditions) { + selenideElement().shouldNotHave(conditions); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModuleCollection.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModuleCollection.java new file mode 100644 index 0000000..3570d1f --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/PageModuleCollection.java @@ -0,0 +1,139 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.CollectionCondition; +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + +public class PageModuleCollection implements Collection { + + private Supplier selector; + private Function itemFunction; + + public PageModuleCollection(final ElementsCollection selector, final Function itemFunction) { + this(() -> selector, itemFunction); + } + + public PageModuleCollection(final Supplier selector, final Function itemFunction) { + this.selector = selector; + this.itemFunction = itemFunction; + } + + public T findBy(Condition condition) { + return itemFunction.apply(selector.get().findBy(condition)); + } + + public T get(int index) { + return itemFunction.apply(selector.get().get(index)); + } + + + public T first() { + return itemFunction.apply(selector.get().first()); + } + + public T last() { + return itemFunction.apply(selector.get().last()); + } + + public List getTexts() { + return selector.get().texts(); + } + + public PageModuleCollection shouldHave(CollectionCondition condition) { + selector.get().shouldHave(condition); + return this; + } + + public PageModuleCollection shouldHaveSize(int expectedSize) { + selector.get().shouldHaveSize(expectedSize); + return this; + } + + public PageModuleCollection shouldBe(CollectionCondition condition) { + selector.get().shouldBe(condition); + return this; + } + + @Override + public int size() { + return selector.get().size(); + } + + @Override + public boolean isEmpty() { + return selector.get().isEmpty(); + } + + @Override + public boolean contains(Object o) { + return (o instanceof PageModule) && selector.get().contains(((PageModule) o).selenideElement()); + } + + @Override + @SuppressWarnings("PMD") + public Iterator iterator() { + final Iterator iterator = selector.get().iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + SelenideElement element = iterator.next(); + return itemFunction.apply(element); + } + }; + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T1[] toArray(T1[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(T t) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SideBar.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SideBar.java new file mode 100644 index 0000000..b1f5137 --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SideBar.java @@ -0,0 +1,46 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +/** + * Created by eurohlam on 19/08/2019. + */ +public class SideBar extends AbstractNavigationBar { + + private SelenideElement profileLink = selenideElement().$x("//a[contains(text(),'My Profile')]"); + private SelenideElement subscribersLink = selenideElement().$x("//a[contains(text(),'Subscribers')]"); + private SelenideElement servicesLink = selenideElement().$x("//a[contains(text(),'Services')]"); + + public SideBar(SelenideElement selector) { + super(selector); + } + + public SideBar(Supplier selector) { + super(selector); + } + + @Override + @Step("Navigate to {item}") + public void navigateTo(NavigationItem item) { + switch (item) { + case PROFILE: + profileLink.shouldBe(Condition.visible); + profileLink.click(); + break; + case SUBSCRIBERS: + subscribersLink.shouldBe(Condition.visible); + subscribersLink.click(); + break; + case SERVICES: + servicesLink.shouldBe(Condition.visible); + servicesLink.click(); + break; + default: + throw new IllegalArgumentException("Incorrect menu item " + item); + } + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriberRecord.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriberRecord.java new file mode 100644 index 0000000..14c0e7a --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriberRecord.java @@ -0,0 +1,85 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +/** + * Created by eurohlam on 31/08/19. + */ +public class SubscriberRecord extends AbstractPageModule { + + private SelenideElement updateBtn = selenideElement().$("button#btn_update"); + private SelenideElement suspendBtn = selenideElement().$("button#btn_suspend"); + private SelenideElement resumeBtn = selenideElement().$("button#btn_resume"); + private SelenideElement removeBtn = selenideElement().$("button#btn_remove"); + + public SubscriberRecord(Supplier selector) { + super(selector); + } + + public SubscriberRecord(SelenideElement selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Click update subscriber button") + public void clickUpdate() { + updateBtn.shouldBe(Condition.visible, Condition.enabled); + updateBtn.click(); + } + + @Step("Click suspend subscriber button") + public void clickSuspend() { + suspendBtn.shouldBe(Condition.visible, Condition.enabled); + suspendBtn.click(); + } + + @Step("Click resume subscriber button") + public void clickResume() { + resumeBtn.shouldBe(Condition.visible, Condition.enabled); + resumeBtn.click(); + } + + @Step("Click remove subscriber button") + public void clickRemove() { + removeBtn.shouldBe(Condition.visible, Condition.enabled); + removeBtn.click(); + } + + @Step("Navigate to subscriber details page") + public void navigateToSubscriberDetails() { + selenideElement().$x("./td[2]/a").click(); + } + + @Step("Get subscriber name") + public String getName() { + return selenideElement().$x("./td[2]/a").getText(); + } + + @Step("Get subscriber email") + public String getEmail() { + return selenideElement().$x("./td[3]/a").getText(); + } + + @Step("Get subscriber status") + public String getStatus() { + return selenideElement().$x("./td[4]").getText(); + } + + @Step("Get number of subscriptions") + public String getNumberOfSubscriptions() { + return selenideElement().$x("./td[5]").getText(); + } + + @Override + public String toString() { + return getName() + " | " + getEmail() + " | " + getStatus(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriptionRecord.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriptionRecord.java new file mode 100644 index 0000000..325ea7b --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/SubscriptionRecord.java @@ -0,0 +1,69 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +public class SubscriptionRecord extends AbstractPageModule { + + public SubscriptionRecord(SelenideElement selector) { + super(selector); + } + + public SubscriptionRecord(Supplier selector) { + super(selector); + } + + @Override + public boolean isDisplayed() { + return selenideElement().isDisplayed(); + } + + @Step("Check subscription") + public SubscriptionRecord check() { + selenideElement().$x("./td[1]/input").setSelected(true); + return this; + } + + @Step("Uncheck subscription") + public SubscriptionRecord uncheck() { + selenideElement().$x("./td[1]/input").setSelected(false); + return this; + } + + @Step("Get number") + public String geNumber() { + return selenideElement().$x("./td[2]/a").getText(); + } + + @Step("Get RSS") + public String getRss() { + return selenideElement().$x("./td[3]/a").getText(); + } + + @Step("Get status") + public String getStatus() { + return selenideElement().$x("./td[4]").getText(); + } + + @Step("Get last polling date") + public String getLastPollingDate() { + return selenideElement().$x("./td[5]").getText(); + } + + @Step("Get error message") + public String getErrorMessage() { + return selenideElement().$x("./td[6]").getText(); + } + + @Step("Get retry count") + public String getRetryCount() { + return selenideElement().$x("./td[7]").getText(); + } + + @Override + public String toString() { + return getRss() + " | " + getStatus(); + } +} diff --git a/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/UpdateSubscriberModalForm.java b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/UpdateSubscriberModalForm.java new file mode 100644 index 0000000..e57162b --- /dev/null +++ b/rss-2-kindle-web-test/src/main/java/org/roag/pages/modules/UpdateSubscriberModalForm.java @@ -0,0 +1,55 @@ +package org.roag.pages.modules; + +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import java.util.function.Supplier; + +public class UpdateSubscriberModalForm extends ModalForm { + + private SelenideElement subscriberName = selenideElement().$("input#update_subscriber_name"); + private SelenideElement subscriberEmail = selenideElement().$("input#update_subscriber_email"); + private SelenideElement subscriberStatus = selenideElement().$("input#update_subscriber_status"); + private SelenideElement rssList = selenideElement().$("select#update_subscriber_rsslist"); + private SelenideElement addRss = selenideElement().$("input#update_subscriber_addrss"); + private SelenideElement addRssBtn = selenideElement().$("button#btn_update_subscriber_addrss"); + private SelenideElement deleteRssBtn = selenideElement().$("button#btn_update_subscriber_deleterss"); + + public UpdateSubscriberModalForm(SelenideElement selector) { + super(selector); + } + + public UpdateSubscriberModalForm(Supplier selector) { + super(selector); + } + + @Step("Set subscriber name {name}") + public UpdateSubscriberModalForm setName(String name) { + subscriberName.setValue(name); + return this; + } + + @Step("Add RSS {url}") + public UpdateSubscriberModalForm addRss(String url) { + addRss.setValue(url); + addRssBtn.click(); + return this; + } + + @Step("Delete RSS {url}") + public UpdateSubscriberModalForm deleteRss(String url) { + rssList.selectOptionByValue(url); + deleteRssBtn.click(); + return this; + } + + @Step("Get subscriber email") + public String getSubscriberEmail() { + return subscriberEmail.getValue(); + } + + @Step("Get subscriber status") + public String setSubscriberStatus() { + return subscriberStatus.getValue(); + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/LandingPageTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/LandingPageTest.java new file mode 100644 index 0000000..216bae2 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/LandingPageTest.java @@ -0,0 +1,47 @@ +package org.roag.web; + +import com.github.javafaker.Faker; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.roag.pages.LandingPage; +import org.roag.pages.modules.NavigationItem; + +import static org.roag.pages.PageUtils.at; +import static org.roag.pages.PageUtils.to; + +@DisplayName("Landing Page tests") +@Tag("LANDING") +public class LandingPageTest { + + private Faker faker = new Faker(); + + @Test + @DisplayName("Test Contact Me form") + void contactMeTest() { + to(LandingPage.class) + .menubar() + .navigateTo(NavigationItem.CONTACT); + at(LandingPage.class) + .sendMessage( + faker.funnyName().name(), + faker.internet().emailAddress(), + faker.phoneNumber().cellPhone(), + faker.chuckNorris().fact()); + } + + @Test + @DisplayName("Test HOWTO section") + void howtoTest() { + to(LandingPage.class) + .menubar() + .navigateTo(NavigationItem.HOWTO); + at(LandingPage.class) + .clickHow() + .closeModalForm() + .clickPrivacyAndSecurity() + .closeModalForm() + .clickWhy() + .closeModalForm(); + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/LoginTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/LoginTest.java new file mode 100644 index 0000000..50f5213 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/LoginTest.java @@ -0,0 +1,32 @@ +package org.roag.web; + +import org.junit.jupiter.api.*; +import org.roag.config.Config; +import org.roag.pages.LoginPage; +import org.roag.pages.ProfilePage; +import org.roag.pages.SignUpPage; +import org.roag.pages.modules.NavigationItem; + +import static org.roag.pages.PageUtils.at; +import static org.roag.pages.PageUtils.to; + +/** + * Created by eurohlam on 17/08/19. + */ +@DisplayName("Login Page Tests") +@Tag("SECURITY") +public class LoginTest { + + @Test + @DisplayName("Login Test") + @Disabled + void loginTest() { + to(SignUpPage.class) + .signUpWith(Config.credentials()) + .menubar() + .navigateTo(NavigationItem.SIGN_IN); + + ProfilePage profilePage = at(LoginPage.class).loginWith(Config.credentials()); + Assertions.assertTrue(profilePage.isDisplayed()); + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/ServicePageTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/ServicePageTest.java new file mode 100644 index 0000000..2227026 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/ServicePageTest.java @@ -0,0 +1,45 @@ +package org.roag.web; + +import com.codeborne.selenide.Condition; +import com.github.javafaker.Faker; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.roag.junit.LifecycleTestExtension; +import org.roag.pages.ProfilePage; +import org.roag.pages.ServicePage; +import org.roag.pages.SubscribersPage; +import org.roag.pages.modules.NavigationItem; + +import static org.roag.pages.PageUtils.at; + +@ExtendWith(LifecycleTestExtension.class) +@DisplayName("Services tests") +@Tag("SERVICES") +public class ServicePageTest { + + private Faker faker = new Faker(); + + @Test + @DisplayName("Test poll subscriptions immediately") + void pollingTest() { + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(faker.name().username()) + .setEmail(faker.internet().emailAddress()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .clickSubmit()) + .sidebar() + .navigateTo(NavigationItem.SERVICES); + at(ServicePage.class) + .clickPollSubscriptionsImmediately() + .alertPanel() + .shouldHave(Condition.text("Success!")); + + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/SignUpTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/SignUpTest.java new file mode 100644 index 0000000..ba1e973 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/SignUpTest.java @@ -0,0 +1,37 @@ +package org.roag.web; + +import org.junit.jupiter.api.*; +import org.roag.pages.SignUpPage; + +import static org.roag.pages.PageUtils.to; + +@DisplayName("SignUp Page Tests") +@Tag("SECURITY") +public class SignUpTest { + + + @Test + @DisplayName("Test password confirmation") + void passwordConfirmationTest() { + String error = to(SignUpPage.class) + .setUsername("robot") + .setEmail("robot@email.com") + .setPassword("robot1") + .setConfirmPassword("robot2") + .clickSignUp() + .errorMessage(); + Assertions.assertTrue(error != null && error.contains("confirmation of password does not match")); + } + + @Test + @DisplayName("Test if username already exists") + void existingUserTest() { + to(SignUpPage.class) + .signUpWith("robot", "robot1", "robot@mail.com"); + String error = to(SignUpPage.class) + .signUpWith("robot", "robot1", "robot@mail.com") + .errorMessage(); + + Assertions.assertTrue(error != null && error.contains("username is already occupied")); + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscriberDetailsPageTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscriberDetailsPageTest.java new file mode 100644 index 0000000..99eb549 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscriberDetailsPageTest.java @@ -0,0 +1,73 @@ +package org.roag.web; + +import com.codeborne.selenide.Condition; +import com.github.javafaker.Faker; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.roag.junit.LifecycleTestExtension; +import org.roag.pages.ProfilePage; +import org.roag.pages.SubscriberDetailsPage; +import org.roag.pages.SubscribersPage; +import org.roag.pages.modules.NavigationItem; + +import static org.roag.pages.PageUtils.at; + +@ExtendWith(LifecycleTestExtension.class) +@DisplayName("Subscriptions operations tests") +@Tag("SUBSCRIBERS") +@Tag("SUBSCRIPTIONS") +public class SubscriberDetailsPageTest { + + private Faker faker = new Faker(); + + @Test + @DisplayName("Test operations with subscriptions: activate, deactivate, remove") + void subscriptionOperationsTest() { + String subscriber = faker.name().username(); + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(subscriber) + .setEmail(faker.internet().emailAddress()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .clickSubmit()) + .editSubscriber(s -> s.navigateToSubscriberDetails(subscriber)); + at(SubscriberDetailsPage.class) + .deactivateFirst() + .activateFirst() + .removeFirst() + .alertPanel() + .shouldHave(Condition.text("Success!")); + } + + @Test + @DisplayName("Test add new subscriptions") + void addNewSubscriptionsTest() { + String subscriber = faker.name().username(); + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(subscriber) + .setEmail(faker.internet().emailAddress()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .clickSubmit()) + .editSubscriber(s -> s.navigateToSubscriberDetails(subscriber)); + at(SubscriberDetailsPage.class) + .addSubscriptions("http://" + faker.internet().url(), + "http://" + faker.internet().url(), + "http://" + faker.internet().url(), + "https://" + faker.internet().url(), + "https://" + faker.internet().url(), + "https://" + faker.internet().url()) + .alertPanel() + .shouldHave(Condition.text("Success!")); + } +} diff --git a/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscribersPageTest.java b/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscribersPageTest.java new file mode 100644 index 0000000..c766ef5 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/java/org/roag/web/SubscribersPageTest.java @@ -0,0 +1,91 @@ +package org.roag.web; + +import com.codeborne.selenide.Condition; +import com.github.javafaker.Faker; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.roag.junit.LifecycleTestExtension; +import org.roag.pages.ProfilePage; +import org.roag.pages.SubscribersPage; +import org.roag.pages.modules.NavigationItem; + +import static org.roag.pages.PageUtils.at; + +@ExtendWith(LifecycleTestExtension.class) +@DisplayName("Subscribers operations tests") +@Tag("SUBSCRIBERS") +public class SubscribersPageTest { + + private Faker faker = new Faker(); + + @Test + @DisplayName("Testing creation of new subscriber") + void addSubscriberTest() { + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(faker.name().username()) + .setEmail(faker.internet().emailAddress()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .clickSubmit() + ) + .alertPanel() + .shouldHave(Condition.text("Success!")); + } + + @Test + @DisplayName("Testing operations with subscriber: suspend, resume and remove") + void subscriberOperationsTest() { + String subscriber = faker.name().username(); + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(subscriber) + .setEmail(faker.internet().emailAddress()) + .addRss("https://" + faker.internet().url()) + .clickSubmit()) + .editSubscriber(s -> s + .suspendSubscriber(subscriber) + .resumeSubscriber(subscriber) + .removeSubscriber(subscriber)) + .alertPanel() + .shouldHave(Condition.text("Success!")); + } + + + @Test + @DisplayName("Test updating subscriber via modal form") + void updateSubscriberTest() { + String subscriber = faker.name().username(); + at(ProfilePage.class) + .sidebar() + .navigateTo(NavigationItem.SUBSCRIBERS); + at(SubscribersPage.class) + .addNewSubscriber(s -> s + .setName(subscriber) + .setEmail(faker.internet().emailAddress()) + .addRss("https://" + faker.internet().url()) + .clickSubmit()) + .editSubscriber(s -> s + .updateSubscriber(subscriber, f -> f + .setName(faker.funnyName().name()) + .addRss("http://" + faker.internet().url()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .addRss("http://" + faker.internet().url()) + .addRss("http://" + faker.internet().url()) + .addRss("https://" + faker.internet().url()) + .addRss("http://" + faker.internet().url()) + .submit())) + .alertPanel() + .shouldHave(Condition.text("Success!")); + } +} diff --git a/rss-2-kindle-web-test/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/rss-2-kindle-web-test/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 0000000..226cf25 --- /dev/null +++ b/rss-2-kindle-web-test/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1 @@ +org.roag.junit.AllureSelenideListener \ No newline at end of file diff --git a/rss-2-kindle-web/src/main/java/org/roag/web/SpringRestController.java b/rss-2-kindle-web/src/main/java/org/roag/web/SpringRestController.java index efacdfe..d02fde5 100644 --- a/rss-2-kindle-web/src/main/java/org/roag/web/SpringRestController.java +++ b/rss-2-kindle-web/src/main/java/org/roag/web/SpringRestController.java @@ -25,56 +25,56 @@ public class SpringRestController { private static final String ACCESS_DENIED_MESSAGE = "{ \"status\" : \"Access denied\" }"; - @RequestMapping(value = "/profile/{username}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON) public String getUserData(@PathVariable("username") String username) { return isAccessAllowed(username) ? profileRestClient.getUserData(username).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/{subscriber:\\w+@\\w+\\.[a-zA-Z]{2,}}", method = RequestMethod.GET) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/{subscriber:[\\w\\.]+@[\\w\\.]+}", method = RequestMethod.GET) public String getSubscriber(@PathVariable("username") String username, @PathVariable("subscriber") String subscriber) { return isAccessAllowed(username) ? profileRestClient.getSubscriber(username, subscriber).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/{subscriber:\\w+@\\w+\\.[a-zA-Z]{2,}}/suspend", method = RequestMethod.GET) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/{subscriber:[\\w\\.]+@[\\w\\.]+}/suspend", method = RequestMethod.GET) public String suspendSubscriber(@PathVariable("username") String username, @PathVariable("subscriber") String subscriber) { return isAccessAllowed(username) ? profileRestClient.suspendSubscriber(username, subscriber).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/{subscriber:\\w+@\\w+\\.[a-zA-Z]{2,}}/resume", method = RequestMethod.GET) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/{subscriber:[\\w\\.]+@[\\w\\.]+}/resume", method = RequestMethod.GET) public String resumeSubscriber(@PathVariable("username") String username, @PathVariable("subscriber") String subscriber) { return isAccessAllowed(username) ? profileRestClient.resumeSubscriber(username, subscriber).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/{subscriber:\\w+@\\w+\\.[a-zA-Z]{2,}}/remove", method = RequestMethod.DELETE) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/{subscriber:[\\w\\.]+@[\\w\\.]+}/remove", method = RequestMethod.DELETE) public String removeSubscriber(@PathVariable("username") String username, @PathVariable("subscriber") String subscriber) { return isAccessAllowed(username) ? profileRestClient.removeSubscriber(username, subscriber).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/update", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/update", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON) public String updateSubscriber(@PathVariable("username") String username, @RequestBody String message) { return isAccessAllowed(username) ? profileRestClient.updateSubscriber(username, message).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/profile/{username}/new", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON) + @RequestMapping(value = "/profile/{username:[a-zA-Z_.0-9]*}/new", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON) public String newSubscriber(@PathVariable("username") String username, @RequestBody String message) { return isAccessAllowed(username) ? profileRestClient.addSubscriber(username, message).readEntity(String.class) : ACCESS_DENIED_MESSAGE; } - @RequestMapping(value = "/service/{username}", method = RequestMethod.GET) + @RequestMapping(value = "/service/{username:[a-zA-Z_.0-9]*}", method = RequestMethod.GET) public String runPolling(@PathVariable("username") String username) { return isAccessAllowed(username) ? profileRestClient.runPolling(username).readEntity(String.class) @@ -87,17 +87,17 @@ public String getAllUsers() { return adminRestClient.getAllUsers().readEntity(String.class); } - @RequestMapping(value = "/admin/{username}/lock", method = RequestMethod.GET) + @RequestMapping(value = "/admin/{username:[a-zA-Z_.0-9]*}/lock", method = RequestMethod.GET) public String lockUser(@PathVariable("username") String username) { return adminRestClient.lockUser(username).readEntity(String.class); } - @RequestMapping(value = "/admin/{username}/unlock", method = RequestMethod.GET) + @RequestMapping(value = "/admin/{username:[a-zA-Z_.0-9]*}/unlock", method = RequestMethod.GET) public String unlockUser(@PathVariable("username") String username) { return adminRestClient.unlockUser(username).readEntity(String.class); } - @RequestMapping(value = "/admin/{username}/remove", method = RequestMethod.DELETE) + @RequestMapping(value = "/admin/{username:[a-zA-Z_.0-9]*}/remove", method = RequestMethod.DELETE) public String removeUser(@PathVariable("username") String username) { return adminRestClient.removeUser(username).readEntity(String.class); } diff --git a/rss-2-kindle-web/src/main/resources/log4j.properties b/rss-2-kindle-web/src/main/resources/log4j.properties index 0d9e0c8..bd16a33 100644 --- a/rss-2-kindle-web/src/main/resources/log4j.properties +++ b/rss-2-kindle-web/src/main/resources/log4j.properties @@ -6,9 +6,7 @@ log4j.rootLogger=DEBUG, out # CONSOLE appender not used by default log4j.appender.out=org.apache.log4j.ConsoleAppender log4j.appender.out.layout=org.apache.log4j.PatternLayout -log4j.appender.out.layout.ConversionPattern=[%30.30t] %-30.30c{1} %-5p %m%n -#log4j.appender.out.layout.ConversionPattern=%d [%-15.15t] %-5p %-30.30c{1} - %m%n - +log4j.appender.out.layout.ConversionPattern=[%-5p][%t] <%c{1}> - %m%n log4j.logger.org.springframework=INFO, SPRING log4j.appender.SPRING=org.apache.log4j.RollingFileAppender diff --git a/rss-2-kindle-web/src/main/webapp/jsp/profile.jsp b/rss-2-kindle-web/src/main/webapp/jsp/profile.jsp index 5f1f0af..ddb8311 100644 --- a/rss-2-kindle-web/src/main/webapp/jsp/profile.jsp +++ b/rss-2-kindle-web/src/main/webapp/jsp/profile.jsp @@ -8,7 +8,7 @@ - <%@include file="_head.jsp"%> + <%@include file="_head.jsp" %> @@ -20,65 +20,73 @@ $.getJSON(rootURL, function (data) { userData = data; - var subscribersTable = '' + - '' + - '' + - '' + - '' + - '' + - ''; - - var rssTable = '
#subscriberemailstatusnumber of subscriptions
' + - '' + - '' + - '' + - '' + - ''; + var subscribersTable = $('
#subscriptionstatussend to
') + .addClass('table table-hover') + .append('' + + '' + + '' + + '' + + '' + + '' + + '') + .append(''); + + var rssTable = $('
#subscriberemailstatusnumber of subscriptions
').addClass('table table-hover') + .append('' + + '' + + '' + + '' + + '' + + '') + .append(''); var rssNumber = 0; var suspendedSubscribersnumber = 0; var deadRssNumber = 0; var offlineRssNumber = 0; $.each(data.subscribers, function (i, item) { - var tr; + var rss = item.rsslist; + var tr = $(''); + if (item.status === 'suspended') { - tr = ''; + tr.append('') + .append('') + .append('') + .append('') + .append(''); + subscribersTable.append(tr); for (j = 0; j < rss.length; j++) { + var rssTr = $(''); if (rss[j].status === 'dead') { - tr = ''; + rssTr.append('') + .append('') + .append('') + .append(''); + rssTable.append(rssTr); } }); - subscribersTable += '
#subscriptionstatussend to
'; + tr.addClass('table-danger'); suspendedSubscribersnumber++; } - else - tr = '
'; - - subscribersTable += tr + (i + 1) + '' - + '' + item.name + '' - + '' + item.email + '' - + item.status + ''; + else { + tr.addClass('table-light'); + } - var rss = item.rsslist; - subscribersTable += rss.length + '
' + (i +1) + '' + item.name + '' + item.email + '' + item.status + ''+ rss.length + '
'; + rssTr.addClass('table-danger'); deadRssNumber++; } else if (rss[j].status === 'offline') { - tr = '
'; + rssTr.addClass('table-warning'); offlineRssNumber++; } - else - tr = '
'; + else { + rssTr.addClass('table-light'); + } rssNumber++; - rssTable += tr + rssNumber + '' - + '' + rss[j].rss + '' - + rss[j].status + '' - + item.email + '
' + rssNumber + '' + rss[j].rss + '' + rss[j].status + '' + item.email + '
'; - rssTable += ''; $('#dashboard_user_status').append('
User status: ' + data.status + '
'); $('#dashboard_user_info').append( @@ -141,10 +149,12 @@
-
+

Subscribers

-
+

Subscriptions

diff --git a/rss-2-kindle-web/src/main/webapp/jsp/subscriberDetails.jsp b/rss-2-kindle-web/src/main/webapp/jsp/subscriberDetails.jsp index 6003de1..729d83a 100644 --- a/rss-2-kindle-web/src/main/webapp/jsp/subscriberDetails.jsp +++ b/rss-2-kindle-web/src/main/webapp/jsp/subscriberDetails.jsp @@ -6,7 +6,7 @@ <%@include file="_include.jsp" %> - <%@include file="_head.jsp"%> + <%@include file="_head.jsp" %> @@ -28,43 +28,45 @@ reloadRssTable(); function reloadRssTable() { - $.getJSON(rootURL+ '/${subscriber}', function (data) { + $.getJSON(rootURL + '/${subscriber}', function (data) { userData = data; - var rssTable = '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - ''; + var rssTable = $('
#rssstatuslast polling dateerror messageretry count
') + .addClass('table table-hover') + .append('' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') + .append(''); $.each(data.rsslist, function (i, item) { - var tr; + var tr = $(''); if (item.status === 'dead') { - tr = ''; + tr.addClass('table-danger'); } else if (item.status === 'offline') { - tr = ''; + tr.addClass('table-warning'); } else { - tr = ''; + tr.addClass('table-light'); } - tr += '' + tr.append('' + + '' + + '' + + '' + + '' + + '' + + ''); + rssTable.append(tr); }); - rssTable += '
#rssstatuslast polling dateerror messageretry count
'; - - rssTable += tr + (i + 1) + '' - + '' + item.rss + '' - + item.status + '' - + item.lastPollingDate + '' - + item.errorMessage + '' - + item.retryCount + '
' + (i + 1) + '' + item.rss + '' + item.status + '' + item.lastPollingDate + '' + item.errorMessage + '' + item.retryCount + '
'; $("#details").html(rssTable); $("#select_all_checkbox").change(function (e) { @@ -183,7 +185,7 @@ } //end of updateSubscriptions $('#btn_addrss').click(function (event) { - var newRssField=$('#new_rss'); + var newRssField = $('#new_rss'); var rss = newRssField.val(); newRssField.popover( { @@ -227,20 +229,24 @@
diff --git a/rss-2-kindle-web/src/main/webapp/test.html b/rss-2-kindle-web/src/main/webapp/test.html index 38beeb4..d0f300f 100644 --- a/rss-2-kindle-web/src/main/webapp/test.html +++ b/rss-2-kindle-web/src/main/webapp/test.html @@ -182,6 +182,60 @@ } }); //select_all_checkbox.change + var table = $('') + .addClass('table table-hover') + .append( '' + + '' + + '' + + '' + + '' + + '') + .append(''); + + $.each(userData.subscribers, function (i, item) { + var tr = $(''); + if (item.status === 'suspended') { + tr.addClass('table-danger'); + } else { + tr.addClass('table-light'); + } + + tr.append('' + + '<' + + '' + + ''); + + var btnDiv = $('
') + .addClass('btn-group') + .attr({'role': 'group'}); + btnDiv.append(''); + + if (item.status === 'suspended') { + btnDiv.append(''); + } else { + btnDiv.append(''); + } + + btnDiv.append(''); + + tr.append($('
#nameemailstatusaction
' + (i + 1) + '' + item.name + '' + item.email + '' + item.status + '').append(btnDiv)); + table.append(tr); + }); + $('#test').append(table); + }); // $(document).ready() function getUserData() { @@ -323,6 +377,7 @@

Users

+