Skip to content

Instantly share code, notes, and snippets.

@adnan-i
Last active December 29, 2017 22:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adnan-i/96c50972550b75f5479ef5581af95fe9 to your computer and use it in GitHub Desktop.
Save adnan-i/96c50972550b75f5479ef5581af95fe9 to your computer and use it in GitHub Desktop.
node module for geocoding the address string
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;
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