diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..de5b8397 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Node.js +node_modules/ +# npm +*.log +*.lock +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +# Environment variables +.env +# Editor +.vscode/ +.idea/ +# Logs and databases +*.log +*.sqlite +*.sqlite3 +*.db +*.sql +# Build output +/build/ +/dist/ +/out/ +/build-output/ +# OS-generated files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/README2.md b/README2.md new file mode 100644 index 00000000..b45850bd --- /dev/null +++ b/README2.md @@ -0,0 +1,106 @@ +# Tienda de ropa + +En esta documentación vamos a explicar el funcionamiento de la tienda de ropa, las tecnologías usadas, endpoints, etc. + +## Índice + +-[Url donce está ubicada nuestra tienda](#URL-Tienda-de-ropa) +-[Funcionamiento de la tienda de ropa](#Funcionamiento-de-la-tienda-de-ropa) +-[Tecnologías usadas para crear esta tienda de ropa](#Tecnologias-usadas) +-[Endpoints utilizados para HTML](#Endpoints-html) +-[Endpoints utilizados para la API](#Endpoints-Api) + +## URL Tienda de ropa +Esta sería la URL donde está alojada la tienda: URL:http******** + +## Funcionamiento de la tienda de ropa + +Una vez que se accede a la url de la web, lo primero que nos vamos ha encontrar es una web con una barra de navegación, la cual consta de las siguientes categorías: + +-[Productos](#Categoría-Productos) +-[Camisetas](#Categoría-Camisetas) +-[Pantalones](#Categoría-Pantalones) +-[Zapatos](#Categoría-Zapatos) +-[Accesorios](#Categoría-Accesorios) +-[login](#Login) +-[La web Logeado](#La-web-Logeado) +-[Logeado Productos](#Logeado-Productos) +-[Botón Actualizar](#Botón-Actualizar) +-[Botón Eliminar](#Botón-Eliminar) +-[Opcion de crear nuevo producto](#Opción-de-crear-nuevo-producto) +-[Opción Logout](#Opción-Logout) + +### Categoría Productos + +En este apartado de la web, nos muestra todos los productos que existen en la web, cada producto tiene un botón para porde ver una vista detalle del producto en cuestión. + +### Categoría Camisetas + +En este apartado de la web, nos muestra solo los productos por camisetas que existan en nuestra web, también con su botón para acceder a su vista detalle. + +### Categoría Pantalones + +En este apartado de la web, nos muestra solo los productos por pantalones que existan en nuestra web, también su botón para acceder a su vista detalle. + +### Categoría Zapatos + +En este apartado de la web, nos muestra solo los productos por zapatos que existan en nuestra web, también su botón para acceder a su vista detalle. + +### Categoría Accesorios + +En este apartado de la web, nos muestra solo los productos por accesorios que existan en nuestra web, también su botón para acceder a su vista detalle. + +#### Login + +En este apartado tienes la opción de logearte en la web para poder acceder a ella con las funciones de administrador y así poder utilizar todas las opciones de Crear, buscar, modificar y borrar los artículos de la tienda. + +#### La web Logeado + +Cuando cliques en la opción de login, te saldrá una pantalla donde deberas introducir tu Usuário y contraseña de administrador de la web, esa pantalla comprueba que la información sea correcta, para poder entrar como administrador. +Una vez logeado te aparece una web con una barra de navegación arriba con; Productos, Camisetas, Pantalones, Zapatos, Accesorios, Nuevo Producto y Logout. + +#### Logeado Productos +A simple vista lo que se muestra es lo mismo que en la opción de inicio de productos pero una vez que presionas el botón Ver detalle, nos muestra una vista detalle del producto con dos botones más, botón Actualizar y Botón Eliminar. + +#### Botón Actualizar + +Una vez presionado este botón, nos muestra una web con la misma barra de navegación que cuando estás logeado y como cuerpo principal de la web un formulario de actualización del producto en cuestión con la información de ese producto y un botón de actualizar, uan vez hechos los cambios pertinentes en dicho producto al pinchar en el botón actualizar no vuelve a mostrar el producto en cuestión con su modificación ya realizada. +Esto es aplicable a todas las categorías de la web pantalones, camisetas, zapatos, accesorios. + + +#### Botón Eliminar + +Una vez pulsado el botón de eleminar, nos mostrará un mensaje el cual no dice que el producto se ha eliminado correctamente. Esto es aplicable a todas las categorías de la web pantalones, camisetas, zapatos, accesorios. + +#### Opción de crear nuevo producto + +Una vez de clicar en la opción de la barra de menus, Nuevo Producto, nos aparece una web cd crear producto con todos los campos para crear dicho producto y un botón de enviar, una vez que se han rellenado todos los campos del nuevo producto, al presionar el botón de enviar, nos redirecciona a la web principal donde nos muestra todos los productos incluido el nuevo producto. + +#### Opción Logout + +Una clicado esta opción, simplemente cerrará la sesón del usuario que estemos usando y nos llevará a la pagina principal de la web que es solo de consulta. + +## Tecnologias usadas + +Estas son las tecnologías o recursos utilizados para nuestra web. + +-[Express](#Epress) + + +- [Express](https://expressjs.com/)(#Express) +- [Mongoose](https://mongoosejs.com/) +- [Atlas](https://www.mongodb.com/cloud/atlas) +- [Fl0](https://fl0.io/) +- [dotenv](https://www.npmjs.com/package/dotenv) +- [express-session](https://www.npmjs.com/package/express-session) +- [express.urlencoded](https://expressjs.com/en/api.html#express.urlencoded) +- [express.static](https://expressjs.com/en/api.html#express.static) +- [Template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) +- [Pug](https://pugjs.org/api/getting-started.html) +- [Firebase](https://firebase.google.com/) + - [Firebase Auth](https://firebase.google.com/docs/auth) + - [Get Started with Firebase Authentication on Websites](https://firebase.google.com/docs/auth/web/start) + + +## Endpoints html + diff --git a/index.js b/index.js new file mode 100644 index 00000000..97d76050 --- /dev/null +++ b/index.js @@ -0,0 +1,36 @@ +const express = require('express'); +const dbConnection = require('./src/config/db.js'); +const app = express(); +const path = require('node:path'); +const session = require('express-session'); +const cors = require('cors'); +require('dotenv').config(); +const {hashedSecret} = require('./src/config/configcryp.js') + +const PORT = process.env.PORT || 3000 +const router = require('./src/routes/productRoutes.js'); + + +app.use(cors()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +app.use(session({ + secret: hashedSecret, + resave: false, + saveUninitialized: true, + cookie:{secure:false}, +})) + + +app.use('/', router); + + +const publicPath = path.resolve(__dirname, 'public') +app.use(express.static(publicPath)); + +dbConnection() + +app.listen(PORT, () => { + console.log(`Express está escuchando en el puerto http://localhost:${PORT}`) +}) \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..a32c5f69 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "backend-project-break", + "version": "1.0.0", + "description": "Vamos a montar una tienda de ropa con un catálogo de productos y un dashboard para el administrador. Los productos se guardarán en una base de datos de mongo en Atlas. Podemos usar como referencia el pdf [web_ejemplo.pdf](web_ejemplo.pdf) que contiene un ejemplo de cómo podría ser la interfaz de la tienda y el dashboard.", + "main": "index.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "jest", + "dev": "nodemon index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "5.1.1", + "buildpack": "0.0.1", + "cors": "2.8.5", + "dotenv": "16.4.5", + "express": "4.18.2", + "express-session": "1.18.0", + "firebase": "10.8.1", + "firebase-admin": "^12.0.0", + "firebaseui": "6.1.0", + "jsonwebtoken": "9.0.2", + "mongoose": "8.2.0" + }, + "devDependencies": { + "jest": "29.7.0", + "nodemon": "3.1.0" + } +} diff --git a/public/styles.css b/public/styles.css new file mode 100644 index 00000000..923e5fea --- /dev/null +++ b/public/styles.css @@ -0,0 +1,231 @@ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +body { + line-height: 1; +} + +ol, +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/*---------*/ +body { + font-family: "Poppins", sans-serif; + font-weight: 400; + font-style: normal; +} + +a { + text-decoration: none; +} + +img { + width: 300px; + height: 300px; +} + +h2 { + font-size: 16px; + font-weight: bold; + text-align: center; +} + +.nav { + display: flex; + justify-content: center; + flex-wrap: wrap; + padding: 15px; + background-color: #062ACD; + gap: 15px; + margin-bottom: 15px; +} + +.nav a { + color: #fff; +} + +.cardContainer { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 10px; +} + +.productCard { + display: flex; + flex-direction: column; + border: 1px solid rgb(178, 176, 176); + border-radius: 10px; + width: 300px; + padding: 10px; + gap: 5px; + -webkit-box-shadow: 6px 6px 5px 0px rgba(128, 126, 128, 1); + -moz-box-shadow: 6px 6px 5px 0px rgba(128, 126, 128, 1); + box-shadow: 6px 6px 5px 0px rgba(128, 126, 128, 1); +} + +.formContainer { + display: flex; + justify-content: center; +} + +.formulario { + display: flex; + flex-direction: column; + gap: 5px; + width: 300px; + margin-top: 10px; +} + +.formcategory { + display: flex; + flex-direction: column; + font-size: 12px; +} + +input { + border-radius: 10px; + padding: 5px; +} + +select { + border-radius: 10px; + padding: 5px; +} + +.containerBoton { + display: flex; + justify-content: center; + gap: 10px; +} + +.boton { + padding: 8px; + background-color: #1239ff; + color: #fff; + border-radius: 10px; + text-align: center; + border: none; + width: 100px; +} \ No newline at end of file diff --git a/src/config/configcryp.js b/src/config/configcryp.js new file mode 100644 index 00000000..f56b23d7 --- /dev/null +++ b/src/config/configcryp.js @@ -0,0 +1,7 @@ +const crypto = require('node:crypto'); +const bcrypt = require('bcrypt'); + +const secret = crypto.randomBytes(64).toString('hex'); +const hashedSecret = bcrypt.hashSync(secret, 10); + +module.exports = {hashedSecret} \ No newline at end of file diff --git a/src/config/db.js b/src/config/db.js new file mode 100644 index 00000000..597c513d --- /dev/null +++ b/src/config/db.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); +require('dotenv').config(); + +const dbConnection = async () => { + try { + await mongoose.connect(process.env.MONGO_URI) + console.log('Base de datos conectada con éxito') + } catch (error) { + console.log(error) + } +} +module.exports = dbConnection; \ No newline at end of file diff --git a/src/config/firebase.js b/src/config/firebase.js new file mode 100644 index 00000000..dd4f39b3 --- /dev/null +++ b/src/config/firebase.js @@ -0,0 +1,33 @@ +require('dotenv').config(); +// Import the functions you need from the SDKs you need +//import { initializeApp } from "firebase/app"; +const {initializeApp} = require('firebase/app') +// TODO: Add SDKs for Firebase products that you want to use +// https://firebase.google.com/docs/web/setup#available-libraries + +// Your web app's Firebase configuration +const firebaseConfig = { + apiKey: process.env.FIREBASEAPI, + authDomain: "my-denda.firebaseapp.com", + projectId: "my-denda", + storageBucket: "my-denda.appspot.com", + messagingSenderId: "860702504622", + appId: "1:860702504622:web:f820dfeb49117098ffdf3f" +}; + +// Initialize Firebase +const fireBaseApp = initializeApp(firebaseConfig); + +module.exports = fireBaseApp; +/* +const admin = require('firebase-admin'); +const {initializeApp, applicationDefault} = require('firebase-admin/app'); + + +const fireBaseApp = admin.initializeApp({ + credential: admin.credential.cert(process.env.GOOGLE_APPLICATION_CREDENTIALS) +}) +module.exports = fireBaseApp +*/ + + diff --git a/src/controllers/authController.js b/src/controllers/authController.js new file mode 100644 index 00000000..3ce9387d --- /dev/null +++ b/src/controllers/authController.js @@ -0,0 +1,29 @@ +const {getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword } = require("firebase/auth"); +const fireBaseApp = require('../config/firebase'); + +const auth = getAuth(fireBaseApp); + +const signIn = async (auth, email, password) =>{ + try { + const userCredential = await signInWithEmailAndPassword(auth, email, password); + const user = userCredential.user; + return user; + + } catch (error) { + console.error("Error signing in:", error); + throw error; + } +} + +const singUp = async (auth, email, password) =>{ + try { + const userCredential = await createUserWithEmailAndPassword(auth, email, password) + const user = userCredential.user; + console.log('user', user.uid) + return user; + } catch (error) { + console.error("Error signing up:", error); + throw error; + } +} +module.exports = {signIn, singUp}; \ No newline at end of file diff --git a/src/controllers/productController.js b/src/controllers/productController.js new file mode 100644 index 00000000..856a7a18 --- /dev/null +++ b/src/controllers/productController.js @@ -0,0 +1,416 @@ +const Product = require('../models/Product') +const {signIn, singUp} = require('../controllers/authController'); +const {getAuth } = require("firebase/auth"); +const fireBaseApp = require('../config/firebase'); +const {generateToken} = require('../middlewares/authMiddleware'); + +const htmlHead = ` + + + + + + + + + + + Document + + `; + +const htmlEnd = `` + +function getNavBar(token) { + let html = `
+ +
` + } else { + html += ` + login + + ` + } + + return html; +} + +function getProductCards(products, token) { + if (!products) { + throw new Error('El producto esta vacio o nulo') + } + let option; + let html = `
`; + if (token) { option = 'dashboard' } else { option = 'products' } + for (let product of products) { + html += ` +
+

