diff --git a/README.md b/README.md index 65fe158696af045d81dd105c1a1b27c3bafa6bc4..1c62bdef76d8d310cf9fc8a1b90cb8377233d8d8 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,15 @@ Un ingrédient comporte uniquement un identifiant et un nom. Sa représentation JSON (I2) prendra donc la forme suivante : { - "id": "f38806a8-7c85-49ef-980c-149dcd81d306", - "name": "mozzarella" - } - + "id": "f38806a8-7c85-49ef-980c-149dcd81d306", + "name": "mozzarella" + } + Lors de la création, l'identifiant n'est pas connu car il sera fourni par le JavaBean qui représente un ingrédient. Aussi on aura une représentation JSON (I1) qui comporte uniquement le nom : - { "name": "mozzarella" } + { "name": "mozzarella" } ### Architecture logicielle de la solution @@ -95,7 +95,7 @@ objets doivent respecter un ensemble de conventions : - la classe est sérialisable - elle fournit au moins un constructeur vide - les attributs privés de la classe sont manipulables via des - méthodes publiques **get**_Attribut_ et **set**_Attribut + méthodes publiques **get**_Attribut_ et **set**_Attribut_ Les DTO et la classe `Ingredient`décrits dans la suite sont des JavaBeans. @@ -143,26 +143,26 @@ montre la mise en place de l'environnement (`configure()`) et l'amorce d'un premier test. ~~~java - public class IngredientResourceTest extends JerseyTest { - - @Override - protected Application configure() { - return new ApiV1(); - } - - @Test - public void testGetEmptyList() { - Response response = target("/ingredients").request().get(); - - // Vérification de la valeur de retour (200) - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); +public class IngredientResourceTest extends JerseyTest { + + @Override + protected Application configure() { + return new ApiV1(); + } + + @Test + public void testGetEmptyList() { + Response response = target("/ingredients").request().get(); + + // Vérification de la valeur de retour (200) + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - // Vérification de la valeur retournée (liste vide) - List<IngredientDto> ingredients; - ingredients = response.readEntity(new GenericType<List<IngredientDto>>(){}); + // Vérification de la valeur retournée (liste vide) + List<IngredientDto> ingredients; + ingredients = response.readEntity(new GenericType<List<IngredientDto>>(){}); - assertEquals(0, ingredients.size()); - } + assertEquals(0, ingredients.size()); + } ~~~ En héritant de JerseyTest, votre classe de test se comporte comme un @@ -183,26 +183,13 @@ Pour commencer, on se contentera de l'implémentation minimale suivante : ~~~java - package fr.ulille.iut.pizzaland.dto; - - public class IngredientDto { - - public IngredientDto() { - - } - } -~~~ - -A ce stade, vous pouvez lancer un premier test au moyen de la commande -`mvn test`. Évidemment, ce test va échouer. +package fr.ulille.iut.pizzaland.dto; -~~~ - $ mvn test - Results : - - Failed tests: testGetEmptyList(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<200> but was:<404> + public class IngredientDto { - Tests run: 1, Failures: 1, Errors: 0, Skipped: 0 + public IngredientDto() { + } +} ~~~ Pour réussir, ce premier test, nous allons mettre en place la @@ -212,32 +199,33 @@ Une première implémentation de la ressource pourra avoir la forme suivante : ~~~java - @Path("/ingredients") - public class IngredientResource { +@Path("/ingredients") +public class IngredientResource { - @Context - public UriInfo uriInfo; + @Context + public UriInfo uriInfo; - public IngredientResource() { - } + public IngredientResource() { + } - @GET - public List<IngredientDto> getAll() { + @GET + public List<IngredientDto> getAll() { - return new ArrayList<IngredientDto>(); - } + return new ArrayList<IngredientDto>(); + } } + ~~~ Avec cette première implémentation, on va pouvoir tester notre ressource : ~~~ - $ mvn test +$ mvn test - Results : +Results : - Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 ~~~ ### Récupérer un ingrédient existant @@ -246,19 +234,23 @@ ingrédient particulier à partir de son identifiant. Pour cela voici un premier test qui permettra de vérifier cela : ~~~java - import fr.ulille.iut.pizzaland.beans.Ingredient; +import fr.ulille.iut.pizzaland.beans.Ingredient; - @Test - public void testGetExistingIngredient() { - Ingredient ingredient = new Ingredient(); - ingredient.setName("mozzarella"); - - Response response = target("/ingredients/1").request().get(); - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + @Test + public void testGetExistingIngredient() { + + Ingredient ingredient = new Ingredient(); + ingredient.setName("Chorizo"); + dao.insert(ingredient); + + Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get(); + + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class)); - assertEquals(ingredient, result); + Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class)); + assertEquals(ingredient, result); } + } ~~~ Vous pourrez vérifier que le test échoue au moyen de la commande `mvn test` @@ -267,27 +259,27 @@ Afin de réussir ce test, nous devons compléter la classe IngredientDto avec les getter/setter correspondant aux propriétés de l'object JSON. ~~~java - public class IngredientDto { - private long id; - private String name; - - public IngredientDto() {} - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public void setName(String name) { - this.name = name; - } - - public String getName() { +public class IngredientDto { + private UUID id; + private String name; + + public IngredientDto() { + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getId() { + return id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { return name; - } } ~~~ @@ -299,10 +291,10 @@ Du côté de la ressource, on peut fournir une première implémentation : @GET @Path("{id}") - public IngredientDto getOneIngredient(@PathParam("id") long id) { + public IngredientDto getOneIngredient(@PathParam("id") UUID id) { Ingredient ingredient = new Ingredient(); - ingredient.setId(1); - ingredient.setName("mozzarella"); + ingredient.setId(id); + ingredient.setName("Chorizo"); return Ingredient.toDto(ingredient); } @@ -313,84 +305,72 @@ JavaBean représente un ingrédient manipulé par la ressource. Voici une implémentation pour cette classe : ~~~java - package fr.ulille.iut.pizzaland.beans; - - import fr.ulille.iut.pizzaland.dto.IngredientDto; - - public class Ingredient { - private long id; - private String name; - - public Ingredient() { - } +package fr.ulille.iut.pizzaland.beans; - public Ingredient(long id, String name) { +import java.util.UUID; + +import fr.ulille.iut.pizzaland.dto.IngredientDto; + +public class Ingredient { + private UUID id = UUID.randomUUID(); + private String name; + + public Ingredient() { + } + + public Ingredient(String name) { + this.name = name; + } + + public Ingredient(UUID id, String name) { this.id = id; this.name = name; - } - - public void setId(long id) { + } + + public void setId(UUID id) { this.id = id; - } + } - public long getId() { + public UUID getId() { return id; - } + } - public String getName() { + public String getName() { return name; - } + } - public void setName(String name) { + public void setName(String name) { this.name = name; - } + } - public static IngredientDto toDto(Ingredient i) { + public static IngredientDto toDto(Ingredient i) { IngredientDto dto = new IngredientDto(); dto.setId(i.getId()); dto.setName(i.getName()); return dto; - } - - public static Ingredient fromDto(IngredientDto dto) { + } + + public static Ingredient fromDto(IngredientDto dto) { Ingredient ingredient = new Ingredient(); ingredient.setId(dto.getId()); ingredient.setName(dto.getName()); return ingredient; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Ingredient other = (Ingredient) obj; - if (id != other.id) - return false; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - return true; - } - - @Override - public String toString() { + } + + @Override + public String toString() { return "Ingredient [id=" + id + ", name=" + name + "]"; - } - } + } +} + ~~~ Le test devrait maintenant réussir : ~~~ - $ mvn test +$ mvn test ~~~ ## Introduction de la persistence @@ -402,44 +382,50 @@ base de données. Pour cela nous allons devoir implémenter le DAO (Data Access Object) `IngredientDao` : ~~~java - package fr.ulille.iut.pizzaland.dao; +package fr.ulille.iut.pizzaland.dao; - import java.util.List; +import java.util.List; +import java.util.UUID; - import org.jdbi.v3.sqlobject.config.RegisterBeanMapper; - import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys; - import org.jdbi.v3.sqlobject.statement.SqlQuery; - import org.jdbi.v3.sqlobject.statement.SqlUpdate; +import org.jdbi.v3.sqlobject.config.RegisterBeanMapper; +import org.jdbi.v3.sqlobject.customizer.Bind; +import org.jdbi.v3.sqlobject.customizer.BindBean; +import org.jdbi.v3.sqlobject.statement.SqlQuery; +import org.jdbi.v3.sqlobject.statement.SqlUpdate; - import fr.ulille.iut.pizzaland.beans.Ingredient; +import fr.ulille.iut.pizzaland.beans.Ingredient; - public interface IngredientDao { +public interface IngredientDao { - @SqlUpdate("CREATE TABLE IF NOT EXISTS ingredients (id INTEGER PRIMARY KEY, name VARCHAR UNIQUE NOT NULL)") - void createTable(); + @SqlUpdate("CREATE TABLE IF NOT EXISTS ingredients (id VARCHAR(128) PRIMARY KEY, name VARCHAR UNIQUE NOT NULL)") + void createTable(); - @SqlUpdate("DROP TABLE IF EXISTS ingredients") - void dropTable(); + @SqlUpdate("DROP TABLE IF EXISTS ingredients") + void dropTable(); - @SqlUpdate("INSERT INTO ingredients (name) VALUES (:name)") - @GetGeneratedKeys - long insert(String name); + @SqlUpdate("INSERT INTO ingredients (id, name) VALUES (:id, :name)") + void insert(@BindBean Ingredient ingredient); - @SqlQuery("SELECT * FROM ingredients") - @RegisterBeanMapper(Ingredient.class) - List<Ingredient> getAll(); + @SqlUpdate("DELETE FROM ingredients WHERE id = :id") + void remove(@Bind("id") UUID id); - @SqlQuery("SELECT * FROM ingredients WHERE id = :id") - @RegisterBeanMapper(Ingredient.class) - Ingredient findById(long id); - } + @SqlQuery("SELECT * FROM ingredients WHERE name = :name") + @RegisterBeanMapper(Ingredient.class) + Ingredient findByName(@Bind("name") String name); + + @SqlQuery("SELECT * FROM ingredients") + @RegisterBeanMapper(Ingredient.class) + List<Ingredient> getAll(); + + @SqlQuery("SELECT * FROM ingredients WHERE id = :id") + @RegisterBeanMapper(Ingredient.class) + Ingredient findById(@Bind("id") UUID id); +} ~~~ JDBI fonctionne par annotations : - Les annotations `sqlUpdate` et `SqlQuery` correspondent à des requêtes SQL en modification ou non. - - `@GetGeneratedKeys` permet de renvoyer la clé primaire générée par - la base de données. - `@RegisterBeanMapper` permet d'associer une classe à un résultat (les champs de la table sont associés aux propriétés du bean). @@ -451,91 +437,89 @@ Nous allons utiliser le DAO pour insérer des données dans la table afin de réaliser nos tests. Nous utiliserons une base de données de tests qui est définie via la classe `BDDFactory`. -Les méthodes `setEnvUp` et `tearEnvDown` permettent de créer et +Dans la classe `IngredientResourceTest`Les méthodes `setEnvUp` et `tearEnvDown` permettent de créer et détruire la base de données entre chaque test. ~~~java - import fr.ulille.iut.pizzaland.dao.IngredientDao; +import fr.ulille.iut.pizzaland.dao.IngredientDao; - public class IngredientResourceTest extends JerseyTest { - private IngredientDao dao; +public class IngredientResourceTest extends JerseyTest { + private IngredientDao dao; - @Override - protected Application configure() { - BDDFactory.setJdbiForTests(); + @Override + protected Application configure() { + BDDFactory.setJdbiForTests(); - return new ApiV1(); - } + return new ApiV1(); + } - @Before - public void setEnvUp() { - dao = BDDFactory.buildDao(IngredientDao.class); - dao.createTable(); - } + @Before + public void setEnvUp() { + dao = BDDFactory.buildDao(IngredientDao.class); + dao.createTable(); + } - @After - public void tearEnvDown() throws Exception { - dao.dropTable(); - } + @After + public void tearEnvDown() throws Exception { + dao.dropTable(); + } - @Test - public void testGetExistingIngredient() { + @Test + public void testGetExistingIngredient() { + Ingredient ingredient = new Ingredient(); + ingredient.setName("Chorizo"); + dao.insert(ingredient); - Ingredient ingredient = new Ingredient(); - ingredient.setName("mozzarella"); + Response response = target("/ingredients").path(ingredient.getId().toString()).request(MediaType.APPLICATION_JSON).get(); - long id = dao.insert(ingredient.getName()); - ingredient.setId(id); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - Response response = target("/ingredients/" + id).request().get(); - - assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - - Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class)); - assertEquals(ingredient, result); - } + Ingredient result = Ingredient.fromDto(response.readEntity(IngredientDto.class)); + assertEquals(ingredient, result); +} ~~~ ### La ressource avec la base de données ~~~java - import fr.ulille.iut.pizzaland.BDDFactory; - import fr.ulille.iut.pizzaland.dao.IngredientDao; - - import java.util.stream.Collectors; - - import jakarta.ws.rs.WebApplicationException; - - public class IngredientResource { - private IngredientDao ingredients; - - public IngredientResource() { +@Produces("application/json") +@Path("/ingredients") +public class IngredientResource { + private static final Logger LOGGER = Logger.getLogger(IngredientResource.class.getName()); + + private IngredientDao ingredients; + + @Context + public UriInfo uriInfo; + + public IngredientResource() { ingredients = BDDFactory.buildDao(IngredientDao.class); ingredients.createTable(); - } - - @GET - public List<IngredientDto> getAll() { + } + + @GET + public List<IngredientDto> getAll() { LOGGER.info("IngredientResource:getAll"); List<IngredientDto> l = ingredients.getAll().stream().map(Ingredient::toDto).collect(Collectors.toList()); + LOGGER.info(l.toString()); return l; - } + } - @GET - @Path("{id}") - public IngredientDto getOneIngredient(@PathParam("id") long id) { + @GET + @Path("{id}") + @Produces({ "application/json", "application/xml" }) + public IngredientDto getOneIngredient(@PathParam("id") UUID id) { LOGGER.info("getOneIngredient(" + id + ")"); try { Ingredient ingredient = ingredients.findById(id); - return Ingredient.toDto(ingredient); - } - catch ( Exception e ) { - // Cette exception générera une réponse avec une erreur 404 + LOGGER.info(ingredient.toString()); + return Ingredient.toDto(ingredient); + } catch (Exception e) { throw new WebApplicationException(Response.Status.NOT_FOUND); } - } - } + } +} ~~~ ### Les tests fonctionnent avec la base de données @@ -543,11 +527,11 @@ Nous pouvons maintenant vérifier que la base fonctionne avec la base de données : ~~~ - $ mvn test +$ mvn test - Results : +Results : - Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 ~~~ ## Complétons maintenant les différents tests @@ -555,20 +539,20 @@ L'implémentation de la classe devrait fonctionner avec le test suivant : ~~~java - @Test - public void testGetNotExistingIngredient() { - Response response = target("/ingredients/125").request().get(); - assertEquals(Response.Status.NOT_FOUND.getStatusCode(),response.getStatus()); - } + @Test + public void testGetNotExistingIngredient() { + Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().get(); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(),response.getStatus()); + } ~~~ ~~~ - $ mvn test +$ mvn test - Results : +Results : - Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 ~~~ ### Implementation de la méthode POST @@ -577,40 +561,31 @@ ingrédients. Commençons par les différents tests : création, création de deux ingrédients identiques et création d'ingrédient sans nom. ~~~java - import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; +import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; - @Test + @Test public void testCreateIngredient() { IngredientCreateDto ingredientCreateDto = new IngredientCreateDto(); - ingredientCreateDto.setName("mozzarella"); + ingredientCreateDto.setName("Chorizo"); - Response response = target("/ingredients") - .request() - .post(Entity.json(ingredientCreateDto)); + Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto)); - // On vérifie le code de status à 201 assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); IngredientDto returnedEntity = response.readEntity(IngredientDto.class); - // On vérifie que le champ d'entête Location correspond à - // l'URI de la nouvelle entité - assertEquals(target("/ingredients/" + - returnedEntity.getId()).getUri(), response.getLocation()); - - // On vérifie que le nom correspond + assertEquals(target("/ingredients/" + returnedEntity.getId()).getUri(), response.getLocation()); assertEquals(returnedEntity.getName(), ingredientCreateDto.getName()); } - - @Test + + @Test public void testCreateSameIngredient() { - IngredientCreateDto ingredientCreateDto = new IngredientCreateDto(); - ingredientCreateDto.setName("mozzarella"); - dao.insert(ingredientCreateDto.getName()); + Ingredient ingredient = new Ingredient(); + ingredient.setName("Chorizo"); + dao.insert(ingredient); - Response response = target("/ingredients") - .request() - .post(Entity.json(ingredientCreateDto)); + IngredientCreateDto ingredientCreateDto = Ingredient.toCreateDto(ingredient); + Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto)); assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); } @@ -619,12 +594,11 @@ de deux ingrédients identiques et création d'ingrédient sans nom. public void testCreateIngredientWithoutName() { IngredientCreateDto ingredientCreateDto = new IngredientCreateDto(); - Response response = target("/ingredients") - .request() - .post(Entity.json(ingredientCreateDto)); + Response response = target("/ingredients").request().post(Entity.json(ingredientCreateDto)); assertEquals(Response.Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus()); } + ~~~ Nous utiliserons un DTO spécifique `IngredientCreateDto` dans la @@ -637,67 +611,65 @@ Nous devons également fournir une implémentation de `IngredientCreateDto` pour pouvoir compiler notre code : ~~~java - package fr.ulille.iut.pizzaland.dto; +package fr.ulille.iut.pizzaland.dto; - public class IngredientCreateDto { - private String name; +public class IngredientCreateDto { + private String name; - public IngredientCreateDto() {} + public IngredientCreateDto() {} - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getName() { - return name; - } + public String getName() { + return name; } +} ~~~ Nous pouvons maintenant compiler notre code de test et constater que ceux-ci échouent. ~~~ - $ mvn test +$ mvn test - Results : +Results : - Failed tests: testCreateSameIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<409> but was:<405> - testCreateIngredientWithoutName(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<406> but was:<405> - testCreateIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<201> but was:<405> +Failed tests: testCreateSameIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<409> but was:<405> + testCreateIngredientWithoutName(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<406> but was:<405> + testCreateIngredient(fr.ulille.iut.pizzaland.IngredientResourceTest): expected:<201> but was:<405> - Tests run: 6, Failures: 3, Errors: 0, Skipped: 0 +Tests run: 6, Failures: 3, Errors: 0, Skipped: 0 ~~~ Nous pouvons maintenant implémenter notre méthode POST dans la ressource : ~~~java - import jakarta.ws.rs.POST; - - import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; +import jakarta.ws.rs.POST; +import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; - @POST + @POST public Response createIngredient(IngredientCreateDto ingredientCreateDto) { Ingredient existing = ingredients.findByName(ingredientCreateDto.getName()); - if ( existing != null ) { + if (existing != null) { throw new WebApplicationException(Response.Status.CONFLICT); } - + try { Ingredient ingredient = Ingredient.fromIngredientCreateDto(ingredientCreateDto); - long id = ingredients.insert(ingredient.getName()); - ingredient.setId(id); + ingredients.insert(ingredient); IngredientDto ingredientDto = Ingredient.toDto(ingredient); - URI uri = uriInfo.getAbsolutePathBuilder().path("" + id).build(); + URI uri = uriInfo.getAbsolutePathBuilder().path(ingredient.getId().toString()).build(); return Response.created(uri).entity(ingredientDto).build(); - } - catch ( Exception e ) { + } catch (Exception e) { e.printStackTrace(); throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE); } + } ~~~ @@ -707,14 +679,14 @@ fourni, nous devont ajouter une méthode `findbyName` à notre DAO ~~~java @SqlQuery("SELECT * FROM ingredients WHERE name = :name") @RegisterBeanMapper(Ingredient.class) - Ingredient findByName(String name); + Ingredient findByName(@Bind("name") String name); ~~~ Nous avons également besoin de rajouter les méthodes de conversion pour ce DTO à notre bean `Ingredient` : ~~~java - import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; +import fr.ulille.iut.pizzaland.dto.IngredientCreateDto; public static IngredientCreateDto toCreateDto(Ingredient ingredient) { IngredientCreateDto dto = new IngredientCreateDto(); @@ -734,11 +706,11 @@ Nous avons également besoin de rajouter les méthodes de conversion Nous pouvons maintenant vérifier nos tests : ~~~ - $ mvn test +$ mvn test - Results : +Results : - Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 +Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 ~~~ Vous aurez peut-être un affichage d'exception liée au test de création @@ -748,39 +720,37 @@ exception qui a été traduite par un code d'erreur HTTP 406. ### Implémentation de la méthode DELETE Les tests liés à la méthode DELETE sont les suivants : -~~~ - @Test +~~~java + @Test public void testDeleteExistingIngredient() { Ingredient ingredient = new Ingredient(); - ingredient.setName("mozzarella"); - long id = dao.insert(ingredient.getName()); - ingredient.setId(id); + ingredient.setName("Chorizo"); + dao.insert(ingredient); - Response response = target("/ingredients/" + id).request().delete(); + Response response = target("/ingredients/").path(ingredient.getId().toString()).request().delete(); assertEquals(Response.Status.ACCEPTED.getStatusCode(), response.getStatus()); - - Ingredient result = dao.findById(id); - assertEquals(result, null); - } + + Ingredient result = dao.findById(ingredient.getId()); + assertEquals(result, null); + } @Test public void testDeleteNotExistingIngredient() { - Response response = target("/ingredients/125").request().delete(); - assertEquals(Response.Status.NOT_FOUND.getStatusCode(), - response.getStatus()); - } + Response response = target("/ingredients").path(UUID.randomUUID().toString()).request().delete(); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + } ~~~ Après avoir constaté que ces tests échouent, nous pouvons fournir une implémentation pour la méthode DELETE : ~~~java - import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.DELETE; @DELETE @Path("{id}") - public Response deleteIngredient(@PathParam("id") long id) { + public Response deleteIngredient(@PathParam("id") UUID id) { if ( ingredients.findById(id) == null ) { throw new WebApplicationException(Response.Status.NOT_FOUND); } @@ -795,8 +765,8 @@ Nous devons également implémenter la méthode remove dans `IngredientDao` : ~~~java - @SqlUpdate("DELETE FROM ingredients WHERE id = :id") - void remove(long id); + @SqlUpdate("DELETE FROM ingredients WHERE id = :id") + void remove(@Bind("id") UUID id); ~~~ Avec cette implémentation, nos tests réussissent. @@ -806,22 +776,22 @@ Commençons par les tests correspondant à cette URI (GET /ingredients/{id}/name) ~~~java - @Test + @Test public void testGetIngredientName() { Ingredient ingredient = new Ingredient(); - ingredient.setName("mozzarella"); - long id = dao.insert(ingredient.getName()); + ingredient.setName("Chorizo"); + dao.insert(ingredient); - Response response = target("ingredients/" + id + "/name").request().get(); + Response response = target("ingredients").path(ingredient.getId().toString()).path("name").request().get(); assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - assertEquals("mozzarella", response.readEntity(String.class)); + assertEquals("Chorizo", response.readEntity(String.class)); } @Test public void testGetNotExistingIngredientName() { - Response response = target("ingredients/125/name").request().get(); + Response response = target("ingredients").path(UUID.randomUUID().toString()).path("name").request().get(); assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); } @@ -830,15 +800,16 @@ Commençons par les tests correspondant à cette URI (GET L'implémentation correspondant à ce test est simple : ~~~java - @GET + @GET @Path("{id}/name") - public String getIngredientName(@PathParam("id") long id) { - Ingredient ingredient = ingredients.findById(id); - if ( ingredient == null ) { - throw new WebApplicationException(Response.Status.NOT_FOUND); - } - - return ingredient.getName(); + public String getIngredientName(@PathParam("id") UUID id) { + Ingredient ingredient = ingredients.findById(id); + + if (ingredient == null) { + throw new WebApplicationException(Response.Status.NOT_FOUND); + } + + return ingredient.getName(); } ~~~ @@ -850,24 +821,19 @@ formulaire Web. Dans ce cas, le type de représentation sera On peut déjà préparer un test pour cette méthode de création : ~~~java - import jakarta.ws.rs.core.MediaType; - import jakarta.ws.rs.core.Form; - - import static org.junit.Assert.assertNotNull; - - @Test + @Test public void testCreateWithForm() { Form form = new Form(); form.param("name", "chorizo"); Entity<Form> formEntity = Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE); Response response = target("ingredients").request().post(formEntity); - + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); String location = response.getHeaderString("Location"); - long id = Integer.parseInt(location.substring(location.lastIndexOf('/') + 1)); - Ingredient result = dao.findById(id); - + String id = location.substring(location.lastIndexOf('/') + 1); + Ingredient result = dao.findById(UUID.fromString(id)); + assertNotNull(result); } ~~~ @@ -875,27 +841,26 @@ On peut déjà préparer un test pour cette méthode de création : On peut maintenant fournir une implémentation pour cette méthode : ~~~java - @POST + @POST @Consumes("application/x-www-form-urlencoded") public Response createIngredient(@FormParam("name") String name) { Ingredient existing = ingredients.findByName(name); - if ( existing != null ) { + if (existing != null) { throw new WebApplicationException(Response.Status.CONFLICT); } - + try { Ingredient ingredient = new Ingredient(); ingredient.setName(name); - - long id = ingredients.insert(ingredient.getName()); - ingredient.setId(id); + + ingredients.insert(ingredient); + IngredientDto ingredientDto = Ingredient.toDto(ingredient); - URI uri = uriInfo.getAbsolutePathBuilder().path("" + id).build(); + URI uri = uriInfo.getAbsolutePathBuilder().path("" + ingredient.getId()).build(); return Response.created(uri).entity(ingredientDto).build(); - } - catch ( Exception e ) { + } catch (Exception e) { e.printStackTrace(); throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE); } @@ -913,68 +878,72 @@ Quand cette variable aura la valeur `withdb`, nous allons remplir la base au démarrage avec le code suivant : ~~~java - import fr.ulille.iut.pizzaland.beans.Ingredient; - import fr.ulille.iut.pizzaland.dao.IngredientDao; +package fr.ulille.iut.pizzaland; - import java.io.FileReader; - import java.io.IOException; - import java.nio.file.Paths; - import java.util.ArrayList; - import java.util.List; +import org.glassfish.jersey.server.ResourceConfig; - import jakarta.json.bind.Jsonb; - import jakarta.json.bind.JsonbBuilder; +import fr.ulille.iut.pizzaland.beans.Ingredient; +import fr.ulille.iut.pizzaland.beans.Pizza; +import fr.ulille.iut.pizzaland.dao.IngredientDao; +import fr.ulille.iut.pizzaland.dao.PizzaDao; +import fr.ulille.iut.pizzaland.dto.PizzaCreateDto; - @ApplicationPath("api/v1/") - public class ApiV1 extends ResourceConfig { - - public ApiV1() { - packages("fr.ulille.iut.pizzaland"); - +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.Logger; + +import jakarta.json.bind.JsonbBuilder; +import jakarta.ws.rs.ApplicationPath; + +@ApplicationPath("api/v1/") +public class ApiV1 extends ResourceConfig { + private static final Logger LOGGER = Logger.getLogger(ApiV1.class.getName()); + + public ApiV1() { + packages("fr.ulille.iut.pizzaland"); String environment = System.getenv("PIZZAENV"); - if ( environment != null && environment.equals("withdb") ) { - LOGGER.info("Loading with database"); - Jsonb jsonb = JsonbBuilder.create(); - try { - FileReader reader = new FileReader( getClass().getClassLoader().getResource("ingredients.json").getFile() ); - List<Ingredient> ingredients = JsonbBuilder.create().fromJson(reader, new ArrayList<Ingredient>(){}.getClass().getGenericSuperclass()); - - IngredientDao ingredientDao = BDDFactory.buildDao(IngredientDao.class); - ingredientDao.dropTable(); - ingredientDao.createTable(); - for ( Ingredient ingredient: ingredients) { - ingredientDao.insert(ingredient.getName()); + if ( environment != null && environment.equals("withdb") ) { + LOGGER.info("Loading with database"); + try { + FileReader reader = new FileReader( getClass().getClassLoader().getResource("ingredients.json").getFile() ); + List<Ingredient> ingredients = JsonbBuilder.create().fromJson(reader, new ArrayList<Ingredient>(){}.getClass().getGenericSuperclass()); + + IngredientDao ingredientDao = BDDFactory.buildDao(IngredientDao.class); + ingredientDao.dropTable(); + ingredientDao.createTable(); + for ( Ingredient ingredient: ingredients) { + ingredientDao.insert(ingredient); + } + } catch ( Exception ex ) { + throw new IllegalStateException(ex); } - } catch ( Exception ex ) { - throw new IllegalStateException(ex); - } } - } } +} ~~~ Dans un terminal, nous pouvons maintenant fixer la variable d'environnemnet et démarrer notre serveur REST au moyen de la -commande `mvn jetty:run` : +commande `mvn exec:java` : ~~~ - $ export PIZZAENV=withdb - $ mvn exec:java +$ export PIZZAENV=withdb +$ mvn exec:java ~~~ Dans un autre terminal, nous pouvons utiliser `curl` pour tester nos différentes méthodes : ~~~ - $ curl -i localhost:8080/api/v1/ingredients - - HTTP/1.1 200 OK - Date: Sun, 09 Feb 2020 22:08:05 GMT - Content-Type: application/json - Content-Length: 319 - Server: Jetty(9.4.26.v20200117) +$ curl -i localhost:8080/api/v1/ingredients + +HTTP/1.1 200 OK +Content-Type: application/json +Content-Length: 760 - [{"id":1,"name":"mozzarella"},{"id":2,"name":"jambon"},{"id":3,"name":"champignons"},{"id":4,"name":"olives"},{"id":5,"name":"tomate"},{"id":6,"name":"merguez"},{"id":7,"name":"lardons"},{"id":8,"name":"fromage"},{"id":9,"name":"oeuf"},{"id":10,"name":"poivrons"},{"id":11,"name":"ananas"},{"id":12,"name":"reblochon"}] +[{"id":"f38806a8-7c85-49ef-980c-149dcd81d306","name":"mozzarella"},{"id":"d36903e1-0cc0-4bd6-a0ed-e0e9bf7b4037","name":"jambon"},{"id":"bc5b315f-442f-4ee4-96de-486d48f20c2f","name":"champignons"},{"id":"6a04320c-3a4f-4570-96d3-61faf3f898b0","name":"olives"},{"id":"c77deeee-d50d-49d5-9695-c98ec811f762","name":"tomate"},{"id":"c9375542-8142-43f6-b54d-0d63597cf614","name":"merguez"},{"id":"dee27dd6-f9b6-4d03-ac4b-216b5c9c8bd7","name":"lardons"},{"id":"657f8dd4-6bc1-4622-9af7-37d248846a23","name":"fromage"},{"id":"070d8077-a713-49a0-af37-3936b63d5ff2","name":"oeuf"},{"id":"5d9ca5c4-517f-40fd-aac3-5a823d680c1d","name":"poivrons"},{"id":"52f68024-24ec-46c0-8e77-c499dba1e27e","name":"ananas"},{"id":"dfdf6fae-f1b2-45fa-8c39-54e522c1933f","name":"reblochon"}] ~~~ # Implémentation de la ressource Pizza diff --git a/src/main/java/fr/ulille/iut/pizzaland/BDDFactory.java b/src/main/java/fr/ulille/iut/pizzaland/BDDFactory.java index b9fc9bb6f8bfc5fee4b0c78e43adfef6afa0cd0a..e2a3ed29c8e95bd4e38021d8c7d0bb4275313da1 100644 --- a/src/main/java/fr/ulille/iut/pizzaland/BDDFactory.java +++ b/src/main/java/fr/ulille/iut/pizzaland/BDDFactory.java @@ -3,28 +3,37 @@ package fr.ulille.iut.pizzaland; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; +import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.sqlite3.SQLitePlugin; import org.jdbi.v3.sqlobject.SqlObjectPlugin; +import fr.ulille.iut.pizzaland.dao.UUIDArgumentFactory; + public class BDDFactory { private static Jdbi jdbi = null; - private static String dbPath = "jdbc:sqlite:" + System.getProperty("java.io.tmpdir") - + System.getProperty("file.separator") + System.getProperty("user.name") + "_"; + private static String dbPath = "jdbc:sqlite:" + + System.getProperty("java.io.tmpdir") + + System.getProperty("file.separator") + + System.getProperty("user.name") + + "_"; public static Jdbi getJdbi() { - if (jdbi == null) { - jdbi = Jdbi.create(dbPath + "pizza.db").installPlugin(new SQLitePlugin()) - .installPlugin(new SqlObjectPlugin()); + if ( jdbi == null ) { + jdbi = Jdbi.create(dbPath + "pizzas.db") + .installPlugin(new SQLitePlugin()) + .installPlugin(new SqlObjectPlugin()).registerArgument(new UUIDArgumentFactory()); } return jdbi; } public static void setJdbiForTests() { - if (jdbi == null) { - jdbi = Jdbi.create(dbPath + "pizza_test.db").installPlugin(new SQLitePlugin()) - .installPlugin(new SqlObjectPlugin()); + if ( jdbi == null ) { + jdbi = Jdbi.create(dbPath + "pizza_test.db") + .installPlugin(new SQLitePlugin()) + .installPlugin(new SqlObjectPlugin()); } } @@ -36,6 +45,17 @@ public class BDDFactory { return exist; } + public static void dropTables() throws SQLException { + Handle handle = getJdbi().open(); + DatabaseMetaData dbm = handle.getConnection().getMetaData(); + ResultSet tables = dbm.getTables(null, null, "", null); + ArrayList<String> tableNames = new ArrayList<String>(); + while ( tables.next() ) { + tableNames.add(tables.getString("TABLE_NAME")); + } + tableNames.forEach(name -> handle.execute("drop table " + name)); + } + public static <T> T buildDao(Class<T> daoClass) { return getJdbi().onDemand(daoClass); } diff --git a/src/main/java/fr/ulille/iut/pizzaland/dao/UUIDArgumentFactory.java b/src/main/java/fr/ulille/iut/pizzaland/dao/UUIDArgumentFactory.java index 1382b412235d1bfe74f561edcb576b8f7753ba18..b8eedcd17e435d206d58bfa1aba7d4404cdfc4ba 100644 --- a/src/main/java/fr/ulille/iut/pizzaland/dao/UUIDArgumentFactory.java +++ b/src/main/java/fr/ulille/iut/pizzaland/dao/UUIDArgumentFactory.java @@ -8,7 +8,7 @@ import org.jdbi.v3.core.argument.Argument; import org.jdbi.v3.core.config.ConfigRegistry; public class UUIDArgumentFactory extends AbstractArgumentFactory<UUID> { - UUIDArgumentFactory() { + public UUIDArgumentFactory() { super(Types.VARCHAR); } diff --git a/src/main/java/fr/ulille/iut/pizzaland/resources/IngredientResource.java b/src/main/java/fr/ulille/iut/pizzaland/resources/IngredientResource.java index b898d7e88abf2db04f0d535bdb314d135cc07c32..8121cada0c6bbb0ebcfb18cf8e0b8d4125e97233 100644 --- a/src/main/java/fr/ulille/iut/pizzaland/resources/IngredientResource.java +++ b/src/main/java/fr/ulille/iut/pizzaland/resources/IngredientResource.java @@ -27,6 +27,6 @@ public class IngredientResource { public List<IngredientDto> getAll() { LOGGER.info("IngredientResource:getAll"); - return null; + return new ArrayList<IngredientDto>(); } } diff --git a/src/test/java/fr/ulille/iut/pizzaland/IngredientResourceTest.java b/src/test/java/fr/ulille/iut/pizzaland/IngredientResourceTest.java index 4a552e3a87c6184acc54a533fb4354350e58d64c..949a8ce9b1083fc2dea586eedd8ea8b1ba3a3c42 100644 --- a/src/test/java/fr/ulille/iut/pizzaland/IngredientResourceTest.java +++ b/src/test/java/fr/ulille/iut/pizzaland/IngredientResourceTest.java @@ -59,7 +59,7 @@ public class IngredientResourceTest extends JerseyTest { // On vérifie la valeur retournée (liste vide) // L'entité (readEntity() correspond au corps de la réponse HTTP. // La classe jakarta.ws.rs.core.GenericType<T> permet de définir le type - // de la réponse lue quand on a un type complexe (typiquement une liste). + // de la réponse lue quand on a un type paramétré (typiquement une liste). List<IngredientDto> ingredients; ingredients = response.readEntity(new GenericType<List<IngredientDto>>() { });