Last active
December 29, 2017 22:15
-
-
Save adnan-i/96c50972550b75f5479ef5581af95fe9 to your computer and use it in GitHub Desktop.
node module for geocoding the address string
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Promise = require('bluebird'); | |
const GMaps = require('@google/maps'); | |
const Joi = require('joi'); | |
const geocodeResponseSchema = Joi.object().keys({ | |
json: Joi.object().keys({ | |
results: Joi.array().required().min(1).items(Joi.object().keys({ | |
address_components: Joi.array().required(), | |
formatted_address: Joi.string().required(), | |
geometry: Joi.object().keys({ | |
location: Joi.object().keys({ | |
lat: Joi.number().required(), | |
lng: Joi.number().required(), | |
}).required() | |
}).required() | |
})).required() | |
}).required() | |
}).required(); | |
class GoogleMapService { | |
constructor(server) { | |
const googleApiKey = server.app.config.get('/google/apiKey'); | |
const googleMapsClient = GMaps.createClient({ key: googleApiKey }); | |
this.client = Promise.promisifyAll(googleMapsClient); | |
this.geocodeResponseSchema = geocodeResponseSchema; | |
} | |
static init(...args) { | |
if (this.instance) return this.instance; | |
this.instance = new this(...args); | |
return this.instance; | |
} | |
/** | |
* Uses Google Maps geocoding API to find the coordinates of the provided address object | |
* @param {string|number} address (e.g. customer.zip ) | |
* @param {object} options | |
* @param {boolean} options.raw Whether to return raw geocode results or not | |
* @returns {Promise.<object>} | |
*/ | |
geocode(address = '', options = {}) { | |
return Promise.resolve() | |
.then(() => this.client.geocodeAsync({ address })) | |
.then((response) => { | |
if (options.raw) { | |
return response; | |
} | |
const result = Joi.validate(response, this.geocodeResponseSchema, { stripUnknown: true }); | |
if (result.error) { | |
throw result.error; | |
} | |
return result.value.json.results[0]; | |
}); | |
} | |
} | |
module.exports = GoogleMapService; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Config = require('../../config'); | |
const LoggerServiceClass = require('../core/LoggerService'); | |
const proxyquire = require('proxyquire'); | |
const Joi = require('joi'); | |
const _ = require('lodash'); | |
describe('GoogleMapService class', () => { | |
let server; | |
const ServiceClassPath = './GoogleMapService'; | |
beforeEach(() => { | |
server = { | |
app: { | |
config: Config | |
}, | |
plugins: { | |
core: {} | |
} | |
}; | |
server.plugins.core.LoggerService = new LoggerServiceClass(server); | |
}); | |
describe('static method init', () => { | |
it('should return a new instance of the service', () => { | |
const ServiceClass = require(ServiceClassPath); | |
const service = ServiceClass.init(server); | |
expect(service).to.an.instanceof(ServiceClass); | |
}); | |
}); | |
describe('constructor', () => { | |
it('should create instance of GMaps client', () => { | |
const client = { | |
geocode: (done) => done() | |
}; | |
const GMaps = { | |
createClient: () => client | |
}; | |
const createClientSpy = box.spy(GMaps, 'createClient'); | |
const googleApiKey = Config.get('/google/apiKey'); | |
const ServiceClass = proxyquire(ServiceClassPath, {'@google/maps': GMaps}); | |
const service = ServiceClass.init(server); | |
expect(createClientSpy.calledWith({ key: googleApiKey })).to.be.true(); | |
expect(service.client).to.exist(); | |
expect(service.client.geocodeAsync).to.exist(); | |
}); | |
}); | |
describe('geocode', () => { | |
let response; | |
before(() => { | |
response = { | |
json: { | |
results: [{ | |
address_components: [], | |
formatted_address: '', | |
geometry: { | |
location: { | |
lat: 5, | |
lng: 4 | |
} | |
} | |
}] | |
} | |
}; | |
}); | |
it('should geocode the provided address and return the first result', () => { | |
box.stub(Joi, 'validate').callsFake((response) => { | |
return { | |
value: response | |
}; | |
}); | |
const client = { | |
geocode: (done) => done() | |
}; | |
const GMaps = { | |
createClient: () => client | |
}; | |
box.spy(GMaps, 'createClient'); | |
box.stub(client, 'geocode').callsFake((address, done) => { | |
done(null, response); | |
}); | |
const ServiceClass = proxyquire(ServiceClassPath, {'@google/maps': GMaps}); | |
const service = ServiceClass.init(server); | |
const zip = 555; | |
return service.geocode(zip) | |
.then((rs) => { | |
expect(rs).to.deep.equal(response.json.results[0]); | |
}); | |
}); | |
it('should return raw Google geocode results if options.raw is true', () => { | |
box.stub(Joi, 'validate').callsFake((response) => { | |
return { | |
value: response | |
}; | |
}); | |
const client = { | |
geocode: (done) => done() | |
}; | |
const GMaps = { | |
createClient: () => client | |
}; | |
box.spy(GMaps, 'createClient'); | |
box.stub(client, 'geocode').callsFake((address, done) => { | |
done(null, response); | |
}); | |
const ServiceClass = proxyquire(ServiceClassPath, {'@google/maps': GMaps}); | |
const service = ServiceClass.init(server); | |
const zip = 555; | |
return service.geocode(zip, {raw: true}) | |
.then((rs) => { | |
expect(rs).to.deep.equal(response); | |
}); | |
}); | |
it('should return rejected promise if geocode response does not pass joi validation', () => { | |
const joiError = new Error('invalid'); | |
box.stub(Joi, 'validate').callsFake((response) => { | |
return { | |
value: response, | |
error: joiError | |
}; | |
}); | |
const client = { | |
geocode: (done) => done() | |
}; | |
const GMaps = { | |
createClient: () => client | |
}; | |
box.spy(GMaps, 'createClient'); | |
box.stub(client, 'geocode').callsFake((address, done) => { | |
const _response = _.cloneDeep(response); | |
_response.json = []; | |
done(null, response); | |
}); | |
const ServiceClass = proxyquire(ServiceClassPath, {'@google/maps': GMaps}); | |
const service = ServiceClass.init(server); | |
const zip = 555; | |
return expect(service.geocode(zip)).to.eventually.be.rejectedWith(joiError); | |
}); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment