diff --git a/bin/.gitignore b/bin/.gitignore index 7eed456..7ac70cd 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -1,2 +1,3 @@ /main/ /test/ +/default/ diff --git a/build.gradle b/build.gradle index 5895c7b..9d18a06 100644 --- a/build.gradle +++ b/build.gradle @@ -5,23 +5,34 @@ * For more details take a look at the Java Libraries chapter in the Gradle * User Manual available at https://docs.gradle.org/6.3/userguide/java_library_plugin.html */ - plugins { // Apply the java-library plugin to add support for Java Library id 'java-library' id 'java' + //según https://github.com/junit-team/junit5-samples/blob/main/junit5-jupiter-starter-gradle/build.gradle + id 'eclipse' + //para poder publicar paquetes en github + id 'maven-publish' } +version = '0.0' + tasks.withType(JavaCompile) { //Añadir la opción Xlint options.deprecation = true options.encoding = 'ISO-8859-1' } -tasks.withType(Javadoc) { +tasks.withType(Javadoc){ + + description = "Genera la documentación" //indicar que la codificación es ISO options.encoding = 'ISO-8859-1' options.charSet = 'ISO-8859-1' options.author = true options.version = true + options.use = true + options.memberLevel = JavadocMemberLevel.PROTECTED + options.footer = "MIT-FS: Curso 2020/21" + title = "API ShopManager" } repositories { @@ -41,6 +52,51 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:28.2-jre' - // Use JUnit test framework - testImplementation 'junit:junit:4.12' + // Use JUnit5 test framework + + //para usar la nueva plataforma de pruebas unitarias de java (jupiter) + testImplementation(platform('org.junit:junit-bom:5.7.0')) + testImplementation('org.junit.jupiter:junit-jupiter') + //Añado para usar mockito + testCompile "org.mockito:mockito-core:2.+" + testCompile "org.mockito:mockito-junit-jupiter:2.+" +} + + //según https://github.com/junit-team/junit5-samples/blob/main/junit5-jupiter-starter-gradle/build.gradle + //para usar la nueva plataforma de pruebas unitarias de java (jupiter) + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } } + +//Para publicar paquetes en github +//group = 'shopmanager' +publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/mit-fs/shopmanager") + credentials { + //las propiedades gpr.user y gpr.key están configuradas en gradle.properties en el raiz del proyecto, y se añade a .gitignore para que no se suban + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_USERNAME") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + } + publications { + gpr(MavenPublication) { + //Del tutorial https://docs.gradle.org/current/userguide/publishing_maven.html#publishing_maven + + groupId = 'mitfs.samples' + artifactId = 'shopmanager' + version = '0.0' + + from components.java + } + + } +} + diff --git a/src/main/java/exceptions/NoEnoughStock.java b/src/main/java/exceptions/NoEnoughStock.java new file mode 100644 index 0000000..a1b6fb5 --- /dev/null +++ b/src/main/java/exceptions/NoEnoughStock.java @@ -0,0 +1,27 @@ +/** + * excepción devuelta cuando se intenta sacar del stock una cantidad que no existe + */ +package exceptions; + +/** + * @author Isabel Román + */ +public class NoEnoughStock extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + private int unidades; + /** + * + * @param unidades el número de unidades que quedan en el stock + */ + public NoEnoughStock(int unidades){ + this.unidades=unidades; + } + @Override + public String getMessage(){ + return "No hay suficientes unidades en el Stock, sólo quedan "+unidades; + } +} diff --git a/src/main/java/exceptions/NotEmpty.java b/src/main/java/exceptions/NotEmpty.java new file mode 100644 index 0000000..9d8206b --- /dev/null +++ b/src/main/java/exceptions/NotEmpty.java @@ -0,0 +1,21 @@ +/** + * excepción devuelta cuando se intenta sacar del stock una cantidad que no existe + */ +package exceptions; + +/** + * @author Isabel Román + */ +public class NotEmpty extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + + @Override + public String getMessage(){ + return "El mapa no está vacío"; + } +} diff --git a/src/main/java/exceptions/NotInBag.java b/src/main/java/exceptions/NotInBag.java new file mode 100644 index 0000000..8bf1ed3 --- /dev/null +++ b/src/main/java/exceptions/NotInBag.java @@ -0,0 +1,25 @@ +/** + * Excepción devuelta si se intenta eliminar o modificar un producto que no está en la cesta + */ +package exceptions; + +/** + * @author Isabel Román + * + */ +public class NotInBag extends Exception { + /** + * + */ + private static final long serialVersionUID = 1L; + private String id; + public NotInBag(String productId){ + this.id=productId; + } + + @Override + public String getMessage() { + return "El producto con id "+id+" no existe en la cesta"; + } + +} diff --git a/src/main/java/exceptions/NotInStock.java b/src/main/java/exceptions/NotInStock.java new file mode 100644 index 0000000..acceae9 --- /dev/null +++ b/src/main/java/exceptions/NotInStock.java @@ -0,0 +1,27 @@ +/** + * Se lanza cuando se intenta modificar la cantidad de un producto que no está en el stock previamente + */ +package exceptions; + +/** + * @author Isabel Román + */ +public class NotInStock extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + private String id; + + public NotInStock(String productId){ + this.id=productId; + } + + @Override + public String getMessage() { + return "El producto con id "+id+" no existe en el Stock"; + } + + +} diff --git a/src/main/java/exceptions/UnknownRepo.java b/src/main/java/exceptions/UnknownRepo.java new file mode 100644 index 0000000..d4cb3bb --- /dev/null +++ b/src/main/java/exceptions/UnknownRepo.java @@ -0,0 +1,22 @@ +/** + * excepción devuelta cuando se intenta sacar del stock una cantidad que no existe + */ +package exceptions; + +/** + * @author Isabel Román + * + */ +public class UnknownRepo extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + + @Override + public String getMessage(){ + return "Debe establecer previamente el repositorio"; + } +} diff --git a/src/main/java/exceptions/package-info.java b/src/main/java/exceptions/package-info.java new file mode 100644 index 0000000..f6c97a9 --- /dev/null +++ b/src/main/java/exceptions/package-info.java @@ -0,0 +1,7 @@ +/** + * Este paquete contiene las excepciones propietarias de la librería + * + * @author Isabel Román + * @version 0.0 + */ +package exceptions; diff --git a/src/main/java/model/MyOrder.java b/src/main/java/model/MyOrder.java new file mode 100644 index 0000000..fb0e44c --- /dev/null +++ b/src/main/java/model/MyOrder.java @@ -0,0 +1,63 @@ +/** + * + */ +package model; + +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Isabel Román + */ +public class MyOrder implements Order { + + /** + * + */ + public MyOrder() { + // TODO Auto-generated constructor stub + } + + @Override + public void setId(String id) { + // TODO Auto-generated method stub + + } + + @Override + public String getId() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setProducts(Iterator products) { + // TODO Auto-generated method stub + + } + + @Override + public void setProducts(Collection products) { + // TODO Auto-generated method stub + + } + + @Override + public Iterator getProductsById() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterator getProductsByPrice() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterator getProductsByUnits() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/shopManager/MyProduct.java b/src/main/java/model/MyProduct.java similarity index 70% rename from src/main/java/shopManager/MyProduct.java rename to src/main/java/model/MyProduct.java index 3a7b995..595c010 100644 --- a/src/main/java/shopManager/MyProduct.java +++ b/src/main/java/model/MyProduct.java @@ -1,76 +1,90 @@ -package shopManager; - -import java.io.Serializable; - -/** - * Producto es la clase que representa todos los productos de la tienda * - * el id (string) se usará como clave del mapa hazelcast * - */ - - -import java.util.logging.*; -/** - * @author Isabel Román - * @version 0.0 - * Primera versión de la clase MyProduct que implementa la interfaz Product y Serializable, porque debe guardarse en un mapa hazelcast - * - */ - -public class MyProduct implements Product,Serializable{ - - private static final long serialVersionUID = 1L; - private static Logger trazador=Logger.getLogger(MyProduct.class.getName()); - - //identificador del producto - private String id; - private int number; - - MyProduct(String id) { - this.id=id; - number=1; - } - - MyProduct(String id,int number) { - this.id=id; - this.number=number; - } - @Override - public void setId(String id) { - trazador.info("Estableciendo id a "+id); - this.id=id; - } - @Override - public String getId() { - trazador.info("Devolviendo id como "+id); - return this.id; - } - @Override - public void setNumber(int number) { - this.number=number; - } - @Override - public int getNumber() { - return number; - } - @Override - public int oneMore() { - number++; - return number; - } - @Override - public int oneLess(){ - if (number!= 0){ - number--; - trazador.info("El número de unidades del producto se reduce uno"); - } else { - trazador.info("El número de unidades del producto no se puede reducir porque es cero"); - } - trazador.info(this.toString()); - return number; - } - - @Override - public String toString() { - return (number+" unidades del producto con id "+id); - } -} +package model; + +import java.io.Serializable; + +/** + * Producto es la clase que representa todos los productos de la tienda * + * el id (string) se usará como clave del mapa hazelcast * + */ + + +import java.util.logging.*; +/** + * @author Isabel Román + * @version 0.0 + * Primera versión de la clase MyProduct que implementa la interfaz Product y Serializable, porque debe guardarse en un mapa hazelcast + * + */ + +public class MyProduct implements Product,Serializable{ + + /** + * Obligatorio en objetos serializables, debe poder identificarse en el destino la clase exacta con la que se creó + */ + private static final long serialVersionUID = 1L; + /** + * Objeto tipo logger para gestionar los mensajes durante la ejecución + */ + private static Logger trazador=Logger.getLogger(MyProduct.class.getName()); + + /** + * Identificador del producto + */ + private String id; + /** + * Número de unidades del producto + */ + private int number; + + public MyProduct(String id) { + this.id=id; + number=1; + } + + public MyProduct(String id,int number) { + this.id=id; + this.number=number; + } + @Override + public void setId(String id) { + String msg="Estableciendo id a "+id; + trazador.info(msg); + this.id=id; + } + @Override + public String getId() { + String msg="Devolviendo el id como "+id; + trazador.info(msg); + return this.id; + } + @Override + public void setNumber(int number) { + this.number=number; + } + @Override + public int getNumber() { + return number; + } + @Override + public int oneMore() { + number++; + return number; + } + @Override + public int oneLess(){ + if (number!= 0){ + number--; + trazador.info("El número de unidades del producto se reduce uno"); + } else { + trazador.info("El número de unidades del producto no se puede reducir porque es cero"); + } + String msg=toString(); + trazador.info(msg); + return number; + } + + @Override + public String toString() { + return (number+" unidades del producto con id "+id); + } +} diff --git a/src/main/java/model/Order.java b/src/main/java/model/Order.java new file mode 100644 index 0000000..a5011d1 --- /dev/null +++ b/src/main/java/model/Order.java @@ -0,0 +1,48 @@ +package model; + +import java.util.Iterator; + +import java.util.Collection; + +public interface Order { + + + /** + * Pone al pedido un identificador + * + * @param id del pedido + */ + void setId(String id); + /** + * Recupera el identificador del pedido + * @return identificador + */ + String getId(); + /** + * Establece los productos incluidos en el pedido, que se pasan como un iterador de productos + * @param products productos del pedido + */ + void setProducts(Iterator products); + /** + * Establece los productos incluidos en el pedido, que se pasan como una colección de productos + * @param products la colección de productos que incluye el pedido + */ + void setProducts(Collection products); + /** + * Obtiene un iterador que recorre los productos en orden alfabético según su id + * @return iterador por identificador + */ + Iterator getProductsById(); + /** + * Obtiene un iterador que recorre los productos en orden de más caro a más barato + * @return iterador por precio (de mayor a menor) + */ + Iterator getProductsByPrice(); + /** + * Obtiene un iterador que recorre los productos ordenados por el número de unidades, desde el producto con más unidades al que tiene menos + * @return iterador por unidades (de mayor a menor) + */ + Iterator getProductsByUnits(); + + +} diff --git a/src/main/java/shopManager/Product.java b/src/main/java/model/Product.java similarity index 55% rename from src/main/java/shopManager/Product.java rename to src/main/java/model/Product.java index 74ab7ff..b160a69 100644 --- a/src/main/java/shopManager/Product.java +++ b/src/main/java/model/Product.java @@ -1,26 +1,41 @@ -package shopManager; -/** - * - * @author Isabel Román - * @version 0.0. - * Primera versión de la interfaz Product, los elementos que vende nuestra tienda - * - */ - -public interface Product { - - public void setId(String id); - public String getId(); - public void setNumber(int number); - public int getNumber(); - /** - * Suma uno - * @return int new value - */ - public int oneMore(); - /** - * Resta 1 - * @return int new value - */ - public int oneLess(); +package model; +/** + * + * @author Isabel Román + * @version 0.0. + * Primera versión de la interfaz Product, los elementos que vende nuestra tienda + * + */ + +public interface Product{ + + /** + * @param id identificador que se le quiere poner al producto + */ + public void setId(String id); + /** + * + * @return devuelve el identificador del producto + */ + public String getId(); + /** + * + * @param number número de unidades del producto + */ + public void setNumber(int number); + /** + * + * @return devuelve el número de unidades del producto + */ + public int getNumber(); + /** + * Suma uno + * @return int new value + */ + public int oneMore(); + /** + * Resta 1 + * @return int new value + */ + public int oneLess(); } \ No newline at end of file diff --git a/src/main/java/persistency/CrudRepository.java b/src/main/java/persistency/CrudRepository.java new file mode 100644 index 0000000..0be5614 --- /dev/null +++ b/src/main/java/persistency/CrudRepository.java @@ -0,0 +1,90 @@ +/** + * Esta clase están "inspirada" en https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html + * Interfaz para manejar la persistencia de entidades de tipo T identificadas con un identificdor de tipo ID + */ +package persistency; +import java.util.Optional; +/** + * @author Isabel Román + * + */ +public interface CrudRepository { + /** + * + * Elimina la entidad pasada como parámetro + * @param entity la entidad que se quiere eliminar + * @throws IllegalArgumentException si la entidad es nula + */ + void delete(T entity)throws IllegalArgumentException; + /** + * Devuelve el número de entidades manejadas por este repositorio + * @return número de entidades + */ + long count(); + /** + * Elimina las entidades que se pasan, de clase T o alguna subclase, de la base de datos, el argumento no puede ser nulo ni contener elementos nulos + * @param entities un iterador con el conjunto de entidades a borrar, de tipo T o alguna subclase + * @throws IllegalArgumentException si el argumento, o alguna de sus entidades, es nulo + */ + void deleteAll(Iterable entities)throws IllegalArgumentException; + /** + * Elimina todas las entidades que maneja este repositorio + */ + void deleteAll(); + /** + * + * Borra la entidad que se corresponde al identificador pasado como parámetro + * @param id Identificador de la entidad de tipo T a borrar + * @throws IllegalArgumentException si el id es nulo + */ + void deleteById(ID id)throws IllegalArgumentException; + /** + * + * Verfica si existe una entidad con el id indicado en el parámetro + * @param id verifica si existe la entidad con identificador id + * @return true si existe /false si no existe + * @throws IllegalArgumentException si el id es nulo + */ + boolean existsByID(ID id) throws IllegalArgumentException; + /** + * Devuelve todas las entidades manejadas por este repositorio + * @return devuelve un iterador con todos las entidades de tipo T de la base de datos + */ + Iterable findAll(); + /** + * + * Devuelve el conjunto de entidades manejadas por este repositorio que corresponden al conjunto de identificadores pasados como parámetros + * @param id identificador de las entidades buscadas + * @return devbuelve un iterador con todas las entidades guardadas que tengan ese identificador + * @throws IllegalArgumentException si alguno de los id es nulo (o el iterador) + */ + Iterable findAllById(Iterable id) throws IllegalArgumentException; + + /** + * + * Devuelve la entidad con el identificador pasado como parámetro + * @param id identificador de la entidad buscada + * @return devuelve un objeto tipo Optional. Si Optional.isPresent()=true Optional.get() devuelve la entidad + * @throws IllegalArgumentException si el id es nulo + */ + Optional findById(ID id) throws IllegalArgumentException; + /** + * + * Persiste la entidad que se pasa como parámetro + * @param entity entidad a guardar + * @param la entidad puede ser de clase T o cualquier subclase + * @return devuelve la entidad guardada, porque al guardar puede haber hecho cambios + * @throws IllegalArgumentException si alguna de las entidades es null + */ + S save(S entity) throws IllegalArgumentException; + /** + * + * Persiste las entidades que se pasan como parámetro, en un iterador + * @param los elementos del iterador deben ser de tipo T o alguna subclase + * @param entities Iterador de las entidades a guardar (podrá ser de clase T o cualquier subclase) + * @return devuelve el iterador de las entidades guardadas, porque al guardar puede haber hecho cambios + * @throws IllegalArgumentException si alguna de las entidades es null + */ + + Iterable saveAll(Iterable entities) throws IllegalArgumentException; +} diff --git a/src/main/java/persistency/OrderRepository.java b/src/main/java/persistency/OrderRepository.java new file mode 100644 index 0000000..8d0bb2c --- /dev/null +++ b/src/main/java/persistency/OrderRepository.java @@ -0,0 +1,12 @@ +/** + * + */ +package persistency; +import model.Order; + +/** + * @author Isabel Román + */ +public interface OrderRepository extends CrudRepository { + +} diff --git a/src/main/java/persistency/ProductRepository.java b/src/main/java/persistency/ProductRepository.java new file mode 100644 index 0000000..e75b0b1 --- /dev/null +++ b/src/main/java/persistency/ProductRepository.java @@ -0,0 +1,13 @@ +/** + * + */ +package persistency; +import model.Product; + +/** + * @author Isabel Román + * + */ +public interface ProductRepository extends CrudRepository { + +} diff --git a/src/main/java/persistency/package-info.java b/src/main/java/persistency/package-info.java new file mode 100644 index 0000000..79e814d --- /dev/null +++ b/src/main/java/persistency/package-info.java @@ -0,0 +1,7 @@ +/** + * Este paquete contiene lo necesario para gestionar la persistencia de las entidades + * + * @author Isabel Román + * @version 0.0 + */ +package persistency; diff --git a/src/main/java/shopManager/MyStockManager.java b/src/main/java/shopManager/MyStockManager.java deleted file mode 100644 index 1e6b2ef..0000000 --- a/src/main/java/shopManager/MyStockManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package shopManager; -import com.hazelcast.core.*; -import com.hazelcast.config.*; - -import java.util.logging.*; -//va a seguir el patrón singleton (Una instancia sólo tiene un manager, pero puede haber varias instancias...) - -/** - * @author Isabel Román - * @version 0.0. - * Primera versión de MyStockManager, clase que implementa StockManager usando el patrón singleton - * - */ -public class MyStockManager implements StockManager{ - - private static Logger trazador=Logger.getLogger(MyStockManager.class.getName()); - private static StockManager my_instance; - private HazelcastInstance hz_instance; - private Config cfg; - //Mapa de productos que será compartido por todos los stockManager que se instancien - private IMap mapProducts; - /** - * El constructor crea la instancia hazelcast, pero es privado, sólo la propia clase puede crear una instancia (un objeto) - */ - private MyStockManager(){ - cfg = new Config(); - - HazelcastInstance hz_instance = Hazelcast.newHazelcastInstance(cfg); - trazador.info("Acabo de crear una instancia hazelcast para el MyStockManager"); - mapProducts = hz_instance.getMap("products"); - trazador.info("Se ha recuperado referencia al mapa products, si no se había usado se ha creado"); - } - /** - * @see - * java.lang.Object - * El compilador me avisa de que uso algo "deprecated" por eso añado la opción de compilación en gradle, para ver más detalle - */ - @Override - protected void finalize() throws Throwable{ - trazador.info("Finalizando el MyStockManager, borra la instancia hazelcast y deja a null la instancia singleton"); - hz_instance.shutdown(); - my_instance=null; - hz_instance=null; - - } - - public static StockManager getInstance() { - if (my_instance==null){ - my_instance=new MyStockManager(); - } - return my_instance; - } - @Override - public void addProduct(Product newProduct) { - Product productTmp; - trazador.info("El tamaño del mapa al entrar es " + mapProducts.size()); - if(mapProducts.containsKey(newProduct.getId())){ - productTmp=searchProduct(newProduct.getId()); - newProduct.setNumber(productTmp.getNumber()+newProduct.getNumber()); - } - mapProducts.put(newProduct.getId(), newProduct); - trazador.info("El mapa de tamaño " + mapProducts.size()+" incluye "+newProduct); - } - @Override - public Product searchProduct(String id) { - Product product=null; - if(mapProducts.containsKey(id)){ - product=mapProducts.get(id); - trazador.info("Hay "+ product); - - }else { - trazador.info("El "+product+" no está en el mapa"); - } - return product; - } - /** - * @see - * java.lang.Object - * El método clone permite crear una instancia a partir de otra, lo que en una clase que siga el patrón singleton no se puede permitir - */ - @Override - public MyStockManager clone() { - try { - throw new CloneNotSupportedException(); - } catch (CloneNotSupportedException ex) { - System.out.println("No se puede clonar un objeto de la clase MyStockManager, sigue patrón singleton"); - } - return null; - } - -} diff --git a/src/main/java/shopManager/StockManager.java b/src/main/java/shopManager/StockManager.java deleted file mode 100644 index 6a4c539..0000000 --- a/src/main/java/shopManager/StockManager.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * - */ -package shopManager; - -/** - * @author Isabel Román - * @version 0.0. - * Primera versión de la interfaz StockManager, la forma de gestionar el stock compartido por distintas instancias hazelcast - * - */ -public interface StockManager { -/** - * - * @param newProduct - * El producto a insertar en el stock, el campo de unidades dentro del producto indica cuántos se insertan. - * Si ya estaba en el mapa se suma el número de unidades. - * Si no estaba en el mapa se inserta el producto nuevo con el número de unidades indicado. - */ - void addProduct(Product newProduct); - /** - * - * @param id - * Identificador del producto a buscar - * @return - * El producto buscado, que incluye el número de unidades existentes, null is no está en el mapa - */ - Product searchProduct(String id); - -} - diff --git a/src/main/java/shopmanager/BagManager.java b/src/main/java/shopmanager/BagManager.java new file mode 100644 index 0000000..f15eb6e --- /dev/null +++ b/src/main/java/shopmanager/BagManager.java @@ -0,0 +1,98 @@ +/** + * + */ +package shopmanager; + +/** + * @author isa + * + */ + +import java.util.Collection; +import java.util.Optional; +import exceptions.NoEnoughStock; +import exceptions.NotInStock; +import model.Product; +import model.Order; + +/** + * @author Isabel Román + * Gestor de la cesta de la compra + * + */ +public interface BagManager { + + + /** + * + * Añade a la cesta tantas unidades del producto pasado como parámetro como indique getNumber() del mismo, actualiza Stock eliminado las unidades añadidas + * + * @param newProduct producto a añadir, en el número de unidades se indica cuántas unidades se añaden + * debe verificar si hay en stock, si no no se añaden y debería lanzar la excepción NoEnoughStock + * actualiza stock disminuyendo el número de unidades añadidas y aumenta el número de unidades en la cesta + * @return El producto tal y como está en la cesta + * @throws NoEnoughStock si el número de unidades en el stock no es suficiente + * @throws NotInStock si el producto no existe en el stock + */ + Product addProduct(Product newProduct) throws NoEnoughStock,NotInStock; + /** + * + * Elimina de la cesta tantas unidades del producto pasado como parámetro como indique getNumber() del mismo, el número mínimo de unidades final es cero + * + * @param oldProduct producto a eliminar, se eliminan las unidades que se marquen, actualiza stock aumentando estas unidades liberadas + * @return newProduct, el producto, indicando el número de unidades que quedan en la cesta + * @throws NotInStock si el producto no estaba en el stock + */ + Product lessProduct(Product oldProduct) throws NotInStock; + /** + * Elimina completamente el producto, actualiza stock sumando las unidades liberadas + * + * @param oldProduct el producto con el número de elementos que se quieren liberar + * @return devuelve true si se eliminó, false si el producto no estaba en la cesta + * @throws NotInStock si no existía en el stock este producto + * + */ + boolean removeProduct(Product oldProduct) throws NotInStock; + /** + * + * Elimina completamente el producto, actualiza stock sumando las unidades liberadas + * + * @param productId el producto que se quiere eliminar + * @throws NotInStock si el producto no estaba en el stock + */ + void removeProduct(String productId) throws NotInStock; + + /** + * + * Obtiene la cesta como una colección (Sea cual sea la forma interna de almacenar los productos) + * @return devuelve la cesta como una lista de productos + */ + Collection getBag(); + /** + * + * Devuelve el producto cuyo id se pasa como parámetro encapsulado en un objeto Optional + * @see java.util.Optional + * @param productId el id del producto a buscar + * @return el producto, con el número de unidades del mismo, si es cero es que no estaba en la cesta + */ + Optional findProduct(String productId); + /** + * + * Devuelve el producto de la bolsa que se corresponde con el id del pasado, encapsulado en un objeto Optional + * @see java.util.Optional + * @param product producto a buscar + * @return el producto, con el número de unidades del mismo, si es cero es que no estaba en la cesta + */ + Optional findProduct(Product product); + /** + * Realiza el pedido, persistiendo los datos del mismo en el repositorio + * @return devuelve el pedido + */ + Order order(); + + /** + * Inicializa la cesta a cero, borra todo lo que había restaurando el stock + */ + void reset(); + +} diff --git a/src/main/java/shopmanager/MyBagManager.java b/src/main/java/shopmanager/MyBagManager.java new file mode 100644 index 0000000..129bed3 --- /dev/null +++ b/src/main/java/shopmanager/MyBagManager.java @@ -0,0 +1,174 @@ +/** + * + */ +package shopmanager; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Collection; +import java.util.HashMap; + +import java.util.logging.Logger; + +import exceptions.NoEnoughStock; +import exceptions.NotInStock; +import exceptions.UnknownRepo; +import model.MyOrder; +import model.MyProduct; +import model.Order; +import model.Product; +import persistency.OrderRepository; +import shopmanager.StockManager; +/** + * @author Isabel Román + * @author Pablo Domínguez + * + */ +public class MyBagManager implements BagManager { + private static Logger trazador=Logger.getLogger(MyStockManager.class.getName()); + private Map cesta; + private OrderRepository repositorio; + private StockManager stock; + private Order order; + + /** + * Al hacer privado el constructor obligo a que la construcción del objeto se haga siempre a través de newBag + */ + public MyBagManager(){ + cesta=new HashMap(); + } + + public MyBagManager(OrderRepository repo,StockManager stockManager){ + cesta=new HashMap(); + repositorio=repo; + stock=stockManager; + } + + + + @Override + public Product addProduct(Product newProduct)throws NoEnoughStock, NotInStock{ + String msg="El id del producto es "+newProduct.getId()+" y las unidades a añadir"+newProduct.getNumber(); + trazador.info(msg); + //quito del stock las unidades solicitadas, si no hubiera suficientes lanza NoEnoughStock, si el producto no existe lanza NotInStock + stock.lessProduct(newProduct); + if(cesta.containsKey(newProduct.getId())){ + int antes=cesta.get(newProduct.getId()).getNumber(); + newProduct.setNumber(newProduct.getNumber()+antes); + cesta.put(newProduct.getId(), newProduct); + } + else + cesta.put(newProduct.getId(), newProduct); + return newProduct; + } + + @Override + public Product lessProduct(Product oldProduct)throws NotInStock { + String msg="El id del producto es "+oldProduct.getId()+" y las unidades a quitar "+oldProduct.getNumber(); + trazador.info(msg); + + //Creamos el nuevo producto a devolver + Product newProduct = new MyProduct(oldProduct.getId(), 0); + + //Comprobamos si el producto no esta en el stock, en ese caso lanza la excepcion NotInStock + if(stock.searchProduct(oldProduct.getId()) == null) { + throw new NotInStock(oldProduct.getId()); + }else { + + //Comprobamos si el producto esta en la cesta + if(cesta.containsKey(oldProduct.getId())){ + + //Unidades que hay en la cesta + int antes=cesta.get(oldProduct.getId()).getNumber(); + + //Comprobamos si el numero de unidades a eliminar es mayor que las que tenemos en la cesta + if (antes>=oldProduct.getNumber()) { + + //añadimos al stock y eliminamos de la cesta las unidades indicadas + stock.addProduct(oldProduct); + newProduct.setNumber(antes - oldProduct.getNumber()); + cesta.put(newProduct.getId(), newProduct); + }else { + + msg="El producto con id "+oldProduct.getId()+" tiene menos unidades de las que se desean borrar,por lo que se borran las que hay"; + trazador.info(msg); + + //Añadimos al stock las unidades actuales + oldProduct.setNumber(antes); + stock.addProduct(oldProduct); + + //Eliminamos todas las unidades del producto y lo devolvemos + newProduct.setNumber(0); + cesta.put(newProduct.getId(), newProduct); + } + }else { + + msg="El producto con "+oldProduct.getId()+" no se encuentra en la cesta y por tanto hay cero unidades"; + trazador.info(msg); + newProduct.setNumber(0); + /*Aqui podriamos añadir el producto con cero unidades a la cesta o no añadirlo. Hemos decidido no añadirlo*/ + } + } + return newProduct; + } + + @Override + public boolean removeProduct(Product oldProduct)throws NotInStock { + // TODO Auto-generated method stub + return false; + + } + + @Override + public void removeProduct(String productId)throws NotInStock { + // TODO Auto-generated method stub + + } + + @Override + public Collection getBag() { + + return (List) cesta.values(); + } + + @Override + public Optional findProduct(String productId) { + Product producto=null; + if(cesta.containsKey(productId)) + producto=cesta.get(productId); + + return Optional.ofNullable(producto); + } + + @Override + public Optional findProduct(Product product) { + return findProduct(product.getId()); + } + + @Override + public Order order() { + // No crea el objeto order, aún no está resuelto quién será el responsable de elegir el tipo concreto + try{ + trazador.info("Intento persistir el stock"); + stock.save(); + trazador.info("Actualizo el pedido"); + order.setProducts(cesta.values()); + trazador.info("Persisto el pedido"); + repositorio.save(order); + + } catch (UnknownRepo ex) { + trazador.info("No ha sido posible guardar el pedido, no se estableció el repositorio en el stock"); + } + + return order; + } + + @Override + public void reset() { + // Debería restaurar el stock, pero por ahora no se hace, sólo borra + cesta.clear(); + + } + +} diff --git a/src/main/java/shopmanager/MyStockManager.java b/src/main/java/shopmanager/MyStockManager.java new file mode 100644 index 0000000..27f7c78 --- /dev/null +++ b/src/main/java/shopmanager/MyStockManager.java @@ -0,0 +1,180 @@ +package shopmanager; +import com.hazelcast.core.*; + +import exceptions.NoEnoughStock; +import exceptions.NotInStock; +import model.Product; +import persistency.ProductRepository; +import exceptions.UnknownRepo; +import exceptions.NotEmpty; + +import com.hazelcast.config.*; + +import java.util.Iterator; +import java.util.Optional; +import java.util.logging.*; +//va a seguir el patrón singleton (Una instancia sólo tiene un manager, pero puede haber varias instancias...) + +/** + * @author Isabel Román + * @version 0.0. + * Primera versión de MyStockManager, clase que implementa StockManager usando el patrón singleton + * + */ +public class MyStockManager implements StockManager{ + + private static Logger trazador=Logger.getLogger(MyStockManager.class.getName()); + private static StockManager myInstance; + private HazelcastInstance hzInstance; + private Config cfg; + //Mapa de productos que será compartido por todos los stockManager que se instancien + private IMap mapProducts; + /** + * Repositorio para persistir el stock, los elementos del mismo serán productos + */ + private ProductRepository repository; + + /** + * El constructor crea la instancia hazelcast, pero es privado, sólo la propia clase puede crear una instancia (un objeto) + */ + + private MyStockManager(){ + cfg = new Config(); + + hzInstance = Hazelcast.newHazelcastInstance(cfg); + trazador.info("Acabo de crear una instancia hazelcast para el MyStockManager"); + mapProducts = hzInstance.getMap("products"); + trazador.info("Se ha recuperado referencia al mapa products, si no se había usado se ha creado"); + } + + public void setRepository(ProductRepository repo) { + repository=repo; + } + //Inicializa el stock en memoria a partir de un repositorio que persistía los productos y que se pasa como parámetro + public void init(ProductRepository repo) throws NotEmpty{ + repository=repo; + //Si el mapa está vacio, recupera todos los productos del repositorio y los introduce en el stock (en memoria) + if (mapProducts.isEmpty()) { + Iterator products=repository.findAll().iterator(); + + while(products.hasNext()) { + addProduct(products.next()); + } + } + //Si el mapa no está vacío avisa de que no se puede iniciar, porque ya tiene productos y los cambios en el stock se perderían + else + throw new NotEmpty(); + } + //Inicializa el stock en memoria a partir del repositorio configurado, si no se ha establecido el repositorio lanza una excepción advirtiéndolo + public void init() throws NotEmpty,UnknownRepo{ + if(repository!=null) { + if (mapProducts.isEmpty()) { + Iterator products=repository.findAll().iterator(); + + while(products.hasNext()) { + addProduct(products.next()); + } + } + else + throw new NotEmpty(); + } + else + throw new UnknownRepo(); + + } + //Persiste los datos del stock en memoria en el repositorio, si el repositorio no estaba establecido lanza una excepción advirtiéndolo + public void save() throws UnknownRepo{ + if (repository!=null) + repository.saveAll(mapProducts.values()); + else + throw new UnknownRepo(); + } + + @Override + public StockManager getManager() { + return getInstance(); + } + @Override + public void clean() { + mapProducts.clear(); + } + /** + * @deprecated + * @see + * java.lang.Object + * El compilador me avisa de que uso algo "deprecated" por eso añado la opción de compilación en gradle, para ver más detalle + */ + @Override + @Deprecated(since="0,0") + protected void finalize() throws Throwable{ + trazador.info("Finalizando el MyStockManager, apaga la instancia hazelcast"); + hzInstance.shutdown(); + } + + public static StockManager getInstance() { + if (myInstance==null){ + myInstance=new MyStockManager(); + } + return myInstance; + } + @Override + public void addProduct(Product newProduct) { + Product productTmp; + String msg="El tamaño del mapa al entrar es " + mapProducts.size(); + trazador.info(msg); + if(mapProducts.containsKey(newProduct.getId())){ + productTmp=searchProduct(newProduct.getId()).get(); + newProduct.setNumber(productTmp.getNumber()+newProduct.getNumber()); + } + mapProducts.put(newProduct.getId(), newProduct); + msg="El mapa de tamaño " + mapProducts.size()+" incluye "+newProduct; + trazador.info(msg); + } + @Override + public Optional searchProduct(String id) { + Product product=null; + String msg; + if(mapProducts.containsKey(id)){ + product=mapProducts.get(id); + msg="Hay "+ product; + trazador.info(msg); + + }else { + msg="El "+product+" no está en el mapa"; + trazador.info(msg); + } + return Optional.ofNullable(product); + } + /** + * @throws CloneNotSupportedException avisa de que no se ha podido clonar + * @see java.lang.Object + * + * El método clone permite crear una instancia a partir de otra, lo que en una clase que siga el patrón singleton no se puede permitir + */ + @Override + public MyStockManager clone() throws CloneNotSupportedException { + trazador.warning("No se puede clonar un objeto de la clase MyStockManager, sigue patrón singleton"); + throw new CloneNotSupportedException(); + } + @Override + /** + *

