From 691ea88189d4e3d9f3956cb54d067b96854b0e58 Mon Sep 17 00:00:00 2001 From: pablo Date: Thu, 21 Mar 2019 09:43:35 -0300 Subject: [PATCH] Resolucion del examen --- app.js | 5 +- controllers/billing.js | 62 ++++- controllers/courses.js | 2 +- controllers/index.js | 2 + controllers/stats.js | 55 +++++ middleware/cacheMiddleware.js | 32 +++ middleware/index.js | 3 +- package.json | 2 +- routes.js | 10 +- test/dummy_tests.js | 426 ++++++++++++++++++++++++++++++++++ 10 files changed, 590 insertions(+), 9 deletions(-) create mode 100644 controllers/stats.js create mode 100644 middleware/cacheMiddleware.js diff --git a/app.js b/app.js index 9cfb46a..69577b3 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,7 @@ const bodyParser = require("body-parser"); const morgan = require("morgan"); const mongoose = require("mongoose"); const config = require("./config"); -const {responseHelpers} = require("./middleware"); +const {responseHelpers, cacheMiddleware} = require("./middleware"); const routes = require("./routes"); require("./models"); @@ -20,9 +20,8 @@ app.use(morgan("dev")); // Add response helpers app.use(responseHelpers); - // Add cache middleware -// app.use(cacheMiddleware); +app.use(cacheMiddleware); // Setup mongoose and load models mongoose.Promise = global.Promise; diff --git a/controllers/billing.js b/controllers/billing.js index e4c98b8..2a8e0e5 100644 --- a/controllers/billing.js +++ b/controllers/billing.js @@ -2,7 +2,7 @@ module.exports = (mongoose) => { const Course = mongoose.model("Course"); const Evaluation = mongoose.model("Evaluation"); const Student = mongoose.model("Student"); - + const request = require("request"); // para cada curso dame la evaluacion // para cada evaluacion dame los aprobados // para cada aprobado dame el precio del curso y el student @@ -81,8 +81,66 @@ module.exports = (mongoose) => { res.response500(err, "Courses couldn't be found!"); }); } + function requestChargeableStudents() { + return new Promise((resolve, reject) => { + request.get("http://localhost:8000/api/admin/billing/getChargeableStudents", (_, __, body) => { + let students = []; + try { + students = JSON.parse(body).data.studentsBillingInfo; + } catch (ex) { + reject(new Error("error")); + } + const studentsAfip = []; + students.forEach((student) => { + studentsAfip.push({nomYAp: `${student.firstName} ${student.lastName}`, dir: student.address.street1, importe: student.price}); + }); + resolve(studentsAfip); + }); + }); + } + + function mapAFIPResponse(invoiceId, student) { + return { + BillingNumber: invoiceId, + FirstAndLastName: student.nomYAp, + address: student.dir, + price: student.importe + }; + } + + function createInvoice(data) { + return new Promise((resolve) => { + request.post("http://localhost:8000/api/afip", {json: data}, (_, resp, body) => { + if (resp.statusCode === 200) { + resolve(mapAFIPResponse(body.data.id, data)); + } else { + resolve(createInvoice(data)); + } + }); + }); + } + + function requestInvoices(students) { + return Promise.all(students.map((student) => { + return createInvoice(student); + })); + } + + async function getInvoices(req, res) { + requestChargeableStudents().then((students) => { + return requestInvoices(students); + }) + .then((invoices) => { + res.response200(invoices); + }) + .catch(() => { + res.response500("There was an error :("); + }); + } + return { - getChargeableStudents + getChargeableStudents, + getInvoices }; }; diff --git a/controllers/courses.js b/controllers/courses.js index 4de1399..6884712 100644 --- a/controllers/courses.js +++ b/controllers/courses.js @@ -1,6 +1,6 @@ module.exports = (mongoose) => { var Course = mongoose.model("Course"), - filterFields = ["status"], + filterFields = ["status", "technologyId"], sortFields = ["status"]; var buildFilterQuery = function(params) { diff --git a/controllers/index.js b/controllers/index.js index 85840f2..8e2423f 100644 --- a/controllers/index.js +++ b/controllers/index.js @@ -3,11 +3,13 @@ const CoursesController = require("./courses"); const StudentsController = require("./students"); const EvaluationsController = require("./evaluations"); const TechnologiesController = require("./technologies"); +const StatsController = require("./stats"); module.exports = { BillingController, EvaluationsController, CoursesController, StudentsController, + StatsController, TechnologiesController }; diff --git a/controllers/stats.js b/controllers/stats.js new file mode 100644 index 0000000..26766f1 --- /dev/null +++ b/controllers/stats.js @@ -0,0 +1,55 @@ +module.exports = (mongoose) => { + const Evaluation = mongoose.model("Evaluation"); + const Student = mongoose.model("Student"); + const stateArray = {}; + const addedStudents = []; + + function addState(state, studentId) { + if (!addedStudents.includes(studentId.toString())) { + addedStudents.push(studentId.toString()); + if (stateArray[state]) { + stateArray[state]++; + } else { + stateArray[state] = 1; + } + } + } + + function addStudents(studentList) { + studentList.forEach((student) => { + addState(student.billingAddress.state, student._id); + }); + return Promise.resolve(stateArray); + } + + function getFailedNotes() { + return Evaluation.find() + .then((failedEvals) => { + const notes = failedEvals.reduce((fullNotes, evaluation) => { + return fullNotes.concat(evaluation.notes); + }, []).filter((note) => { + return note.status === "failed"; + }); + + return Promise.all(notes); + }); + } + + function getStudentsFromNotes(notes) { + const students = notes.map((note) => { + return Student.findOne({_id: note.studentId}); + }); + return Promise.all(students); + } + function failuresByStates(req, res) { + getFailedNotes() + .then(getStudentsFromNotes) + .then(addStudents) + .then(res.response200); + } + + + return { + failuresByStates + }; +}; diff --git a/middleware/cacheMiddleware.js b/middleware/cacheMiddleware.js new file mode 100644 index 0000000..42338db --- /dev/null +++ b/middleware/cacheMiddleware.js @@ -0,0 +1,32 @@ +const excludedFromCache = [ + "/api/admin/billing/getChargeableStudents" +]; +const cache = {}; + +function isCached(req) { + return cache[req.url]; +} +function getFromCache(req) { + return cache[req.url]; +} + +function removeFromCache(req) { + delete cache[req.url]; +} +module.exports = (req, res, next) => { + if (excludedFromCache.includes(req.originalUrl)) { + return next(); + } + if (req.method === "GET") { + if (isCached(req)) { + return res.response200(getFromCache(req), "Cached"); + } + res.response200 = (data = {}, message = "ok") => { + cache[req.originalUrl] = data; + res.json({data, status: "success", message}); + }; + } else { + removeFromCache(req); + } + return next(); +}; diff --git a/middleware/index.js b/middleware/index.js index f7c2174..2b53879 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -1,3 +1,4 @@ const responseHelpers = require("./responseHelpers"); +const cacheMiddleware = require("./cacheMiddleware"); -module.exports = {responseHelpers}; +module.exports = {responseHelpers, cacheMiddleware}; diff --git a/package.json b/package.json index ac05951..797de2e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "app.js", "scripts": { "start": "node ./app.js", - "test": "NODE_ENV=test ./node_modules/mocha/bin/_mocha --timeout 1000 --exit" + "test": "NODE_ENV=test ./node_modules/mocha/bin/_mocha --timeout 100000 --exit" }, "keywords": [ "exam", diff --git a/routes.js b/routes.js index 5b0d932..514a7f0 100644 --- a/routes.js +++ b/routes.js @@ -3,7 +3,8 @@ const { EvaluationsController, CoursesController, StudentsController, - TechnologiesController + TechnologiesController, + StatsController } = require("./controllers"); const afipAPI = require("./services/afip-mock-api"); @@ -27,6 +28,7 @@ module.exports = (app, router) => { const evaluationController = EvaluationsController(mongoose); const technologyController = TechnologiesController(mongoose); const billingController = BillingController(mongoose); + const statsController = StatsController(mongoose); const controllers = [ {basePath: "/evaluations", controller: evaluationController}, @@ -37,11 +39,17 @@ module.exports = (app, router) => { mapGenericControllerRoutes(controllers, router); + router.route("/stats/failuresByStates") + .get(statsController.failuresByStates); + router.route("/admin/billing/getChargeableStudents") .get(billingController.getChargeableStudents); router.route("/afip") .post(afipAPI.getInvoice); + router.route("/admin/billing/getInvoices").get( + billingController.getInvoices + ); return router; }; diff --git a/test/dummy_tests.js b/test/dummy_tests.js index e69de29..94db367 100644 --- a/test/dummy_tests.js +++ b/test/dummy_tests.js @@ -0,0 +1,426 @@ +const expect = require("chai").expect; +const request = require("supertest"); +const app = require("../app"); +const mongoose = app.get("mongoose"); +const Technology = mongoose.model("Technology"); +const Course = mongoose.model("Course"); +const Evaluation = mongoose.model("Evaluation"); +const Student = mongoose.model("Student"); +describe("Issue #1 test", () => { + const PATH = "/api/courses"; + before(async () => { + await Technology.deleteMany({}); + await Course.deleteMany({}); + const techs = [{ + "name": "NodeJs", + "technologyId": "JS-000" + }, + { + "name": "VueJs", + "technologyId": "JS-001" + }]; + const courses = [ + { + "technologyId": "JS-000", + "date": { + "from": "2019-05-01T00:00:00.000Z", + "to": "2019-05-15T00:00:00.000Z" + }, + "description": "Node Js course", + "status": "new", + "classes": [ + { + "name": "Programming basics with Node JS", + "description": "A class to start programming with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + }, + { + "name": "Programming with Node JS", + "description": "A class to continue programming with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + }, + { + "name": "Programming advanced with Node JS", + "description": "A class to program with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + } + ], + "price": 123400, + "students": [] + }, + { + "technologyId": "JS-001", + "date": { + "from": "2019-05-01T00:00:00.000Z", + "to": "2019-05-15T00:00:00.000Z" + }, + "description": "Node Js course", + "status": "new", + "classes": [ + { + "name": "Programming basics with Node JS", + "description": "A class to start programming with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + }, + { + "name": "Programming with Node JS", + "description": "A class to continue programming with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + }, + { + "name": "Programming advanced with Node JS", + "description": "A class to program with Node JS", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. ", + "tags": "" + } + ], + "price": 123400, + "students": [] + } + ]; + return Promise.all([Technology.insertMany(techs), Course.insertMany(courses)]); + }); + + + it("/courses should be able to filter by technologyId", () => { + return request(app) + .get(`${PATH}?technologyId=JS-000`) + .expect(200) + .then((res) => { + expect(res.body.data.courses).to.be.an("array"); + expect(res.body.data.courses.length).to.be.eql(1); + expect(res.body.data.courses[0].technologyId).to.be.eql("JS-000"); + }); + }); +}); + +describe("Issue #2 test", () => { + const PATH = "/api/admin/billing/getInvoices"; + before(async () => { + + }); + + it("/getInvoices returns the list of students with their respective invoice", () => { + return request(app) + .get(PATH) + .expect(200) + .then((res) => { + expect(res.body.data.data); + expect(res.body.data.length).to.be.eql(7); + const invoice = res.body.data[0]; + expect(invoice.BillingNumber).to.be.a("number"); + expect(invoice.FirstAndLastName).to.be.eql("Everette Lehner"); + expect(invoice.address).to.be.eql("126 Schuppe Shore"); + expect(invoice.price).to.be.eql(932100); + }); + }); +}); + +describe("Issue #3 tests", () => { + context("Testing cache MiddleWare", () => { + it("Should return message 'Cached'", () => { + return request(app) + .get("/api/courses") + .expect(200) + .then(() => { + return request(app) + .get("/api/courses") + .expect(200); + }) + .then((res) => { + expect(res.body.status).to.eql("success"); + expect(res.body.message).to.be.eql("Cached"); + }); + }); + + it("Should not return message 'Cached' When Posting new Course", () => { + return request(app) + .get("/api/courses") + .expect(200) + .then(() => { + return request(app) + .post("/api/courses") + .send({ + "technologyId": "Ruby", + "date": { + "from": "2018-10-20T00:00:00Z", + "to": "2018-10-21T00:00:00Z" + }, + "description": "A Ruby starter course", + "classes": [{"name": "Class 1", "description": "zaraza"}], + "price": 3000, + "students": ["pepe"] + }) + .expect(200); + }) + .then(() => { + return request(app) + .get("/api/courses") + .expect(200); + }) + .then((res) => { + expect(res.body.status).to.eql("success"); + expect(res.body.message).not.to.be.eql("Cached"); + }); + }); + + it("Should not return message 'Cached' When Modifiyng a value", () => { + Course.find().then((courses) => { + const course = courses[0]; + return request(app) + .get("/api/courses") + .expect(200) + .then(() => { + return request(app) + .put(`/api/courses/${course._id}`) + .send({ + "description": "A Ruby starter couasrse" + }) + .expect(200); + }) + .then(() => { + return request(app) + .get("/api/courses") + .expect(200); + }) + .then((res) => { + expect(res.body.status).to.eql("success"); + expect(res.body.message).not.to.be.eql("Cached"); + }); + }); + }); + }); +}); + +describe("Issue #4 test", () => { + const PATH = "/api/stats/failuresByStates"; + before(async () => { + await Student.deleteMany({}); + await Evaluation.deleteMany({}); + const students = [{ + "_id": "5c92b9f262e5287d7db5602f", + "firstName": "Pupa", + "lastName": "Polainas", + "billingAddress": { + "_id": "5c92b9f262e5287d7db56030", + "street1": "Av. De Mayo 776", + "city": "Buenos Aires", + "state": "Buenos Aires", + "zipCode": "1084", + "country": "Argentina" + }, + "creditCards": [ + ] + }, + { + "_id": "5c92b9f262e5287d7db56fff", + "firstName": "Pupa", + "lastName": "Polainas", + "billingAddress": { + "_id": "5c92b9f262e5287d7db56030", + "street1": "Av. De Mayo 776", + "city": "Buenos Aires", + "state": "Catamarca", + "zipCode": "1084", + "country": "Argentina" + }, + "creditCards": [ + ] + }, + { + "_id": "5c92b9f262e5287d7db5ffff", + "firstName": "Pupa", + "lastName": "Polainas", + "billingAddress": { + "_id": "5c92b9f262e5287d7db56030", + "street1": "Av. De Mayo 776", + "city": "Buenos Aires", + "state": "Santiago", + "zipCode": "1084", + "country": "Argentina" + }, + "creditCards": [ + ] + }, + { + "_id": "5c92b9f262e5287d7db56aaa", + "firstName": "Pupa", + "lastName": "Polainas", + "billingAddress": { + "_id": "5c92b9f262e5287d7db56030", + "street1": "Av. De Mayo 776", + "city": "Buenos Aires", + "state": "CABA", + "zipCode": "1084", + "country": "Argentina" + }, + "creditCards": [ + ] + }, + { + "_id": "5c92b9f262e5287d7db56aab", + "firstName": "Pupa", + "lastName": "Polainas", + "billingAddress": { + "_id": "5c92b9f262e5287d7db56030", + "street1": "Av. De Mayo 776", + "city": "Buenos Aires", + "state": "CABA", + "zipCode": "1084", + "country": "Argentina" + }, + "creditCards": [ + ] + }]; + const evaluations = [{ + "_id": "5c92d654e34b581661a5779a", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db5602f", + "qualification": 9, + "status": "failed" + } + ] + }, + { + "_id": "5c92d654e34b581661a577dd", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db5602f", + "qualification": 9, + "status": "failed" + } + ] + }, + { + "_id": "5c92d654e34b581661a577de", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db56fff", + "qualification": 9, + "status": "failed" + } + ] + }, + { + "_id": "5c92d654e34b581661a577ee", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db56aaa", + "qualification": 9, + "status": "failed" + } + ] + }, + { + "_id": "5c92d654e34b581661a577e2", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db56aab", + "qualification": 9, + "status": "failed" + } + ] + }, + { + "_id": "5c92d654e34b581661a5ffe2", + "courseId": "5c8c09ff444bfa0dfa450066", + "date": { + "from": "2019-03-21T00:00:00.000Z", + "to": "2019-03-22T00:00:00.000Z" + }, + "abstract": "The evaluation for the Ruby course. Write JS stuff to pass", + "notes": [ + { + "_id": "5c92d654e34b581661a5779b", + "studentId": "5c92b9f262e5287d7db5ffff", + "qualification": 9, + "status": "passed" + } + ] + }]; + return Promise.all([Student.insertMany(students), Evaluation.insertMany(evaluations)]); + }); + + it("failuresByStates returns the Number of students by city that have failed an evaluation - One evaluation", () => { + return request(app) + .get(PATH) + .expect(200) + .then((res) => { + expect(res.body.data.data); + console.log(res.body.data); + expect(res.body.data.Catamarca).to.be.eql(1); + }); + }); + + it("failuresByStates returns the Number of students by city that have failed an evaluation - Two evaluations same student", () => { + return request(app) + .get(PATH) + .expect(200) + .then((res) => { + expect(res.body.data.data); + expect(res.body.data["Buenos Aires"]).to.be.eql(1); + }); + }); + + it("failuresByStates returns the Number of students by city that have failed an evaluation - Two evaluations diff student", () => { + return request(app) + .get(PATH) + .expect(200) + .then((res) => { + expect(res.body.data.data); + expect(res.body.data.CABA).to.be.eql(2); + }); + }); + + it("failuresByStates returns the Number of students by city that have failed an evaluation - All approved", () => { + return request(app) + .get(PATH) + .expect(200) + .then((res) => { + expect(res.body.data.data); + // eslint-disable-next-line + expect(res.body.data.Santiago).to.be.undefined; + }); + }); +});