From 9756daf2eb2c36e973d4c163bdf22712a613afc8 Mon Sep 17 00:00:00 2001 From: douglas Date: Sun, 2 Sep 2018 21:34:55 +1000 Subject: [PATCH] #13 - Added endpoint to update ride --- backend/src/main/database/DatabaseManager.js | 14 +-- backend/src/main/rides/CreateRideService.js | 32 +---- backend/src/main/rides/FindOneRideService.js | 9 +- backend/src/main/rides/ListRidesService.js | 4 +- backend/src/main/rides/RideMapper.js | 66 +++++++++++ backend/src/main/rides/RideRepository.js | 101 +++++++++------- backend/src/main/rides/RideStatus.js | 16 +-- backend/src/main/rides/UpdateRideService.js | 60 ++++++++++ .../src/main/rides/aws/AwsLambdaRideApis.js | 9 ++ backend/src/main/rides/findone.js | 54 --------- backend/src/main/rides/rides-mapper.js | 27 ----- backend/src/main/rides/update.js | 83 ------------- .../test/rides/CreateRide.integration.test.js | 32 ++--- .../rides/FindOneRide.integration.test.js | 25 +--- .../test/rides/ListRides.integration.test.js | 50 +++----- backend/src/test/rides/RideEntityBuilder.js | 43 ++++--- .../src/test/rides/RideMapper.unit.test.js | 110 +++++++++++++++++ backend/src/test/rides/RideTestRepository.js | 4 +- .../test/rides/UpdateRide.integration.test.js | 111 ++++++++++++++++++ 19 files changed, 502 insertions(+), 348 deletions(-) create mode 100644 backend/src/main/rides/RideMapper.js create mode 100644 backend/src/main/rides/UpdateRideService.js delete mode 100644 backend/src/main/rides/findone.js delete mode 100644 backend/src/main/rides/rides-mapper.js delete mode 100644 backend/src/main/rides/update.js create mode 100644 backend/src/test/rides/RideMapper.unit.test.js create mode 100644 backend/src/test/rides/UpdateRide.integration.test.js diff --git a/backend/src/main/database/DatabaseManager.js b/backend/src/main/database/DatabaseManager.js index 7ef74525..018699c6 100644 --- a/backend/src/main/database/DatabaseManager.js +++ b/backend/src/main/database/DatabaseManager.js @@ -27,13 +27,13 @@ class DatabaseManager { return new Promise((resolve, reject) => { connection.query(queryString, (error, results, fields) => { if (closeConnection) { - return this.closeConnection(connection) - .finally(() => { - if (error) { - return reject(error); - } - resolve(results); - }); + let closePromise = this.closeConnection(connection); + return closePromise.finally(() => { + if (error) { + return reject(error); + } + resolve(results); + }); } if (error) { console.log("Error executing", queryString, error); diff --git a/backend/src/main/rides/CreateRideService.js b/backend/src/main/rides/CreateRideService.js index a576e05c..db4ae7a1 100644 --- a/backend/src/main/rides/CreateRideService.js +++ b/backend/src/main/rides/CreateRideService.js @@ -4,7 +4,7 @@ const jsonValidator = require('jsonschema'); const rideSchema = require('../schema/ride.json'); const RideStatus = require('./RideStatus'); const RideRepository = require('./RideRepository'); -const Coordinates = require('./Coordinates'); +const RideMapper = require('./RideMapper'); class CreateRideService { @@ -25,34 +25,14 @@ class CreateRideService { let validationError = this._validate(rideObject); if (validationError) { - return reject(validationError); + return Promise.reject(validationError); } - let facilitatorEmail = loginData && loginData.email; - let payload = this._parseBody(rideObject, facilitatorEmail); - return this._rideRepository.create(payload, connection); - } + const payload = RideMapper.dtoToEntity(rideObject, loginData && loginData.email); + payload.status = RideStatus.OPEN; + payload.deleted = '0'; - _parseBody(data, facilitatorEmail) { - return { - client: `${data.client}`, - facilitatorEmail: facilitatorEmail, - pickupTimeAndDateInUTC: new Date(data.pickupTimeAndDateInUTC), - locationFrom: new Coordinates(data.locationFrom.latitude, data.locationFrom.longitude), - locationTo: new Coordinates(data.locationTo.latitude, data.locationTo.longitude), - fbLink: data.fbLink, - driverGender: data.driverGender, - carType: data.carType, - status: RideStatus.OPEN, - deleted: `0`, - suburbFrom: data.locationFrom.suburb, - placeNameFrom: data.locationFrom.placeName, - postCodeFrom: data.locationFrom.postcode, - suburbTo: data.locationTo.suburb, - placeNameTo: data.locationTo.placeName, - postCodeTo: data.locationTo.postcode, - description: data.description - }; + return this._rideRepository.create(payload, connection); } _validate(data) { diff --git a/backend/src/main/rides/FindOneRideService.js b/backend/src/main/rides/FindOneRideService.js index 00e81884..f53981f4 100644 --- a/backend/src/main/rides/FindOneRideService.js +++ b/backend/src/main/rides/FindOneRideService.js @@ -1,6 +1,7 @@ 'use strict'; const RideRepository = require('./RideRepository'); +const RidesMapper = require('./RideMapper'); class FindOneRideService { @@ -10,7 +11,7 @@ class FindOneRideService { } findOne(id, loginData) { - if(loginData.role === 'driver'){ + if (loginData.role === 'driver') { return Promise.resolve(null); } @@ -21,14 +22,14 @@ class FindOneRideService { _findOne(id, loginData, connection) { let jsonQuery = this._createQuery(id, loginData); - return this._rideRepository.list(jsonQuery, connection) - .then(items => items[0] || null); + return this._rideRepository.findOne(jsonQuery, connection) + .then(item => RidesMapper.entityToDto(item)); } _createQuery(id, loginData) { return { id: id, - facilitatorEmail: loginData.role === 'facilitator' ? loginData.email : undefined, + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, includePickupTimeInPast: true }; } diff --git a/backend/src/main/rides/ListRidesService.js b/backend/src/main/rides/ListRidesService.js index 84e077e8..48b20115 100644 --- a/backend/src/main/rides/ListRidesService.js +++ b/backend/src/main/rides/ListRidesService.js @@ -1,6 +1,7 @@ 'use strict'; const RideRepository = require('./RideRepository'); +const RidesMapper = require('./RideMapper'); class ListRidesService { @@ -13,6 +14,7 @@ class ListRidesService { const connection = this._databaseManager.createConnection(); return this._listRides(queryParams, loginData, connection) + .then(rides => rides.map(RidesMapper.entityToDto)) .finally(() => this._databaseManager.closeConnection(connection)); } @@ -29,7 +31,7 @@ class ListRidesService { fromLatitude: queryParams.fromLatitude, driverGenders: loginData.role === 'driver' ? ['any', loginData.driverGender] : undefined, includePickupTimeInPast: loginData.role !== 'driver', - facilitatorEmail: loginData.role === 'facilitator' ? loginData.email : undefined, + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, }; } } diff --git a/backend/src/main/rides/RideMapper.js b/backend/src/main/rides/RideMapper.js new file mode 100644 index 00000000..0da9ff32 --- /dev/null +++ b/backend/src/main/rides/RideMapper.js @@ -0,0 +1,66 @@ +class RideMapper { + static entityToDto(ride) { + if(!ride){ + return null; + } + return { + "client": ride.client, + "pickupTimeAndDateInUTC": new Date(ride.pickupTimeAndDateInUTC), + "locationFrom": { + "latitude": ride.locationFrom.x, + "longitude": ride.locationFrom.y, + "suburb": ride.suburbFrom, + "postcode": ride.postCodeFrom, + "placeName": ride.placeNameFrom + }, + "locationTo": { + "latitude": ride.locationTo.x, + "longitude": ride.locationTo.y, + "suburb": ride.suburbTo, + "postcode": ride.postCodeTo, + "placeName": ride.placeNameTo + }, + "fbLink": ride.fbLink, + "driverGender": ride.driverGender, + "carType": ride.carType, + "status": ride.status, + "deleted": parseInt(ride.deleted + ''), + "facilitatorId": ride.facilitatorId || ride.facilitatorEmail, + "description": ride.description, + "id": ride.id + } + } + + static dtoToEntity(ride, facilitatorId) { + if(!ride){ + return null; + } + return { + client: `${ride.client}`, + pickupTimeAndDateInUTC: new Date(ride.pickupTimeAndDateInUTC), + locationFrom: { + latitude: ride.locationFrom.latitude, + longitude: ride.locationFrom.longitude, + suburb: ride.locationFrom.suburb, + placeName: ride.locationFrom.placeName, + postcode: ride.locationFrom.postcode, + }, + locationTo: { + latitude: ride.locationTo.latitude, + longitude: ride.locationTo.longitude, + suburb: ride.locationTo.suburb, + placeName: ride.locationTo.placeName, + postcode: ride.locationTo.postcode, + }, + fbLink: ride.fbLink, + driverGender: ride.driverGender, + carType: ride.carType, + status: ride.status, + deleted: 0, + facilitatorId: facilitatorId, + description: ride.description + } + } +} + +module.exports = RideMapper; diff --git a/backend/src/main/rides/RideRepository.js b/backend/src/main/rides/RideRepository.js index 2193c7ad..520edab7 100644 --- a/backend/src/main/rides/RideRepository.js +++ b/backend/src/main/rides/RideRepository.js @@ -7,28 +7,6 @@ class RideRepository { this._databaseManager = databaseManager; } - /** - * @param ride: - * client {string} - * facilitatorEmail {string} - * pickupTimeAndDateInUTC {Date} - * locationFrom {Coordinates} - * locationTo {Coordinates} - * fbLink {string} - * driverGender {string} - * carType {string} - * status {RideStatus} - * deleted {number - 0/1} - * suburbFrom {string} - * placeNameFrom {string} - * postCodeFrom {string} - * suburbTo {string} - * placeNameTo {string} - * postCodeTo {string} - * description - * @param connection - * @returns {*} - */ create(ride, connection) { const escape = (data) => connection.escape(data); const locationFrom = `POINT(${ride.locationFrom.latitude}, ${ride.locationFrom.longitude})`; @@ -52,22 +30,22 @@ class RideRepository { description) VALUES (${ - [ escape(ride.client), - escape(ride.facilitatorEmail), + [escape(ride.client), + escape(ride.facilitatorId), escape(moment(ride.pickupTimeAndDateInUTC).format('YYYY-MM-DD HH:mm:ss')), locationFrom, locationTo, escape(ride.fbLink), escape(ride.driverGender), escape(ride.carType), - escape(ride.status.statusName), + escape(ride.status), escape(ride.deleted), - escape(ride.suburbFrom), - escape(ride.placeNameFrom), - escape(ride.postCodeFrom), - escape(ride.suburbTo), - escape(ride.placeNameTo), - escape(ride.postCodeTo), + escape(ride.locationFrom.suburb), + escape(ride.locationFrom.placeName), + escape(ride.locationFrom.postcode), + escape(ride.locationTo.suburb), + escape(ride.locationTo.placeName), + escape(ride.locationTo.postcode), escape(ride.description) ].join(',')})`; console.log(query); @@ -75,6 +53,42 @@ class RideRepository { return this._databaseManager.query(query, connection); } + update(id, ride, connection) { + if (!id) { + throw new Error('No id specified when updating ride.'); + } + const escape = (data) => connection.escape(data); + const locationFrom = `POINT(${ride.locationFrom.latitude}, ${ride.locationFrom.longitude})`; + const locationTo = `POINT(${ride.locationTo.latitude}, ${ride.locationTo.longitude})`; + let query = `UPDATE rides SET client = ${escape(ride.client)}, + facilitatorEmail = ${escape(ride.facilitatorId)}, + pickupTimeAndDateInUTC = ${escape(moment(ride.pickupTimeAndDateInUTC).format('YYYY-MM-DD HH:mm:ss'))}, + locationFrom = ${locationFrom}, + locationTo = ${locationTo}, + fbLink = ${escape(ride.fbLink)}, + driverGender = ${escape(ride.driverGender)}, + carType = ${escape(ride.carType)}, + status = ${escape(ride.status)}, + deleted = ${ride.deleted}, + suburbFrom = ${escape(ride.locationFrom.suburb)}, + placeNameFrom = ${escape(ride.locationFrom.placeName)}, + postCodeFrom = ${escape(ride.locationFrom.postcode)}, + suburbTo = ${escape(ride.locationTo.suburb)}, + placeNameTo = ${escape(ride.locationTo.placeName)}, + postCodeTo = ${escape(ride.locationTo.postcode)}, + description = ${escape(ride.description)} + WHERE + id = ${id}`; + console.log(query); + + return this._databaseManager.query(query, connection); + } + + findOne(jsonQuery, connection) { + return this.list(jsonQuery, connection) + .then(results => results[0] || null); + } + /** * @param jsonQuery: * toLongitude {number} @@ -82,35 +96,42 @@ class RideRepository { * fromLongitude {number} * fromLatitude {number} * driverGenders {string[]} - * facilitatorEmail {string} + * facilitatorId {string} * includePickupTimeInPast {boolean} * @param connection */ list(jsonQuery, connection) { const escape = (data) => connection.escape(data); let where = []; - if(jsonQuery.toLongitude && jsonQuery.toLatitude && jsonQuery.fromLongitude && jsonQuery.fromLatitude) { + if (jsonQuery.toLongitude && jsonQuery.toLatitude && jsonQuery.fromLongitude && jsonQuery.fromLatitude) { where.push(`ST_Contains(ST_Envelope(ST_GeomFromText('LINESTRING(${jsonQuery.toLongitude} ${jsonQuery.toLatitude}, ${jsonQuery.fromLongitude} ${jsonQuery.fromLatitude})')), locationFrom)`); } - if(!jsonQuery.includePickupTimeInPast){ + if (!jsonQuery.includePickupTimeInPast) { where.push('pickupTimeAndDateInUTC >= NOW()') } - if(jsonQuery.id){ + if (jsonQuery.id) { where.push(`id = ${jsonQuery.id}`) } - if(jsonQuery.driverGenders && jsonQuery.driverGenders.length){ + if (jsonQuery.driverGenders && jsonQuery.driverGenders.length) { let genders = jsonQuery.driverGenders.map(g => ` driverGender = '${g}'`).join(' or '); where.push(genders.length === 1 ? genders : `(${genders})`) } - if(jsonQuery.facilitatorEmail){ - where.push(`facilitatorEmail = ${escape(jsonQuery.facilitatorEmail)}`) + if (jsonQuery.facilitatorId) { + where.push(`facilitatorEmail = ${escape(jsonQuery.facilitatorId)}`) } let query = `SELECT * FROM rides ${where.length ? ' WHERE ' + where.join(' AND ') : ''} ORDER BY pickupTimeAndDateInUTC ASC;`; console.log(query); - return this._databaseManager.query(query, connection); + return this._databaseManager.query(query, connection) + .then(rides => + rides.map(ride => { + // Workaround to map facilitatorEmail from database to the facilitatorId in the entity + ride.facilitatorId = ride.facilitatorId || ride.facilitatorEmail; + delete ride.facilitatorEmail; + return ride; + }) + ) } - } module.exports = RideRepository; diff --git a/backend/src/main/rides/RideStatus.js b/backend/src/main/rides/RideStatus.js index 5ccd6686..6bba9867 100644 --- a/backend/src/main/rides/RideStatus.js +++ b/backend/src/main/rides/RideStatus.js @@ -1,12 +1,8 @@ -class RideStatus { - constructor(status) { - this.statusName = status; - } - - static get OPEN(){ return new RideStatus('OPEN'); } - static get CONFIRMED(){ return new RideStatus('CONFIRMED'); } - static get ENDED(){ return new RideStatus('ENDED'); } - static get CANCELLED(){ return new RideStatus('CANCELLED'); } -} +const RideStatus = { + OPEN: 'OPEN', + CONFIRMED: 'CONFIRMED', + ENDED: 'ENDED', + CANCELLED: 'CANCELLED', +}; module.exports = RideStatus; \ No newline at end of file diff --git a/backend/src/main/rides/UpdateRideService.js b/backend/src/main/rides/UpdateRideService.js new file mode 100644 index 00000000..08f331f5 --- /dev/null +++ b/backend/src/main/rides/UpdateRideService.js @@ -0,0 +1,60 @@ +'use strict'; + +const jsonValidator = require('jsonschema'); +const rideSchema = require('../schema/ride.json'); +const RideRepository = require('./RideRepository'); +const RideMapper = require('./RideMapper'); + +class UpdateRideService { + + constructor(databaseManager) { + this._databaseManager = databaseManager; + this._rideRepository = new RideRepository(databaseManager); + } + + updateRide(id, ride, loginData) { + const connection = this._databaseManager.createConnection(); + + let updatePromise = this._updateRide(id, ride, loginData, connection); + return updatePromise.finally(() => this._databaseManager.closeConnection(connection)); + } + + _updateRide(id, rideObject, loginData, connection) { + rideObject.datetime = new Date().getTime(); + + let validationError = this._validate(rideObject); + if (validationError) { + return Promise.reject(validationError); + } + + return this._rideRepository + .findOne({ + id: id, + facilitatorId: loginData.role === 'facilitator' ? loginData.email : undefined, + includePickupTimeInPast: true + }) + .then(ride => { + if (!ride || ride.deleted) { + return null; + } + let rideEntity = RideMapper.dtoToEntity(rideObject); + rideEntity.facilitatorId = ride.facilitatorId; + return this._rideRepository.update(ride.id, rideEntity, connection); + }); + } + + _validate(data) { + const validationResult = jsonValidator.validate(data, rideSchema); + if (validationResult.errors && validationResult.errors.length) { + return { + statusCode: 400, + headers: { + 'Content-Type': 'text/plain' + }, + body: JSON.stringify({"error": validationResult.errors}) + }; + } + } +} + +module.exports = UpdateRideService; \ No newline at end of file diff --git a/backend/src/main/rides/aws/AwsLambdaRideApis.js b/backend/src/main/rides/aws/AwsLambdaRideApis.js index e2d6d42d..54f83db9 100644 --- a/backend/src/main/rides/aws/AwsLambdaRideApis.js +++ b/backend/src/main/rides/aws/AwsLambdaRideApis.js @@ -16,6 +16,15 @@ class AwsLambdaRideApis { .catch(result => callback(result)); } + update(event, context, callback) { + const loginData = decodeJwt(event); + const ride = JSON.parse(event.body); + const id = event.pathParameters.id; + return this.createRideService.updateRide(id, ride, loginData) + .then(result => callback(null, result)) + .catch(result => callback(result)); + } + list(event, context, callback) { let loginData = decodeJwt(event); let queryParams = event.queryStringParameters || {}; diff --git a/backend/src/main/rides/findone.js b/backend/src/main/rides/findone.js deleted file mode 100644 index 226edc63..00000000 --- a/backend/src/main/rides/findone.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; -const db = require('../database/db-utils').createConnection(); -const mapToDto = require('./rides-mapper').mapToDto; - -module.exports.findone = (event, context, callback) => { - console.log('Querying mysql'); - const connection = db(); - let id = event.pathParameters.id; - if (!id) { - return callback(null, { - statusCode: 400, - headers: { - 'Content-Type': 'text/plain' - }, - body: 'Invalid id' - }); - } - - queryDatabase(connection, `SELECT * FROM rides where id = ${id}`, (error, result) => { - if (error) { - console.error(error); - callback(null, { - statusCode: error.statusCode || 501, - headers: { - 'Content-Type': 'text/plain' - }, - body: JSON.stringify(error) - }); - return; - } - console.log(mapToDto(result)); - - // create a response - const response = { - statusCode: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Credentials': true - }, - body: JSON.stringify(mapToDto(result)) - }; - callback(null, response); - }); - -}; - - -function queryDatabase(connection, query, callback) { - connection.query(query, (error, results, fields) => { - connection.end(function (err) { - callback(error, results); - }); - }); -} \ No newline at end of file diff --git a/backend/src/main/rides/rides-mapper.js b/backend/src/main/rides/rides-mapper.js deleted file mode 100644 index 1fae2052..00000000 --- a/backend/src/main/rides/rides-mapper.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports.mapToDto = (rides) => { - return (rides || []).map(ride => ({ - "client": ride.client, - "pickupTimeAndDateInUTC": new Date(ride.pickupTimeAndDateInUTC), - "locationFrom": { - "latitude": ride.locationFrom.x, - "longitude": ride.locationFrom.y, - "suburb": ride.suburbFrom, - "postcode": ride.postCodeFrom, - "placeName": ride.placeNameFrom - }, - "locationTo": { - "latitude": ride.locationTo.x, - "longitude": ride.locationTo.y, - "suburb": ride.suburbTo, - "postcode": ride.postCodeTo, - "placeName": ride.placeNameTo - }, - "fbLink": ride.fbLink, - "driverGender": ride.driverGender, - "carType": ride.carType, - "status": ride.status, - "facilitatorId": ride.facilitatorEmail, - "id": ride.id, - "description": ride.description - })) -}; diff --git a/backend/src/main/rides/update.js b/backend/src/main/rides/update.js deleted file mode 100644 index 2726a373..00000000 --- a/backend/src/main/rides/update.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict'; - -const db = require('../database/db-utils').createConnection() -const rideStatus = require('./ride-status'); -const decodeJwt = require('../utils/jwt').decodeJwt; - -module.exports.update = (event, context, callback) => { - const timestamp = new Date().getTime(); - const connection = db(); - - const data = JSON.parse(event.body); - data.datetime = timestamp; - let loginData = decodeJwt(event); - let facilitatorEmail = loginData && loginData.email; - - let id = event.pathParameters.id; - if(!id){ - return callback(null, { - statusCode: 400, - headers: { - 'Content-Type': 'text/plain' - }, - body: 'Invalid id' - }); - } - - let escape = function (data) { - return connection.escape(data) - }; - let payload = { - client: `${escape(data.client)}`, - facilitatorEmail: `${escape(facilitatorEmail)}`, - pickupTimeAndDateInUTC: `"${new Date(data.pickupTimeAndDateInUTC).toISOString()}"`, - locationFrom: `POINT(${data.locationFrom.latitude}, ${data.locationFrom.longitude})`, - locationTo: `POINT(${data.locationTo.latitude}, ${data.locationTo.longitude})`, - fbLink: `${escape(data.fbLink)}`, - driverGender: `${escape(data.driverGender)}`, - carType: `${escape(data.carType)}`, - status: `${rideStatus[escape(data.status).toUpperCase()]}`, - deleted: `0`, - suburbFrom: `${escape(data.locationFrom.suburb)}`, - placeNameFrom: `${escape(data.locationFrom.placeName)}`, - postCodeFrom: `${escape(data.locationFrom.postcode)}`, - suburbTo: `${escape(data.locationTo.suburb)}`, - placeNameTo: `${escape(data.locationTo.placeName)}`, - postCodeTo: `${escape(data.locationTo.postcode)}`, - description: `${escape(data.description)}` - }; - - let values = Reflect.ownKeys(payload).map(key => `${key} = ${payload[key]}`).join(','); - let query = `UPDATE rides SET ${values} WHERE id = ${id}`; - console.log(query); - - queryDatabase(connection, query, (err, result) => { - if (err) { - console.log('ERROR', err); - return callback(null, { - statusCode: err.statusCode || 501, - headers: { - 'Content-Type': 'text/plain' - }, - body: JSON.stringify(err) - }); - } - callback(null, { - statusCode: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Credentials': true, - 'Content-Type': 'text/plain' - }, - body: JSON.stringify(result) - }) - }); -}; - -function queryDatabase(connection, query, callback) { - connection.query(query, (error, results, fields) => { - connection.end(function (err) { - callback(error, results); - }); - }); -} diff --git a/backend/src/test/rides/CreateRide.integration.test.js b/backend/src/test/rides/CreateRide.integration.test.js index 0f2be972..1924c64e 100644 --- a/backend/src/test/rides/CreateRide.integration.test.js +++ b/backend/src/test/rides/CreateRide.integration.test.js @@ -1,4 +1,5 @@ const DatabaseManager = require('../../main/database/DatabaseManager'); +const RideStatus = require('../../main/rides/RideStatus'); const CreateRideService = require('../../main/rides/CreateRideService'); const FindRideTestRepository = require('./RideTestRepository'); const databaseTestConfig = require('../database/databaseTestConfig'); @@ -11,12 +12,12 @@ chai.use(chaiExclude); let createRideService; let rideRequest; -let expectedRide; let databaseManager; let mockDatabaseManager; let findRideTestRepository; let connection; let loginData; +let pickupTimeAndDateInUTC; before(async () => { databaseManager = new DatabaseManager(databaseTestConfig); @@ -43,7 +44,7 @@ afterEach(async () => { beforeEach(function setupData() { loginData = {email: RandomUtils.randomEmail()} - const currentTime = moment().format('YYYY-MM-DD HH:mm:ss.SSS'); + pickupTimeAndDateInUTC = moment(); const clientEmail = `client.test.${Date.now()}@carpal.com`; rideRequest = { 'carType': 'suv', @@ -66,28 +67,9 @@ beforeEach(function setupData() { "suburb": "Bondi", "postcode": "2011" }, - 'pickupTimeAndDateInUTC': currentTime, - 'status': 'OPEN' + 'pickupTimeAndDateInUTC': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN }; - expectedRide = { - "carType": "suv", - "client": clientEmail, - "deleted": 0, - "description": "wanna surf", - "driverGender": "male", - "facilitatorEmail": loginData.email, - "fbLink": "http://facebook.com/profile/facilitator.test.1", - "locationFrom": {"x": 1234, "y": 4567}, - "locationTo": {"x": 9999, "y": 7777}, - "pickupTimeAndDateInUTC": new Date(new Date(currentTime).setMilliseconds(0)), - "placeNameFrom": "home", - "placeNameTo": "beach", - "postCodeFrom": "2010", - "postCodeTo": "2011", - "status": "OPEN", - "suburbFrom": "Bondi Junction", - "suburbTo": "Bondi", - } }); describe('SQL', function () { @@ -95,7 +77,9 @@ describe('SQL', function () { await createRideService.createRide(rideRequest, loginData); let storedRide = await findRideTestRepository.findOneByClientEmail(rideRequest.client, connection); - assert.deepEqualExcluding(storedRide, expectedRide, 'id', 'datetime'); + assert.deepEqualExcluding(storedRide, rideRequest, ['id', 'datetime', 'pickupTimeAndDateInUTC', 'facilitatorId']); + assert.equal(storedRide.facilitatorId, loginData.email); + assert.equal(storedRide.pickupTimeAndDateInUTC.setMilliseconds(0), pickupTimeAndDateInUTC.toDate().setMilliseconds(0)); }); }); diff --git a/backend/src/test/rides/FindOneRide.integration.test.js b/backend/src/test/rides/FindOneRide.integration.test.js index ec26d96b..6a3027be 100644 --- a/backend/src/test/rides/FindOneRide.integration.test.js +++ b/backend/src/test/rides/FindOneRide.integration.test.js @@ -21,7 +21,7 @@ chai.use(chaiExclude); before(async () => { databaseManager = new DatabaseManager(databaseTestConfig); mockDatabaseManager = new DatabaseManager(databaseTestConfig); - connection = databaseManager.createConnection() ; + connection = databaseManager.createConnection(); mockDatabaseManager.createConnection = () => connection; mockDatabaseManager.closeConnection = () => Promise.resolve(null); }); @@ -54,7 +54,7 @@ describe('When find one ride', async () => { const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); // then - assert.deepEqualExcluding(transformRideToDbFormat(ride), storedRide, 'id'); + assert.deepEqualExcluding(ride, storedRide, 'id'); }); it('should NOT show ride that was created by other facilitator', async function () { @@ -80,7 +80,7 @@ describe('When find one ride', async () => { const storedRide = await findOneRideService.findOne(rideEntity.id, loginData); // then - assert.deepEqualExcluding(transformRideToDbFormat(ride), storedRide, 'id'); + assert.deepEqualExcluding(ride, storedRide, 'id'); }); it('should not show single ride when user is driver', async function () { @@ -96,28 +96,15 @@ describe('When find one ride', async () => { assert.isNull(storedRide); }); - function transformRideToDbFormat(ride){ - ride.locationFrom.x = ride.locationFrom.latitude; - ride.locationFrom.y = ride.locationFrom.longitude; - ride.locationTo.x = ride.locationTo.latitude; - ride.locationTo.y = ride.locationTo.longitude; - ride.status = ride.status.statusName; - delete ride.locationFrom.latitude; - delete ride.locationFrom.longitude; - delete ride.locationTo.latitude; - delete ride.locationTo.longitude; - return ride; - } - async function databaseContainsRide(ride) { await rideRepository.create(ride, connection); let rideEntity = rideTestRepository.findOneByClientEmail(ride.client); return rideEntity; } - function randomRideWithFacilitator(facilitatorEmail) { - const ride = RideEntityBuilder.randomRide(); - ride.facilitatorEmail = facilitatorEmail; + function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; return ride; } }); diff --git a/backend/src/test/rides/ListRides.integration.test.js b/backend/src/test/rides/ListRides.integration.test.js index 228a30a7..580d681d 100644 --- a/backend/src/test/rides/ListRides.integration.test.js +++ b/backend/src/test/rides/ListRides.integration.test.js @@ -21,9 +21,9 @@ chai.use(chaiExclude); before(async () => { databaseManager = new DatabaseManager(databaseTestConfig); mockDatabaseManager = new DatabaseManager(databaseTestConfig); - connection = databaseManager.createConnection() ; + connection = databaseManager.createConnection(); mockDatabaseManager.createConnection = () => connection; - mockDatabaseManager.closeConnection = () => null; + mockDatabaseManager.closeConnection = () => Promise.resolve(null); }); after(async () => { @@ -54,22 +54,21 @@ describe('When listing rides', async () => { // when const rides = await listRideController.listRides({}, loginData); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + assert.deepEqualExcluding(rides, [ride1, ride2], 'id'); }); it('should show all rides for admin', async function () { // given const loginData = {email: RandomUtils.randomEmail(), role: 'admin'}; - const ride1 = RideEntityBuilder.randomRide(); - const ride2 = RideEntityBuilder.randomRide(); + const ride1 = RideEntityBuilder.randomRideEntity(); + const ride2 = RideEntityBuilder.randomRideEntity(); await databaseContainsRides(ride1, ride2); // when - const rides = await listRideController.listRides({}, loginData); + const rides = (await listRideController.listRides({}, loginData)).map(removeId); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + assert.deepInclude(rides, ride1); + assert.deepInclude(rides, ride2); }); it('should show all rides for driver', async function () { @@ -83,22 +82,13 @@ describe('When listing rides', async () => { // when const rides = await listRideController.listRides({}, loginData); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride1)); - assert.deepInclude(rides.map(removeId), transformRideToDbFormat(ride3)); - assert.notDeepInclude(rides.map(removeId), transformRideToDbFormat(ride2)); + assert.deepEqualExcluding(rides, [ride1, ride3], 'id'); }); - function transformRideToDbFormat(ride){ - ride.locationFrom.x = ride.locationFrom.latitude; - ride.locationFrom.y = ride.locationFrom.longitude; - ride.locationTo.x = ride.locationTo.latitude; - ride.locationTo.y = ride.locationTo.longitude; - ride.status = ride.status.statusName; - delete ride.locationFrom.latitude; - delete ride.locationFrom.longitude; - delete ride.locationTo.latitude; - delete ride.locationTo.longitude; - return ride; + async function databaseContainsRides(...rides) { + for (let ride of rides) { + await rideRepository.create(ride, connection); + } } function removeId(ride){ @@ -106,21 +96,15 @@ describe('When listing rides', async () => { return ride; } - async function databaseContainsRides(...rides) { - for(let ride of rides){ - await rideRepository.create(ride, connection); - } - } - function randomRideWithGender(gender) { - const ride = RideEntityBuilder.randomRide(); + const ride = RideEntityBuilder.randomRideEntity(); ride.driverGender = gender; return ride; } - function randomRideWithFacilitator(facilitatorEmail) { - const ride = RideEntityBuilder.randomRide(); - ride.facilitatorEmail = facilitatorEmail; + function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; return ride; } }); diff --git a/backend/src/test/rides/RideEntityBuilder.js b/backend/src/test/rides/RideEntityBuilder.js index f90086ac..974f41e5 100644 --- a/backend/src/test/rides/RideEntityBuilder.js +++ b/backend/src/test/rides/RideEntityBuilder.js @@ -2,28 +2,35 @@ const RandomUtils = require('../RandomUtils'); const RideStatus = require('../../main/rides/RideStatus'); const now = new Date(); + class RideEntityBuilder { - static randomRide() { + static randomRideEntity() { const client = `client.${Date.now()}`; const facilitator = `facilitator.${Date.now()}`; return { - "carType": "suv", - "client": `${client}@${RandomUtils.randomString(5)}.com`, - "deleted": 0, - "description": RandomUtils.randomString(10), - "driverGender": (Math.random() * 1000) % 2 ? "male" : "female", - "facilitatorEmail": `${facilitator}@${RandomUtils.randomString(5)}.com`, - "fbLink": `http://facebook.com/profile/${client}`, - "locationFrom": {"latitude": RandomUtils.randomNumber(4), "longitude": RandomUtils.randomNumber(4)}, - "locationTo": {"latitude": RandomUtils.randomNumber(4), "longitude": RandomUtils.randomNumber(4)}, - "pickupTimeAndDateInUTC": new Date(new Date(now.setMilliseconds(0)).setDate(now.getDate() + 5)), - "placeNameFrom": RandomUtils.randomString(10), - "placeNameTo": RandomUtils.randomString(10), - "postCodeFrom": RandomUtils.randomNumber(4).toString(), - "postCodeTo": RandomUtils.randomNumber(4).toString(), - "status": RideStatus.OPEN, - "suburbFrom": RandomUtils.randomString(10), - "suburbTo": RandomUtils.randomString(10), + carType: "suv", + client: `${client}@${RandomUtils.randomString(5)}.com`, + deleted: 0, + driverGender: (Math.random() * 1000) % 2 ? "male" : "female", + facilitatorId: `${facilitator}@${RandomUtils.randomString(5)}.com`, + fbLink: `http://facebook.com/profile/${client}`, + locationFrom: { + latitude: RandomUtils.randomNumber(4), + longitude: RandomUtils.randomNumber(4), + placeName: RandomUtils.randomString(10), + postcode: RandomUtils.randomNumber(4).toString(), + suburb: RandomUtils.randomString(10) + }, + locationTo: { + latitude: RandomUtils.randomNumber(4), + longitude: RandomUtils.randomNumber(4), + placeName: RandomUtils.randomString(10), + postcode: RandomUtils.randomNumber(4).toString(), + suburb: RandomUtils.randomString(10) + }, + pickupTimeAndDateInUTC: new Date(new Date(now.setMilliseconds(0)).setDate(now.getDate() + 5)), + status: RideStatus.OPEN, + description: RandomUtils.randomString(10), } } } diff --git a/backend/src/test/rides/RideMapper.unit.test.js b/backend/src/test/rides/RideMapper.unit.test.js new file mode 100644 index 00000000..e78ad4d5 --- /dev/null +++ b/backend/src/test/rides/RideMapper.unit.test.js @@ -0,0 +1,110 @@ +const RideMapper = require('../../main/rides/RideMapper'); +const RandomUtils = require('../RandomUtils'); +const RideStatus = require('../../main/rides/RideStatus'); +const RideEntityBuilder = require('./RideEntityBuilder'); + +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +const moment = require('moment'); +chai.use(chaiExclude); + + +describe('RideMapper', async () => { + it('should convert dto to entity', async function () { + const pickupTimeAndDateInUTC = moment(); + const facilitatorId = RandomUtils.randomEmail(); + const dto = { + 'carType': 'suv', + 'client': RandomUtils.randomEmail(), + 'deleted': 0, + 'description': RandomUtils.randomString(10), + 'driverGender': (RandomUtils.randomNumber(2) % 2) ? 'male' : 'female', + 'fbLink': 'http://facebook.com/profile/' + RandomUtils.randomString(10), + 'locationFrom': { + "latitude": RandomUtils.randomNumber(4), + "longitude": RandomUtils.randomNumber(4), + "placeName": RandomUtils.randomString(10), + "suburb": RandomUtils.randomString(10), + "postcode": RandomUtils.randomNumber(4) + }, + 'locationTo': { + "latitude": RandomUtils.randomNumber(4), + "longitude": RandomUtils.randomNumber(4), + "placeName": RandomUtils.randomString(10), + "suburb": RandomUtils.randomString(10), + "postcode": RandomUtils.randomNumber(4) + }, + 'pickupTimeAndDateInUTC': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN + }; + + const entity = RideMapper.dtoToEntity(dto, facilitatorId); + + assert.deepEqual(entity, { + client: dto.client, + pickupTimeAndDateInUTC: new Date(dto.pickupTimeAndDateInUTC), + locationFrom: { + latitude: dto.locationFrom.latitude, + longitude: dto.locationFrom.longitude, + suburb: dto.locationFrom.suburb, + placeName: dto.locationFrom.placeName, + postcode: dto.locationFrom.postcode, + }, + locationTo: { + latitude: dto.locationTo.latitude, + longitude: dto.locationTo.longitude, + suburb: dto.locationTo.suburb, + placeName: dto.locationTo.placeName, + postcode: dto.locationTo.postcode, + }, + fbLink: dto.fbLink, + driverGender: dto.driverGender, + carType: dto.carType, + status: dto.status, + deleted: 0, + facilitatorId: facilitatorId, + description: dto.description + }); + }); + + it('should convert entity to dto', async function () { + // given + const entity = RideEntityBuilder.randomRideEntity(); + + // then + const dto = RideMapper.entityToDto(entity); + + // then + assert.deepEqual(dto, { + client: entity.client, + pickupTimeAndDateInUTC: new Date(entity.pickupTimeAndDateInUTC), + locationFrom: { + latitude: entity.locationFrom.x, + longitude: entity.locationFrom.y, + suburb: entity.suburbFrom, + postcode: entity.postCodeFrom, + placeName: entity.placeNameFrom + }, + locationTo: { + latitude: entity.locationTo.x, + longitude: entity.locationTo.y, + suburb: entity.suburbTo, + postcode: entity.postCodeTo, + placeName: entity.placeNameTo + }, + fbLink: entity.fbLink, + driverGender: entity.driverGender, + carType: entity.carType, + status: entity.status, + deleted: entity.deleted, + facilitatorId: entity.facilitatorId, + description: entity.description, + id: entity.id + }); + }); + +}); + + + diff --git a/backend/src/test/rides/RideTestRepository.js b/backend/src/test/rides/RideTestRepository.js index 02180c01..c8705d3b 100644 --- a/backend/src/test/rides/RideTestRepository.js +++ b/backend/src/test/rides/RideTestRepository.js @@ -1,4 +1,4 @@ -'use strict'; +const RidesMapper = require('../../main/rides/RideMapper'); class RideTestRepository { constructor(databaseManager) { @@ -7,7 +7,7 @@ class RideTestRepository { findOneByClientEmail(email, connection){ return this._databaseManager.query(`SELECT * FROM rides WHERE client = '${email}'`, connection) - .then(result => result instanceof Array && result[0]); + .then(result => result instanceof Array && RidesMapper.entityToDto(result[0])); } } diff --git a/backend/src/test/rides/UpdateRide.integration.test.js b/backend/src/test/rides/UpdateRide.integration.test.js new file mode 100644 index 00000000..c6e49c68 --- /dev/null +++ b/backend/src/test/rides/UpdateRide.integration.test.js @@ -0,0 +1,111 @@ +const DatabaseManager = require('../../main/database/DatabaseManager'); +const RideStatus = require('../../main/rides/RideStatus'); +const UpdateRideService = require('../../main/rides/UpdateRideService'); +const FindRideTestRepository = require('./RideTestRepository'); +const databaseTestConfig = require('../database/databaseTestConfig'); +const RideEntityBuilder = require('./RideEntityBuilder'); +const RideRepository = require('../../main/rides/RideRepository'); +const RandomUtils = require('../RandomUtils'); +const RideMapper = require('../../main/rides/RideMapper'); +const moment = require('moment'); +const chai = require('chai'); +const chaiExclude = require('chai-exclude'); +const assert = chai.assert; +chai.use(chaiExclude); + +let updateRideService; +let databaseManager; +let mockDatabaseManager; +let findRideTestRepository; +let connection; +let loginData; +let pickupTimeAndDateInUTC; +let rideRepository; + +before(async () => { + databaseManager = new DatabaseManager(databaseTestConfig); + mockDatabaseManager = new DatabaseManager(databaseTestConfig); + rideRepository = new RideRepository(databaseManager); + connection = databaseManager.createConnection(); + mockDatabaseManager.createConnection = () => connection; + mockDatabaseManager.closeConnection = () => Promise.resolve(); +}); + +after(async () => { + await databaseManager.closeConnection(connection); +}); + +beforeEach(async () => { + await databaseManager.beginTransaction(connection); + + updateRideService = new UpdateRideService(mockDatabaseManager); + findRideTestRepository = new FindRideTestRepository(databaseManager); +}); + +afterEach(async () => { + await databaseManager.rollback(connection); +}); + +beforeEach(function setupData() { + loginData = {email: RandomUtils.randomEmail()}; + pickupTimeAndDateInUTC = moment(); +}); + +describe('SQL', function () { + it('should update and retrieve ride', async function () { + // given + const ride = randomRideWithFacilitator(loginData.email); + await databaseContainsRide(ride); + const modifiedRide = modifyRide(ride); + let storedRide = await findRideTestRepository.findOneByClientEmail(ride.client, connection); + + // when + await updateRideService.updateRide(storedRide.id, modifiedRide, loginData); + let modifiedRideFromDb = await findRideTestRepository.findOneByClientEmail(storedRide.client, connection); + + // then + assert.deepEqualExcluding(modifiedRideFromDb, RideMapper.dtoToEntity(modifiedRide), ['id', 'datetime', 'facilitatorId', 'pickupTimeAndDateInUTC']); + assert.equal(modifiedRideFromDb.facilitatorId, loginData.email); + assert.equal(modifiedRideFromDb.pickupTimeAndDateInUTC.setMilliseconds(0), pickupTimeAndDateInUTC.toDate().setMilliseconds(0)); + }); +}); + +function modifyRide(ride) { + return { + 'carType': 'suv', + 'client': ride.client, + 'deleted': 0, + 'description': 'wanna surf', + 'driverGender': 'male', + 'fbLink': 'http://facebook.com/profile/facilitator.test.1', + 'locationFrom': { + "latitude": 1234, + "longitude": 4567, + "placeName": "home", + "suburb": "Bondi Junction", + "postcode": "2010" + }, + 'locationTo': { + "latitude": 9999, + "longitude": 7777, + "placeName": "beach", + "suburb": "Bondi", + "postcode": "2011" + }, + 'pickupTimeAndDateInUTC': pickupTimeAndDateInUTC.format('YYYY-MM-DD HH:mm:ss.SSS'), + 'status': RideStatus.OPEN + }; +} + +async function databaseContainsRide(ride) { + await rideRepository.create(ride, connection); +} + +function randomRideWithFacilitator(facilitatorId) { + const ride = RideEntityBuilder.randomRideEntity(); + ride.facilitatorId = facilitatorId; + return ride; +} + + +