La clase Optional es un contenedor que dentro tendrá un producto o no (según se haya encontrado o no en el stock), el método isPresent permite saber si está o no + * Puede consultar Optional en la api de Java

+ */ + public Product lessProduct(Product product) throws NoEnoughStock,NotInStock { + Optional productTmp = searchProduct(product.getId()); + + if(productTmp.isPresent()) + { + if(productTmp.get().getNumber() searchProduct(String id); + /** + * Vacia el stock + */ + void clean(); + /** + * Devuelve una instancia de StockManager + * + * @return objeto único StockManager + */ + StockManager getManager(); + +} + diff --git a/src/main/java/shopmanager/package-info.java b/src/main/java/shopmanager/package-info.java new file mode 100644 index 0000000..5e3f1ca --- /dev/null +++ b/src/main/java/shopmanager/package-info.java @@ -0,0 +1,7 @@ +/** + * Este paquete contiene las clases para los productos y el gestión del stock + * + * @author Isabel Román + * @version 0.0 + */ +package shopmanager; \ No newline at end of file diff --git a/src/test/java/shopManager/BagManagerTest.java b/src/test/java/shopManager/BagManagerTest.java new file mode 100644 index 0000000..cfcd1ef --- /dev/null +++ b/src/test/java/shopManager/BagManagerTest.java @@ -0,0 +1,275 @@ +/** + * + */ +package shopManager; + +import shopmanager.*; + +import static org.junit.jupiter.api.Assertions.*; + +import static org.mockito.Mockito.mockitoSession; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.junit.jupiter.MockitoExtension; + +import exceptions.NoEnoughStock; +import exceptions.NotInStock; +import exceptions.UnknownRepo; +import model.Product; +import model.Order; +import persistency.OrderRepository; + + +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; + + + +/** + * @author Isabel Román + * Clase para realizar los test a la clase MyBagManager, o a cualquier otra clase que implemente BagManager siempre que se sustituya la declaración private static MyBagManager micestaTesteada; + * + */ +@ExtendWith(MockitoExtension.class) +class BagManagerTest { + private static Logger trazador=Logger.getLogger(ProductTest.class.getName()); + + //Creo los objetos sustitutos (representantes o mocks) + //Son objetos contenidos en MyBagManager de los que aún no disponemos el código + @Mock(serializable = true) + private static Product producto1Mock= Mockito.mock(Product.class); + @Mock(serializable = true) + private static Product producto2Mock= Mockito.mock(Product.class); + @Mock + private static StockManager stockMock= Mockito.mock(StockManager.class); + @Mock + private static OrderRepository repositoryMock= Mockito.mock(OrderRepository.class); + @Mock + private static Order orderMock=Mockito.mock(Order.class); + + //Inyección de dependencias + //Los objetos contenidos en micestaTesteada son reemplazados automáticamente por los sustitutos (mocks) + @InjectMocks + private static MyBagManager micestaTesteada; + + + //Servirán para conocer el argumento con el que se ha invocado algún método de alguno de los mocks (sustitutos o representantes) + //ArgumentCaptor es un genérico, indico al declararlo el tipo del argumento que quiero capturar + @Captor + private ArgumentCaptor intCaptor; + @Captor + private ArgumentCaptor productCaptor; + + + /** + * @see BeforeEach {@link org.junit.jupiter.api.BeforeEach} + */ + + @BeforeEach + void setUpBeforeClass(){ + //Todos los tests empiezan con la bolsa vacía + + micestaTesteada.reset(); + } + /** + * Test para probar el método de efectuar un pedido {@link shopmanager.BagManager#order()} + * + * @throws NoEnoughStock Se intenta añadir un número de unidades de un producto, pero no hay suficientes en stock + * @throws NotInStock Se intenta añadir un producto, pero no existe ese tipo en el stock + * @throws UnknownRepo Se intenta guardar algo en un repositorio, pero no se ha establecido bien esta referencia y no sabe dónde guardar + */ + + @Test + @Tag("unidad") + @DisplayName("Prueba del método que asienta el pedido") + void testOrder() throws NoEnoughStock, NotInStock, UnknownRepo { + trazador.info("Comienza el test de order"); + //Hago un pedido que no debe tener problemas + trazador.info("Primero sin problemas"); + //El procedimiento rellenaCesta mete dos productos (mocks) en la cesta + //REVISE EL PROCEDIMIENTO RELLENACESTA + rellenaCesta(); + //Si no hay problema se guarda + micestaTesteada.order(); + + Mockito.verify(stockMock,Mockito.times(1)).save(); + //Se ha invocado save con el orderMock + Mockito.verify(repositoryMock).save(orderMock); + + //EJERCICIO: Elimine este comentario, ejecute los test + // Mockito.verifyZeroInteractions(repositoryMock); + // ¿Por qué falla el test si se pone aquí esta comprobación? + + //si no se pueda guardar el stock no se guarda el pedido, no se llega a tocar el repositorio ni se modifica order, y mi cesta gestiona la excepción, no debe propagarse y por tanto no debe lanzarla + trazador.info("Ahora hago que salte la excepción UnknownRepo en el stock, para ver si la gestiona bien BagManager"); + Mockito.doThrow(new UnknownRepo()).when(stockMock).save(); + try { + micestaTesteada.order(); + //Me aseguro de que el pedido no se guarda en el repositorio de pedidos + Mockito.verifyNoMoreInteractions(repositoryMock); + + } + catch(Exception e) { + //Me aseguro de que BagManager gestiona esta excepción y no la propaga + fail("BagManager debe gestionar la excepción UnknownRepo y no propagarla"); + + } + + } + + /** + * Test method for {@link shopmanager.BagManager#addProduct(model.Product)}. + * @throws NotInStock lanza cualquier excepción de sus clientes, no las gestiona siempre internamente + * @throws NoEnoughStock lanza cualquier excepción de sus clientes, no las gestiona siempre internamente + */ + @Test + @Tag("unidad") + @DisplayName("Prueba del método que añade un producto") + void testAddProduct() throws NoEnoughStock, NotInStock { + Mockito.when(producto1Mock.getId()).thenReturn("id1"); + Mockito.when(producto1Mock.getNumber()).thenReturn(1); + Mockito.when(producto2Mock.getId()).thenReturn("id2"); + Mockito.when(producto2Mock.getNumber()).thenReturn(2); + + micestaTesteada.addProduct(producto1Mock); + assertFalse(micestaTesteada.findProduct("id1").isEmpty()); + assertEquals(1,micestaTesteada.findProduct("id1").get().getNumber(),"El producto insertado debía tener una unidad"); + micestaTesteada.addProduct(producto2Mock); + assertEquals(2,micestaTesteada.findProduct("id2").get().getNumber(),"El producto insertado debía tener dos unidades"); + assertTrue(micestaTesteada.findProduct("id1").isPresent()); + /**Cuidado con los mock, no son el objeto de verdad son sustitutos y no implementan la lógica de los objetos**/ + /**Analizar por qué estos dos test que vienen a continuación no son correctos, mientras que los de arriba sí*/ + + micestaTesteada.addProduct(producto1Mock); + assertEquals(2,micestaTesteada.findProduct("id1").get().getNumber(),"El incremento de un producto en una unidad no se hace bien"); + micestaTesteada.addProduct(producto2Mock); + assertEquals(4,micestaTesteada.findProduct("id2").get().getNumber(),"El incremento de un producto en dos unidades no se hace bien"); + + + //Para ver si realmente hace bien la actualización de valores lo que deberíamos es asegurar que el método + //newProduct.setNumber(newProduct.getNumber()+antes); + //se invoca con el valor correcto (no invoca la primera vez, ni la segunda porque el producto no estaba, la tercera se invoca con 2 y la cuarta con 4, porque hay que cambiarle el valor) + //estoy suponiendo que se guarda exactamente el mismo producto que se pasa, no se hace ningún tipo de copia (en realidad no tendría por qué suponer esto... + //Es para probar las prestaciones de los ArgumentCaptors + + //la segunda vez que añado el producto debe sumarse el número de unidades a las que ya había + micestaTesteada.addProduct(producto1Mock); + //quiero verificar el argumento que se ha usado en el mock para poner el número de unidades + Mockito.verify(producto1Mock).setNumber(intCaptor.capture()); + assertEquals(2,intCaptor.getValue(), "El argumento para actualizar el número de unidades en el producto no se calcula bien"); + + micestaTesteada.addProduct(producto2Mock); + Mockito.verify(producto2Mock).setNumber(intCaptor.capture()); + assertEquals(4,intCaptor.getValue(), "El argumento para actualizar el número de unidades en el producto no se calcula bien"); + + //Si hay no hay stock el producto no se debe añadir, parto de nuevo de la cesta vacía + micestaTesteada.reset(); + Mockito.doThrow(new NoEnoughStock(0)).when(stockMock).lessProduct(producto1Mock); + try { + micestaTesteada.addProduct(producto1Mock); + //debe saltar la excepción así que no debe llegar aquí + fail("No salta la excepción del stock"); + }catch(NoEnoughStock e){ + assertEquals("No hay suficientes unidades en el Stock, sólo quedan 0",e.getMessage(),"El mensaje de la excepción no es correcto"); + + } + //Aseguro que si no había suficientes unidades no se ha agregado a la cesta + assertTrue(micestaTesteada.findProduct("id1").isEmpty(),"Se agrega un producto cuando no había suficientes unidades"); + assertFalse(micestaTesteada.findProduct("id1").isPresent(),"Se agrega un producto cuando no había suficientes unidades"); + + //Ahora pruebo la gestión de la excepción NotInStock, no se debe agregar a la cesta y debe lanzar la excepción + //aseguro que parto de la cesta vacía + micestaTesteada.reset(); + Mockito.doThrow(new NotInStock("id1")).when(stockMock).lessProduct(producto1Mock); + try { + micestaTesteada.addProduct(producto1Mock); + //debe saltar la excepción así que no debe llegar aquí + fail("No salta la excepción NotInStock stock"); + }catch(NotInStock e){ + assertEquals("El producto con id id1 no existe en el Stock",e.getMessage(),"El mensaje de la excepción no es correcto"); + + } + //Aseguro que si no existía en el stock no se ha agregado a la cesta + assertTrue(micestaTesteada.findProduct("id1").isEmpty(),"Se agrega un producto que no existe en el stock"); + assertFalse(micestaTesteada.findProduct("id1").isPresent(),"Se agrega un producto que no existe en el stock"); + } + + /** + * Test method for {@link shopmanager.BagManager#lessProduct(model.Product)}. + */ + @Test + @Tag("unidad") + + void testLessProduct() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link shopmanager.BagManager#removeProduct(model.Product)}. + */ + @Test + @Tag("unidad") + void testRemoveProductProduct() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link shopmanager.MyBagManager#removeProduct(java.lang.String)}. + */ + @Test + @Tag("unidad") + void testRemoveProductString() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link shopmanager.MyBagManager#getBag()}. + */ + @Test + @Tag("unidad") + void testGetBag() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link shopmanager.MyBagManager#findProduct(java.lang.String)}. + */ + @Test + @Tag("unidad") + void testFindProductString() { + fail("Not yet implemented"); + } + + /** + * Test method for {@link shopmanager.MyBagManager#findProduct(model.Product)}. + */ + @Test + @Tag("unidad") + void testFindProductProduct() { + fail("Not yet implemented"); + } + /** + * Rellena una cesta con los dos mocks declarados al inicio + * @throws NoEnoughStock Si no hay suficiente stock del producto a añadir + * @throws NotInStock Si no existe el producto en el stock + */ + void rellenaCesta() throws NoEnoughStock, NotInStock { + Mockito.when(producto1Mock.getId()).thenReturn("id1"); + Mockito.when(producto1Mock.getNumber()).thenReturn(1); + Mockito.when(producto2Mock.getId()).thenReturn("id2"); + Mockito.when(producto2Mock.getNumber()).thenReturn(2); + micestaTesteada.addProduct(producto1Mock); + micestaTesteada.addProduct(producto2Mock); + } + +} diff --git a/src/test/java/shopManager/BagManagerTestLessProduct.java b/src/test/java/shopManager/BagManagerTestLessProduct.java new file mode 100644 index 0000000..3d61d4e --- /dev/null +++ b/src/test/java/shopManager/BagManagerTestLessProduct.java @@ -0,0 +1,256 @@ +package shopManager; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import shopmanager.*; + +import static org.junit.jupiter.api.Assertions.*; + +import static org.mockito.Mockito.mockitoSession; + +import java.util.Optional; +import java.util.logging.Logger; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.junit.jupiter.MockitoExtension; + +import exceptions.NoEnoughStock; +import exceptions.NotInStock; +import exceptions.UnknownRepo; +import model.Product; +import model.MyProduct; +import model.Order; +import persistency.OrderRepository; + + +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; + +/** + * @author Daniel Neira + * Clase para realizar los test de Less Product a la clase MyBagManager, o a cualquier otra clase que implemente BagManager siempre que se sustituya la declaración private static MyBagManager micestaTesteada; + * + */ +@ExtendWith(MockitoExtension.class) +class BagManagerTestLessProduct { + +private static Logger trazador=Logger.getLogger(ProductTest.class.getName()); + + //Creo los objetos sustitutos (representantes o mocks) + //Son objetos contenidos en MyBagManager de los que aún no disponemos el código + @Mock(serializable = true) + private static Product producto1Mock= Mockito.mock(Product.class); + @Mock(serializable = true) + private static Product producto2Mock= Mockito.mock(Product.class); + @Mock + private static StockManager stockMock= Mockito.mock(StockManager.class); + @Mock + private static OrderRepository repositoryMock= Mockito.mock(OrderRepository.class); + @Mock + private static Order orderMock=Mockito.mock(Order.class); + + //Inyección de dependencias + //Los objetos contenidos en micestaTesteada son reemplazados automáticamente por los sustitutos (mocks) + @InjectMocks + private static MyBagManager micestaTesteada; + + //Servirán para conocer el argumento con el que se ha invocado algún método de alguno de los mocks (sustitutos o representantes) + //ArgumentCaptor es un genérico, indico al declararlo el tipo del argumento que quiero capturar + @Captor + private ArgumentCaptor intCaptor; + @Captor + private ArgumentCaptor productCaptor; + + /** + * @see BeforeEach {@link org.junit.jupiter.api.BeforeEach} + */ + + @BeforeEach + void setUpBeforeClass(){ + //Todos los tests empiezan con la bolsa vacía + + micestaTesteada.reset(); + } + + /** + * Test method for {@link shopmanager.BagManager#lessProduct(model.Product)}. + * @throws NotInStock lanza cualquier excepción de sus clientes, no las gestiona siempre internamente + */ + @Test + @Tag("unidad") + @DisplayName("Prueba del método que elimina unidades de un producto") + void testLessProduct() throws NotInStock { + + Mockito.when(producto1Mock.getId()).thenReturn("id1"); + Mockito.when(producto1Mock.getNumber()).thenReturn(7); + Mockito.when(producto2Mock.getId()).thenReturn("id1"); + Mockito.when(producto2Mock.getNumber()).thenReturn(5); + + /*Programamos el comportamiento del stockMock a la hora de buscar un producto cuando no esta en el stock*/ + Mockito.when(stockMock.searchProduct(producto1Mock.getId())).thenReturn(null); + + /* Intentamos eliminar unidades de un producto que no esta en el stock, debería lanzar la excepción NoInStock */ + try { + if(micestaTesteada.lessProduct(producto1Mock).getNumber() == 0) { + trazador.info("Si el producto no esta en el stock no podemos eliminarlo. OK"); + }else { + /* Debe saltar la excepción así que no debe llegar aquí */ + fail("Deberia devolver 0 ya que el producto no esta en la cesta"); + } + } + catch(NotInStock e) { + /* Si es la excepcion que esperamos, la capturamos y proseguimos con el test */ + trazador.info("Si el producto no existe no podemos eliminarlo. OK"); + assertEquals("El producto con id id1 no existe en el Stock",e.getMessage(),"El mensaje de la excepción no es correcto"); + } + + + /*Programamos el comportamiento del stockMock a la hora de buscar un producto cuando esta en el stock*/ + Mockito.when(stockMock.searchProduct(producto1Mock.getId())).thenReturn(Optional.of(producto1Mock)); + + /* Intentamos eliminar unidades de un producto que no esta en la cesta*/ + try { + if(micestaTesteada.lessProduct(producto1Mock).getNumber() == 0) { + trazador.info("Si el producto no esta en la cesta no podemos eliminarlo. OK"); + }else { + /*No debería llegar aquí*/ + fail("Deberia devolver 0 ya que el producto no esta en la cesta"); + } + } + catch(NotInStock e) { + /* No deberia llegar aqui */ + fail("No debería lanzar la excepción"); + } + + + /* Necesitamos añadir un producto para comprobar que se eliminan unidades correctamente */ + try { + /* No debería haber problemas, pero lo hacemos en un try-catch para controlar mejor las excepciones */ + micestaTesteada.addProduct(producto1Mock); + /* Nos aseguramos de que el producto está añadido correctamente */ + assertFalse(micestaTesteada.findProduct("id1").isEmpty(), "No debería estar vacío"); + assertEquals(7,micestaTesteada.findProduct("id1").get().getNumber(),"El producto insertado debía tener 7 unidades"); + + } + catch(NoEnoughStock e) { + /* Por si existe algun problema con el metodo addProduct */ + trazador.info("No se ha podido añadir el producto para después eliminarlo. ERROR"); + fail("No se ha podido añadir el producto para después eliminarlo"); + + } + + + /* Intentamos eliminar todas las unidades */ + try { + if(micestaTesteada.lessProduct(producto1Mock).getNumber() == 0) { + + /*Comprobamos que se añaden las unidades correctamente al stock*/ + Mockito.verify(stockMock).addProduct(productCaptor.capture()); + assertEquals(7,productCaptor.getValue().getNumber(), "No se añaden las unidades correctamente al stock"); + + trazador.info("Eliminamos las unidades indicadas. OK"); + + }else { + + /* No debe llegar aquí */ + fail("Deberia devolver 0 ya que intentamos eliminar 7 unidades habiendo 7"); + } + } + catch(NotInStock e) { + + /* No debe saltar la excepción así que no debería llegar aquí */ + fail("Salta la excepción del stock que no debería. ERROR"); + + } + + //Aseguro que se ha eliminado las unidades deseadas de la cesta + assertEquals(0,micestaTesteada.findProduct("id1").get().getNumber(),"No se han borrado las unidades indicadas"); + + + /* Comprobamos que al eliminar menos unidades de las que hay en la cesta queda el resto. + * Para ello, necesitamos añadir unidades del producto para comprobar que se eliminan correctamente ya que anteriomente se borraron todas*/ + try { + /* No debería haber problemas, pero lo hacemos en un try-catch para controlar mejor las excepciones */ + micestaTesteada.addProduct(producto1Mock); + /* Nos aseguramos de que el producto está añadido correctamente */ + assertFalse(micestaTesteada.findProduct("id1").isEmpty(), "No debería estar vacío"); + assertEquals(7,micestaTesteada.findProduct("id1").get().getNumber(),"El producto insertado debía tener 7 unidades"); + + } + catch(NoEnoughStock e) { + /* Por si existe algun problema con el metodo addProduct */ + trazador.info("No se ha podido añadir el producto para después eliminarlo. ERROR"); + fail("No se ha podido añadir el producto para después eliminarlo"); + + } + + + /*Reseteamos el stockMock para poder volver a capturar el argumento y comprobar que es correcto*/ + Mockito.reset(stockMock); + + /* Intentamos eliminar menos unidades unidades de las que hay en la cesta*/ + try { + if(micestaTesteada.lessProduct(producto2Mock).getNumber() == 2) { + + /*Comprobamos que se añaden las unidades correctamente al stock*/ + Mockito.verify(stockMock).addProduct(productCaptor.capture()); + assertEquals(5,productCaptor.getValue().getNumber(), "No se añaden las unidades correctamente al stock"); + + trazador.info("Eliminamos las unidades indicadas. OK"); + + }else { + + /* No debe llegar aquí */ + fail("Deberia devolver 2 ya que intentamos eliminar 5 unidades habiendo 7"); + } + } + catch(NotInStock e) { + + /* No debe saltar la excepción así que no debería llegar aquí */ + fail("Salta la excepción del stock que no debería. ERROR"); + + } + + //Aseguro que se ha eliminado las unidades deseadas de la cesta + assertEquals(2,micestaTesteada.findProduct("id1").get().getNumber(),"No se han borrado las unidades indicadas"); + + + /*Por ultimo, comprobamos que al eliminar mas unidades de las que quedan en la cesta se pone 0 unidades*/ + /* Intentamos eliminar mas unidades unidades de las que hay en la cesta*/ + try { + if(micestaTesteada.lessProduct(producto2Mock).getNumber() == 0) { + + /*Comprobamos que se añaden las unidades correctamente al stock para ello tenemos que ver que el setNumber del producto que añadimos al stock es correcto*/ + Mockito.verify(producto2Mock).setNumber(intCaptor.capture()); + assertEquals(2,intCaptor.getValue(), "No se añaden las unidades correctamente al stock"); + + trazador.info("Eliminamos las unidades indicadas. OK"); + + }else { + + /* No debe llegar aquí */ + fail("Deberia devolver 0 ya que intentamos eliminar 5 unidades habiendo 2"); + } + } + catch(NotInStock e) { + + /* No debe saltar la excepción así que no debería llegar aquí */ + fail("Salta la excepción del stock que no debería. ERROR"); + + } + + //Aseguro que se ha eliminado las unidades deseadas de la cesta + assertEquals(0,micestaTesteada.findProduct("id1").get().getNumber(),"No se han borrado las unidades indicadas"); + + } +} diff --git a/src/test/java/shopManager/MyStockManagerTest.java b/src/test/java/shopManager/MyStockManagerTest.java index 0ef911e..1bfd1fd 100644 --- a/src/test/java/shopManager/MyStockManagerTest.java +++ b/src/test/java/shopManager/MyStockManagerTest.java @@ -3,56 +3,135 @@ */ package shopManager; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.BeforeEach; -import shopManager.MyProduct; -import shopManager.Product; -import shopManager.StockManager; -import shopManager.MyStockManager; +import shopmanager.MyStockManager; +import shopmanager.StockManager; + +import exceptions.NotInStock; +import model.MyProduct; +import model.Product; +import exceptions.NoEnoughStock; /** - * @author isabo + * + * Clase para los tests de MyStockManager + * @author Isabel Román + * * */ + public class MyStockManagerTest { StockManager underTest; String underTestAsString; + + + + @BeforeEach + public void setup() { + MyStockManager.getInstance().clean(); + } /** - * Test method for {@link shopManager.MyStockManager#getInstance()}. + * Test para probar {@link shopmanager.MyStockManager#getInstance()}. */ @Test + @Tag("unidad") + @Tag("integracion") + @DisplayName("Prueba del método que devuelve la instancia única") public void testGetInstance() { underTest=MyStockManager.getInstance(); underTestAsString=underTest.toString(); - - assertNotNull("getInstance no devuelve una referencia válida",underTest); - assertEquals("getInstance devuelve una referencia diferente",underTest,MyStockManager.getInstance()); + + assertNotNull(underTest,"getInstance no devuelve una referencia válida"); + assertEquals(underTest,MyStockManager.getInstance(),"getInstance debe devolver siempre la misma instancia"); } /** - * Test method for {@link shopManager.MyStockManager#addProduct(shopManager.Product)}. + * Test para probar {@link shopmanager.MyStockManager#addProduct(model.Product)}. */ @Test + @Tag("integracion") + @DisplayName("Prueba para el método que añade un producto") public void testAddProduct() { Product product=new MyProduct("nuevoProducto",1); + MyStockManager.getInstance().addProduct(product); - assertEquals("Los objetos como string no son iguales",product.toString(),MyStockManager.getInstance().searchProduct("nuevoProducto").toString()); - assertEquals("El id del producto recuperado no es el buscado", "nuevoProducto", MyStockManager.getInstance().searchProduct("nuevoProducto").getId()); - assertEquals("El número de unidades del producto recuperado no es el esperado", 1, MyStockManager.getInstance().searchProduct("nuevoProducto").getNumber()); + + assertEquals(product.toString(),MyStockManager.getInstance().searchProduct("nuevoProducto").get().toString(),"El objeto obtenido debe ser igual al introducido"); + assertEquals("nuevoProducto", MyStockManager.getInstance().searchProduct("nuevoProducto").get().getId(),"El id del producto recuperado no es el buscado"); + assertEquals(1, MyStockManager.getInstance().searchProduct("nuevoProducto").get().getNumber(),"El número de unidades del producto recuperado no es el esperado"); + + MyStockManager.getInstance().addProduct(product); + assertEquals(2, MyStockManager.getInstance().searchProduct("nuevoProducto").get().getNumber(),"Si añado un producto que ya estaba se añade el número de unidades"); } - + /** - * Test method for {@link shopManager.MyStockManager#searchProduct(java.lang.String)}. + * Test para probar {@link shopmanager.MyStockManager#searchProduct(java.lang.String)}. */ @Test + @Tag("integracion") + @DisplayName("Prueba para el método de búsqueda de un producto") public void testSearchProduct() { - assertNull("Encuentra un producto que no existe",MyStockManager.getInstance().searchProduct("noexiste")); + assertTrue(MyStockManager.getInstance().searchProduct("noexiste").isEmpty(),"No debe encontrar un producto que no existe"); + + Product product=new MyProduct("nuevoProducto",1); + MyStockManager.getInstance().addProduct(product); + + assertFalse(MyStockManager.getInstance().searchProduct("nuevoProducto").isEmpty(),"Optional no debe estar vacío"); + assertEquals(product.toString(),MyStockManager.getInstance().searchProduct("nuevoProducto").get().toString(),"El objeto obtenido debe ser igual al introducido"); + } + + /** + * Test para probar {@link shopmanager.MyStockManager#lessProduct(Product)} + * @throws NoEnoughStock si se intenta eliminar unidades de un producto sin suficiente cantidad lanzar la excepción NoEnoughStock, el método de test lo verifica, pero no gestiona la excepción + * @throws NotInStock si se intenta eliminar unidades de un producto que no existe en el stock se debe lanzar NotInStock, el método de test lo verifica, pero no gestiona la excepción + */ + @Test + @Tag("integracion") + @DisplayName("Prueba para el método que reduce el número de unidades en stock") + + public void testLessProduct() throws NoEnoughStock, NotInStock { + + //creamos un producto tipo "id1" con 5 unidades, todavía no lo introduzco en el stock + Product product1=new MyProduct("id1",5); + + //Si intento reducir unidades de un producto que no existe debe lanzar la excepción NotInStock + assertThrows(NotInStock.class,()->{MyStockManager.getInstance().lessProduct(product1);},"Debería lanzar una excepción de tipo NotInStock si no existe este producto"); + + //Ahora sí, lo añado al stock + + MyStockManager.getInstance().addProduct(product1); + + //Si se intentan eliminar más unidades de las que hay debe lanzar la excepción NoEnoughStock + product1.setNumber(8); + assertThrows(NoEnoughStock.class,()->{MyStockManager.getInstance().lessProduct(product1);},"Debería lanzar una excepción de tipo NoEnougStock si no hay suficientes unidades de este producto"); + product1.setNumber(1); + //Si a 5 que había le quito 1 deberían quedar 4 + assertEquals(4,MyStockManager.getInstance().lessProduct(product1).getNumber(),"Debería restar el número de unidades pasadas a las que había y devolver el producto actualizado"); + } + + /** + *

Test para probar {@link shopmanager.MyStockManager#clone()} + * + * Usa expresiones Expresiones lambda en Oracle + *

+ * @see java.lang.reflect.Executable + */ + @Test + @Tag("unidad") + @Tag("integracion") + @DisplayName ("Aseguro que no se puede clonar el StockManager") + public void testClone() { + assertThrows(CloneNotSupportedException.class,()->{((MyStockManager)MyStockManager.getInstance()).clone();},"Debe lanzar la excepción clone no soportado al intentar clonar"); + } - } diff --git a/src/test/java/shopManager/ProductTest.java b/src/test/java/shopManager/ProductTest.java index 4d5f2d6..ad1c0b5 100644 --- a/src/test/java/shopManager/ProductTest.java +++ b/src/test/java/shopManager/ProductTest.java @@ -3,67 +3,108 @@ */ package shopManager; -import static org.junit.Assert.*; -import java.util.logging.*; -import org.junit.Before; +import static org.junit.jupiter.api.Assertions.*; + + +import org.junit.jupiter.api.Test; + +import model.MyProduct; +import model.Product; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; -import shopManager.MyProduct; -import shopManager.Product; +import java.util.logging.*; /** + * Pruebas unidad para el casi POJO myProduct + * Pueden servir para cualquier implementación de Product siempre y cuando se cambie el método setUp, que instancia el objeto concreto * @author Isabel Román * */ +@Tag("unidad") public class ProductTest { private static Logger trazador=Logger.getLogger(ProductTest.class.getName()); static Product producto; /** - * @throws java.lang.Exception + * @throws java.lang.Exception el constructor puede lanzar excepciones, no las gestiona internamente + * @see org.junit.jupiter.api + * @see org.junit.jupiter.api.BeforeAll */ - @Before - public void setUp() throws Exception { + @BeforeAll + @DisplayName("Construye el objeto y verifica constructor") + public static void setUp() throws Exception { producto=new MyProduct("primerId"); - assertEquals("Al crear producto sin número de unidades debe tener 1",1,producto.getNumber()); - assertEquals("El id del producto es el pasado en el constructor","primerId",producto.getId()); + + assertEquals(1,producto.getNumber(),"Al crear producto sin número de unidades debe tener 1"); + assertEquals("primerId",producto.getId(),"El id del producto es el pasado en el constructor"); } - + /** + * Prueba el método {@link model.Product#getId()} + * @see org.junit.jupiter.api.Test + * @see org.junit.jupiter.api.Tag + * @see org.junit.jupiter.api.DisplayName + */ @Test + @Tag("unidad") + @DisplayName("Prueba para el método que establece el id del producto") public void TestSetId() { trazador.info("Test del setId"); producto.setId("nuevoId"); - assertEquals("El setId no funciona ","nuevoId",producto.getId()); + assertEquals("nuevoId",producto.getId(),"El setId no funciona "); } + /** + * Prueba el método {@link model.Product#setNumber(int)}. + * + */ @Test + @Tag("unidad") + @DisplayName("Prueba para el método que establece el número de unidades del producto") public void TestSetNumber() { trazador.info("Test del setNumber"); producto.setNumber(33); - assertEquals("El setNumber no funciona ",33,producto.getNumber()); + assertEquals(33,producto.getNumber(),"El setNumber no funciona "); } + /** + * Prueba el método {@link model.Product#oneMore()}. + * + */ + @Test + @Tag("unidad") + @DisplayName("Prueba para el método que añade una unidad al producto") public void TestOneMore() { trazador.info("Test del oneMore"); producto.setNumber(33); - assertEquals("El oneMore no funciona ",34,producto.oneMore()); + assertEquals(34,producto.oneMore(),"El oneMore no funciona "); } + + + /** + * Prueba el método {@link model.Product#oneLess()}. + * + */ + @Test + @Tag("unidad") + @DisplayName("Prueba para el método que elimina una unidad del producto") public void TestOneLess() { trazador.info("Test del OneLess"); producto.setNumber(1); - assertEquals("El oneLess no funciona cuando es distinto de 0 ",0,producto.oneLess()); + assertEquals(0,producto.oneLess(),"El oneLess no funciona cuando es distinto de 0 "); producto.oneLess(); - assertEquals("El oneLess no funciona cuando es 0 ",0,producto.oneLess()); + assertEquals(0,producto.oneLess(),"El oneLess no funciona cuando es 0 "); } diff --git a/src/test/java/shopManager/package-info.java b/src/test/java/shopManager/package-info.java new file mode 100644 index 0000000..426b864 --- /dev/null +++ b/src/test/java/shopManager/package-info.java @@ -0,0 +1,13 @@ +/** + *

Este paquete contiene los test

+ *

Usa

+ *

Junit.5 (Jupiter)

+ *

Mockito5

+ * @see junit.org/junit5/docs/current/user-guide/ + * + * @see mockito + * + * @author Isabel Román + * @version 0.0 + */ +package shopManager;