${product.name}

+ ${product.name} + +
+ ` + } + return html + '
'; +} + +function getProductOneCard(product, token) { + if (!product) { + throw new Error('El producto esta vacio o nulo') + } + let html = `
+
+

${product.name}

+ ${product.name} +

${product.description}

+

Precio: ${product.price}€

+

Talla: ${product.size.toString().toUpperCase()}

+

Categoria: ${product.category}

+ ` + if (token) { + html += ` + + ` + } + + return html + '
'; +} + +const ProductController = { + async showProducts(req, res) { + try { + // controlar con dashboard + const token = req.session.token; + const products = await Product.find(); + if (!products) { + throw new Error('error de busqueda de productos') + } + const productCards = getProductCards(products, token); + const html = htmlHead + getNavBar(token) + productCards + htmlEnd + res.send(html); + } catch (error) { + console.error(error) + res.status(500).send('error de busqueda de productos') + } + }, + async showProductById(req, res) { + try { + // controlar con dashboard + const token = req.session.token; + const idProduct = req.params.productId; + + const product = await Product.findById(idProduct); + + const productCards = getProductOneCard(product, token) + + const html = htmlHead + getNavBar(token) + productCards + htmlEnd + res.send(html); + + + } catch (error) { + console.error(error) + res.status(500).send('error de busqueda de producto por ID') + } + }, + async showNewProduct(req, res) { + try { + const token = req.session.token; + if (token){ + const form = ` +

Crear nuevo producto

+
+
+ + + + + + + + + + + +
+ +
+
+ + + `; + + html = htmlHead + getNavBar(token) + form + htmlEnd + + res.send(html); + } + + } catch (error) { + console.error(error) + res.status(500).send('error de articulo nuevo') + } + }, + async showProductCategory(req, res) { + try { + const token = req.session.token; + const tipo = req.params; + const productCategory = await Product.find({ category: tipo.category }); + const productCards = getProductCards(productCategory, token); + const html = htmlHead + getNavBar(token) + productCards + htmlEnd + res.send(html); + + } catch (error) { + console.error(error) + res.status(500).send('error de articulo nuevo') + } + + }, + async createProduct(req, res) { + + const token = req.session.token; + if(token){ + const product = await Product.create({ ...req.body }); + if (!product) { + throw new Error('Error al añadir un articulo') + } + res.redirect('/dashboard'); + }else{ + const html = htmlHead + getNavBar(token) + '

Credenciales incorrectas

'+ htmlEnd; + res.send(html); + } + + + }, + async showEditProduct(req, res) { + try { + const token = req.session.token; + if (token){ + const idProduct = req.params.productId; + const product = await Product.findById(idProduct); + + const form = ` +

Actualizar producto

+
+
+ + + + + + + + + + + + +
+

Categoria:

+ + + + + + + + + + + +

+
+

Tallas:

+ + + + + + + + + + + + + + +

+ +
+
+ + `; + + html = htmlHead + getNavBar(token) + form + htmlEnd + res.send(html) + } + + + } catch (error) { + console.error(error) + res.status(500).send('error de edicion articulo nuevo') + } + }, + + + async updateProduct(req, res) { + try { + const token = req.session.token; + if(token){ + const idProduct = req.params.productId; + const pBody = req.body + const updateProduct = await Product.findByIdAndUpdate( + idProduct, { + name: pBody.name, + description: pBody.description, + category: pBody.category, + price: pBody.price, + image: pBody.image, + size: pBody.size + }, { new: true }) + if (!updateProduct) { + return res.status(404).json({ mensaje: 'Product id not found' }) + } + + res.redirect(`${idProduct}`) + + } + + } catch (error) { + console.error(error) + res.status(500).send('error de actualización articulo') + } + }, + async deleteProduct(req, res) { + try { + const token = req.session.token; + if(token){ + const idProduct = req.params.productId; + const deletedProduct = await Product.findByIdAndDelete(idProduct) + if (!deletedProduct) { + throw new Error('Producto no encontrado') + } + let message = `

Producto eliminado correctamente

` + html = htmlHead + getNavBar(token) + message + htmlEnd + res.send(html) + } + + } catch (error) { + res.status(500).send('error de eliminacion articulo') + } + }, + async login(req, res) { + const form = ` +

Acceso usuario

+
+
+ + + + +
+ + Registrarse +
+
+ +
+ ` + + const html = htmlHead + getNavBar() + form + htmlEnd; + + res.send(html); + + }, + async register(req, res) { + const form = ` +

Registro de usuario

+
+
+ + + + + +
+
+ ` + const html = htmlHead + getNavBar() + form + htmlEnd; + res.send(html); + + }, + + async loginFirebase (req, res){ + try { + const {email, password} = req.body; + const auth = getAuth(fireBaseApp); + const user = await signIn(auth, email, password); + console.log(user.uid); + if(user){ + const token = generateToken(user); + req.session.token = token; + res.redirect('/dashboard') + } + + } catch (error) { + const htmlError = error + html = htmlHead + getNavBar()+ htmlError + htmlEnd + res.send(html) + + } + }, + async singupFirebase (req, res){ + try { + const {email, password} = req.body; + const auth = getAuth(fireBaseApp); + const user = await singUp(auth, email, password) + if(user){ + const token = generateToken(user); + req.session.token = token; + res.redirect('/dashboard') + } + + } catch (error) { + const htmlError = error + html = htmlHead + getNavBar()+ htmlError + htmlEnd + res.send(html) + } + + }, + + async logout(req, res) { + req.session.destroy(); + res.redirect('/') + } +} + +module.exports = { + ProductController, + getProductOneCard, + getProductCards +} \ No newline at end of file diff --git a/src/middlewares/authMiddleware.js b/src/middlewares/authMiddleware.js new file mode 100644 index 00000000..9a3e4e33 --- /dev/null +++ b/src/middlewares/authMiddleware.js @@ -0,0 +1,40 @@ +const {getAuth} = require('firebase/auth'); +const firebaseApp = require('../config/firebase'); +const jwt = require('jsonwebtoken'); +const {hashedSecret} = require('../config/configcryp') + +const auth = getAuth(firebaseApp); + +function generateToken(user){ + return jwt.sign({user: user.uid},hashedSecret, {expiresIn: '1h'}) +} + + + +const authenticate = async (req, res, next) =>{ + + const token = req.session.token; + if(!token){ + return res.status(401).json({mensaje:'token no generado'}); + } + jwt.verify(token, hashedSecret, (err, decoded)=>{ + if(err){ + return res.status(401).json({mensaje:'token invalido'}); + } + req.user = decoded.user; + next(); + }) + + /* + try { + const idToken = req.headers.authorization; + const decodedToken = await auth.verifyIdToken(idToken); + req.user = decodedToken; + next(); + + } catch (error) { + res.status(401).json({ success: false, error: "Unauthorized user" }); + }*/ +} + +module.exports = {authenticate, generateToken} \ No newline at end of file diff --git a/src/models/Product.js b/src/models/Product.js new file mode 100644 index 00000000..56e54910 --- /dev/null +++ b/src/models/Product.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); + +const ProductSchema = new mongoose.Schema({ + name: String, + description: String, + image: String, + category: String, + size: String, + price: String +}, { timestamps: true }); + +const Product = mongoose.model('Product', ProductSchema); + +module.exports = Product \ No newline at end of file diff --git a/src/routes/productRoutes.js b/src/routes/productRoutes.js new file mode 100644 index 00000000..191c473f --- /dev/null +++ b/src/routes/productRoutes.js @@ -0,0 +1,31 @@ +const express = require('express'); +const router = express.Router(); +const { ProductController } = require('../controllers/productController'); +const {authenticate} = require('../middlewares/authMiddleware') + +router.get('/', ProductController.showProducts); +router.get('/products', ProductController.showProducts); +router.get('/products/:productId', ProductController.showProductById); + +router.get('/products/category/:category', ProductController.showProductCategory); + +// dashboard +router.get('/login', ProductController.login); +router.post('/login', ProductController.loginFirebase); +router.get('/register', ProductController.register); +router.post('/register', ProductController.singupFirebase) +router.get('/dashboard', authenticate, ProductController.showProducts); +router.get('/dashboard/new', authenticate, ProductController.showNewProduct); + +router.post('/dashboard', authenticate, ProductController.createProduct); +router.get('/dashboard/:productId', authenticate, ProductController.showProductById); + +router.get('/dashboard/:productId/edit', authenticate, ProductController.showEditProduct); +router.post('/dashboard/:productId', authenticate, ProductController.updateProduct); + +router.get('/dashboard/:productId/delete', authenticate, ProductController.deleteProduct); +router.get('/logout', ProductController.logout) + + + +module.exports = router; \ No newline at end of file diff --git a/test/productController.test.js b/test/productController.test.js new file mode 100644 index 00000000..b8380cbf --- /dev/null +++ b/test/productController.test.js @@ -0,0 +1,86 @@ + +const { ProductController, getProductOneCard, getProductCards } = require('../src/controllers/productController'); +const Product = require('../src/models/Product') + +jest.setTimeout(30000) + +describe('ProductController', () => { + const req ={}; + const res ={ + status: jest.fn(()=> res), + send: jest.fn(), + redirect: jest.fn() // jest.fn() para simular el comportamiento de una funcion + } + const product = { + name:'zapato', + description: 'zapato negro', + image:'http://imagen.com', + category: 'zapatos', + size: 'm', + price: '25' + } + + describe('showProducts', () =>{ + it('should return all products', async()=>{ + await ProductController.showProducts(req, res); + expect(res.send).toHaveBeenCalled(); + }); + it('should handle errors', async()=>{ + const errorMessage = 'error de busqueda de productos'; + jest.spyOn(Product, 'find').mockRejectedValue(new Error(errorMessage)); + await ProductController.showProducts(req, res); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.send).toHaveBeenCalledWith(errorMessage); + }) + }) + + describe('showProductById', () =>{ + it('should return a product by id', async()=>{ + req.params = {productId: '65645121566'} + await ProductController.showProductById(req, res); + expect(res.send).toHaveBeenCalled(); + }); + it('should handle errors', async ()=>{ + req.params = {productId: ''}; + + const errorMessage = 'error de busqueda de producto por ID'; + jest.spyOn(Product, 'findById').mockRejectedValue(new Error(errorMessage)); + + await ProductController.showProductById(req, res); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.send).toHaveBeenCalledWith(errorMessage); + }) + }) + + + +}); + + +describe('printing products', () => { + it('lanza error si el producto es nulo o vacio', () => { + expect(() => getProductCards('')).toThrow('El producto esta vacio o nulo') + }); + it('lanza error si el producto es nulo o vacio', () => { + expect(() => getProductOneCard('')).toThrow('El producto esta vacio o nulo') + }); + const product = { + name: 'zapato', + description: 'zapato negro', + image: 'http://imagen.com', + category: 'zapatos', + size: 'm', + price: '25' + } + it('should print a product', () => { + expect(getProductOneCard(product)).toEqual(`
+
+

zapato

+ \"zapato\" +

zapato negro

+

Precio: 25€

+

Talla: M

+

Categoria: zapatos

+
`) + }) +}) \ No newline at end of file