...
 
Commits (9)
......@@ -15,3 +15,6 @@ WebContent
# Testing
/servers
C:\\nppdf32Log\\debuglog.txt
# Angular
src/main/angular
......@@ -5,6 +5,16 @@ Aplicación y arquitectura de ejemplo para la asignatura Desarrollo Ágil de
Aplicaciones del Grado en Ingeniería Informática de la Escuela Superior de
Ingeniería Informática de la Universidad de Vigo.
## Dependencias
Este proyecto está diseñado para ser desarrollado en un entorno con:
* Maven 3
* Java 8
* MySQL 5.7.6+ o 8+
Además, se recomienda emplear la última versión de Eclipse IDE for Enterprise
Java Developers.
## Ejecución con Maven
La configuración de Maven ha sido preparada para permitir varios tipos de
ejecución.
......@@ -26,7 +36,7 @@ comandos (desde la raíz el proyecto):
Una vez configurada la base de datos podemos lanzar la ejecución con el comando:
`mvn -Prun-tomcat-mysql -DskipTests=true package cargo:run`
`mvn -Prun -DskipTests=true package cargo:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
......@@ -38,7 +48,7 @@ Durante el desarrollo es interesante que la apliación se redespliegue de forma
automática cada vez que se hace un cambio. Para ello podemos utilizar el
siguiente comand:
`mvn -Prun-tomcat-mysql -DskipTests=true package cargo:start fizzed-watcher:run`
`mvn -Prun -DskipTests=true package cargo:start fizzed-watcher:run`
La aplicación se servirá en la URL local: http://localhost:9080/DAAExample
......@@ -57,16 +67,3 @@ memoria.
El comando para lanzar esta construcción es:
`mvn install`
### Construcción con tests de unidad, integración y aceptación
Esta construcción es similar a la previa, añadiendo las **pruebas de
aceptación**, que comprueban que las fucionalidades de la aplicación están
correctamente implementadas.
En estas pruebas se descarga y arranca el un servidor Tomcat 8 en el que se
despliega la aplicación configurada para utilizar una base de datos HSQL. Las
pruebas se hacen sobre la interfaz web con Selenium, que iniciará un Firefox
local de forma automática.
`mvn -Pacceptance-tests-cargo install`
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
......@@ -5,16 +6,17 @@ CREATE TABLE `daaexample`.`people` (
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost' IDENTIFIED BY 'daa';
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Antón','Pérez');
INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Manuel','Martínez');
......
DROP DATABASE IF EXISTS `daaexample`;
CREATE DATABASE `daaexample`;
CREATE TABLE `daaexample`.`people` (
......@@ -5,13 +6,14 @@ CREATE TABLE `daaexample`.`people` (
`name` varchar(50) NOT NULL,
`surname` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `daaexample`.`users` (
`login` varchar(100) NOT NULL,
`password` varchar(64) NOT NULL,
`role` varchar(10) NOT NULL,
PRIMARY KEY (`login`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost' IDENTIFIED BY 'daa';
CREATE USER IF NOT EXISTS 'daa'@'localhost' IDENTIFIED WITH mysql_native_password BY 'daa';
GRANT ALL ON `daaexample`.* TO 'daa'@'localhost';
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<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>DAAExample</display-name>
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>DAAExample</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/rest/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
<role-name>USER</role-name>
</auth-constraint>
</security-constraint>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Authorization</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET, POST, DELETE, PUT</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/rest/people/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>OPTIONS</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Area</web-resource-name>
<url-pattern>/rest/*</url-pattern>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
<role-name>USER</role-name>
</auth-constraint>
</security-constraint>
<!-- Security roles referenced by this web application -->
<security-role>
<role-name>ADMIN</role-name>
</security-role>
<security-role>
<role-name>USER</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DAAExample</realm-name>
</login-config>
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin Area</web-resource-name>
<url-pattern>/rest/people/*</url-pattern>
<http-method>GET</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>ADMIN</role-name>
</auth-constraint>
</security-constraint>
<!-- Security roles referenced by this web application -->
<security-role>
<role-name>ADMIN</role-name>
</security-role>
<security-role>
<role-name>USER</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>DAAExample</realm-name>
</login-config>
</web-app>
\ No newline at end of file
......@@ -21,6 +21,9 @@ var PeopleView = (function() {
$.each(people, function(key, person) {
appendToTable(person);
});
},
function() {
alert('No has sido posible acceder al listado de personas.');
});
// La acción por defecto de enviar formulario (submit) se sobreescribe
......
package es.uvigo.esei.daa.dao;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.nonExistentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.dataset.PeopleDataset.peopleWithout;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.listeners.ApplicationContextBinding;
import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener;
import es.uvigo.esei.daa.listeners.DbManagement;
import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:contexts/mem-context.xml")
@TestExecutionListeners({
DbUnitTestExecutionListener.class,
DbManagementTestExecutionListener.class,
ApplicationContextJndiBindingTestExecutionListener.class
})
@ApplicationContextBinding(
jndiUrl = "java:/comp/env/jdbc/daaexample",
type = DataSource.class
)
@DbManagement(
create = "classpath:db/hsqldb.sql",
drop = "classpath:db/hsqldb-drop.sql"
)
@DatabaseSetup("/datasets/dataset.xml")
@ExpectedDatabase("/datasets/dataset.xml")
public class PeopleDAOTest {
private PeopleDAO dao;
@Before
public void setUp() throws Exception {
this.dao = new PeopleDAO();
}
@Test
public void testList() throws DAOException {
assertThat(this.dao.list(), containsPeopleInAnyOrder(people()));
}
@Test
public void testGet() throws DAOException {
final Person person = this.dao.get(existentId());
assertThat(person, is(equalsToPerson(existentPerson())));
}
@Test(expected = IllegalArgumentException.class)
public void testGetNonExistentId() throws DAOException {
this.dao.get(nonExistentId());
}
@Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws DAOException {
this.dao.delete(existentId());
assertThat(this.dao.list(), containsPeopleInAnyOrder(peopleWithout(existentId())));
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteNonExistentId() throws DAOException {
this.dao.delete(nonExistentId());
}
@Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testModify() throws DAOException {
final Person person = existentPerson();
person.setName(newName());
person.setSurname(newSurname());
this.dao.modify(person);
final Person persistentPerson = this.dao.get(person.getId());
assertThat(persistentPerson, is(equalsToPerson(person)));
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNonExistentId() throws DAOException {
this.dao.modify(nonExistentPerson());
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullPerson() throws DAOException {
this.dao.modify(null);
}
@Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws DAOException {
final Person person = this.dao.add(newName(), newSurname());
assertThat(person, is(equalsToPerson(newPerson())));
final Person persistentPerson = this.dao.get(person.getId());
assertThat(persistentPerson, is(equalsToPerson(newPerson())));
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullName() throws DAOException {
this.dao.add(null, newSurname());
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullSurname() throws DAOException {
this.dao.add(newName(), null);
}
}
package es.uvigo.esei.daa.dao;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.reset;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.sql.SQLException;
import org.junit.Test;
import com.mysql.jdbc.Statement;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.util.DatabaseQueryUnitTest;
public class PeopleDAOUnitTest extends DatabaseQueryUnitTest {
@Test
public void testList() throws Exception {
final Person[] people = people();
for (Person person : people) {
expectPersonRow(person);
}
expect(result.next()).andReturn(false);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
assertThat(peopleDAO.list(), containsPeopleInAnyOrder(people));
}
@Test(expected = DAOException.class)
public void testListUnexpectedException() throws Exception {
expect(result.next()).andThrow(new SQLException());
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.list();
}
@Test
public void testGet() throws Exception {
final Person existentPerson = existentPerson();
expectPersonRow(existentPerson);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
assertThat(peopleDAO.get(existentId()), is(equalTo(existentPerson)));
}
@Test(expected = IllegalArgumentException.class)
public void testGetMissing() throws Exception {
expect(result.next()).andReturn(false);
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.get(existentId());
}
@Test(expected = DAOException.class)
public void testGetUnexpectedException() throws Exception {
expect(result.next()).andThrow(new SQLException());
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.get(existentId());
}
@Test
public void testAdd() throws Exception {
final Person person = newPerson();
reset(connection);
expect(connection.prepareStatement(anyString(), eq(Statement.RETURN_GENERATED_KEYS)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(1);
expect(statement.getGeneratedKeys()).andReturn(result);
// Key retrieval
expect(result.next()).andReturn(true);
expect(result.getInt(1)).andReturn(person.getId());
connection.close();
result.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
final Person newPerson = peopleDAO.add(person.getName(), person.getSurname());
assertThat(newPerson, is(equalsToPerson(person)));
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullName() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.add(null, newSurname());
}
@Test(expected = IllegalArgumentException.class)
public void testAddNullSurname() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.add(newName(), null);
}
@Test(expected = DAOException.class)
public void testAddZeroUpdatedRows() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(0);
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test(expected = DAOException.class)
public void testAddNoGeneratedKey() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andReturn(1);
expect(statement.getGeneratedKeys()).andReturn(result);
expect(result.next()).andReturn(false);
result.close();
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test(expected = DAOException.class)
public void testAddUnexpectedException() throws Exception {
reset(connection);
expect(connection.prepareStatement(anyString(), eq(1)))
.andReturn(statement);
expect(statement.executeUpdate()).andThrow(new SQLException());
connection.close();
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.add(newName(), newSurname());
}
@Test
public void testDelete() throws Exception {
expect(statement.executeUpdate()).andReturn(1);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test(expected = IllegalArgumentException.class)
public void testDeleteInvalidId() throws Exception {
expect(statement.executeUpdate()).andReturn(0);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test(expected = DAOException.class)
public void testDeleteUnexpectedException() throws Exception {
expect(statement.executeUpdate()).andThrow(new SQLException());
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.delete(existentId());
}
@Test
public void testModify() throws Exception {
expect(statement.executeUpdate()).andReturn(1);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
@Test(expected = IllegalArgumentException.class)
public void testModifyNullPerson() throws Exception {
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
resetAll(); // No expectations
peopleDAO.modify(null);
}
@Test(expected = IllegalArgumentException.class)
public void testModifyZeroUpdatedRows() throws Exception {
expect(statement.executeUpdate()).andReturn(0);
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
@Test(expected = DAOException.class)
public void testModifyUnexpectedException() throws Exception {
expect(statement.executeUpdate()).andThrow(new SQLException());
replayAll();
final PeopleDAO peopleDAO = new PeopleDAO();
peopleDAO.modify(existentPerson());
}
private void expectPersonRow(Person person) throws SQLException {
expect(result.next()).andReturn(true);
expect(result.getInt("id")).andReturn(person.getId());
expect(result.getString("name")).andReturn(person.getName());
expect(result.getString("surname")).andReturn(person.getSurname());
}
}
......@@ -2,7 +2,7 @@ package es.uvigo.esei.daa.entities;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
......
......@@ -2,13 +2,16 @@ package es.uvigo.esei.daa.filters;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;
......@@ -18,9 +21,18 @@ import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.UsersDAO;
import es.uvigo.esei.daa.entities.User;
/**
* This performs the Basic HTTP authentication following (almost) the same
* rules as the defined in the web.xml file.
*
* @author Miguel Reboiro Jato
*/
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthorizationFilter implements ContainerRequestFilter {
// Add here the list of REST paths that an administrator can access.
private final static List<String> ADMIN_PATHS = Arrays.asList("people");
private final UsersDAO dao;
public AuthorizationFilter() {
......@@ -46,7 +58,11 @@ public class AuthorizationFilter implements ContainerRequestFilter {
if (this.dao.checkLogin(userPass[0], userPass[1])) {
final User user = this.dao.get(userPass[0]);
requestContext.setSecurityContext(new UserSecurityContext(user));
if (isAdminPath(requestContext) && !user.getRole().equals("ADMIN")) {
requestContext.abortWith(createResponse());
} else {
requestContext.setSecurityContext(new UserSecurityContext(user));
}
} else {
requestContext.abortWith(createResponse());
}
......@@ -59,6 +75,17 @@ public class AuthorizationFilter implements ContainerRequestFilter {
}
}
private static boolean isAdminPath(ContainerRequestContext context) {
final List<PathSegment> pathSegments = context.getUriInfo().getPathSegments();
if (pathSegments.isEmpty()) {
return false;
} else {
final String path = pathSegments.get(0).getPath();
return ADMIN_PATHS.contains(path);
}
}
private static Response createResponse() {
return Response.status(Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"DAAExample\"")
......
package es.uvigo.esei.daa.rest;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasBadRequestStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasInternalServerErrorStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static java.util.Arrays.asList;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.List;
import javax.ws.rs.core.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import es.uvigo.esei.daa.dao.DAOException;
import es.uvigo.esei.daa.dao.PeopleDAO;
import es.uvigo.esei.daa.entities.Person;
public class PeopleResourceUnitTest {
private PeopleDAO daoMock;
private PeopleResource resource;
@Before
public void setUp() throws Exception {
daoMock = createMock(PeopleDAO.class);
resource = new PeopleResource(daoMock);
}
@After
public void tearDown() throws Exception {
try {
verify(daoMock);
} finally {
daoMock = null;
resource = null;
}
}
@Test
@SuppressWarnings("unchecked")
public void testList() throws Exception {
final List<Person> people = asList(people());
expect(daoMock.list()).andReturn(people);
replay(daoMock);
final Response response = resource.list();
assertThat(response, hasOkStatus());
assertThat((List<Person>) response.getEntity(), containsPeopleInAnyOrder(people()));
}
@Test
public void testListDAOException() throws Exception {
expect(daoMock.list()).andThrow(new DAOException());
replay(daoMock);
final Response response = resource.list();
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testGet() throws Exception {
final Person person = existentPerson();
expect(daoMock.get(person.getId())).andReturn(person);
replay(daoMock);
final Response response = resource.get(person.getId());
assertThat(response, hasOkStatus());
assertThat((Person) response.getEntity(), is(equalsToPerson(person)));
}
@Test
public void testGetDAOException() throws Exception {
expect(daoMock.get(anyInt())).andThrow(new DAOException());
replay(daoMock);
final Response response = resource.get(existentId());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testGetIllegalArgumentException() throws Exception {
expect(daoMock.get(anyInt())).andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.get(existentId());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testDelete() throws Exception {
daoMock.delete(anyInt());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasOkStatus());
}
@Test
public void testDeleteDAOException() throws Exception {
daoMock.delete(anyInt());
expectLastCall().andThrow(new DAOException());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testDeleteIllegalArgumentException() throws Exception {
daoMock.delete(anyInt());
expectLastCall().andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.delete(1);
assertThat(response, hasBadRequestStatus());
}
@Test
public void testModify() throws Exception {
final Person person = existentPerson();
person.setName(newName());
person.setSurname(newSurname());
daoMock.modify(person);
replay(daoMock);
final Response response = resource.modify(
person.getId(), person.getName(), person.getSurname());
assertThat(response, hasOkStatus());
assertEquals(person, response.getEntity());
}
@Test
public void testModifyDAOException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new DAOException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testModifyIllegalArgumentException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testModifyNullPointerException() throws Exception {
daoMock.modify(anyObject());
expectLastCall().andThrow(new NullPointerException());
replay(daoMock);
final Response response = resource.modify(existentId(), newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
@Test
public void testAdd() throws Exception {
expect(daoMock.add(newName(), newSurname()))
.andReturn(newPerson());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasOkStatus());
assertThat((Person) response.getEntity(), is(equalsToPerson(newPerson())));
}
@Test
public void testAddDAOException() throws Exception {
expect(daoMock.add(anyString(), anyString()))
.andThrow(new DAOException());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasInternalServerErrorStatus());
}
@Test
public void testAddIllegalArgumentException() throws Exception {
expect(daoMock.add(anyString(), anyString()))
.andThrow(new IllegalArgumentException());
replay(daoMock);
final Response response = resource.add(newName(), newSurname());
assertThat(response, hasBadRequestStatus());
}
}
......@@ -8,7 +8,7 @@ import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus;
import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized;
import static es.uvigo.esei.daa.matchers.IsEqualToUser.equalsToUser;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import java.io.IOException;
......@@ -17,13 +17,7 @@ import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.servlet.ServletContainer;
import org.glassfish.jersey.test.DeploymentContext;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.ServletDeploymentContext;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.glassfish.jersey.test.spi.TestContainerFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
......@@ -74,20 +68,6 @@ public class UsersResourceTest extends JerseyTest {
config.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
}
@Override
protected TestContainerFactory getTestContainerFactory() {
return new GrizzlyWebTestContainerFactory();
}
@Override
protected DeploymentContext configureDeployment() {
return ServletDeploymentContext.forServlet(
new ServletContainer(ResourceConfig.forApplication(configure()))
)
.servletPath("/rest")
.build();
}
@Test
public void testGetAdminOwnUser() throws IOException {
final String admin = adminLogin();
......
package es.uvigo.esei.daa.suites;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.web.PeopleWebTest;
@SuiteClasses({
PeopleWebTest.class
})
@RunWith(Suite.class)
public class AcceptanceTestSuite {
}
......@@ -4,12 +4,10 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.dao.PeopleDAOTest;
import es.uvigo.esei.daa.rest.PeopleResourceTest;
import es.uvigo.esei.daa.rest.UsersResourceTest;
@SuiteClasses({
PeopleDAOTest.class,
PeopleResourceTest.class,
UsersResourceTest.class
})
......
......@@ -4,14 +4,10 @@ import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
import es.uvigo.esei.daa.dao.PeopleDAOUnitTest;
import es.uvigo.esei.daa.entities.PersonUnitTest;
import es.uvigo.esei.daa.rest.PeopleResourceUnitTest;
@SuiteClasses({
PersonUnitTest.class,
PeopleDAOUnitTest.class,
PeopleResourceUnitTest.class
PersonUnitTest.class
})
@RunWith(Suite.class)
public class UnitTestSuite {
......
package es.uvigo.esei.daa.util;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.support.ui.ExpectedCondition;
/*
* Implementation based on https://stackoverflow.com/questions/33348600/selenium-wait-for-ajax-content-to-load-universal-approach
*/
public class AdditionalConditions {
public static ExpectedCondition<Boolean> jQueryAjaxCallsHaveCompleted() {
return driver ->
(Boolean) ((JavascriptExecutor) driver).executeScript("return (window.jQuery !== null) && (jQuery.active === 0)");
}
}
package es.uvigo.esei.daa.web;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentId;
import static es.uvigo.esei.daa.dataset.PeopleDataset.existentPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newName;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newPerson;
import static es.uvigo.esei.daa.dataset.PeopleDataset.newSurname;
import static es.uvigo.esei.daa.dataset.PeopleDataset.people;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.containsPeopleInAnyOrder;
import static es.uvigo.esei.daa.matchers.IsEqualToPerson.equalsToPerson;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import java.util.concurrent.TimeUnit;
import javax.sql.DataSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.html5.LocalStorage;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import es.uvigo.esei.daa.entities.Person;
import es.uvigo.esei.daa.listeners.ApplicationContextBinding;
import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener;
import es.uvigo.esei.daa.listeners.DbManagement;
import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener;
import es.uvigo.esei.daa.web.pages.MainPage;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:contexts/hsql-context.xml")
@TestExecutionListeners({
DbUnitTestExecutionListener.class,
DbManagementTestExecutionListener.class,
ApplicationContextJndiBindingTestExecutionListener.class
})
@ApplicationContextBinding(
jndiUrl = "java:/comp/env/jdbc/daaexample",
type = DataSource.class
)
@DbManagement(
create = "classpath:db/hsqldb.sql",
drop = "classpath:db/hsqldb-drop.sql"
)
@DatabaseSetup("/datasets/dataset.xml")
@ExpectedDatabase("/datasets/dataset.xml")
public class PeopleWebTest {
private static final int DEFAULT_WAIT_TIME = 1;
private WebDriver driver;
private MainPage mainPage;
@Before
public void setUp() throws Exception {
final String baseUrl = "http://localhost:9080/DAAExample/";
final FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("browser.privatebrowsing.autostart", true);
final FirefoxOptions options = new FirefoxOptions(DesiredCapabilities.firefox());
options.setProfile(profile);
final FirefoxDriver firefoxDriver;
driver = firefoxDriver = new FirefoxDriver();
driver.get(baseUrl);
// Driver will wait DEFAULT_WAIT_TIME if it doesn't find and element.
driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS);
driver.manage().window().maximize();
// Login as "admin:adminpass"
final LocalStorage localStorage = firefoxDriver.getLocalStorage();
localStorage.setItem("authorization-token", "YWRtaW46YWRtaW5wYXNz");
mainPage = new MainPage(driver, baseUrl);
mainPage.navigateTo();
}
@After
public void tearDown() throws Exception {
driver.quit();
driver = null;
mainPage = null;
}
@Test
public void testList() throws Exception {
assertThat(mainPage.listPeople(), containsPeopleInAnyOrder(people()));
}
@Test
@ExpectedDatabase("/datasets/dataset-add.xml")
public void testAdd() throws Exception {
final Person newPerson = mainPage.addPerson(newName(), newSurname());
assertThat(newPerson, is(equalsToPerson(newPerson())));
}
@Test
@ExpectedDatabase("/datasets/dataset-modify.xml")
public void testEdit() throws Exception {
final Person person = existentPerson();
person.setName(newName());
person.setSurname(newSurname());
mainPage.editPerson(person);
final Person webPerson = mainPage.getPerson(person.getId());
assertThat(webPerson, is(equalsToPerson(person)));
}
@Test
@ExpectedDatabase("/datasets/dataset-delete.xml")
public void testDelete() throws Exception {
mainPage.deletePerson(existentId());
assertFalse(mainPage.hasPerson(existentId()));
}
}
package es.uvigo.esei.daa.web.pages;
import static es.uvigo.esei.daa.util.AdditionalConditions.jQueryAjaxCallsHaveCompleted;
import static java.util.stream.Collectors.toList;
import static org.openqa.selenium.support.ui.ExpectedConditions.presenceOfElementLocated;
import static org.openqa.selenium.support.ui.ExpectedConditions.textToBePresentInElement;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import es.uvigo.esei.daa.entities.Person;
public class MainPage {
private static final String TABLE_ID = "people-list";
private static final String FORM_ID = "people-form";
private static final String ID_PREFIX = "person-";
private final WebDriver driver;
private final WebDriverWait wait;
private final String baseUrl;
public MainPage(WebDriver driver, String baseUrl) {
this.driver = driver;
this.baseUrl = baseUrl;
this.wait = new WebDriverWait(driver, 1);
}
public void navigateTo() {
this.driver.get(this.baseUrl + "main.html");
this.wait.until(presenceOfElementLocated(By.id("people-list")));
}
public int countPeople() {
return new PeopleTable(this.driver).countPeople();
}
public List<Person> listPeople() {
return new PeopleTable(this.driver).listPeople();
}
public Person getLastPerson() {
return new PeopleTable(this.driver).getPersonInLastRow();
}
public Person getPerson(int id) {
return new PeopleTable(this.driver).getPersonById(id);
}
public boolean hasPerson(int id) {
return new PeopleTable(this.driver).hasPerson(id);
}
public Person addPerson(String name, String surname) {
final PersonForm form = new PersonForm(this.driver);
form.clear();
form.setName(name);
form.setSurname(surname);
form.submit();
final PeopleTable table = new PeopleTable(driver);
return table.getPerson(name, surname);
}
public void editPerson(Person person) {
final PeopleTable table = new PeopleTable(this.driver);
table.editPerson(person.getId());
final PersonForm form = new PersonForm(this.driver);
form.setName(person.getName());
form.setSurname(person.getSurname());
form.submit();
}
public void deletePerson(int id) {
final PeopleTable table = new PeopleTable(this.driver);
table.deletePerson(id);
wait.until(jQueryAjaxCallsHaveCompleted());
}
private final static class PeopleTable {
private final WebDriver driver;
private final WebElement table;
public PeopleTable(WebDriver driver) {
this.driver = driver;
this.table = this.driver.findElement(By.id(TABLE_ID));
}
public boolean hasPerson(int id) {
try {
return this.getPersonRow(id) != null;
} catch (NoSuchElementException nsee) {
return false;
}
}
public void editPerson(int id) {
final WebElement personRow = this.getPersonRow(id);
personRow.findElement(By.className("edit")).click();
}
public void deletePerson(int id) {
final WebElement personRow = this.getPersonRow(id);
personRow.findElement(By.className("delete")).click();
this.acceptDialog();
}
public Person getPersonById(int id) {
return rowToPerson(getPersonRow(id));
}
public Person getPerson(String name, String surname) {
return rowToPerson(getPersonRow(name, surname));
}
public Person getPersonInLastRow() {
final WebElement row = this.table.findElement(By.cssSelector("tbody > tr:last-child"));
return rowToPerson(row);
}
private WebElement getPersonRow(int id) {
return this.table.findElement(By.id(ID_PREFIX + id));
}
public WebElement getPersonRow(String name, String surname) {
final List<WebElement> rows = table.findElements(By.cssSelector("tbody > tr"));
for (WebElement row : rows) {
final String rowName = row.findElement(By.className("name")).getText();
final String rowSurname = row.findElement(By.className("surname")).getText();
if (rowName.equals(name) && rowSurname.equals(surname)) {
return row;
}
}
throw new IllegalArgumentException(String.format("No row found with name '%s' and surname '%s'", name, surname));
}
public int countPeople() {
return getRows().size();
}
public List<Person> listPeople() {
return getRows().stream()
.map(this::rowToPerson)
.collect(toList());
}
private List<WebElement> getRows() {
final String xpathQuery = "//tbody/tr[starts-with(@id, '" + ID_PREFIX + "')]";
return this.table.findElements(By.xpath(xpathQuery));
}
private Person rowToPerson(WebElement row) {
return new Person(
Integer.parseInt(row.getAttribute("id").substring(ID_PREFIX.length())),
row.findElement(By.className("name")).getText(),
row.findElement(By.className("surname")).getText()
);
}
private void acceptDialog() {
driver.switchTo().alert().accept();
}
}
public final static class PersonForm {
private final WebDriverWait wait;
private final WebElement fieldName;
private final WebElement fieldSurname;
private final WebElement buttonClear;
private final WebElement buttonSubmit;
public PersonForm(WebDriver driver) {
this.wait = new WebDriverWait(driver, 1);
final WebElement form = driver.findElement(By.id(FORM_ID));
this.fieldName = form.findElement(By.name("name"));
this.fieldSurname = form.findElement(By.name("surname"));
this.buttonClear = form.findElement(By.id("btnClear"));
this.buttonSubmit = form.findElement(By.id("btnSubmit"));
}
public void submit() {
this.buttonSubmit.click();
this.waitForCleanFields();
}
public void clear() {
this.buttonClear.click();
this.waitForCleanFields();
}
public void setName(String name) {
this.fieldName.clear();
this.fieldName.sendKeys(name);
}
public void setSurname(String surname) {
this.fieldSurname.clear();
this.fieldSurname.sendKeys(surname);
}
public String getName() {
return this.fieldName.getText();
}
public String getSurname() {
return this.fieldSurname.getText();
}
private void waitForCleanFields() {
wait.until(textToBePresentInElement(fieldName, ""));
wait.until(textToBePresentInElement(fieldSurname, ""));
}
}
}
......@@ -138,7 +138,7 @@
<Realm className="org.apache.catalina.realm.JDBCRealm"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost/daaexample"
connectionURL="jdbc:mysql://localhost/daaexample?useSSL=false"
connectionName="daa"
connectionPassword="daa"
userTable="users" userNameCol="login" userCredCol="password"
......