diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f46f82b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# package directories +../node_modules +../jspm_packages +../.idea + +../package-lock.json + +.env +# Serverless directories +.serverless + +.gitignore + diff --git a/README.md b/README.md index ae62362..d02b3f6 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,21 @@ Endpoint backend: https://o3lc79zr1i.execute-api.us-east-1.amazonaws.com/dev #### Description: Get a product by ID - productId is required - productId is String -- curl --location --request GET 'https://o3lc79zr1i.execute-api.us-east-1.amazonaws.com/dev/products/ABCDE102030' +- curl --location --request GET 'https://o3lc79zr1i.execute-api.us-east-1.amazonaws.com/dev/products/ABC001' + +### POST - Create product - /product +#### Description: Create a product + +- title: String - is required +- description: String - is requires +- price: Number - is required +- count": Number - is required + +## Import + +### Get - Image +#### Description: Get image from bucket +- curl --location --request GET 'https://guouzuadzd.execute-api.us-east-1.amazonaws.com/dev/import/Alexa' + +## Frontend +### URL: http://epam-shopcart.s3-website-us-east-1.amazonaws.com/ \ No newline at end of file diff --git a/import-service/config.js b/import-service/config.js new file mode 100644 index 0000000..cb2dc21 --- /dev/null +++ b/import-service/config.js @@ -0,0 +1,11 @@ +const dotenv = require('dotenv'); + +module.exports = async ({ options, resolveConfigurationProperty }) => { + // Load env vars into Serverless environment + // You can do more complicated env var resolution with dotenv here + const envVars = dotenv.config({ path: '.env' }).parsed; + return Object.assign( + {}, + envVars, // `dotenv` environment variables + ); +}; \ No newline at end of file diff --git a/import-service/handler.js b/import-service/handler.js new file mode 100644 index 0000000..7f8b0cc --- /dev/null +++ b/import-service/handler.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports.hello = async (event) => { + return { + statusCode: 200, + body: JSON.stringify( + { + message: 'Go Serverless v1.0! Your function executed successfully!', + input: event, + }, + null, + 2 + ), + }; + + // Use this code if you don't use the http event with the LAMBDA-PROXY integration + // return { message: 'Go Serverless v1.0! Your function executed successfully!', event }; +}; diff --git a/import-service/image.js b/import-service/image.js new file mode 100644 index 0000000..5498111 --- /dev/null +++ b/import-service/image.js @@ -0,0 +1,15 @@ +'use strict'; + +const utils = require('./utils/utils.aws.functions'); + +module.exports.importFileParser = async (event) => { + console.log(`Event: ${JSON.stringify(event)}`); + for (const record of event.Records) { + await utils.moveImage(record.s3.object.key); + } + + return { + statusCode: 200, + body: `importFileParser executed` + }; +} \ No newline at end of file diff --git a/import-service/images.js b/import-service/images.js new file mode 100644 index 0000000..159dcb3 --- /dev/null +++ b/import-service/images.js @@ -0,0 +1,27 @@ +'use strict'; +const utils = require('./utils/utils.aws.functions'); + +module.exports.importProductsFile = async (event) => { + const body = {}; + let status = 200 + + if (!event[`pathParameters`].nameImage) { + status = 500; + body.message = 'Name image not provided' + } else { + const image = event[`pathParameters`].nameImage; + const result = await utils.getSignedImage(image).then((data) => data).catch(error => error); + + body.message = result.message; + body.url = result.status === 200 ? result.url : ''; + } + + return { + statusCode: status, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Credentials': true, + }, + body: JSON.stringify(body) + }; +} \ No newline at end of file diff --git a/import-service/serverless.yml b/import-service/serverless.yml new file mode 100644 index 0000000..fe1f60e --- /dev/null +++ b/import-service/serverless.yml @@ -0,0 +1,90 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: import-service +# app and org for use with dashboard.serverless.com +#app: your-app-name +#org: your-org-name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +frameworkVersion: '3' + +provider: + name: aws + runtime: nodejs12.x + stage: dev + region: us-east-1 + profile: default + environment: + REGION: ${self:custom.dotenvVars.REGION, env:REGION, ''} + BUCKET: ${self:custom.dotenvVars.BUCKET, env:BUCKET, ''} + PATH_AWS: ${self:custom.dotenvVars.PATH_AWS, env:PATH_AWS, ''} + SQS_URL: ${self:custom.dotenvVars.SQS_URL, env:SQS_URL, ''} + iamRoleStatements: + - Effect: Allow + Action: 's3:ListBucket' + Resource: + - 'arn:aws:s3:::src-image-shop' + - Effect: Allow + Action: + - 's3:*' + Resource: + - 'arn:aws:s3:::src-image-shop/*' + - Effect: Allow + Action: 'sqs:SendMessage' + Resource: 'arn:aws:sqs:us-east-1:436988374415:catalogItemsQueue.fifo' + +plugins: + - serverless-webpack + - serverless-dotenv-plugin + +custom: + webpack: + webpackConfig: 'webpack.config.js' # Name of webpack configuration file + includeModules: false + packager: 'npm' + dotenvVars: ${./configs.js)} + +functions: + hello: + handler: handler.hello + getSignedImage: + handler: images.importProductsFile + events: + - http: + path: /import/{nameImage} + method: get + cors: + origins: '*' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: false + request: + parameters: + paths: + nameImage: true + importFileParser: + handler: image.importFileParser + events: + - s3: + bucket: src-image-shop + event: s3:ObjectCreated:* + rules: + - prefix: images/ + existing: true \ No newline at end of file diff --git a/import-service/test/image.test.js b/import-service/test/image.test.js new file mode 100644 index 0000000..184a522 --- /dev/null +++ b/import-service/test/image.test.js @@ -0,0 +1,76 @@ +const expect = require('chai').expect; +const lambda = require('lambda-tester'); +const getImage = require('../images').importProductsFile; +const moveImage = require('../image').importFileParser; + +describe('import-service', () => { + describe('lambdas', () => { + describe('get-image', () => { + it('Should return 500', async () => { + const result = await lambda(getImage) + .event({'pathParameters': {'nameImage': ''}}) + .expectResult(data => data); + expect(JSON.parse(result.statusCode)).equals(500); + expect(JSON.parse(result.body).message).equals('Name image not provided') + }); + + it('Should return 200', async () => { + const result = await lambda(getImage) + .event({'pathParameters': {'nameImage': 'Alexa'}}) + .expectResult(data => data); + expect(JSON.parse(result.statusCode)).equals(200); + expect(JSON.parse(result.body).message).equals('Success'); + }); + }); + + describe('move-image', () => { + it('Should return', async () => { + const result = await lambda(moveImage) + .event( + { + "Records": [ + { + "eventVersion": "2.1", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "2022-08-06T14:29:57.320Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "AWS:AIDAWLPUJNWH4HTC6FCEQ" + }, + "requestParameters": { + "sourceIPAddress": "187.193.146.100" + }, + "responseElements": { + "x-amz-request-id": "398JW4JQ4K36K77H", + "x-amz-id-2": "fNnnzzT9SbH+TsRwnOyqL3VpRskNgK7bF8TlstrSHTeQDk8Y70RkwrroUuN44gWxb5chRgfIERPFGtJLEzbd513QkiXFMEuf" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "import-service-dev-importFileParser-057a87b0c6d7a62748d8b7e8958d9482", + "bucket": { + "name": "src-image-shop", + "ownerIdentity": { + "principalId": "A62BSUDDNWO6V" + }, + "arn": "arn:aws:s3:::src-image-shop" + }, + "object": { + "key": "images/Alexa.jpg", + "size": 31761, + "eTag": "607177f4d4599df93e5528f477d8c315", + "sequencer": "0062EE7AE54AF819E8" + } + } + } + ] + } + ) + .expectResult(data => data); + + expect(result.statusCode).equals(200); + }); + }); + + }); +}); \ No newline at end of file diff --git a/import-service/utils/utils.aws.functions.js b/import-service/utils/utils.aws.functions.js new file mode 100644 index 0000000..06ea3d9 --- /dev/null +++ b/import-service/utils/utils.aws.functions.js @@ -0,0 +1,127 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const path = require('path'); +const csv = require('csv-parser') +const {v4: uuidv4} = require('uuid'); + +require('dotenv').config({path: path.resolve(__dirname, '../.env')}); + +const s3 = new AWS.S3({region: 'us-east-1'}); +const sqs = new AWS.SQS({region: 'us-east-1'}); + +const bucket = process.env.BUCKET; +const uploaded = process.env.PATH_AWS; +const SQS_URL = process.env.SQS_URL; + +const getSignedImage = (image) => new Promise(async (resolve, reject) => { + try { + console.log(`getSignedImage executing`); + console.log(`Image: ${image}`); + const params = { + Bucket: bucket, + Key: `${uploaded}/${image}.jpg` + }; + + console.log(JSON.stringify(params)); + + console.log(`Start getSignedUrl function`); + + await s3.getSignedUrl('getObject', params, (error, url) => { + if (error) { + console.log(error); + reject({status: 500, error, message: 'An error occurred getting signed url'}); + } + resolve({status: 200, url, message: 'Success'}); + }); + } catch (error) { + console.log(`Error on getSignedImage: ${error}`); + reject({status: 500, message: `Error on getSignedImage: ${error}`}) + } +}); + +const moveImage = async (image) => { + try { + const params = { + Bucket: bucket, + Key: image + }; + + /*console.log(`Params: ${JSON.stringify(params)}`); + + const s3Stream = await s3.getObject(params).createReadStream(); + + console.log(`s3Stream: ${JSON.stringify(s3Stream)}`); + + await s3Stream.pipe(csv()) + .on('data', data => { + console.log(data); + }) + .on('end', async () => { + console.log(`${bucket}/${image}`); + await s3.copyObject({ + Bucket: bucket, + CopySource: `${bucket}/${image}`, + Key: image.replace('images', path) + }).promise(); + + await s3.deleteObject({ + Bucket: bucket, + Key: image + }).promise(); + + console.log(`Copied into ${bucket}/${image.replace('images', path)}`); + });*/ + + await s3.copyObject({ + Bucket: bucket, + CopySource: `${bucket}/${image}`, + Key: image.replace('images', uploaded) + }).promise(); + + await s3.deleteObject(params).promise(); + + const product = {} + + product.id = uuidv4(); + product.title = image.substring(7, image.length - 4); + product.description = image.substring(7, image.length - 4); + product.price = 0; + product.count = 0; + + await sender(product); + + console.log(`Copied into ${bucket}/${image.replace('images', uploaded)}`); + return true; + } catch (error) { + console.log(`Error on moveImage: ${error}`); + return false; + } +} + +const sender = async (product) => { + try { + console.log(`Sender SQS executing`); + const params = { + MessageBody: JSON.stringify(product), + MessageDeduplicationId: product.id, + MessageGroupId: product.id, + QueueUrl: SQS_URL + }; + + console.log(`Params SQS ${JSON.stringify(params)}`) + + await sqs.sendMessage(params, (error, data) => { + console.log(`sqs.sendMessage`); + if (error) { + console.log(`Error on sqs send message ${JSON.stringify(error)}`); + } + console.log(`Data: ${JSON.stringify(data)}`); + }) + } catch (error) { + console.log(error); + } + +} + +module.exports = {getSignedImage, moveImage} \ No newline at end of file diff --git a/import-service/webpack.config.js b/import-service/webpack.config.js new file mode 100644 index 0000000..0266cb6 --- /dev/null +++ b/import-service/webpack.config.js @@ -0,0 +1,11 @@ +const slsw = require('serverless-webpack'); +const nodeExternals = require('webpack-node-externals'); +module.exports = { + context: __dirname, + mode: slsw.lib.webpack.isLocal ? 'development' : 'production', + entry: slsw.lib.entries, + target: 'node', + /*target: 'node', + mode: 'none',*/ + externals: [nodeExternals(), 'pg-native'], +}; diff --git a/package.json b/package.json index 8341761..2a16f57 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,20 @@ "author": "", "license": "ISC", "dependencies": { + "@supercharge/strings": "^1.28.0", + "aws-sdk": "^2.1181.0", "chai": "^4.3.6", + "csv-parser": "^3.0.0", + "dotenv": "^16.0.1", "lambda-tester": "^4.0.1", - "mocha": "^10.0.0" + "mocha": "^10.0.0", + "pg": "^8.7.3", + "uuid": "^8.3.2", + "webpack": "^5.73.0", + "webpack-node-externals": "^3.0.0" + }, + "devDependencies": { + "serverless-dotenv-plugin": "^4.0.1", + "serverless-webpack": "^5.7.1" } } diff --git a/product-service/batch-process.js b/product-service/batch-process.js new file mode 100644 index 0000000..d4299a2 --- /dev/null +++ b/product-service/batch-process.js @@ -0,0 +1,42 @@ +'use strict'; + +const util = require('./utils/utils.aws.functions'); + +const product = require('./controller/product.controller').create; +const stock = require('./controller/stock.controller').create; + +module.exports.catalogBatchProcess = async (event) => { + console.log(JSON.stringify(event)); + //revived sqs event + for (const record of event.Records) { + //create product + console.log(JSON.stringify(record)); + const body = JSON.parse(record.body); + const p = {}; + p.id = body.id; + p.title = body.title; + p.price = body.price; + p.description = body.description; + + console.log(JSON.stringify(p)); + + let isCreated = await product(p); + + const s = {} + s.productId = p.id; + s.count = body.count; + + if(isCreated) { + isCreated = await stock(s); + + if (isCreated) { + await util.sendEmail(p); + } + } + } + + return { + statusCode: 200, + body: `catalogBatchProcess executed` + }; +}; \ No newline at end of file diff --git a/product-service/config.js b/product-service/config.js new file mode 100644 index 0000000..cb2dc21 --- /dev/null +++ b/product-service/config.js @@ -0,0 +1,11 @@ +const dotenv = require('dotenv'); + +module.exports = async ({ options, resolveConfigurationProperty }) => { + // Load env vars into Serverless environment + // You can do more complicated env var resolution with dotenv here + const envVars = dotenv.config({ path: '.env' }).parsed; + return Object.assign( + {}, + envVars, // `dotenv` environment variables + ); +}; \ No newline at end of file diff --git a/product-service/controller/product.controller.js b/product-service/controller/product.controller.js index 0fd534e..46f5fe8 100644 --- a/product-service/controller/product.controller.js +++ b/product-service/controller/product.controller.js @@ -1,12 +1,69 @@ 'use strict'; -const model = require('../model/product.model').products; +const {db} = require('../utils/util.connection'); -const selectAll = function () { - return model; + +const selectAll = async () => { + try { + console.log(`selectedAll executing`); + console.log(`DB Connection`); + const client = await db.connect(); + console.log(`Selecting from Product Table`); + const select = await client.query('SELECT p.*, s.count FROM product p INNER JOIN stock s ON p.id = s.product_id'); + client.release(); + console.log(`Products: ${JSON.stringify(select.rows)}`); + return select.rows; + } catch (error) { + console.log(`Error occurred: ${error}`); + return undefined; + } } -const selectById = function (product_id) { - return model.find(product => product.id === product_id); +const selectById = async (id) => { + try { + console.log(`selectById executing`); + console.log(`DB Connection`); + const client = await db.connect(); + console.log(`Selecting from Product Table by ${id}`); + const select = await client.query( + `SELECT p.*, s.count FROM product p INNER JOIN stock s ON p.id = s.product_id WHERE p.id = $1`, + [id] + ); + client.release(); + console.log(`Product: ${JSON.stringify(select.rows)}`); + return select.rows; + } catch (error) { + console.log(`Error occurred: ${error}`); + return undefined; + } } -module.exports = {selectAll, selectById}; \ No newline at end of file +const create = async (product) => { + console.log(`create product executing`); + console.log(`DB Connection`); + const client = await db.connect(); + let isCreated = true; + try { + console.log(`BEGIN TRANSACTION`); + await client.query('BEGIN'); + + console.log(`INSERT product`); + await client.query( + 'INSERT INTO public.product (id, title, description, price) VALUES ($1, $2, $3, $4)', + [product.id, product.title, product.description, product.price] + ); + console.log(`COMMIT TRANSACTION`); + await client.query('COMMIT'); + + } catch (error) { + console.log(`ROLLBACK TRANSACTION`); + await client.query('ROLLBACK'); + console.log(`An error occurred ${error}`); + isCreated = false; + } finally { + client.release(); + } + + return isCreated; +}; + +module.exports = {selectAll, selectById, create}; \ No newline at end of file diff --git a/product-service/controller/stock.controller.js b/product-service/controller/stock.controller.js new file mode 100644 index 0000000..7bc9c9c --- /dev/null +++ b/product-service/controller/stock.controller.js @@ -0,0 +1,29 @@ +'use strict'; +const {db} = require('../utils/util.connection'); + +const create = async (stock) => { + console.log(`create stock executing`); + console.log(`DB Connection`); + const client = await db.connect(); + let isCreated = true; + try { + console.log(`BEGIN TRANSACTION`); + await client.query('BEGIN'); + + console.log(`INSERT stock`); + await client.query('INSERT INTO public.stock (product_id, count) VALUES ($1, $2)', [stock.productId, stock.count]); + + console.log(`COMMIT TRANSACTION`); + await client.query('COMMIT'); + } catch (error) { + console.log(`ROLLBACK TRANSACTION`); + await client.query('ROLLBACK'); + console.log(`An error occurred ${error}`); + isCreated = false; + } finally { + client.release(); + } + return isCreated; +}; + +module.exports = {create}; \ No newline at end of file diff --git a/product-service/create-product.js b/product-service/create-product.js new file mode 100644 index 0000000..d7f60fc --- /dev/null +++ b/product-service/create-product.js @@ -0,0 +1,74 @@ +'use strict'; +const {v4: uuidv4} = require('uuid'); + +const product = require('./controller/product.controller').create; +const stock = require('./controller/stock.controller').create; + +const utils = require('./utils/utils.functions'); + +module.exports.createProduct = async (event) => { + let data; + let status = 201; + + try { + if (!event.body) { + status = 501; + data = 'Data not provided' + } else { + let body = JSON.parse(event.body); + + if (body.title === undefined + || body.description === undefined + || body.price === undefined + || body.count === undefined) { + status = 502; + data = 'Missing Data'; + } else { + + if (!utils.validateProductData(body)) { + status = 400; + data = 'Data is Invalid'; + } else { + const p = {} + p.id = uuidv4(); + p.title = body.title; + p.price = body.price; + + let isCreated = await product(p); + + if(isCreated) { + console.log(`Create Stock from Product`); + const s = {}; + s.productId = p.id; + s.count = body.count; + + isCreated = await stock(s); + + if(isCreated) { + data = `Product created ID: ${p.id}`; + } else { + status = 504; + data = `An error occurred on Stock creation`; + } + } else { + status = 503; + data = `Can't created product, an error occurred`; + } + } + } + } + } catch (error) { + console.log(`Error: ${error}`); + status = 500; + data = `An error occurred` + } + + return { + statusCode: status, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Credentials': true, + }, + body: JSON.stringify(data) + }; +} \ No newline at end of file diff --git a/product-service/get-product.js b/product-service/get-product.js index 764b8b4..205d036 100644 --- a/product-service/get-product.js +++ b/product-service/get-product.js @@ -2,18 +2,22 @@ const getById = require('./controller/product.controller').selectById; -module.exports.getProductsById = async (event) => { +module.exports.getProductById = async (event) => { let status = 200; let product = {} if (event[`pathParameters`].productId === '' || !event[`pathParameters`].productId) { status = 500; } else { const id = event[`pathParameters`].productId; - product = getById(id); + product = await getById(id); if(product === undefined) { status = 404 + product = { + message: 'Product not found' + } } } + console.log(product); return { statusCode: status, headers: { diff --git a/product-service/get-products.js b/product-service/get-products.js index 66477b1..d194d9d 100644 --- a/product-service/get-products.js +++ b/product-service/get-products.js @@ -3,7 +3,10 @@ const selectAll = require('./controller/product.controller').selectAll; module.exports.getProductsList = async () => { - const products = selectAll(); + console.log('getProductsList Started') + const products = await selectAll(); + console.log('getProductsList executed') + console.log(JSON.stringify(products)); return { statusCode: 200, headers: { diff --git a/product-service/path.json b/product-service/path.json new file mode 100644 index 0000000..cdef318 --- /dev/null +++ b/product-service/path.json @@ -0,0 +1 @@ +{"pathParameters":{"productId":"ABCDE102030"}} \ No newline at end of file diff --git a/product-service/serverless.yml b/product-service/serverless.yml index b8493cc..7d4f287 100644 --- a/product-service/serverless.yml +++ b/product-service/serverless.yml @@ -25,6 +25,31 @@ provider: runtime: nodejs12.x stage: dev region: us-east-1 + environment: + USER_DB: ${self:custom.dotenvVars.USER_DB, env:USER_DB, ''} + HOST_DB: ${self:custom.dotenvVars.HOST_DB, env:HOST_DB, ''} + PWD_DB: ${self:custom.dotenvVars.PWD_DB, env:PWD_DB, ''} + PORT_DB: ${self:custom.dotenvVars.PORT_DB, env:PORT_DB, ''} + DB_NAME: ${self:custom.dotenvVars.DB_NAME, env:DB_NAME, ''} + SNS_TOPIC: ${self:custom.dotenvVars.SNS_TOPIC, env:SNS_TOPIC, ''} + iamRoleStatements: + - Effect: Allow + Action: 'sqs:GetQueueAttributes' + Resource: '*' + - Effect: Allow + Action: 'sns:Publish' + Resource: 'arn:aws:sns:us-east-1:436988374415:createProductTopic' + +plugins: + - serverless-webpack + - serverless-dotenv-plugin + +custom: + dotenvVars: ${./configs.js)} + webpack: + webpackConfig: 'webpack.config.js' # Name of webpack configuration file + includeModules: false + packager: 'npm' # you can overwrite defaults here # stage: dev @@ -47,23 +72,48 @@ functions: - X-Amz-Security-Token - X-Amz-User-Agent allowCredentials: false - getProductsById: + getProductById: handler: get-product.getProductById events: - http: path: /products/{productId} method: get - cors: - origins: '*' - headers: - - Content-Type - - X-Amz-Date - - Authorization - - X-Api-Key - - X-Amz-Security-Token - - X-Amz-User-Agent - allowCredentials: false - request: - parameters: - paths: - productId: true \ No newline at end of file + cors: + origins: '*' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: false + request: + parameters: + paths: + productId: true + createProduct: + handler: create-product.createProduct + events: + - http: + path: products + method: post + cors: + origin: '*' # <-- Specify allowed origin + headers: # <-- Specify allowed headers + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token + - X-Amz-User-Agent + allowCredentials: false + catalogBatchProcess: + handler: batch-process.catalogBatchProcess + events: + - sqs: + arn: 'arn:aws:sqs:us-east-1:436988374415:catalogItemsQueue.fifo' + batchSize: 5 +# - sns: +# arn: 'arn:aws:sns:us-east-1:436988374415:createProductTopic' +# topicName: createProductTopic \ No newline at end of file diff --git a/product-service/test/lambda.product.test.js b/product-service/test/lambda.product.test.js index c2c0fb1..6492e0c 100644 --- a/product-service/test/lambda.product.test.js +++ b/product-service/test/lambda.product.test.js @@ -3,20 +3,22 @@ const expect = require('chai').expect; const lambda = require('lambda-tester'); const getProductList = require('../get-products').getProductsList; -const getProductById = require('../get-product').getProductsById; +const getProductById = require('../get-product').getProductById; +const createProduct = require('../create-product').createProduct; +const batch = require('../batch-process').catalogBatchProcess describe('product-service', () => { describe('lambdas-aws', () => { describe('get-products', () => { - it('Should return 200 - all products', async () => { - const result = await lambda(getProductList) - .event() - .expectResult(response => response); - - expect(result.statusCode).equals(200); - expect(JSON.parse(result.body).length).greaterThan(1); - }); + it('Should return 200 - all products', async () => { + const result = await lambda(getProductList) + .event() + .expectResult(response => response); + + expect(result.statusCode).equals(200); + expect(JSON.parse(result.body).length).greaterThan(1); + }); }); describe('get-product', () => { @@ -35,9 +37,10 @@ describe('product-service', () => { .expectResult(response => response); expect(result.statusCode).equals(404); - expect(result.body).equals(undefined); + expect(JSON.parse(result.body).message).equals('Product not found'); }); + it('Should return 200 with productId', async () => { const result = await lambda(getProductById) .event({'pathParameters': {'productId': 'ABCDE102030'}}) @@ -47,6 +50,540 @@ describe('product-service', () => { expect(JSON.parse(result.body).id).equals('ABCDE102030'); }); }); + + describe('create-product', () => { + it('Should return 500 when event is wrong' , async () => { + const result = await lambda(createProduct) + .event( { + resource: '/products', + path: '/products', + httpMethod: 'POST', + headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'CloudFront-Forwarded-Proto': 'https', + 'CloudFront-Is-Desktop-Viewer': 'true', + 'CloudFront-Is-Mobile-Viewer': 'false', + 'CloudFront-Is-SmartTV-Viewer': 'false', + 'CloudFront-Is-Tablet-Viewer': 'false', + 'CloudFront-Viewer-ASN': '28509', + 'CloudFront-Viewer-Country': 'MX', + 'Content-Type': 'application/json', + Host: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + 'Postman-Token': 'da51eb72-aa2e-41ea-a120-6f14e21b1d94', + 'User-Agent': 'PostmanRuntime/7.29.2', + Via: '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)', + 'X-Amz-Cf-Id': 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==', + 'X-Amzn-Trace-Id': 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df', + 'X-Forwarded-For': '187.253.120.36, 130.176.179.78', + 'X-Forwarded-Port': '443', + 'X-Forwarded-Proto': 'https' + }, + multiValueHeaders: { + Accept: [ '*/*' ], + 'Accept-Encoding': [ 'gzip, deflate, br' ], + 'CloudFront-Forwarded-Proto': [ 'https' ], + 'CloudFront-Is-Desktop-Viewer': [ 'true' ], + 'CloudFront-Is-Mobile-Viewer': [ 'false' ], + 'CloudFront-Is-SmartTV-Viewer': [ 'false' ], + 'CloudFront-Is-Tablet-Viewer': [ 'false' ], + 'CloudFront-Viewer-ASN': [ '28509' ], + 'CloudFront-Viewer-Country': [ 'MX' ], + 'Content-Type': [ 'application/json' ], + Host: [ 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com' ], + 'Postman-Token': [ 'da51eb72-aa2e-41ea-a120-6f14e21b1d94' ], + 'User-Agent': [ 'PostmanRuntime/7.29.2' ], + Via: [ + '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)' + ], + 'X-Amz-Cf-Id': [ 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==' ], + 'X-Amzn-Trace-Id': [ 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df' ], + 'X-Forwarded-For': [ '187.253.120.36, 130.176.179.78' ], + 'X-Forwarded-Port': [ '443' ], + 'X-Forwarded-Proto': [ 'https' ] + }, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: { + resourceId: 'cic4y4', + resourcePath: '/products', + httpMethod: 'POST', + extendedRequestId: 'Vr046GH6oAMF93A=', + requestTime: '22/Jul/2022:20:00:18 +0000', + path: '/dev/products', + accountId: '436988374415', + protocol: 'HTTP/1.1', + stage: 'dev', + domainPrefix: 'o3lc79zr1i', + requestTimeEpoch: 1658520018467, + requestId: 'be81d25d-203b-4d50-9908-b173622b9b35', + identity: { + cognitoIdentityPoolId: null, + accountId: null, + cognitoIdentityId: null, + caller: null, + sourceIp: '187.253.120.36', + principalOrgId: null, + accessKey: null, + cognitoAuthenticationType: null, + cognitoAuthenticationProvider: null, + userArn: null, + userAgent: 'PostmanRuntime/7.29.2', + user: null + }, + domainName: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + apiId: 'o3lc79zr1i' + }, + body: '{\r\n' + + ' "title": "Test product :D",\r\n' + + ' "description": "Test description",\r\n' + + ' "price": 10,\r\n' + + '}', + isBase64Encoded: false + }) + .expectResult(response => response); + + expect(result.statusCode).equals(500); + expect(JSON.parse(result.body)).equals('An error occurred'); + }); + + it('Should return 501 with not event data', async () => { + const result = await lambda(createProduct) + .event({}) + .expectResult(response => response); + + expect(result.statusCode).equals(501); + expect(JSON.parse(result.body)).equals('Data not provided'); + }); + + it('Should return 502 whit missing data', async () => { + const result = await lambda(createProduct) + .event( { + resource: '/products', + path: '/products', + httpMethod: 'POST', + headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'CloudFront-Forwarded-Proto': 'https', + 'CloudFront-Is-Desktop-Viewer': 'true', + 'CloudFront-Is-Mobile-Viewer': 'false', + 'CloudFront-Is-SmartTV-Viewer': 'false', + 'CloudFront-Is-Tablet-Viewer': 'false', + 'CloudFront-Viewer-ASN': '28509', + 'CloudFront-Viewer-Country': 'MX', + 'Content-Type': 'application/json', + Host: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + 'Postman-Token': 'da51eb72-aa2e-41ea-a120-6f14e21b1d94', + 'User-Agent': 'PostmanRuntime/7.29.2', + Via: '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)', + 'X-Amz-Cf-Id': 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==', + 'X-Amzn-Trace-Id': 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df', + 'X-Forwarded-For': '187.253.120.36, 130.176.179.78', + 'X-Forwarded-Port': '443', + 'X-Forwarded-Proto': 'https' + }, + multiValueHeaders: { + Accept: [ '*/*' ], + 'Accept-Encoding': [ 'gzip, deflate, br' ], + 'CloudFront-Forwarded-Proto': [ 'https' ], + 'CloudFront-Is-Desktop-Viewer': [ 'true' ], + 'CloudFront-Is-Mobile-Viewer': [ 'false' ], + 'CloudFront-Is-SmartTV-Viewer': [ 'false' ], + 'CloudFront-Is-Tablet-Viewer': [ 'false' ], + 'CloudFront-Viewer-ASN': [ '28509' ], + 'CloudFront-Viewer-Country': [ 'MX' ], + 'Content-Type': [ 'application/json' ], + Host: [ 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com' ], + 'Postman-Token': [ 'da51eb72-aa2e-41ea-a120-6f14e21b1d94' ], + 'User-Agent': [ 'PostmanRuntime/7.29.2' ], + Via: [ + '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)' + ], + 'X-Amz-Cf-Id': [ 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==' ], + 'X-Amzn-Trace-Id': [ 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df' ], + 'X-Forwarded-For': [ '187.253.120.36, 130.176.179.78' ], + 'X-Forwarded-Port': [ '443' ], + 'X-Forwarded-Proto': [ 'https' ] + }, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: { + resourceId: 'cic4y4', + resourcePath: '/products', + httpMethod: 'POST', + extendedRequestId: 'Vr046GH6oAMF93A=', + requestTime: '22/Jul/2022:20:00:18 +0000', + path: '/dev/products', + accountId: '436988374415', + protocol: 'HTTP/1.1', + stage: 'dev', + domainPrefix: 'o3lc79zr1i', + requestTimeEpoch: 1658520018467, + requestId: 'be81d25d-203b-4d50-9908-b173622b9b35', + identity: { + cognitoIdentityPoolId: null, + accountId: null, + cognitoIdentityId: null, + caller: null, + sourceIp: '187.253.120.36', + principalOrgId: null, + accessKey: null, + cognitoAuthenticationType: null, + cognitoAuthenticationProvider: null, + userArn: null, + userAgent: 'PostmanRuntime/7.29.2', + user: null + }, + domainName: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + apiId: 'o3lc79zr1i' + }, + body: '{\r\n' + + ' "title": "Test product :D",\r\n' + + ' "description": "Test description",\r\n' + + ' "price": 10\r\n' + + '}', + isBase64Encoded: false + }) + .expectResult(response => response); + + expect(result.statusCode).equals(502); + expect(JSON.parse(result.body)).equals('Missing Data'); + }); + + it('Should return 400 whit wrong data typeof', async () => { + const result = await lambda(createProduct) + .event( { + resource: '/products', + path: '/products', + httpMethod: 'POST', + headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'CloudFront-Forwarded-Proto': 'https', + 'CloudFront-Is-Desktop-Viewer': 'true', + 'CloudFront-Is-Mobile-Viewer': 'false', + 'CloudFront-Is-SmartTV-Viewer': 'false', + 'CloudFront-Is-Tablet-Viewer': 'false', + 'CloudFront-Viewer-ASN': '28509', + 'CloudFront-Viewer-Country': 'MX', + 'Content-Type': 'application/json', + Host: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + 'Postman-Token': 'da51eb72-aa2e-41ea-a120-6f14e21b1d94', + 'User-Agent': 'PostmanRuntime/7.29.2', + Via: '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)', + 'X-Amz-Cf-Id': 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==', + 'X-Amzn-Trace-Id': 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df', + 'X-Forwarded-For': '187.253.120.36, 130.176.179.78', + 'X-Forwarded-Port': '443', + 'X-Forwarded-Proto': 'https' + }, + multiValueHeaders: { + Accept: [ '*/*' ], + 'Accept-Encoding': [ 'gzip, deflate, br' ], + 'CloudFront-Forwarded-Proto': [ 'https' ], + 'CloudFront-Is-Desktop-Viewer': [ 'true' ], + 'CloudFront-Is-Mobile-Viewer': [ 'false' ], + 'CloudFront-Is-SmartTV-Viewer': [ 'false' ], + 'CloudFront-Is-Tablet-Viewer': [ 'false' ], + 'CloudFront-Viewer-ASN': [ '28509' ], + 'CloudFront-Viewer-Country': [ 'MX' ], + 'Content-Type': [ 'application/json' ], + Host: [ 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com' ], + 'Postman-Token': [ 'da51eb72-aa2e-41ea-a120-6f14e21b1d94' ], + 'User-Agent': [ 'PostmanRuntime/7.29.2' ], + Via: [ + '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)' + ], + 'X-Amz-Cf-Id': [ 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==' ], + 'X-Amzn-Trace-Id': [ 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df' ], + 'X-Forwarded-For': [ '187.253.120.36, 130.176.179.78' ], + 'X-Forwarded-Port': [ '443' ], + 'X-Forwarded-Proto': [ 'https' ] + }, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: { + resourceId: 'cic4y4', + resourcePath: '/products', + httpMethod: 'POST', + extendedRequestId: 'Vr046GH6oAMF93A=', + requestTime: '22/Jul/2022:20:00:18 +0000', + path: '/dev/products', + accountId: '436988374415', + protocol: 'HTTP/1.1', + stage: 'dev', + domainPrefix: 'o3lc79zr1i', + requestTimeEpoch: 1658520018467, + requestId: 'be81d25d-203b-4d50-9908-b173622b9b35', + identity: { + cognitoIdentityPoolId: null, + accountId: null, + cognitoIdentityId: null, + caller: null, + sourceIp: '187.253.120.36', + principalOrgId: null, + accessKey: null, + cognitoAuthenticationType: null, + cognitoAuthenticationProvider: null, + userArn: null, + userAgent: 'PostmanRuntime/7.29.2', + user: null + }, + domainName: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + apiId: 'o3lc79zr1i' + }, + body: '{\r\n' + + ' "title": false,\r\n' + + ' "description": "Test description",\r\n' + + ' "price": "10",\r\n' + + ' "count": false\r\n' + + '}', + isBase64Encoded: false + }) + .expectResult(response => response); + + expect(result.statusCode).equals(400); + expect(JSON.parse(result.body)).equals('Data is Invalid'); + }); + + it('Should return 504 whit wrong data typeof', async () => { + const result = await lambda(createProduct) + .event( { + resource: '/products', + path: '/products', + httpMethod: 'POST', + headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'CloudFront-Forwarded-Proto': 'https', + 'CloudFront-Is-Desktop-Viewer': 'true', + 'CloudFront-Is-Mobile-Viewer': 'false', + 'CloudFront-Is-SmartTV-Viewer': 'false', + 'CloudFront-Is-Tablet-Viewer': 'false', + 'CloudFront-Viewer-ASN': '28509', + 'CloudFront-Viewer-Country': 'MX', + 'Content-Type': 'application/json', + Host: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + 'Postman-Token': 'da51eb72-aa2e-41ea-a120-6f14e21b1d94', + 'User-Agent': 'PostmanRuntime/7.29.2', + Via: '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)', + 'X-Amz-Cf-Id': 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==', + 'X-Amzn-Trace-Id': 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df', + 'X-Forwarded-For': '187.253.120.36, 130.176.179.78', + 'X-Forwarded-Port': '443', + 'X-Forwarded-Proto': 'https' + }, + multiValueHeaders: { + Accept: [ '*/*' ], + 'Accept-Encoding': [ 'gzip, deflate, br' ], + 'CloudFront-Forwarded-Proto': [ 'https' ], + 'CloudFront-Is-Desktop-Viewer': [ 'true' ], + 'CloudFront-Is-Mobile-Viewer': [ 'false' ], + 'CloudFront-Is-SmartTV-Viewer': [ 'false' ], + 'CloudFront-Is-Tablet-Viewer': [ 'false' ], + 'CloudFront-Viewer-ASN': [ '28509' ], + 'CloudFront-Viewer-Country': [ 'MX' ], + 'Content-Type': [ 'application/json' ], + Host: [ 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com' ], + 'Postman-Token': [ 'da51eb72-aa2e-41ea-a120-6f14e21b1d94' ], + 'User-Agent': [ 'PostmanRuntime/7.29.2' ], + Via: [ + '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)' + ], + 'X-Amz-Cf-Id': [ 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==' ], + 'X-Amzn-Trace-Id': [ 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df' ], + 'X-Forwarded-For': [ '187.253.120.36, 130.176.179.78' ], + 'X-Forwarded-Port': [ '443' ], + 'X-Forwarded-Proto': [ 'https' ] + }, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: { + resourceId: 'cic4y4', + resourcePath: '/products', + httpMethod: 'POST', + extendedRequestId: 'Vr046GH6oAMF93A=', + requestTime: '22/Jul/2022:20:00:18 +0000', + path: '/dev/products', + accountId: '436988374415', + protocol: 'HTTP/1.1', + stage: 'dev', + domainPrefix: 'o3lc79zr1i', + requestTimeEpoch: 1658520018467, + requestId: 'be81d25d-203b-4d50-9908-b173622b9b35', + identity: { + cognitoIdentityPoolId: null, + accountId: null, + cognitoIdentityId: null, + caller: null, + sourceIp: '187.253.120.36', + principalOrgId: null, + accessKey: null, + cognitoAuthenticationType: null, + cognitoAuthenticationProvider: null, + userArn: null, + userAgent: 'PostmanRuntime/7.29.2', + user: null + }, + domainName: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + apiId: 'o3lc79zr1i' + }, + body: '{\r\n' + + ' "title": "Test product :D",\r\n' + + ' "description": "Test description",\r\n' + + ' "price": "10",\r\n' + + ' "count": false\r\n' + + '}', + isBase64Encoded: false + }) + .expectResult(response => response); + + expect(result.statusCode).equals(504); + expect(JSON.parse(result.body)).equals('An error occurred on Stock creation'); + }); + + it('Should return 200 with correct json', async () => { + const result = await lambda(createProduct) + .event( + { + resource: '/products', + path: '/products', + httpMethod: 'POST', + headers: { + Accept: '*/*', + 'Accept-Encoding': 'gzip, deflate, br', + 'CloudFront-Forwarded-Proto': 'https', + 'CloudFront-Is-Desktop-Viewer': 'true', + 'CloudFront-Is-Mobile-Viewer': 'false', + 'CloudFront-Is-SmartTV-Viewer': 'false', + 'CloudFront-Is-Tablet-Viewer': 'false', + 'CloudFront-Viewer-ASN': '28509', + 'CloudFront-Viewer-Country': 'MX', + 'Content-Type': 'application/json', + Host: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + 'Postman-Token': 'da51eb72-aa2e-41ea-a120-6f14e21b1d94', + 'User-Agent': 'PostmanRuntime/7.29.2', + Via: '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)', + 'X-Amz-Cf-Id': 'AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w==', + 'X-Amzn-Trace-Id': 'Root=1-62db01d2-5bca1ccb57b50f957f73a1df', + 'X-Forwarded-For': '187.253.120.36, 130.176.179.78', + 'X-Forwarded-Port': '443', + 'X-Forwarded-Proto': 'https' + }, + multiValueHeaders: { + Accept: ['*/*'], + 'Accept-Encoding': ['gzip, deflate, br'], + 'CloudFront-Forwarded-Proto': ['https'], + 'CloudFront-Is-Desktop-Viewer': ['true'], + 'CloudFront-Is-Mobile-Viewer': ['false'], + 'CloudFront-Is-SmartTV-Viewer': ['false'], + 'CloudFront-Is-Tablet-Viewer': ['false'], + 'CloudFront-Viewer-ASN': ['28509'], + 'CloudFront-Viewer-Country': ['MX'], + 'Content-Type': ['application/json'], + Host: ['o3lc79zr1i.execute-api.us-east-1.amazonaws.com'], + 'Postman-Token': ['da51eb72-aa2e-41ea-a120-6f14e21b1d94'], + 'User-Agent': ['PostmanRuntime/7.29.2'], + Via: [ + '1.1 e453cfec7ab7b0f50057381607edb486.cloudfront.net (CloudFront)' + ], + 'X-Amz-Cf-Id': ['AL_XrwZDLODp365r4ALZNt8piYvuuKet9zEQGxBGUrTB68EID6Tr2w=='], + 'X-Amzn-Trace-Id': ['Root=1-62db01d2-5bca1ccb57b50f957f73a1df'], + 'X-Forwarded-For': ['187.253.120.36, 130.176.179.78'], + 'X-Forwarded-Port': ['443'], + 'X-Forwarded-Proto': ['https'] + }, + queryStringParameters: null, + multiValueQueryStringParameters: null, + pathParameters: null, + stageVariables: null, + requestContext: { + resourceId: 'cic4y4', + resourcePath: '/products', + httpMethod: 'POST', + extendedRequestId: 'Vr046GH6oAMF93A=', + requestTime: '22/Jul/2022:20:00:18 +0000', + path: '/dev/products', + accountId: '436988374415', + protocol: 'HTTP/1.1', + stage: 'dev', + domainPrefix: 'o3lc79zr1i', + requestTimeEpoch: 1658520018467, + requestId: 'be81d25d-203b-4d50-9908-b173622b9b35', + identity: { + cognitoIdentityPoolId: null, + accountId: null, + cognitoIdentityId: null, + caller: null, + sourceIp: '187.253.120.36', + principalOrgId: null, + accessKey: null, + cognitoAuthenticationType: null, + cognitoAuthenticationProvider: null, + userArn: null, + userAgent: 'PostmanRuntime/7.29.2', + user: null + }, + domainName: 'o3lc79zr1i.execute-api.us-east-1.amazonaws.com', + apiId: 'o3lc79zr1i' + }, + body: '{\r\n' + + ' "title": "Test product :D",\r\n' + + ' "description": "Test description",\r\n' + + ' "price": 10,\r\n' + + ' "count": 5\r\n' + + '}', + isBase64Encoded: false + } + ) + .expectResult(result => result); + + expect(result.statusCode).equals(201); + }); + }); + + describe('batch-lambda', () => { + it('Getting SQS Message', async () => { + const result = await lambda(batch) + .event( + { + "Records": [ + { + "messageId": "48e3ff89-33cc-451f-b564-4036a0094d65", + "receiptHandle": "AQEBteTrXlEu1TBfJzaJMyipS+McKYkf8UrBKn/jzFaRfNbq8edSXg25eZ1nl1gomZzmqdKqOpaHRgxoQklgj/g4shF41b2uLGCk8BezFMKD1Wwmns+U7pgk6zcNotVBmmtn2WLOOEIOek3fpNALG8s6DRJ87xyMuy5izE0Q/YUkIcb9knXRfdWgLBLHlm1P/cJ/3v3jIlOZKcMX4zB/DSQIFIqaXHH+3PLXq9T9rdAMa7g1ScCUQ+g49dP5wWCJ80H8+attjoc1PkW/i/UT0lMxiTy0BLqGe5WljJG6s0ghMzc=", + "body": "{\"id\":\"c835d671-a3f6-49be-8142-9c7dc3bb2c31\",\"title\":\"Test SQS\",\"description\":\"Description From SQS\",\"price\":0,\"count\":0}", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1659737693375", + "SequenceNumber": "18871636923213551616", + "MessageGroupId": "test", + "SenderId": "AIDAWLPUJNWH4HTC6FCEQ", + "MessageDeduplicationId": "test", + "ApproximateFirstReceiveTimestamp": "1659737693375" + }, + "messageAttributes": {}, + "md5OfBody": "5552aaf6126dd9668cdf421186622f2d", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:436988374415:catalogItemsQueue.fifo", + "awsRegion": "us-east-1" + } + ] + } + ) + .expectResult(result => result); + + expect(result.statusCode).equals(200); + }) + }); }); }); \ No newline at end of file diff --git a/product-service/utils/util.connection.js b/product-service/utils/util.connection.js new file mode 100644 index 0000000..07ed936 --- /dev/null +++ b/product-service/utils/util.connection.js @@ -0,0 +1,16 @@ +'use strict'; +const {Pool} = require('pg'); +const path = require('path') +require('dotenv').config({ path: path.resolve(__dirname, '../.env') }) + +const config = { + user: process.env.USER_DB, + host: process.env.HOST_DB, + password: process.env.PWD_DB, + database: process.env.DB_NAME, + port: process.env.PORT_DB +} + +const db = new Pool(config); + +module.exports = {db}; \ No newline at end of file diff --git a/product-service/utils/utils.aws.functions.js b/product-service/utils/utils.aws.functions.js new file mode 100644 index 0000000..acd0fb0 --- /dev/null +++ b/product-service/utils/utils.aws.functions.js @@ -0,0 +1,30 @@ +const AWS = require('aws-sdk'); +const path = require('path'); +require('dotenv').config({path: path.resolve(__dirname, '../.env')}); + +const sns = new AWS.SNS({region: 'us-east-1'}); +const topic = process.env.SNS_TOPIC; + +const sendEmail = async (product) => { + console.log(`sendEmail executing`); + console.log(topic) + try { + const params = { + TopicArn: topic, + Message: `The product ${product.time} with ID: ${product.id} has been created` + } + + console.log(`Params SNS: ${params}`); + + await sns.publish(params, (error, data) => { + if(error) { + console.log(`Error on sns.publish: ${error}`); + } + console.log(`Data: ${JSON.stringify(data)}`); + }); + } catch (error) { + console.log(`Error on sendEmail: ${error}`); + } +}; + +module.exports = {sendEmail}; \ No newline at end of file diff --git a/product-service/utils/utils.functions.js b/product-service/utils/utils.functions.js new file mode 100644 index 0000000..ec2e81a --- /dev/null +++ b/product-service/utils/utils.functions.js @@ -0,0 +1,7 @@ +const Str = require('@supercharge/strings'); + +const validateProductData = (body) => { + return !!(Str.isString(body.title) && Str.isString(body.description) && !isNaN(body.price) && !isNaN(body.count)); +} + +module.exports = {validateProductData} \ No newline at end of file diff --git a/product-service/webpack.config.js b/product-service/webpack.config.js new file mode 100644 index 0000000..0266cb6 --- /dev/null +++ b/product-service/webpack.config.js @@ -0,0 +1,11 @@ +const slsw = require('serverless-webpack'); +const nodeExternals = require('webpack-node-externals'); +module.exports = { + context: __dirname, + mode: slsw.lib.webpack.isLocal ? 'development' : 'production', + entry: slsw.lib.entries, + target: 'node', + /*target: 'node', + mode: 'none',*/ + externals: [nodeExternals(), 'pg-native'], +};