Commit f739d617 authored by Administrator's avatar Administrator

Adds the REST layer implementation partially tested

The REST layer has been added but with incomplete tests. Students will
have to complete the tests.

This commit includes an important refactorization regarding tests. Test
classes and datasets have been moved from the "service" project to a new
"tests" project. This way, any project can include this "tests" project
as a dependency in order to have access to these common testing
resources.
parent 7fe00cf8
...@@ -5,3 +5,6 @@ ...@@ -5,3 +5,6 @@
#Maven #Maven
target target
#General
bak
...@@ -42,22 +42,26 @@ Eclipse should then import 2 projects (`xcs-sample` and `domain`) ...@@ -42,22 +42,26 @@ Eclipse should then import 2 projects (`xcs-sample` and `domain`)
## Sample 1: Testing entities ## Sample 1: Testing entities
Using JUnit and Hamcrest, we will see how to test JPA entities or any other Using JUnit and Hamcrest, we will see how to test JPA entities or any other
basic Java class. Java class. This libraries are the base for every test done in the application.
## Sample 2: Testing with test doubles ## Sample 2: Testing EJBs
Coming soon.... Using Arquillian and Arquillian Persistence, the EJBs are tested. We wouldn't do
unit testing in this layer, as we don't want to mock the `EntityManager`.
## Sample 3: Testing EJBs In this layer we will use some workarounds to set the desired role and principal
Coming soon... in the tests.
## Sample 4: Testing JAX-RS ## Sample 3: Testing with test doubles
Coming soon... Using EasyMock, we will mock the EJBs to test the REST classes isolated from the
underlying layer.
## Sample 5: Testing AngularJS ## Sample 4: Testing JAX-RS
Coming soon... Using Arquillian REST Client, we will test the REST API accessing it as real
HTTP clients.
## Sample 6: Testing JSF ## Sample 5: Testing JSF
Coming soon... Using Arquillian Drone, Arquillian Graphene and Selenium, we will test the JSF
web interface accessing it as real Web clients.
## Sample 7: Additional Testing Tools ## Sample 6: Additional Testing Tools
Coming soon... Coming soon...
...@@ -4,6 +4,7 @@ import java.io.Serializable; ...@@ -4,6 +4,7 @@ import java.io.Serializable;
import javax.persistence.DiscriminatorValue; import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.xml.bind.annotation.XmlRootElement;
/** /**
* An administrator of the application. * An administrator of the application.
...@@ -12,6 +13,7 @@ import javax.persistence.Entity; ...@@ -12,6 +13,7 @@ import javax.persistence.Entity;
*/ */
@Entity @Entity
@DiscriminatorValue("ADMIN") @DiscriminatorValue("ADMIN")
@XmlRootElement(name = "admin", namespace = "http://entities.domain.xcs.esei.uvigo.es")
public class Administrator extends User implements Serializable { public class Administrator extends User implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
......
...@@ -11,7 +11,9 @@ import java.util.Collection; ...@@ -11,7 +11,9 @@ import java.util.Collection;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.DiscriminatorValue; import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.xml.bind.annotation.XmlRootElement;
/** /**
* A pet owner. * A pet owner.
...@@ -20,6 +22,7 @@ import javax.persistence.OneToMany; ...@@ -20,6 +22,7 @@ import javax.persistence.OneToMany;
*/ */
@Entity @Entity
@DiscriminatorValue("OWNER") @DiscriminatorValue("OWNER")
@XmlRootElement(name = "owner", namespace = "http://entities.domain.xcs.esei.uvigo.es")
public class Owner extends User implements Serializable { public class Owner extends User implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -27,7 +30,8 @@ public class Owner extends User implements Serializable { ...@@ -27,7 +30,8 @@ public class Owner extends User implements Serializable {
mappedBy = "owner", mappedBy = "owner",
targetEntity = Pet.class, targetEntity = Pet.class,
cascade = CascadeType.ALL, cascade = CascadeType.ALL,
orphanRemoval = true orphanRemoval = true,
fetch = FetchType.EAGER
) )
private Collection<Pet> pets; private Collection<Pet> pets;
......
...@@ -17,6 +17,10 @@ import javax.persistence.JoinColumn; ...@@ -17,6 +17,10 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Temporal; import javax.persistence.Temporal;
import javax.persistence.TemporalType; import javax.persistence.TemporalType;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/** /**
* A pet. * A pet.
...@@ -24,6 +28,8 @@ import javax.persistence.TemporalType; ...@@ -24,6 +28,8 @@ import javax.persistence.TemporalType;
* @author Miguel Reboiro-Jato * @author Miguel Reboiro-Jato
*/ */
@Entity(name = "Pet") @Entity(name = "Pet")
@XmlRootElement(name = "pet", namespace = "http://entities.domain.xcs.esei.uvigo.es")
@XmlAccessorType(XmlAccessType.FIELD)
public class Pet implements Serializable { public class Pet implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -43,7 +49,8 @@ public class Pet implements Serializable { ...@@ -43,7 +49,8 @@ public class Pet implements Serializable {
private Date birth; private Date birth;
@ManyToOne @ManyToOne
@JoinColumn(name = "owner", referencedColumnName = "login") @JoinColumn(name = "owner", referencedColumnName = "login", nullable = false)
@XmlTransient
private Owner owner; private Owner owner;
// Required for JPA. // Required for JPA.
......
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
<modules> <modules>
<module>domain</module> <module>domain</module>
<module>tests</module>
<module>service</module> <module>service</module>
<module>rest</module>
</modules> </modules>
<properties> <properties>
...@@ -24,6 +26,8 @@ ...@@ -24,6 +26,8 @@
<!-- BOM versions --> <!-- BOM versions -->
<javaee.api.version>7.0</javaee.api.version> <javaee.api.version>7.0</javaee.api.version>
<arquillian.version>1.1.9.Final</arquillian.version> <arquillian.version>1.1.9.Final</arquillian.version>
<arquillian.rest.version>1.0.0.Alpha3</arquillian.rest.version>
<arquillian.selenium.version>2.47.1</arquillian.selenium.version>
<!-- Dependencies versions --> <!-- Dependencies versions -->
<wildfly.version>8.2.1.Final</wildfly.version> <wildfly.version>8.2.1.Final</wildfly.version>
...@@ -36,8 +40,8 @@ ...@@ -36,8 +40,8 @@
<mysql.connector.java.version>5.1.21</mysql.connector.java.version> <mysql.connector.java.version>5.1.21</mysql.connector.java.version>
<slf4j.version>1.5.10</slf4j.version> <slf4j.version>1.5.10</slf4j.version>
<easymock.version>3.4</easymock.version> <easymock.version>3.4</easymock.version>
<arquillian.glassfish.embedded.version>1.0.0.CR4</arquillian.glassfish.embedded.version> <resteasy.version>3.0.13.Final</resteasy.version>
<glassfish.embedded.all.version>4.1</glassfish.embedded.all.version> <graphene.webdrive.version>2.1.0.Alpha2</graphene.webdrive.version>
<!-- Plugins versions --> <!-- Plugins versions -->
<wildfly.maven.plugin.version>1.1.0.Alpha1</wildfly.maven.plugin.version> <wildfly.maven.plugin.version>1.1.0.Alpha1</wildfly.maven.plugin.version>
...@@ -61,6 +65,13 @@ ...@@ -61,6 +65,13 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.arquillian.selenium</groupId>
<artifactId>selenium-bom</artifactId>
<version>${arquillian.selenium.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Modules --> <!-- Modules -->
<dependency> <dependency>
...@@ -73,6 +84,17 @@ ...@@ -73,6 +84,17 @@
<artifactId>service</artifactId> <artifactId>service</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>tests</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<!-- General --> <!-- General -->
<dependency> <dependency>
...@@ -102,6 +124,23 @@ ...@@ -102,6 +124,23 @@
<artifactId>arquillian-persistence-dbunit</artifactId> <artifactId>arquillian-persistence-dbunit</artifactId>
<version>${arquillian.persistence.dbunit.version}</version> <version>${arquillian.persistence.dbunit.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-rest-client-impl-3x</artifactId>
<version>${arquillian.rest.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.graphene</groupId>
<artifactId>graphene-webdriver</artifactId>
<version>${graphene.webdrive.version}</version>
<type>pom</type>
</dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-java</artifactId>
...@@ -132,17 +171,6 @@ ...@@ -132,17 +171,6 @@
<artifactId>wildfly-arquillian-container-embedded</artifactId> <artifactId>wildfly-arquillian-container-embedded</artifactId>
<version>${wildfly.version}</version> <version>${wildfly.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>arquillian-glassfish-embedded-3.1</artifactId>
<version>${arquillian.glassfish.embedded.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.main.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>${glassfish.embedded.all.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
......
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>es.uvigo.esei.xcs</groupId>
<artifactId>sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>rest</artifactId>
<packaging>war</packaging>
<name>REST</name>
<description>XCS Sample - REST</description>
<dependencies>
<!-- General -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>es.uvigo.esei.xcs</groupId>
<artifactId>service</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>es.uvigo.esei.xcs</groupId>
<artifactId>tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-persistence-dbunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.extension</groupId>
<artifactId>arquillian-rest-client-impl-3x</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package es.uvigo.esei.xcs.rest;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
@Provider
public class CORSFilter implements ContainerResponseFilter {
@Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext cres)
throws IOException {
cres.getHeaders().add("Access-Control-Allow-Origin", "*");
cres.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
cres.getHeaders().add("Access-Control-Allow-Credentials", "true");
cres.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
cres.getHeaders().add("Access-Control-Max-Age", "1209600");
}
}
\ No newline at end of file
package es.uvigo.esei.xcs.rest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class IllegalArgumentExceptionMapper
implements ExceptionMapper<IllegalArgumentException> {
@Override
public Response toResponse(IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(e.getMessage())
.type(MediaType.TEXT_PLAIN)
.build();
}
}
package es.uvigo.esei.xcs.rest;
import java.net.URI;
import javax.ejb.EJB;
import javax.persistence.EntityExistsException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import es.uvigo.esei.xcs.domain.entities.Owner;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.service.OwnerService;
/**
* Resource that represents the owners in the application.
*
* @author Miguel Reboiro Jato
*/
@Path("owner")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class OwnerResource {
@EJB
private OwnerService service;
@Context
private UriInfo uriInfo;
/**
* Returns the owner identified by the login.
*
* @param login the login of an owner.
* @return an {@code OK} response containing the {@link Owner} with the
* provided login.
* @throws IllegalArgumentException if {@code login} is {@code null} or
* if it does not correspond with any owner.
*/
@Path("{login}")
@GET
public Response get(@PathParam("login") String login) {
if (login == null)
throw new IllegalArgumentException("login can't be null");
final Owner owner = this.service.get(login);
if (owner == null)
throw new IllegalArgumentException("Owner not found: " + login);
else
return Response.ok(owner).build();
}
/**
* Returns the list of owners stored in the application.
*
* @return an {@code OK} response containing the list of owners stored in
* the application.
*/
@GET
public Response list() {
return Response.ok(this.service.list()).build();
}
/**
* Creates a new owner. This owner may include a list of pets, that will be
* also created.
*
* @param owner a new owner to be stored.
* @return a {@code CREATED} response with the URI of the new owner in the
* {@code Location} header.
* @throws IllegalArgumentException if owner is {@code null} or if an owner
* with the same login already exists.
*/
@POST
public Response create(Owner owner) {
// Pets are serialized without owner.
assignOwnerToPets(owner);
try {
final Owner newOwner = this.service.create(owner);
final URI ownerUri = uriInfo.getAbsolutePathBuilder()
.path(newOwner.getLogin())
.build();
return Response.created(ownerUri).build();
} catch (EntityExistsException eee) {
throw new IllegalArgumentException("The owner already exists");
}
}
/**
* Updates an owner. This owner may include a list of pets, that will be
* also created or updated. If the owner does not exists it will be created.
*
* @param owner an owner to be updated.
* @return an empty {@code OK} response.
* @throws IllegalArgumentException if owner is {@code null}.
*/
@PUT
public Response update(Owner owner) {
// Pets are serialized without owner.
assignOwnerToPets(owner);
this.service.update(owner);
return Response.ok().build();
}
/**
* Deletes an owner.
*
* @param login the login of the owner to be deleted.
* @throws IllegalArgumentException if {@code login} is {@code null} or if
* it does not identifies a valid owner.
*/
@Path("{login}")
@DELETE
public Response delete(@PathParam("login") String login) {
if (login == null)
throw new IllegalArgumentException("login can't be null");
this.service.remove(login);
return Response.ok().build();
}
private static void assignOwnerToPets(Owner owner) {
if (owner == null)
throw new IllegalArgumentException("owner can't be null");
for (Pet pet : owner.getPets()) {
if (pet.getOwner() != owner)
pet.setOwner(owner);
}
}
}
package es.uvigo.esei.xcs.rest;
import java.net.URI;
import javax.ejb.EJB;
import javax.ejb.EJBAccessException;
import javax.persistence.EntityExistsException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import es.uvigo.esei.xcs.domain.entities.Pet;
import es.uvigo.esei.xcs.service.PetService;
/**
* Resource that represents the pets in the application.
*
* @author Miguel Reboiro Jato
*/
@Path("pet")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class PetResource {
@EJB
private PetService service;
@Context
private UriInfo uriInfo;
/**
* Returns the owner identified by the login.
*
* @param id the identified of a pet.
* @return an {@code OK} response containing the {@link Pet} with the
* provided identifier.
* @throws SecurityException if the current owner does not owns the pet.
*/
@Path("{id}")
@GET
public Response get(@PathParam("id") int id) throws SecurityException {
try {
final Pet pet = this.service.get(id);
if (pet == null)
throw new IllegalArgumentException("Pet not found: " + id);
else
return Response.ok(pet).build();
} catch (EJBAccessException eae) {
throw new SecurityException(eae);
}
}
/**
* Returns the complete list of pets of the current owner.
*
* @return an {@code OK} response containing the complete list of pets of
* the current owner.
*/
@GET
public Response list() {
return Response.ok(this.service.list()).build();
}
/**
* Creates a new pet owned by the current user.
*
* @param pet a new owner to be stored.
* @return a {@code CREATED} response with the URI of the new pet in the
* {@code Location} header.
* @throws IllegalArgumentException if pet is {@code null} or if a pet with
* the same identifier already exists.
* @throws SecurityException if the pet already has an owner and it is not
* the current user. If the pet has no owner, this exception will be never
* thrown.
*/
@POST
public Response create(Pet pet) throws SecurityException {
if (pet == null)
throw new IllegalArgumentException("pet can't be null");
try {
final Pet newPet = this.service.create(pet);
final URI petUri = uriInfo.getAbsolutePathBuilder()
.path(Integer.toString(newPet.getId()))
.build();
return Response.created(petUri).build();
} catch (EntityExistsException eee) {
throw new IllegalArgumentException("The pet already exists");
} catch (EJBAccessException eae) {
throw new SecurityException(eae);
}
}
/**
* Updates the information of a pet. If the pet is not stored, it will be
* created.
*
* @param pet a pet to be updated.
* @return an empty {@code OK} response.
* @throws IllegalArgumentException if pet is {@code null} of it has no
* owner.
* @throws SecurityException if the pet's owner is not the current user.
*/
@PUT
public Response update(Pet pet) throws SecurityException {
if (pet == null)
throw new IllegalArgumentException("pet can't be null");
try {
this.service.update(pet);
return Response.ok().build();
} catch (EJBAccessException eae) {
throw new SecurityException(eae);
}
}
/**
* Deletes a pet.
*
* @param id the identifier of the pet to be deleted.
* @throws IllegalArgumentException if there is no pet with the provided
* identifier.
* @throws SecurityException if the pet's owner is not the current user.
*/
@Path("{id}")
@DELETE
public Response delete(@PathParam("id") int id) throws SecurityException {
try {
this.service.remove(id);
return Response.ok().build();
} catch (EJBAccessException eae) {
throw new SecurityException(eae);
}
}
}
package es.uvigo.esei.xcs.rest;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class SecurityExceptionMapper implements ExceptionMapper<SecurityException> {
@Override
public Response toResponse(SecurityException e) {
return Response.status(Response.Status.FORBIDDEN)
.entity(e.getMessage())
.type(MediaType.TEXT_PLAIN)
.build();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="runtime">
<jta-data-source>java:jboss/datasources/xcs</jta-data-source>
<class>es.uvigo.esei.xcs.domain.entities.Owner</class>
<class>es.uvigo.esei.xcs.domain.entities.Pet</class>
<class>es.uvigo.esei.xcs.domain.entities.Administrator</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
</persistence>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>xcs-sample-security-domain</security-domain>
</jboss-web>
\ No newline at end of file
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Pet Store REST</display-name>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!--Defining security constraint for type of roles available -->
<security-constraint>
<web-resource-collection>
<web-resource-name>admin</web-resource-name>
<url-pattern>/api/owner/*</url-pattern>
<http-method-omission>OPTIONS</http-method-omission>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>owner</web-resource-name>
<url-pattern>/api/pet/*</url-pattern>
<http-method-omission>OPTIONS</http-method-omission>
</web-resource-collection>
<auth-constraint>
<role-name>OWNER</role-name>
</auth-constraint>
</security-constraint>
<!--Defining security constraint for type of roles available -->
<!--Denining security role -->
<security-role>
<role-name>ADMIN</role-name>
</security-role>
<security-role>
<role-name>OWNER</role-name>
</security-role>
<!--Denining security role -->
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>default</realm-name>
</login-config>
</web-app>
\ No newline at end of file
package es.uvigo.esei.xcs.rest;
import java.util.List;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import es.uvigo.esei.xcs.domain.entities.Owner;
public final class GenericTypes {
private GenericTypes() {}
public static class ListOwnerType extends GenericType<List<Owner>> {
public static ListOwnerType INSTANCE = new ListOwnerType();
public static List<Owner> readEntity(Response response) {
return response.readEntity(INSTANCE);
}
}
}
package es.uvigo.esei.xcs.rest;
import static es.uvigo.esei.xcs.domain.entities.IsEqualsToOwner.containsOwnersInAnyOrder;
import static es.uvigo.esei.xcs.domain.entities.IsEqualsToOwner.equalsToOwner;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.anyLogin;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.anyOwner;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.newOwnerWithFreshPets;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.newOwnerWithPersistentPets;
import static es.uvigo.esei.xcs.domain.entities.OwnersDataset.owners;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.util.List;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.easymock.EasyMockRunner;
import org.easymock.EasyMockSupport;
import org.easymock.Mock;
import org.easymock.TestSubject;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import es.uvigo.esei.xcs.domain.entities.Owner;
import es.uvigo.esei.xcs.domain.entities.OwnersDataset;
import es.uvigo.esei.xcs.service.OwnerService;
@RunWith(EasyMockRunner.class)
public class OwnerResourceUnitTest extends EasyMockSupport {
@TestSubject
private OwnerResource resource = new OwnerResource();
@Mock
private OwnerService facade;
@Mock
private UriInfo uriInfo;
@Mock
private UriBuilder uriBuilder;
@After
public void tearDown() throws Exception {
verifyAll();
}
@Test
public void testGet() {
final Owner owner = OwnersDataset.anyOwner();
expect(facade.get(owner.getLogin()))
.andReturn(owner);
replayAll();
final Response response = resource.get(owner.getLogin());
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.OK)));
assertThat(response.getEntity(), is(instanceOf(Owner.class)));
assertThat((Owner) response.getEntity(), is(equalsToOwner(owner)));
}
@Test(expected = IllegalArgumentException.class)
public void testGetNull() {
replayAll();
resource.get(null);
}
@Test(expected = IllegalArgumentException.class)
public void testGetMissing() {
final String login = anyLogin();
expect(facade.get(login))
.andReturn(null);
replayAll();
resource.get(login);
}
@Test
@SuppressWarnings("unchecked")
public void testList() {
final Owner[] owners = owners();
expect(facade.list())
.andReturn(asList(owners));
replayAll();
final Response response = resource.list();
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.OK)));
assertThat(response.getEntity(), is(instanceOf(List.class)));
assertThat((List<Owner>) response.getEntity(), containsOwnersInAnyOrder(owners));
}
@Test
@SuppressWarnings("unchecked")
public void testListEmpty() {
expect(facade.list())
.andReturn(emptyList());
replayAll();
final Response response = resource.list();
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.OK)));
assertThat(response.getEntity(), is(instanceOf(List.class)));
assertThat((List<Owner>) response.getEntity(), is(empty()));
}
@Test
public void testCreate() throws Exception {
final Owner newOwner = newOwnerWithFreshPets();
final Owner createdOwner = newOwnerWithPersistentPets();
final URI mockUri = new URI("http://host/api/owner/" + newOwner.getLogin());
expect(facade.create(newOwner))
.andReturn(createdOwner);
expect(uriInfo.getAbsolutePathBuilder())
.andReturn(uriBuilder);
expect(uriBuilder.path(newOwner.getLogin()))
.andReturn(uriBuilder);
expect(uriBuilder.build())
.andReturn(mockUri);
replayAll();
final Response response = resource.create(newOwner);
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.CREATED)));
assertThat(response.getHeaderString("Location"), is(equalTo(mockUri.toString())));
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNull() {
replayAll();
resource.create(null);
}
@Test
public void testUpdate() {
final Owner owner = anyOwner();
facade.update(owner);
expectLastCall().andReturn(owner);
replayAll();
final Response response = resource.update(owner);
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.OK)));
}
@Test(expected = IllegalArgumentException.class)
public void testUpdateNull() {
replayAll();
resource.update(null);
}
@Test
public void testDelete() {
final String login = OwnersDataset.anyLogin();
facade.remove(login);
replayAll();
final Response response = resource.delete(login);
assertThat(response.getStatusInfo(), is(equalTo(Response.Status.OK)));
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteNull() {
replayAll();
resource.delete(null);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<class>es.uvigo.esei.xcs.service.util.security.TestPrincipal</class>
</alternatives>
</beans>
ALTER TABLE Pet ALTER COLUMN id RESTART WITH 1;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>xcs-sample-security-domain</security-domain>
</jboss-web>
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="test">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
bean-discovery-mode="all">
<alternatives>
<class>es.uvigo.esei.xcs.service.util.security.TestPrincipal</class>
</alternatives>
</beans>
ALTER TABLE Pet AUTO_INCREMENT = 1;
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>xcs-sample-security-domain</security-domain>
</jboss-web>
\ No newline at end of file
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.ironjacamar.org/doc/schema"
xsi:schemaLocation="http://www.ironjacamar.org/doc/schema http://www.ironjacamar.org/doc/schema/datasources_1_1.xsd">
<datasource jndi-name="java:jboss/datasources/xcs" pool-name="MySQLPool">
<connection-url>jdbc:mysql://localhost:3306/xcs</connection-url>
<driver>mysql-connector-java-${mysql.version}.jar</driver>
<pool>
<max-pool-size>30</max-pool-size>
</pool>
<security>
<user-name>xcs</user-name>
<password>xcs</password>
</security>
</datasource>
</datasources>
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="test">
<jta-data-source>java:jboss/datasources/xcs</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<arquillian xmlns="http://jboss.org/schema/arquillian"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://jboss.org/schema/arquillian
http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
<extension qualifier="persistence">
<property name="defaultDataSeedStrategy">CLEAN_INSERT</property>
</extension>
<container qualifier="wildfly-embedded-h2" default="true">
<configuration>
<property name="jbossHome">target/wildfly-${wildfly.version}</property>
<property name="modulePath">target/wildfly-${wildfly.version}/modules</property>
</configuration>
</container>
<container qualifier="wildfly-embedded-mysql">
<configuration>
<property name="jbossHome">target/wildfly-${wildfly.version}</property>
<property name="modulePath">target/wildfly-${wildfly.version}/modules</property>
</configuration>
</container>
</arquillian>
\ No newline at end of file
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Pet Store REST</display-name>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<!--Defining security constraint for type of roles available -->
<security-constraint>
<web-resource-collection>
<web-resource-name>admin</web-resource-name>
<url-pattern>/api/owner/*</url-pattern>
<http-method-omission>OPTIONS</http-method-omission>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>owner</web-resource-name>
<url-pattern>/api/pet/*</url-pattern>
<http-method-omission>OPTIONS</http-method-omission>
</web-resource-collection>
<auth-constraint>
<role-name>OWNER</role-name>
</auth-constraint>
</security-constraint>
<!--Defining security constraint for type of roles available -->
<!--Denining security role -->
<security-role>
<role-name>ADMIN</role-name>
</security-role>
<security-role>
<role-name>OWNER</role-name>
</security-role>
<!--Denining security role -->
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>default</realm-name>
</login-config>
</web-app>
\ No newline at end of file
...@@ -25,6 +25,11 @@ ...@@ -25,6 +25,11 @@
</dependency> </dependency>
<!-- Testing --> <!-- Testing -->
<dependency>
<groupId>es.uvigo.esei.xcs</groupId>
<artifactId>tests</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
......
...@@ -89,7 +89,7 @@ public class PetService { ...@@ -89,7 +89,7 @@ public class PetService {
* Updates the information of a pet. If the pet is not stored, it will be * Updates the information of a pet. If the pet is not stored, it will be
* created. * created.
* *
* @param pet the pet to be updated. * @param pet a pet to be updated.
* @throws IllegalArgumentException if the pet has no owner. * @throws IllegalArgumentException if the pet has no owner.
* @throws EJBAccessException if the pet's owner is not the current user. * @throws EJBAccessException if the pet's owner is not the current user.
*/ */
......
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>es.uvigo.esei.xcs</groupId>
<artifactId>sample</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>tests</artifactId>
<packaging>jar</packaging>
<name>Tests</name>
<description>XCS Sample - Test Utility Classes</description>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>domain</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
...@@ -8,6 +8,11 @@ import java.util.Date; ...@@ -8,6 +8,11 @@ import java.util.Date;
import java.util.Set; import java.util.Set;
public class OwnersDataset { public class OwnersDataset {
public static final String EXISTENT_LOGIN = "pepe";
public static final String NON_EXISTENT_LOGIN = "non-existent";
public static final String OWNER_WITH_PETS_LOGIN = "juan";
public static final String OWNER_WITHOUT_PETS_LOGIN = "lorena";
public static Owner owner(String login) { public static Owner owner(String login) {
return stream(owners()) return stream(owners())
.filter(owner -> owner.getLogin().equals(login)) .filter(owner -> owner.getLogin().equals(login))
...@@ -32,9 +37,9 @@ public class OwnersDataset { ...@@ -32,9 +37,9 @@ public class OwnersDataset {
public static Owner[] owners() { public static Owner[] owners() {
return new Owner[] { return new Owner[] {
new Owner("pepe", "pepepass", new Owner(EXISTENT_LOGIN, "pepepass",
new Pet(1, "Pepecat", AnimalType.CAT, new Date(946684861000L))), new Pet(1, "Pepecat", AnimalType.CAT, new Date(946684861000L))),
new Owner("juan", "juanpass", new Owner(OWNER_WITH_PETS_LOGIN, "juanpass",
new Pet(2, "Max", AnimalType.CAT, new Date(946684861000L)), new Pet(2, "Max", AnimalType.CAT, new Date(946684861000L)),
new Pet(3, "Juandog", AnimalType.DOG, new Date(946684861000L)) new Pet(3, "Juandog", AnimalType.DOG, new Date(946684861000L))
), ),
...@@ -43,7 +48,7 @@ public class OwnersDataset { ...@@ -43,7 +48,7 @@ public class OwnersDataset {
new Pet(5, "Max", AnimalType.DOG, new Date(946684861000L)), new Pet(5, "Max", AnimalType.DOG, new Date(946684861000L)),
new Pet(6, "Anabird", AnimalType.BIRD, new Date(946684861000L)) new Pet(6, "Anabird", AnimalType.BIRD, new Date(946684861000L))
), ),
new Owner("lorena", "lorenapass") new Owner(OWNER_WITHOUT_PETS_LOGIN, "lorenapass")
}; };
} }
...@@ -79,11 +84,11 @@ public class OwnersDataset { ...@@ -79,11 +84,11 @@ public class OwnersDataset {
} }
public static Owner ownerWithPets() { public static Owner ownerWithPets() {
return owners()[1]; return owner(OWNER_WITH_PETS_LOGIN);
} }
public static Owner ownerWithoutPets() { public static Owner ownerWithoutPets() {
return owners()[3]; return owner(OWNER_WITHOUT_PETS_LOGIN);
} }
public static Pet anyPet() { public static Pet anyPet() {
...@@ -98,14 +103,26 @@ public class OwnersDataset { ...@@ -98,14 +103,26 @@ public class OwnersDataset {
return new Pet("Lorenacat", AnimalType.CAT, new Date(946684861000L), owner); return new Pet("Lorenacat", AnimalType.CAT, new Date(946684861000L), owner);
} }
public static String anyLogin() {
return EXISTENT_LOGIN;
}
public static String existentLogin() { public static String existentLogin() {
return "pepe"; return EXISTENT_LOGIN;
}
public static String nonExistentLogin() {
return NON_EXISTENT_LOGIN;
} }
public static String existentPetName() { public static String existentPetName() {
return "Pepecat"; return "Pepecat";
} }
public static String nonExistentPetName() {
return "NonExistentPet";
}
public static int existentPetId() { public static int existentPetId() {
return 2; return 2;
} }
...@@ -113,4 +130,9 @@ public class OwnersDataset { ...@@ -113,4 +130,9 @@ public class OwnersDataset {
public static int nonExistentPetId() { public static int nonExistentPetId() {
return 1000000; return 1000000;
} }
public static Owner nonExistentOwner() {
final String login = nonExistentLogin();
return new Owner(login, login + "pass");
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment