diff --git a/.gitignore b/.gitignore index 7164da4c246..70b24471080 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ test/files/main.js package-lock.json -.config* +.config.js # Compiled docs docs/*.html @@ -50,6 +50,9 @@ docs/typescript/*.html docs/api/*.html index.html +# Local Netlify folder +.netlify + # yarn package-lock yarn.lock diff --git a/examples/ecommerce-netlify-functions/.config/development.js b/examples/ecommerce-netlify-functions/.config/development.js new file mode 100644 index 00000000000..b0629c97c88 --- /dev/null +++ b/examples/ecommerce-netlify-functions/.config/development.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = Object.freeze({ + mongodbUri: 'mongodb://localhost:27017/ecommerce', + stripeSecretKey: 'YOUR STRIPE KEY HERE', + success_url: 'localhost:3000/success', + cancel_url: 'localhost:3000/cancel' +}); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/.config/index.js b/examples/ecommerce-netlify-functions/.config/index.js new file mode 100644 index 00000000000..2c58f15f9ac --- /dev/null +++ b/examples/ecommerce-netlify-functions/.config/index.js @@ -0,0 +1,13 @@ +'use strict'; + +if (process.env.NODE_ENV) { + try { + module.exports = require('./' + process.env.NODE_ENV); + console.log('Using ' + process.env.NODE_ENV); + } catch (err) { + module.exports = require('./development'); + } +} else { + console.log('using production'); + module.exports = require('./production'); +} \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/.config/test.js b/examples/ecommerce-netlify-functions/.config/test.js new file mode 100644 index 00000000000..6dc295150ea --- /dev/null +++ b/examples/ecommerce-netlify-functions/.config/test.js @@ -0,0 +1,9 @@ +'use strict'; + +module.exports = Object.freeze({ + mongodbUri: 'mongodb://localhost:27017/ecommerce_test', + stripeSecretKey: 'test', + success_url: 'localhost:3000/success', + cancel_url: 'localhost:3000/cancel' + +}); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/.netlify/edge-functions-import-map.json b/examples/ecommerce-netlify-functions/.netlify/edge-functions-import-map.json new file mode 100644 index 00000000000..aee80cf31b4 --- /dev/null +++ b/examples/ecommerce-netlify-functions/.netlify/edge-functions-import-map.json @@ -0,0 +1 @@ +{"imports":{"netlify:edge":"https://edge-bootstrap.netlify.app/v1/index.ts"}} \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/README.md b/examples/ecommerce-netlify-functions/README.md new file mode 100644 index 00000000000..8889dddd8c9 --- /dev/null +++ b/examples/ecommerce-netlify-functions/README.md @@ -0,0 +1,54 @@ +# ecommerce-netlify-functions + +This sample demonstrates using Mongoose to build an eCommerce shopping cart using [Netlify Functions](https://www.netlify.com/products/functions/), which runs on [AWS Lambda](https://mongoosejs.com/docs/lambda.html). + +Other tools include: + +1. Stripe for payment processing +2. [Mocha](https://masteringjs.io/mocha) and [Sinon](https://masteringjs.io/sinon) for testing + +## Running This Example + +1. Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/development.js` to your MongoDB server's address. +2. Run `npm install` +3. Run `npm run seed` +4. Run `npm start` +5. Visit `http://localhost:8888/.netlify/functions/getProducts` to list all available products +6. Run other endpoints using curl or postman + +## Testing + +Make sure you have a MongoDB instance running on `localhost:27017`, or update `mongodbUri` in `.config/test.js` to your MongoDB server's address. +Then run `npm test`. + +``` +$ npm test + +> test +> env NODE_ENV=test mocha ./test/*.test.js + +Using test + + + Add to Cart + ✔ Should create a cart and add a product to the cart + ✔ Should find the cart and add to the cart + ✔ Should find the cart and increase the quantity of the item(s) in the cart + + Checkout + ✔ Should do a successful checkout run + + Get the cart given an id + ✔ Should create a cart and then find the cart. + + Products + ✔ Should get all products. + + Remove From Cart + ✔ Should create a cart and then it should remove the entire item from it. + ✔ Should create a cart and then it should reduce the quantity of an item from it. + + + 8 passing (112ms) + +``` \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/connect.js b/examples/ecommerce-netlify-functions/connect.js new file mode 100644 index 00000000000..22c325f054c --- /dev/null +++ b/examples/ecommerce-netlify-functions/connect.js @@ -0,0 +1,15 @@ +'use strict'; + +const config = require('./.config'); +const mongoose = require('mongoose'); + +let conn = null; + +module.exports = async function connect() { + if (conn != null) { + return conn; + } + conn = mongoose.connection; + await mongoose.connect(config.mongodbUri); + return conn; +} \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/integrations/stripe.js b/examples/ecommerce-netlify-functions/integrations/stripe.js new file mode 100644 index 00000000000..5349ae6cbb0 --- /dev/null +++ b/examples/ecommerce-netlify-functions/integrations/stripe.js @@ -0,0 +1,5 @@ +'use strict'; + +const config = require('../.config') + +module.exports = require('stripe')(config.stripeSecretKey); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/models.js b/examples/ecommerce-netlify-functions/models.js new file mode 100644 index 00000000000..7514b9986a2 --- /dev/null +++ b/examples/ecommerce-netlify-functions/models.js @@ -0,0 +1,84 @@ +'use strict'; +const mongoose = require('mongoose'); + +const productSchema = new mongoose.Schema({ + name: String, + price: Number, + image: String +}); + +const Product = mongoose.model('Product', productSchema); + +module.exports.Product = Product; + +const orderSchema = new mongoose.Schema({ + items: [ + { productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, + quantity: { type: Number, required: true, validate: v => v > 0 } + } + ], + total: { + type: Number, + default: 0 + }, + status: { + type: String, + enum: ['PAID', 'IN_PROGRESS', 'SHIPPED', 'DELIVERED'], + default: 'PAID' + }, + orderNumber: { + type: Number, + required: true + }, + name: { + type: String, + required: true + }, + email: { + type: String, + required: true + }, + address1: { + type: String, + required: true + }, + address2: { + type: String + }, + city: { + type: String, + required: true + }, + state: { + type: String, + required: true + }, + zip: { + type: String, + required: true + }, + shipping: { + type: String, + required: true, + enum: ['standard', '2day'] + }, + paymentMethod: { + id: String, + brand: String, + last4: String + } +}, { optimisticConcurrency: true }); + +const Order = mongoose.model('Order', orderSchema); + +module.exports.Order = Order; + +const cartSchema = new mongoose.Schema({ + items: [{ productId: { type: mongoose.ObjectId, required: true, ref: 'Product' }, quantity: { type: Number, required: true } }], + orderId: { type: mongoose.ObjectId, ref: 'Order' } +}, { timestamps: true }); + +const Cart = mongoose.model('Cart', cartSchema); + +module.exports.Cart = Cart; + diff --git a/examples/ecommerce-netlify-functions/netlify/functions/addToCart.js b/examples/ecommerce-netlify-functions/netlify/functions/addToCart.js new file mode 100644 index 00000000000..70182f7021e --- /dev/null +++ b/examples/ecommerce-netlify-functions/netlify/functions/addToCart.js @@ -0,0 +1,48 @@ +'use strict'; + +const { Cart, Product } = require('../../models'); +const connect = require('../../connect'); + +const handler = async(event) => { + try { + event.body = JSON.parse(event.body || {}); + await connect(); + const products = await Product.find(); + if (event.body.cartId) { + // get the document containing the specified cartId + const cart = await Cart.findOne({ _id: event.body.cartId }).setOptions({ sanitizeFilter: true }); + + if (cart == null) { + return { statusCode: 404, body: JSON.stringify({ message: 'Cart not found' }) }; + } + if(!Array.isArray(event.body.items)) { + return { statusCode: 500, body: JSON.stringify({ error: 'items is not an array' }) }; + } + for (const product of event.body.items) { + const exists = cart.items.find(item => item?.productId?.toString() === product?.productId?.toString()); + if (!exists && products.find(p => product?.productId?.toString() === p?._id?.toString())) { + cart.items.push(product); + await cart.save(); + } else { + exists.quantity += product.quantity; + await cart.save(); + } + } + + if (!cart.items.length) { + return { statusCode: 200, body: JSON.stringify({ cart: null }) }; + } + + await cart.save(); + return { statusCode: 200, body: JSON.stringify(cart) }; + } else { + // If no cartId, create a new cart + const cart = await Cart.create({ items: event.body.items }); + return { statusCode: 200, body: JSON.stringify(cart) }; + } + } catch (error) { + return { statusCode: 500, body: error.toString() }; + } +}; + +module.exports = { handler }; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/netlify/functions/checkout.js b/examples/ecommerce-netlify-functions/netlify/functions/checkout.js new file mode 100644 index 00000000000..579f62e7c42 --- /dev/null +++ b/examples/ecommerce-netlify-functions/netlify/functions/checkout.js @@ -0,0 +1,69 @@ +'use strict'; + +const stripe = require('../../integrations/stripe') +const config = require('../../.config'); +const { Cart, Order, Product } = require('../../models'); +const connect = require('../../connect'); + +const handler = async(event) => { + try { + event.body = JSON.parse(event.body || {}); + await connect(); + const cart = await Cart.findOne({ _id: event.body.cartId }); + + const stripeProducts = { line_items: [] }; + let total = 0; + for (let i = 0; i < cart.items.length; i++) { + const product = await Product.findOne({ _id: cart.items[i].productId }); + stripeProducts.line_items.push({ + price_data: { + currency: 'usd', + product_data: { + name: product.name + }, + unit_amount: product.price + }, + quantity: cart.items[i].quantity + }); + total = total + (product.price * cart.items[i].quantity); + } + const session = await stripe.checkout.sessions.create({ + line_items: stripeProducts.line_items, + mode: 'payment', + success_url: config.success_url, + cancel_url: config.cancel_url + }); + const intent = await stripe.paymentIntents.retrieve(session.payment_intent); + if (intent.status !== 'succeeded') { + throw new Error(`Checkout failed because intent has status "${intent.status}"`); + } + const paymentMethod = await stripe.paymentMethods.retrieve(intent['payment_method']); + const orders = await Order.find(); + const orderNumber = orders.length ? orders.length + 1 : 1; + const order = await Order.create({ + items: event.body.product, + total: total, + orderNumber: orderNumber, + name: event.body.name, + email: event.body.email, + address1: event.body.address1, + city: event.body.city, + state: event.body.state, + zip: event.body.zip, + shipping: event.body.shipping, + paymentMethod: paymentMethod ? { id: paymentMethod.id, brand: paymentMethod.brand, last4: paymentMethod.last4 } : null + }); + + cart.orderId = order._id; + await cart.save(); + return { + statusCode: 200, + body: JSON.stringify({ order: order, cart: cart }), + headers: { Location: session.url } + }; + } catch (error) { + return { statusCode: 500, body: error.toString() }; + } +}; + +module.exports = { handler }; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/netlify/functions/getCart.js b/examples/ecommerce-netlify-functions/netlify/functions/getCart.js new file mode 100644 index 00000000000..a2b72ad998e --- /dev/null +++ b/examples/ecommerce-netlify-functions/netlify/functions/getCart.js @@ -0,0 +1,19 @@ +'use strict'; + +const { Cart } = require('../../models'); +const connect = require('../../connect'); + +const handler = async(event) => { + try { + await connect(); + // get the document containing the specified cartId + const cart = await Cart. + findOne({ _id: event.queryStringParameters.cartId }). + setOptions({ sanitizeFilter: true }); + return { statusCode: 200, body: JSON.stringify({ cart }) }; + } catch (error) { + return { statusCode: 500, body: error.toString() }; + } +}; + +module.exports = { handler }; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/netlify/functions/getProducts.js b/examples/ecommerce-netlify-functions/netlify/functions/getProducts.js new file mode 100644 index 00000000000..3fbc8fd9a9b --- /dev/null +++ b/examples/ecommerce-netlify-functions/netlify/functions/getProducts.js @@ -0,0 +1,16 @@ +'use strict'; + +const { Product } = require('../../models'); +const connect = require('../../connect'); + +const handler = async(event) => { + try { + await connect(); + const products = await Product.find(); + return { statusCode: 200, body: JSON.stringify(products) }; + } catch (error) { + return { statusCode: 500, body: error.toString() }; + } +}; + +module.exports = { handler }; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/netlify/functions/removeFromCart.js b/examples/ecommerce-netlify-functions/netlify/functions/removeFromCart.js new file mode 100644 index 00000000000..7124f4d36d3 --- /dev/null +++ b/examples/ecommerce-netlify-functions/netlify/functions/removeFromCart.js @@ -0,0 +1,34 @@ +'use strict'; + +const { Cart } = require('../../models'); +const connect = require('../../connect'); + +const handler = async(event) => { + try { + event.body = JSON.parse(event.body || {}); + await connect(); + const cart = await Cart.findOne({ _id: event.body.cartId }); + const index = cart.items.findIndex((item) => + item.productId.toString() == event.body.item.productId.toString() + ); + if (index == -1) { + return { statusCode: 200, body: cart }; + } + if (event.body?.item?.quantity) { + cart.items[index].quantity -= event.body.item.quantity; + if (cart.items[index].quantity <= 0) { + cart.items.splice(index, 1); + } + await cart.save(); + } else { + cart.items.splice(index, 1); + await cart.save(); + } + return { statusCode: 200, body: JSON.stringify(cart) }; + } catch (error) { + console.log(error); + return { statusCode: 500, body: error.toString() }; + } +}; + +module.exports = { handler }; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/package.json b/examples/ecommerce-netlify-functions/package.json new file mode 100644 index 00000000000..3252c62446f --- /dev/null +++ b/examples/ecommerce-netlify-functions/package.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "mongoose": "6.3.5", + "sinon": "14.0.0", + "stripe": "9.6.0" + }, + "devDependencies": { + "mocha": "10.0.0", + "netlify-cli": "10.7.1" + }, + "scripts": { + "seed": "env NODE_ENV=local node ./seed", + "start": "env NODE_ENV=local netlify dev", + "test": "env NODE_ENV=test mocha ./test/*.test.js" + } +} diff --git a/examples/ecommerce-netlify-functions/seed.js b/examples/ecommerce-netlify-functions/seed.js new file mode 100644 index 00000000000..bb7a6ceae5a --- /dev/null +++ b/examples/ecommerce-netlify-functions/seed.js @@ -0,0 +1,39 @@ +'use strict'; + +const { Product } = require('./models'); +const config = require('./.config'); +const mongoose = require('mongoose'); + +async function createProducts() { + await mongoose.connect(config.mongodbUri); + await mongoose.connection.dropDatabase(); + + await Product.create({ + name: 'iPhone 12', + price: 500, + image: 'https://images.unsplash.com/photo-1611472173362-3f53dbd65d80' + }); + + await Product.create({ + name: 'iPhone SE', + price: 600, + image: 'https://images.unsplash.com/photo-1529618160092-2f8ccc8e087b' + }); + + await Product.create({ + name: 'iPhone 12 Pro', + price: 700, + image: 'https://images.unsplash.com/photo-1603921326210-6edd2d60ca68' + }); + + await Product.create({ + name: 'iPhone 11', + price: 800, + image: 'https://images.unsplash.com/photo-1574755393849-623942496936' + }); + + console.log('done'); + process.exit(0); +} + +createProducts(); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/addToCart.test.js b/examples/ecommerce-netlify-functions/test/addToCart.test.js new file mode 100644 index 00000000000..f771516490e --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/addToCart.test.js @@ -0,0 +1,79 @@ +'use strict'; + +const { describe, it, before, after } = require('mocha'); +const assert = require('assert'); +const { handler: addToCart } = require('../netlify/functions/addToCart'); +const mongoose = require('mongoose'); +const fixtures = require('../test/fixtures'); + +describe('Add to Cart', function() { + it('Should create a cart and add a product to the cart', async function() { + const products = await fixtures.createProducts({ + product: [ + { name: 'A Test Products', price: 500 }, + { name: 'Another Test Product', price: 600 } + ] + }).then((res) => res.products); + const params = { + body: { + cartId: null, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const result = await addToCart(params); + result.body = JSON.parse(result.body); + assert(result.body); + assert(result.body.items.length); + }); + it('Should find the cart and add to the cart', async function() { + const products = await fixtures.createProducts({product: [{ productName: 'A Test Products', productPrice: 500 }, {productName: 'Another Test Product', productPrice: 600 }]}) + .then((res) => res.products); + const { cart } = await fixtures.createCart({products: []}); + const params = { + body: { + cartId: cart._id, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const findCart = await addToCart(params); + findCart.body = JSON.parse(findCart.body); + assert(findCart.body); + assert.equal(findCart.body.items.length, 2) + }); + it('Should find the cart and increase the quantity of the item(s) in the cart', async function() { + const products = await fixtures.createProducts({product: [{ productName: 'A Test Products', productPrice: 500 }, {productName: 'Another Test Product', productPrice: 600 }]}) + .then((res) => res.products); + const { cart } = await fixtures.createCart({products: []}); + const params = { + body: { + cartId: cart._id, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const findCart = await addToCart(params); + findCart.body = JSON.parse(findCart.body); + assert(findCart.body); + assert.equal(findCart.body.items.length, 2); + assert.equal(findCart.body.items[0].quantity, 2); + assert.equal(findCart.body.items[1].quantity, 1); + params.body = JSON.stringify(params.body) + const updateCart = await addToCart(params); + updateCart.body = JSON.parse(updateCart.body); + assert(updateCart.body); + assert.equal(updateCart.body.items.length, 2); + assert.equal(updateCart.body.items[0].quantity, 4); + assert.equal(updateCart.body.items[1].quantity, 2); + }); +}); diff --git a/examples/ecommerce-netlify-functions/test/checkout.test.js b/examples/ecommerce-netlify-functions/test/checkout.test.js new file mode 100644 index 00000000000..82844554bc2 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/checkout.test.js @@ -0,0 +1,52 @@ +'use strict'; + +const { describe, it, before, after } = require('mocha'); +const assert = require('assert'); +const { handler: addToCart } = require('../netlify/functions/addToCart'); +const { handler: checkout } = require('../netlify/functions/checkout'); +const mongoose = require('mongoose'); +const fixtures = require('./fixtures'); +const sinon = require('sinon'); +const stripe = require('../integrations/stripe') + +describe('Checkout', function() { + it('Should do a successful checkout run', async function() { + const products = await fixtures.createProducts({ + product: [ + { name: 'A Test Products', price: 500 }, + { name: 'Another Test Product', price: 600 } + ] + }).then((res) => res.products); + const params = { + body: { + cartId: null, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const result = await addToCart(params); + result.body = JSON.parse(result.body); + assert(result.body); + assert(result.body.items.length); + params.body.cartId = result.body._id; + sinon.stub(stripe.paymentIntents, 'retrieve').returns({status: 'succeeded', id: '123', brand: 'visa', last4: '1234'}); + sinon.stub(stripe.paymentMethods, 'retrieve').returns({status: 'succeeded', id: '123', brand: 'visa', last4: '1234'}); + sinon.stub(stripe.checkout.sessions, 'create').returns({status: 'succeeded', id: '123', brand: 'visa', last4: '1234'}); + params.body.product = params.body.items; + params.body.name = 'Test Testerson'; + params.body.email = 'test@localhost.com'; + params.body.address1 = '12345 Syndey Street'; + params.body.city = 'Miami'; + params.body.state = 'Florida'; + params.body.zip = '33145'; + params.body.shipping = 'standard'; + params.body = JSON.stringify(params.body); + const finish = await checkout(params); + finish.body = JSON.parse(finish.body); + assert(finish.body.order); + assert(finish.body.cart); + }); +}); diff --git a/examples/ecommerce-netlify-functions/test/fixtures/createCart.js b/examples/ecommerce-netlify-functions/test/fixtures/createCart.js new file mode 100644 index 00000000000..ea8ad627257 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/fixtures/createCart.js @@ -0,0 +1,7 @@ +const {Cart} = require('../../models'); + + +module.exports = async function createCart(params) { + const cart = await Cart.create({items: params.products}); + return { cart }; +} \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/fixtures/createOrder.js b/examples/ecommerce-netlify-functions/test/fixtures/createOrder.js new file mode 100644 index 00000000000..83396855605 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/fixtures/createOrder.js @@ -0,0 +1,7 @@ +const { Order } = require('../../models'); + +module.exports = async function createOrder(params) { + + const order = await Order.create(params.order); + return { order }; +}; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/fixtures/createProducts.js b/examples/ecommerce-netlify-functions/test/fixtures/createProducts.js new file mode 100644 index 00000000000..50d1e56e9e5 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/fixtures/createProducts.js @@ -0,0 +1,7 @@ +const { Product } = require('../../models'); + +module.exports = async function createProducts(params) { + const products = await Product.create(params.product); + + return { products }; +}; \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/fixtures/index.js b/examples/ecommerce-netlify-functions/test/fixtures/index.js new file mode 100644 index 00000000000..aa1b62a407a --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/fixtures/index.js @@ -0,0 +1,11 @@ +'use strict'; + +const createCart = require('./createCart'); +const createProducts = require('./createProducts'); +const createOrder = require('./createOrder'); + +module.exports = { + createCart: createCart, + createProducts: createProducts, + createOrder: createOrder +} \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/getCart.test.js b/examples/ecommerce-netlify-functions/test/getCart.test.js new file mode 100644 index 00000000000..847e39e31ea --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/getCart.test.js @@ -0,0 +1,20 @@ +'use strict'; + +const { describe, it, before, after } = require('mocha'); +const assert = require('assert'); +const { handler: getCart } = require('../netlify/functions/getCart'); +const fixtures = require('./fixtures'); +const mongoose = require('mongoose'); + +describe('Get the cart given an id', function() { + it('Should create a cart and then find the cart.', async function() { + const cart = await fixtures.createCart({ products: null }); + const params = { + queryStringParameters: { + cartId: cart._id + }, + }; + const findCart = await getCart(params); + assert.equal(findCart.statusCode, 200); + }); +}); diff --git a/examples/ecommerce-netlify-functions/test/getProducts.test.js b/examples/ecommerce-netlify-functions/test/getProducts.test.js new file mode 100644 index 00000000000..8ed414645c7 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/getProducts.test.js @@ -0,0 +1,20 @@ +'use strict'; + +const { describe, it, before, after } = require('mocha'); +const assert = require('assert'); +const { handler: getProducts } = require('../netlify/functions/getProducts'); +const fixtures = require('./fixtures'); +const mongoose = require('mongoose'); + +describe('Products', function() { + it('Should get all products.', async function() { + await fixtures.createProducts({ + product: [ + { name: 'A Test Products', price: 500 }, + { name: 'Another Test Product', price: 600 } + ] + }).then((res) => res.products); + const result = await getProducts(); + assert(result); + }); +}); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/removeFromCart.test.js b/examples/ecommerce-netlify-functions/test/removeFromCart.test.js new file mode 100644 index 00000000000..1c646ef4718 --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/removeFromCart.test.js @@ -0,0 +1,77 @@ +'use strict'; + +const { describe, it, before, after } = require('mocha'); +const assert = require('assert'); +const { handler: addToCart } = require('../netlify/functions/addToCart'); +const { handler: removeFromCart } = require('../netlify/functions/removeFromCart'); +const mongoose = require('mongoose'); +const fixtures = require('./fixtures'); + +describe('Remove From Cart', function() { + it('Should create a cart and then it should remove the entire item from it.', async function() { + const products = await fixtures.createProducts({ + product: [ + { name: 'A Test Products', price: 500 }, + { name: 'Another Test Product', price: 600 } + ] + }).then((res) => res.products); + const params = { + body: { + cartId: null, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const result = await addToCart(params); + result.body = JSON.parse(result.body); + assert(result.body); + assert.equal(result.body.items.length, 2); + const newParams = { + body: { + cartId: result.body._id, + item: { + productId: products[0]._id, + } + } + }; + newParams.body = JSON.stringify(newParams.body); + const remove = await removeFromCart(newParams); + remove.body = JSON.parse(remove.body); + assert.equal(remove.body.items.length, 1); + }); + + it('Should create a cart and then it should reduce the quantity of an item from it.', async function() { + const products = await fixtures.createProducts({product: [{ productName: 'A Test Products', productPrice: 500 }, {productName: 'Another Test Product', productPrice: 600 }]}) + .then((res) => res.products); + const params = { + body: { + cartId: null, + items: [ + { productId: products[0]._id, quantity: 2 }, + { productId: products[1]._id, quantity: 1 } + ] + } + }; + params.body = JSON.stringify(params.body); + const result = await addToCart(params); + result.body = JSON.parse(result.body); + assert(result.body); + assert(result.body.items.length); + const newParams = { + body: { + cartId: result.body._id, + item: { + productId: products[0]._id, + quantity: 1 + } + } + }; + newParams.body = JSON.stringify(newParams.body); + const remove = await removeFromCart(newParams); + remove.body = JSON.parse(remove.body); + assert.equal(remove.body.items[0].quantity, 1); + }); +}); \ No newline at end of file diff --git a/examples/ecommerce-netlify-functions/test/setup.test.js b/examples/ecommerce-netlify-functions/test/setup.test.js new file mode 100644 index 00000000000..7870e5a8bde --- /dev/null +++ b/examples/ecommerce-netlify-functions/test/setup.test.js @@ -0,0 +1,14 @@ +'use strict'; + +const { after } = require('mocha'); +const config = require('../.config'); +const mongoose = require('mongoose'); + +before(async() => { + await mongoose.connect(config.mongodbUri); + await mongoose.connection.dropDatabase(); +}); + +after(async function() { + await mongoose.disconnect(); +}); \ No newline at end of file