From 8215178d573df357cfebddcef434815dad14a5ea Mon Sep 17 00:00:00 2001 From: Daniel Olojakpoke Date: Tue, 12 Oct 2021 16:18:42 +0100 Subject: [PATCH] feat: tests added --- api-tests/earnings-api.spec.js | 533 ++++++++++++++++++ api-tests/earningsFailedTestInvalidHeader.csv | 4 + .../earningsFailedTestInvalidHeader2.csv | 4 + .../earningsFailedTestInvalidHeader3.csv | 4 + .../earningsFailedTestInvalidHeader4.csv | 4 + .../earningsFailedTestInvalidHeader5.csv | 4 + .../earningsFailedTestInvalidHeader6.csv | 4 + api-tests/earningsFailedTestInvalidRow.csv | 4 + ...gsFailedTestRowWithNotCalculatedStatus.csv | 6 + api-tests/earningsSuccessfulTest.csv | 4 + api-tests/generic-class.js | 17 + api-tests/seed-data-creation.js | 89 +++ .../migrations/20211010192212-createBatch.js | 54 ++ .../20211010192838-createStakeholder.js | 54 ++ ...gs.js => 20211010193244-createEarnings.js} | 4 +- ...ture.js => 20211010193339-alterCapture.js} | 4 +- .../sqls/20211008100925-alterCapture-up.sql | 1 - .../sqls/20211010192212-createBatch-down.sql | 0 .../sqls/20211010192212-createBatch-up.sql | 6 + .../20211010192838-createStakeholder-down.sql | 1 + .../20211010192838-createStakeholder-up.sql | 4 + ...=> 20211010193244-createEarnings-down.sql} | 1 + ...l => 20211010193244-createEarnings-up.sql} | 3 +- ...l => 20211010193339-alterCapture-down.sql} | 1 + .../sqls/20211010193339-alterCapture-up.sql | 6 + package-lock.json | 2 +- package.json | 4 +- server/handlers/earningsHandler.js | 21 +- server/services/aws.js | 7 +- server/services/s3.js | 7 + 30 files changed, 827 insertions(+), 30 deletions(-) create mode 100644 api-tests/earnings-api.spec.js create mode 100644 api-tests/earningsFailedTestInvalidHeader.csv create mode 100644 api-tests/earningsFailedTestInvalidHeader2.csv create mode 100644 api-tests/earningsFailedTestInvalidHeader3.csv create mode 100644 api-tests/earningsFailedTestInvalidHeader4.csv create mode 100644 api-tests/earningsFailedTestInvalidHeader5.csv create mode 100644 api-tests/earningsFailedTestInvalidHeader6.csv create mode 100644 api-tests/earningsFailedTestInvalidRow.csv create mode 100644 api-tests/earningsFailedTestRowWithNotCalculatedStatus.csv create mode 100644 api-tests/earningsSuccessfulTest.csv create mode 100644 api-tests/generic-class.js create mode 100644 api-tests/seed-data-creation.js create mode 100644 database/migrations/20211010192212-createBatch.js create mode 100644 database/migrations/20211010192838-createStakeholder.js rename database/migrations/{20211005092140-createEarnings.js => 20211010193244-createEarnings.js} (89%) rename database/migrations/{20211008100925-alterCapture.js => 20211010193339-alterCapture.js} (89%) delete mode 100644 database/migrations/sqls/20211008100925-alterCapture-up.sql create mode 100644 database/migrations/sqls/20211010192212-createBatch-down.sql create mode 100644 database/migrations/sqls/20211010192212-createBatch-up.sql create mode 100644 database/migrations/sqls/20211010192838-createStakeholder-down.sql create mode 100644 database/migrations/sqls/20211010192838-createStakeholder-up.sql rename database/migrations/sqls/{20211005092140-createEarnings-down.sql => 20211010193244-createEarnings-down.sql} (75%) rename database/migrations/sqls/{20211005092140-createEarnings-up.sql => 20211010193244-createEarnings-up.sql} (89%) rename database/migrations/sqls/{20211008100925-alterCapture-down.sql => 20211010193339-alterCapture-down.sql} (56%) create mode 100644 database/migrations/sqls/20211010193339-alterCapture-up.sql create mode 100644 server/services/s3.js diff --git a/api-tests/earnings-api.spec.js b/api-tests/earnings-api.spec.js new file mode 100644 index 0000000..d8d244a --- /dev/null +++ b/api-tests/earnings-api.spec.js @@ -0,0 +1,533 @@ +require('dotenv').config(); +const request = require('supertest'); +const { expect, assert } = require('chai'); +const { v4: uuid } = require('uuid'); +const server = require('../server/app'); +const { + earnings: earningsOne, + earningsPaid: earningsWithPaidStatus, + earningsCancelled: earningsWithCancelledStatus, +} = require('./seed-data-creation'); +const { GenericObject } = require('./generic-class'); + +describe('Earnings API tests.', () => { + describe('Earnings PATCH', () => { + it(`Should raise validation error with error code 422 -- id is required `, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('id'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- id should be a uuid `, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('id', 'id'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- worker_id should be required`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('worker_id'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- worker_id should be a uuid`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('worker_id', 'worker_id'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- amount should be required`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('amount'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- amount should be a number`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('amount', 'amount'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- currency should be required`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('currency'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- payment_confirmation_id should be required`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('payment_confirmation_id'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- payment_system should be required`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.delete_property('payment_system'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- paid_at should be an iso date`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('paid_at', 'paid_at'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise a 409 error -- paid status is not allowed to be updated`, function (done) { + const earnings = new GenericObject({ ...earningsWithPaidStatus }); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(409) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise a 409 error -- cancelled status is not allowed to be updated`, function (done) { + const earnings = new GenericObject({ ...earningsWithCancelledStatus }); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(409) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise a 409 error -- worker_id not the same as one in the database`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('worker_id', uuid()); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(409) + .end(function (err, res) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise a 409 error -- currency not the same as one in the database`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('currency', 'NFT'); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(409) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise a 409 error -- amount not the same as one in the database`, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + earnings.change_property('amount', 5000000); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(409) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should be successful `, function (done) { + const earnings = new GenericObject({ ...earningsOne }); + request(server) + .patch(`/earnings`) + .send(earnings._object) + .set('Accept', 'application/json') + .expect(200) + .end(function (err, res) { + expect(res.body).eql({ + status: 'completed', + count: 1, + }); + if (err) return done(err); + return done(); + }); + }); + }); + + describe('Earnings GET', () => { + it(`Should raise validation error with error code 422 -- 'start_date' query parameter should be a date `, function (done) { + request(server) + .get(`/earnings`) + .query({ + start_date: 'start_date', + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'end_date' query parameter should be a date `, function (done) { + request(server) + .get(`/earnings`) + .query({ + end_date: 'end_date', + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'limit' query parameter should be an integer `, function (done) { + request(server) + .get(`/earnings`) + .query({ + limit: 'limit', + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'limit' query parameter should be greater than 0 `, function (done) { + request(server) + .get(`/earnings`) + .query({ + limit: 0, + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'limit' query parameter should be less than 101 `, function (done) { + request(server) + .get(`/earnings`) + .query({ + limit: 101, + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'offset' query parameter should be an integer `, function (done) { + request(server) + .get(`/earnings`) + .query({ + offset: 'offset', + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- 'offset' query parameter should be at least 0 `, function (done) { + request(server) + .get(`/earnings`) + .query({ + offset: -1, + }) + .set('Accept', 'application/json') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should get earnings successfully`, function (done) { + request(server) + .get(`/earnings`) + .set('Accept', 'application/json') + .expect(200) + .end(function (err, res) { + if (err) return done(err); + expect(res.body).to.have.keys(['earnings', 'links']); + expect(res.body.links).to.have.keys(['prev', 'next']); + + // test if surveys were added successfully + const earnings = new GenericObject(earningsOne); + + let earnings_updated = false; + for (const earning of res.body.earnings) { + expect(earning).to.have.keys([ + 'worker_id', + 'funder_id', + 'amount', + 'currency', + 'calculated_at', + 'consolidation_rule', + 'consolidation_period_start', + 'consolidation_period_end', + 'payment_confirmation_id', + 'payment_system', + 'payment_confirmed_by', + 'payment_confirmation_method', + 'paid_at', + 'payment_confirmed_at', + 'status', + 'batch_id', + ]); + if ( + earning.payment_confirmation_id === + earnings._object.payment_confirmation_id && + earning.payment_system === earnings._object.payment_system + ) { + expect(earning.status).equal('paid'); + earnings_updated = true; + } + } + + expect(earnings_updated).to.be.true; + + return done(); + }); + }); + }); + + describe('Earnings Batch Patch', () => { + it(`Should raise validation error with error code 422 -- all rows should be valid `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidRow.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; payment_system does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; payment_confirmation_id does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader2.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; worker_id does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader3.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; amount does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader4.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; currency does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader5.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise validation error with error code 422 -- invalid headers; earnings_id does not exist `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsFailedTestInvalidHeader6.csv') + .expect(422) + .end(function (err) { + if (err) return done(err); + return done(); + }); + }); + + it(`Should raise conflict error with error code 409 -- one of the rows has status paid `, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach( + 'csv', + 'api-tests\\earningsFailedTestRowWithNotCalculatedStatus.csv', + ) + .expect(409) + .end(function (err, res) { + console.log(res.body); + if (err) return done(err); + return done(); + }); + }); + + it(`Successful batch request`, function (done) { + request(server) + .patch(`/earnings/batch`) + .set('Accept', 'multipart/form-data') + .attach('csv', 'api-tests\\earningsSuccessfulTest.csv') + .expect(200) + .end(function (err, res) { + expect(res.body).eql({ + status: 'completed', + count: 3, + }); + if (err) return done(err); + return done(); + }); + }); + }); + + describe('Earnings BATCH GET', () => { + const binaryParser = (res, callback) => { + res.setEncoding('binary'); + res.data = ''; + res.on('data', function (chunk) { + res.data += chunk; + }); + res.on('end', function () { + callback(null, Buffer.from(res.data, 'binary')); + }); + }; + it(`Should get earnings successfully`, function (done) { + request(server) + .get(`/earnings/batch`) + .expect('Content-Type', 'text/csv; charset=utf-8') + .buffer() + .parse(binaryParser) + .expect(200) + .end(function (err, res) { + if (err) return done(err); + expect(res.body instanceof Buffer).to.be.true; + return done(); + }); + }); + }); +}); diff --git a/api-tests/earningsFailedTestInvalidHeader.csv b/api-tests/earningsFailedTestInvalidHeader.csv new file mode 100644 index 0000000..2ce05e1 --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,currency,amount,payment_confirmation_id,payment_id +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidHeader2.csv b/api-tests/earningsFailedTestInvalidHeader2.csv new file mode 100644 index 0000000..bed9c60 --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader2.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,currency,amount,payment_id,payment_system +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidHeader3.csv b/api-tests/earningsFailedTestInvalidHeader3.csv new file mode 100644 index 0000000..1c68c73 --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader3.csv @@ -0,0 +1,4 @@ +earnings_id,phone,currency,amount,payment_confirmation_id,payment_system +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidHeader4.csv b/api-tests/earningsFailedTestInvalidHeader4.csv new file mode 100644 index 0000000..120be17 --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader4.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,currency,payment_confirmation_id,payment_system +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidHeader5.csv b/api-tests/earningsFailedTestInvalidHeader5.csv new file mode 100644 index 0000000..8d13049 --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader5.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,amount,payment_confirmation_id,payment_system +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidHeader6.csv b/api-tests/earningsFailedTestInvalidHeader6.csv new file mode 100644 index 0000000..7e51acf --- /dev/null +++ b/api-tests/earningsFailedTestInvalidHeader6.csv @@ -0,0 +1,4 @@ +worker_id,phone,currency,amount,payment_confirmation_id,payment_system +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestInvalidRow.csv b/api-tests/earningsFailedTestInvalidRow.csv new file mode 100644 index 0000000..8271bbd --- /dev/null +++ b/api-tests/earningsFailedTestInvalidRow.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,currency,amount,payment_confirmation_id,payment_system,paid_at +f3056760-4c36-4cf7-b625-ed6,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsFailedTestRowWithNotCalculatedStatus.csv b/api-tests/earningsFailedTestRowWithNotCalculatedStatus.csv new file mode 100644 index 0000000..74dec80 --- /dev/null +++ b/api-tests/earningsFailedTestRowWithNotCalculatedStatus.csv @@ -0,0 +1,6 @@ +earnings_id,worker_id,phone,currency,amount,payment_confirmation_id,payment_system,paid_at +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z +9c10e443-4e08-40d4-9b73-5a931886f896,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z +3ab96dfd-274a-4097-8e7d-942e58203784,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/earningsSuccessfulTest.csv b/api-tests/earningsSuccessfulTest.csv new file mode 100644 index 0000000..ae690ee --- /dev/null +++ b/api-tests/earningsSuccessfulTest.csv @@ -0,0 +1,4 @@ +earnings_id,worker_id,phone,currency,amount,payment_confirmation_id,payment_system,paid_at +f3056760-4c36-4cf7-b625-ed656d314794,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,2,jomzy jor jor,2021-10-10T00:00:00.000Z +a69e5cec-945f-4693-8a94-01623ee44187,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,3,jomzy jor jor,2021-10-10T00:00:00.000Z +61714d24-6131-4296-ae36-30a7199cc645,71be6266-81fe-476f-a563-9bc1c61fc037,Not sure for now,USD,700,4,jomzy jor jor,2021-10-10T00:00:00.000Z \ No newline at end of file diff --git a/api-tests/generic-class.js b/api-tests/generic-class.js new file mode 100644 index 0000000..589cac6 --- /dev/null +++ b/api-tests/generic-class.js @@ -0,0 +1,17 @@ +class GenericObject { + constructor(payload) { + this._object = payload; + } + + delete_property(property) { + delete this._object[property]; + } + + change_property(property, value) { + this._object[property] = value; + } +} + +module.exports = { + GenericObject, +}; diff --git a/api-tests/seed-data-creation.js b/api-tests/seed-data-creation.js new file mode 100644 index 0000000..f647989 --- /dev/null +++ b/api-tests/seed-data-creation.js @@ -0,0 +1,89 @@ +const { v4: uuid } = require('uuid'); +const sinon = require('sinon'); +const knex = require('../server/database/knex'); +const s3 = require('../server/services/s3'); + +const workerId = '71be6266-81fe-476f-a563-9bc1c61fc037'; +const earningsPaymentObject = { + worker_id: workerId, + amount: 700, + payment_confirmation_id: uuid(), + payment_system: 'cash', + currency: 'USD', + status: 'calculated', + paid_at: new Date().toISOString(), +}; +const earningsOne = { + ...earningsPaymentObject, + payment_system: 'bike', + id: '915b1c8c-e1d0-44f8-a6a7-7026e2a8f04b', +}; +const earningsTwo = { + ...earningsPaymentObject, + id: 'f3056760-4c36-4cf7-b625-ed656d314794', +}; +const earningsThree = { + ...earningsPaymentObject, + id: 'a69e5cec-945f-4693-8a94-01623ee44187', +}; +const earningsFour = { + ...earningsPaymentObject, + id: '61714d24-6131-4296-ae36-30a7199cc645', +}; +const earningsWithPaidStatus = { + ...earningsPaymentObject, + id: '9c10e443-4e08-40d4-9b73-5a931886f896', + status: 'paid', +}; +const earningsWithCancelledStatus = { + ...earningsPaymentObject, + id: '3ab96dfd-274a-4097-8e7d-942e58203784', + status: 'cancelled', +}; +let stub; + +before(async () => { + stub = sinon.stub(s3, 'putObject').returns({ promise: () => 'a' }); + // prettier-ignore + await knex.raw(` + ALTER TABLE earnings ALTER COLUMN batch_id DROP NOT NULL; + + INSERT INTO stakeholder.stakeholder(id) + VALUES ('${workerId}'); + + INSERT INTO public.earnings( + id, worker_id, funder_id, amount, currency, calculated_at, consolidation_id, consolidation_period_start, consolidation_period_end, payment_confirmed_by, payment_confirmation_method, status, active) + VALUES + ('${earningsOne.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','single', '${earningsOne.status}', true), + ('${earningsTwo.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','single', '${earningsTwo.status}', true), + ('${earningsThree.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','single', '${earningsThree.status}', true), + ('${earningsFour.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','batch', '${earningsFour.status}', true), + ('${earningsWithCancelledStatus.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','single', '${earningsWithCancelledStatus.status}', true), + ('${earningsWithPaidStatus.id}','${workerId}', '${uuid()}', '${earningsPaymentObject.amount}', '${earningsPaymentObject.currency}', now(), '${uuid()}', now(), now(), '${uuid()}','single', '${earningsWithPaidStatus.status}', true); + `); +}); + +after(async () => { + stub.restore(); + await knex.raw(` + + DELETE FROM public.earnings + WHERE worker_id = '${workerId}'; + + DELETE FROM stakeholder.stakeholder + WHERE id = '${workerId}'; + + ALTER TABLE earnings ALTER COLUMN batch_id SET NOT NULL; + `); +}); + +// should not be in the PATCH request body +const { status, ...earnings } = earningsOne; +const { status: status2, ...earningsPaid } = earningsWithPaidStatus; +const { status: status3, ...earningsCancelled } = earningsWithCancelledStatus; + +module.exports = { + earnings, + earningsPaid, + earningsCancelled, +}; diff --git a/database/migrations/20211010192212-createBatch.js b/database/migrations/20211010192212-createBatch.js new file mode 100644 index 0000000..7ee361b --- /dev/null +++ b/database/migrations/20211010192212-createBatch.js @@ -0,0 +1,54 @@ + + +let dbm; +let type; +let seed; +const fs = require('fs'); +const path = require('path'); + +let Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function(options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function(db) { + const filePath = path.join(__dirname, 'sqls', '20211010192212-createBatch-up.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log(`received data: ${ data}`); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports.down = function(db) { + const filePath = path.join(__dirname, 'sqls', '20211010192212-createBatch-down.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log(`received data: ${ data}`); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports._meta = { + "version": 1 +}; diff --git a/database/migrations/20211010192838-createStakeholder.js b/database/migrations/20211010192838-createStakeholder.js new file mode 100644 index 0000000..4aca4ee --- /dev/null +++ b/database/migrations/20211010192838-createStakeholder.js @@ -0,0 +1,54 @@ + + +let dbm; +let type; +let seed; +const fs = require('fs'); +const path = require('path'); + +let Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function(options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function(db) { + const filePath = path.join(__dirname, 'sqls', '20211010192838-createStakeholder-up.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log(`received data: ${ data}`); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports.down = function(db) { + const filePath = path.join(__dirname, 'sqls', '20211010192838-createStakeholder-down.sql'); + return new Promise( function( resolve, reject ) { + fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ + if (err) return reject(err); + console.log(`received data: ${ data}`); + + resolve(data); + }); + }) + .then(function(data) { + return db.runSql(data); + }); +}; + +exports._meta = { + "version": 1 +}; diff --git a/database/migrations/20211005092140-createEarnings.js b/database/migrations/20211010193244-createEarnings.js similarity index 89% rename from database/migrations/20211005092140-createEarnings.js rename to database/migrations/20211010193244-createEarnings.js index 457626e..0faff94 100644 --- a/database/migrations/20211005092140-createEarnings.js +++ b/database/migrations/20211010193244-createEarnings.js @@ -20,7 +20,7 @@ exports.setup = function(options, seedLink) { }; exports.up = function(db) { - const filePath = path.join(__dirname, 'sqls', '20211005092140-createEarnings-up.sql'); + const filePath = path.join(__dirname, 'sqls', '20211010193244-createEarnings-up.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); @@ -35,7 +35,7 @@ exports.up = function(db) { }; exports.down = function(db) { - const filePath = path.join(__dirname, 'sqls', '20211005092140-createEarnings-down.sql'); + const filePath = path.join(__dirname, 'sqls', '20211010193244-createEarnings-down.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); diff --git a/database/migrations/20211008100925-alterCapture.js b/database/migrations/20211010193339-alterCapture.js similarity index 89% rename from database/migrations/20211008100925-alterCapture.js rename to database/migrations/20211010193339-alterCapture.js index 5df085d..203f375 100644 --- a/database/migrations/20211008100925-alterCapture.js +++ b/database/migrations/20211010193339-alterCapture.js @@ -20,7 +20,7 @@ exports.setup = function(options, seedLink) { }; exports.up = function(db) { - const filePath = path.join(__dirname, 'sqls', '20211008100925-alterCapture-up.sql'); + const filePath = path.join(__dirname, 'sqls', '20211010193339-alterCapture-up.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); @@ -35,7 +35,7 @@ exports.up = function(db) { }; exports.down = function(db) { - const filePath = path.join(__dirname, 'sqls', '20211008100925-alterCapture-down.sql'); + const filePath = path.join(__dirname, 'sqls', '20211010193339-alterCapture-down.sql'); return new Promise( function( resolve, reject ) { fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){ if (err) return reject(err); diff --git a/database/migrations/sqls/20211008100925-alterCapture-up.sql b/database/migrations/sqls/20211008100925-alterCapture-up.sql deleted file mode 100644 index b9201d9..0000000 --- a/database/migrations/sqls/20211008100925-alterCapture-up.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE treetracker.capture ADD earnings_id uuid REFERENCES public.earnings(id); \ No newline at end of file diff --git a/database/migrations/sqls/20211010192212-createBatch-down.sql b/database/migrations/sqls/20211010192212-createBatch-down.sql new file mode 100644 index 0000000..e69de29 diff --git a/database/migrations/sqls/20211010192212-createBatch-up.sql b/database/migrations/sqls/20211010192212-createBatch-up.sql new file mode 100644 index 0000000..6c3efce --- /dev/null +++ b/database/migrations/sqls/20211010192212-createBatch-up.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS batch ( + id uuid NOT NULL PRIMARY KEY, + url varchar NOT NULL, + status varchar NOT NULL, + active boolean NOT NULL +) \ No newline at end of file diff --git a/database/migrations/sqls/20211010192838-createStakeholder-down.sql b/database/migrations/sqls/20211010192838-createStakeholder-down.sql new file mode 100644 index 0000000..44f074e --- /dev/null +++ b/database/migrations/sqls/20211010192838-createStakeholder-down.sql @@ -0,0 +1 @@ +/* Replace with your SQL commands */ \ No newline at end of file diff --git a/database/migrations/sqls/20211010192838-createStakeholder-up.sql b/database/migrations/sqls/20211010192838-createStakeholder-up.sql new file mode 100644 index 0000000..ae4781e --- /dev/null +++ b/database/migrations/sqls/20211010192838-createStakeholder-up.sql @@ -0,0 +1,4 @@ +CREATE SCHEMA IF NOT EXISTS stakeholder; +CREATE TABLE IF NOT EXISTS stakeholder.stakeholder( + id uuid NOT NULL PRIMARY KEY +) \ No newline at end of file diff --git a/database/migrations/sqls/20211005092140-createEarnings-down.sql b/database/migrations/sqls/20211010193244-createEarnings-down.sql similarity index 75% rename from database/migrations/sqls/20211005092140-createEarnings-down.sql rename to database/migrations/sqls/20211010193244-createEarnings-down.sql index cd25a39..ac3833b 100644 --- a/database/migrations/sqls/20211005092140-createEarnings-down.sql +++ b/database/migrations/sqls/20211010193244-createEarnings-down.sql @@ -1,3 +1,4 @@ +/* Replace with your SQL commands */ DROP TABLE earnings; DROP TYPE earning_status_enum; DROP TYPE confirmation_method_enum; diff --git a/database/migrations/sqls/20211005092140-createEarnings-up.sql b/database/migrations/sqls/20211010193244-createEarnings-up.sql similarity index 89% rename from database/migrations/sqls/20211005092140-createEarnings-up.sql rename to database/migrations/sqls/20211010193244-createEarnings-up.sql index 9d30d65..5a3454e 100644 --- a/database/migrations/sqls/20211005092140-createEarnings-up.sql +++ b/database/migrations/sqls/20211010193244-createEarnings-up.sql @@ -1,3 +1,4 @@ +/* Replace with your SQL commands */ CREATE TYPE earning_status_enum AS ENUM ('calculated', 'cancelled', 'paid'); CREATE TYPE confirmation_method_enum AS ENUM ('single', 'batch'); CREATE TYPE currency_enum AS ENUM ('USD'); @@ -16,7 +17,7 @@ CREATE TABLE public.earnings payment_confirmation_id varchar, payment_system varchar, payment_confirmed_by uuid NOT NULL, - payment_confirmation_method uuid NOT NULL, + payment_confirmation_method confirmation_method_enum NOT NULL, paid_at timestamptz, status earning_status_enum NOT NULL, active boolean NOT NULL, diff --git a/database/migrations/sqls/20211008100925-alterCapture-down.sql b/database/migrations/sqls/20211010193339-alterCapture-down.sql similarity index 56% rename from database/migrations/sqls/20211008100925-alterCapture-down.sql rename to database/migrations/sqls/20211010193339-alterCapture-down.sql index 9b0b845..e911ea8 100644 --- a/database/migrations/sqls/20211008100925-alterCapture-down.sql +++ b/database/migrations/sqls/20211010193339-alterCapture-down.sql @@ -1 +1,2 @@ +/* Replace with your SQL commands */ ALTER TABLE treetracker.capture DROP earnings_id \ No newline at end of file diff --git a/database/migrations/sqls/20211010193339-alterCapture-up.sql b/database/migrations/sqls/20211010193339-alterCapture-up.sql new file mode 100644 index 0000000..4043cc6 --- /dev/null +++ b/database/migrations/sqls/20211010193339-alterCapture-up.sql @@ -0,0 +1,6 @@ +/* Replace with your SQL commands */ +CREATE SCHEMA IF NOT EXISTS treetracker; +CREATE TABLE IF NOT EXISTS treetracker.capture ( + id uuid NOT NULL PRIMARY KEY +); +ALTER TABLE treetracker.capture ADD earnings_id uuid REFERENCES public.earnings(id); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ece2b2b..bf1556d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "mock-knex": "^0.4.9", "nodemon": "^2.0.4", "prettier": "^2.1.2", - "sinon": "^9.0.3", + "sinon": "^9.2.4", "sinon-chai": "^2.14.0", "supertest": "^4.0.2" }, diff --git a/package.json b/package.json index e34707f..504f3bc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "server-test": "DEBUG=express:* NODE_LOG_LEVEL=debug nodemon server/serverTest.js", "server": "nodemon server/server.js", "test-seedDB": "NODE_ENV=test mocha -r dotenv/config dotenv_config_path=.env.test --timeout 10000 --require co-mocha './**/*.spec.js'", - "test-integration-ci": "NODE_ENV=test mocha -r dotenv/config dotenv_config_path=.env.test --exit --timeout 20000 --require co-mocha './__tests__/supertest.js'", + "test-integration-ci": "mocha -r dotenv/config --dotenv_config_path=.env.test --exit --timeout 30000 --require co-mocha './api-tests'", "test-watch": "NODE_ENV=test NODE_LOG_LEVEL=info mocha -r dotenv/config dotenv_config_path=.env.test --timeout 10000 --require co-mocha -w -b --ignore './server/repositories/**/*.spec.js' './server/setup.js' './server/**/*.spec.js' './__tests__/seed.spec.js' './__tests__/supertest.js'", "test-watch-debug": "NODE_ENV=test NODE_LOG_LEVEL=debug mocha -r dotenv/config dotenv_config_path=.env.test --timeout 10000 --require co-mocha -w -b --ignore './server/repositories/**/*.spec.js' './server/setup.js' './server/**/*.spec.js' './__tests__/seed.spec.js' './__tests__/supertest.js'", "prettier-fix": "prettier ./ --write", @@ -68,7 +68,7 @@ "mock-knex": "^0.4.9", "nodemon": "^2.0.4", "prettier": "^2.1.2", - "sinon": "^9.0.3", + "sinon": "^9.2.4", "sinon-chai": "^2.14.0", "supertest": "^4.0.2" }, diff --git a/server/handlers/earningsHandler.js b/server/handlers/earningsHandler.js index 30ebe6e..6caac3f 100644 --- a/server/handlers/earningsHandler.js +++ b/server/handlers/earningsHandler.js @@ -26,25 +26,16 @@ const earningsGetQuerySchema = Joi.object({ }).unknown(false); const earningsPatchSchema = Joi.object({ - id: Joi.string().uuid().required(), + id: Joi.string().uuid(), + earnings_id: Joi.string().uuid(), worker_id: Joi.string().uuid().required(), amount: Joi.number().required(), currency: Joi.string().required(), payment_confirmation_id: Joi.string().required(), payment_system: Joi.string().required(), paid_at: Joi.date().iso(), -}); - -const earningsBatchPatchSchema = Joi.object({ - earnings_id: Joi.string().uuid().required(), - worker_id: Joi.string().uuid().required(), - amount: Joi.number().required(), - currency: Joi.string().required(), - payment_confirmation_id: Joi.string().required(), - payment_system: Joi.string().required(), - paid_at: Joi.date().iso(), - phone: Joi.string().required(), -}); + phone: Joi.string(), +}).xor('id', 'earnings_id'); const earningsGet = async (req, res, next) => { await earningsGetQuerySchema.validateAsync(req.query, { abortEarly: false }); @@ -90,7 +81,7 @@ const earningsBatchGet = async (req, res, next) => { const result = await executeGetBatchEarnings(req.query); const json2csv = new Parser(); const csv = json2csv.parse(result.earnings); - res.header('Content-Type', 'text/csv'); + res.header('Content-Type', 'text/csv; charset=utf-8'); res.attachment('batchEarnings.csv'); res.send(csv); res.end(); @@ -110,7 +101,7 @@ const earningsBatchPatch = async (req, res, next) => { let count = 0; await session.beginTransaction(); for (const row of jsonArray) { - await earningsBatchPatchSchema.validateAsync(row, { abortEarly: false }); + await earningsPatchSchema.validateAsync(row, { abortEarly: false }); await updateEarnings(earningsRepo, row); count++; } diff --git a/server/services/aws.js b/server/services/aws.js index 975f3a1..48920b2 100644 --- a/server/services/aws.js +++ b/server/services/aws.js @@ -1,9 +1,4 @@ -const AWS = require('aws-sdk'); - -const s3 = new AWS.S3({ - accessKeyId: process.env.ACCESS_KEY_ID, - secretAccessKey: process.env.SECRET_ACCESS_KEY, -}); +const s3 = require('../services/s3'); const upload_csv = async (csv, Key) => { const params = { diff --git a/server/services/s3.js b/server/services/s3.js new file mode 100644 index 0000000..4358a01 --- /dev/null +++ b/server/services/s3.js @@ -0,0 +1,7 @@ +const AWS = require('aws-sdk'); +const s3 = new AWS.S3({ + accessKeyId: process.env.ACCESS_KEY_ID, + secretAccessKey: process.env.SECRET_ACCESS_KEY, +}); + +module.exports = s3;