From 7c3f1735f01db073020409a4dfe4cf794f14a044 Mon Sep 17 00:00:00 2001 From: Daniel da Silva Date: Tue, 11 Sep 2018 16:44:49 +0100 Subject: [PATCH] Add tests for profile editor API --- app/routes/projects--source-data.js | 2 +- app/s3/structure.js | 9 ++ app/s3/utils.js | 12 +- app/utils/osrm-profile.js | 6 +- test/test-projects-source-data.js | 243 ++++++++++++++++++++++++++++ test/test-projects.js | 2 +- test/utils/data.js | 16 +- 7 files changed, 272 insertions(+), 18 deletions(-) diff --git a/app/routes/projects--source-data.js b/app/routes/projects--source-data.js index c28c5b01..043570ff 100644 --- a/app/routes/projects--source-data.js +++ b/app/routes/projects--source-data.js @@ -299,7 +299,7 @@ export default [ .where('project_id', projId) .where('type', 'profile'); - return reply({statusCode: 200, message: 'Profile settigns uploaded'}); + return reply({statusCode: 200, message: 'Profile settings uploaded'}); } catch (err) { console.log('err', err); return reply(Boom.badImplementation(err)); diff --git a/app/s3/structure.js b/app/s3/structure.js index 4ea3010a..eb190d8a 100644 --- a/app/s3/structure.js +++ b/app/s3/structure.js @@ -98,3 +98,12 @@ export function putObjectFromFile (bucket, name, filepath) { }); }); } + +export function putObject (bucket, file, stream) { + return new Promise((resolve, reject) => { + s3.putObject(bucket, file, stream, (err, etag) => { + if (err) return reject(err); + return resolve(etag); + }); + }); +} diff --git a/app/s3/utils.js b/app/s3/utils.js index 1c1a56f6..3b701b82 100644 --- a/app/s3/utils.js +++ b/app/s3/utils.js @@ -3,7 +3,7 @@ import fs from 'fs-extra'; import Promise from 'bluebird'; import s3, { bucket } from './'; -import { removeObject, putObjectFromFile, listObjects, emptyBucket } from './structure'; +import { removeObject, putObjectFromFile, listObjects, emptyBucket, putObject } from './structure'; const readFile = Promise.promisify(fs.readFile); @@ -115,14 +115,10 @@ export function getJSONFileContents (file) { .then(result => JSON.parse(result)); } -// Put file from stream +// Put object +// Proxy of putObject function, assuming the bucket. export function putFileStream (file, stream) { - return new Promise((resolve, reject) => { - s3.putObject(bucket, file, stream, (err, etag) => { - if (err) return reject(err); - return resolve(etag); - }); - }); + return putObject(bucket, file, stream); } // Put file diff --git a/app/utils/osrm-profile.js b/app/utils/osrm-profile.js index fe7cbd02..1752359e 100644 --- a/app/utils/osrm-profile.js +++ b/app/utils/osrm-profile.js @@ -1,7 +1,7 @@ 'use strict'; import renderProfile from './default.profile.template'; -function toLua (element) { +export function toLua (element) { let properties = []; // Array @@ -12,7 +12,9 @@ function toLua (element) { // Object } else if (typeof element === 'object') { Object.keys(element).forEach(key => { - properties.push(` ["${key}"] = ${toLua(element[key])}`); + // Ensure correct indentation. + const lua = toLua(element[key]).toString().replace(/\n/gm, '\n '); + properties.push(` ["${key}"] = ${lua}`); }); return `{\n${properties.join(',\n')}\n}`; diff --git a/test/test-projects-source-data.js b/test/test-projects-source-data.js index 0f491d1a..ffb40c78 100644 --- a/test/test-projects-source-data.js +++ b/test/test-projects-source-data.js @@ -9,6 +9,7 @@ import db from '../app/db'; import { setupStructure as setupDdStructure } from '../app/db/structure'; import { setupStructure as setupStorageStructure } from '../app/s3/structure'; import { fixMeUp } from './utils/data'; +import { getOSRMProfileDefaultSpeedSettings, toLua } from '../app/utils/osrm-profile'; var options = { connection: {port: 2000, host: '0.0.0.0'} @@ -455,4 +456,246 @@ describe('Projects source data', function () { }); }); }); + + describe('GET /projects/{projId}/source-data/editor -- profile', function () { + it('should fail without a type', function () { + return instance.injectThen({ + method: 'GET', + url: '/projects/1000/source-data/editor' + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"type" is required/); + }); + }); + + it('should fail without a type different than profile', function () { + return instance.injectThen({ + method: 'GET', + url: '/projects/1000/source-data/editor?type=invalid' + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"type" must be one of \[profile\]/); + }); + }); + + it('should fail if project is not found', function () { + return instance.injectThen({ + method: 'GET', + url: '/projects/0000/source-data/editor?type=profile' + }).then(res => { + assert.equal(res.statusCode, 404, 'Status code is 404'); + var result = res.result; + assert.equal(result.message, 'Project not found'); + }); + }); + + it('should fail if project setup is not completed', function () { + return instance.injectThen({ + method: 'GET', + url: '/projects/1000/source-data/editor?type=profile' + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.equal(result.message, 'Project setup not completed'); + }); + }); + + it('should return the profile settings', function () { + return instance.injectThen({ + method: 'GET', + url: '/projects/2000/source-data/editor?type=profile' + }).then(res => { + assert.equal(res.statusCode, 200, 'Status code is 200'); + var result = res.result; + assert.deepEqual(result, getOSRMProfileDefaultSpeedSettings()); + }); + }); + }); + + describe('POST /projects/{projId}/source-data/editor -- profile', function () { + it('should fail without a type', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/1000/source-data/editor' + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"type" is required/); + }); + }); + + it('should fail without a type different than profile', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/1000/source-data/editor?type=invalid' + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"type" must be one of \[profile\]/); + }); + }); + + it('should fail with missing keys', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/2000/source-data/editor?type=profile', + payload: { + + } + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"speed_profile" is required/); + }); + }); + + const emptySettings = { + speed_profile: {}, + surface_speeds: {}, + tracktype_speeds: {}, + smoothness_speeds: {}, + maxspeed_table_default: {}, + maxspeed_table: {} + }; + + it('should fail with invalid section keys', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/2000/source-data/editor?type=profile', + payload: Object.assign({}, emptySettings, { + invalid: {} + }) + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.equal(result.message, '"invalid" is not allowed'); + }); + }); + + it('should fail with invalid speed keys', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/2000/source-data/editor?type=profile', + payload: Object.assign({}, emptySettings, { + speed_profile: { + 'invalid key': 10 + } + }) + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"invalid key" is not allowed/); + }); + }); + + it('should fail with invalid speed values', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/2000/source-data/editor?type=profile', + payload: Object.assign({}, emptySettings, { + speed_profile: { + 'valid_key': 'invalid' + } + }) + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.match(result.message, /"valid_key" must be a number/); + }); + }); + + it('should fail if project is not found', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/0000/source-data/editor?type=profile', + payload: emptySettings + }).then(res => { + assert.equal(res.statusCode, 404, 'Status code is 404'); + var result = res.result; + assert.equal(result.message, 'Project not found'); + }); + }); + + it('should fail if project setup is not completed', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/1000/source-data/editor?type=profile', + payload: emptySettings + }).then(res => { + assert.equal(res.statusCode, 400, 'Status code is 400'); + var result = res.result; + assert.equal(result.message, 'Project setup not completed'); + }); + }); + + it('should render the object to lua', function () { + const data = { + foo: 'bar', + another: 'baz' + }; + assert.equal(toLua(data), `{ + ["foo"] = "bar", + ["another"] = "baz" +}`); + }); + + it('should render the nested object to lua', function () { + const data = { + foo: 'bar', + another: { + baz: 10, + more: 20, + double: { + nesting: 'all the way' + } + }, + bax: 10 + }; + assert.equal(toLua(data), `{ + ["foo"] = "bar", + ["another"] = { + ["baz"] = 10, + ["more"] = 20, + ["double"] = { + ["nesting"] = "all the way" + } + }, + ["bax"] = 10 +}`); + }); + + it('should update the profile settings', function () { + return instance.injectThen({ + method: 'POST', + url: '/projects/2000/source-data/editor?type=profile', + payload: Object.assign({}, emptySettings, { + speed_profile: { + highway: '100', + secondary: 20 + } + }) + }).then(res => { + assert.equal(res.statusCode, 200, 'Status code is 200'); + var result = res.result; + assert.equal(result.message, 'Profile settings uploaded'); + }) + .then(() => { + return db('projects_source_data') + .select('*') + .where('project_id', 2000) + .where('name', 'profile') + .first(); + }) + .then(({data}) => { + assert.deepEqual(data.settings, Object.assign({}, emptySettings, { + speed_profile: { + highway: 100, + secondary: 20 + } + })); + }); + }); + }); }); diff --git a/test/test-projects.js b/test/test-projects.js index 2ce83a85..dbfdee6e 100644 --- a/test/test-projects.js +++ b/test/test-projects.js @@ -141,7 +141,7 @@ describe('Projects', function () { let project = res.result; assert.deepEqual(project.sourceData, { profile: { - type: 'file', + type: 'default', files: [ { 'id': 2000, diff --git a/test/utils/data.js b/test/utils/data.js index 4796a7c2..cb6bc3e2 100644 --- a/test/utils/data.js +++ b/test/utils/data.js @@ -8,7 +8,8 @@ import fs from 'fs'; import config from '../../app/config'; import db from '../../app/db'; import { bucket } from '../../app/s3/'; -import { putObjectFromFile } from '../../app/s3/structure'; +import { putObjectFromFile, putObject } from '../../app/s3/structure'; +import { getOSRMProfileDefaultSpeedSettings, renderProfileFile } from '../../app/utils/osrm-profile'; function readJSONSync (file) { return JSON.parse(fs.readFileSync(file, 'utf8')); @@ -846,11 +847,12 @@ export function project1200 () { // Project 2000 in active state with one scenarios and all files. // Files represent real data from Sergipe, Brazil +// Profile is default. export function project2000 () { return project({ 'id': 2000, 'name': 'Sergipe, Brazil', - 'description': 'Townhalls in a part of Sergipe, brazil.', + 'description': 'Townhalls in a part of Sergipe, Brazil. Includes a default profile to allow editing.', 'status': 'active', 'bbox': JSON.stringify(ADMIN_AREAS_BBOX), 'created_at': '2017-02-01T12:00:06.000Z', @@ -888,16 +890,18 @@ export function project2000 () { ])) .then(() => projectAA(getAdminAreasForProject(2000))) .then(() => projectOrigins(getOriginsForProject(2000))) - .then(() => putObjectFromFile(bucket, 'project-2000/profile_000000', FILE_PROFILE)) + .then(() => putObject(bucket, 'project-2000/profile_000000', renderProfileFile(getOSRMProfileDefaultSpeedSettings()))) .then(() => putObjectFromFile(bucket, 'project-2000/origins_000000', FILE_ORIGINS)) .then(() => putObjectFromFile(bucket, 'project-2000/admin-bounds_000000', FILE_ADMIN)) .then(() => projectSourceData([ { 'id': 2000, 'name': 'profile', - 'type': 'file', - 'project_id': 2000 - // 'data': + 'type': 'default', + 'project_id': 2000, + 'data': { + 'settings': getOSRMProfileDefaultSpeedSettings() + } }, { 'id': 2001,