├── .gitignore ├── .travis.yml ├── .jshintrc ├── LICENSE ├── test ├── mocks │ ├── reverse_200.json │ └── geocode_200.json └── index.js ├── package.json ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .npmignore 3 | .DS_Store 4 | .npm-debug.log 5 | coverage 6 | bench.js 7 | test.js 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | - 10 5 | - 12 6 | branches: 7 | only: 8 | - master 9 | notifications: 10 | email: 11 | - joaquim.serafim@gmail.com 12 | script: npm test -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "node": true, 4 | "strict": false, 5 | "smarttabs": true, 6 | "maxlen": 80, 7 | "newcap": false, 8 | "undef": true, 9 | "unused": true, 10 | "onecase": true, 11 | "indent": 2, 12 | "sub": true 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) 2014, Joaquim José F. Serafim 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/mocks/reverse_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "place_id": "109962736", 3 | "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright", 4 | "osm_type": "way", 5 | "osm_id": "196984895", 6 | "lat": "38.578832", 7 | "lon": "-8.9469721", 8 | "display_name": "Avenida Doutor António de Matos Fortuna, Quinta do Anjo, Palmela, Setúbal, Península de Setúbal, Área Metropolitana de Lisboa, Portugal", 9 | "address": { 10 | "road": "Avenida Doutor António de Matos Fortuna", 11 | "residential": "Quinta do Anjo", 12 | "village": "Quinta do Anjo", 13 | "county": "Palmela", 14 | "state_district": "Península de Setúbal", 15 | "state": "Área Metropolitana de Lisboa", 16 | "country": "Portugal", 17 | "country_code": "pt" 18 | }, 19 | "boundingbox": [ 20 | "38.5746729", 21 | "38.5801647", 22 | "-8.9483526", 23 | "-8.9465671" 24 | ] 25 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-open-geocoder", 3 | "version": "4.0.0", 4 | "description": "Open Street Map API client for geocoding and reverse geocoding", 5 | "main": "index.js", 6 | "files": [ 7 | "LICENSE", 8 | "README.md", 9 | "index.js" 10 | ], 11 | "scripts": { 12 | "coverage": "open coverage/lcov-report/index.html", 13 | "coverage:check": "istanbul check-coverage --statements 100 --functions 100 --lines 100 --branches 100", 14 | "test": "standard --fix && istanbul cover -x test/index.js _mocha test/index.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/joaquimserafim/node-open-geocoder.git" 19 | }, 20 | "keywords": [ 21 | "geocoder", 22 | "reverse", 23 | "openstreetmap" 24 | ], 25 | "author": "@JoaquimSerafim", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/joaquimserafim/node-open-geocoder/issues" 29 | }, 30 | "homepage": "https://github.com/joaquimserafim/node-open-geocoder", 31 | "devDependencies": { 32 | "chai": "^4.2.0", 33 | "istanbul": "0.4.5", 34 | "mocha": "^6.2.2", 35 | "nock": "^11.7.0", 36 | "pre-commit": "^1.2.2", 37 | "repeat-element": "^1.1.3", 38 | "standard": "^14.3.1" 39 | }, 40 | "dependencies": { 41 | "get-property-value": "^2.0.0", 42 | "is-valid-coordinates": "^1.0.0", 43 | "is.object": "^1.0.0", 44 | "json-parse-safe": "^2.0.0", 45 | "qs": "^6.9.1" 46 | }, 47 | "engines": { 48 | "node": ">=6.1" 49 | }, 50 | "pre-commit": [ 51 | "test", 52 | "coverage:check" 53 | ], 54 | "standard": { 55 | "ignore": [ 56 | "bench.js" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/mocks/geocode_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "place_id": "83656556", 4 | "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright", 5 | "osm_type": "way", 6 | "osm_id": "90394480", 7 | "boundingbox": [ 8 | "52.5487473", 9 | "52.5488481", 10 | "-1.8165129", 11 | "-1.8163463" 12 | ], 13 | "lat": "52.5487921", 14 | "lon": "-1.8164307339635", 15 | "display_name": "135, Pilkington Avenue, Sutton Coldfield, Maney, Birmingham, West Midlands, England, B72 1LH, UK", 16 | "class": "building", 17 | "type": "yes", 18 | "importance": 0.301, 19 | "address": { 20 | "house_number": "135", 21 | "road": "Pilkington Avenue", 22 | "suburb": "Sutton Coldfield", 23 | "hamlet": "Maney", 24 | "city": "Birmingham", 25 | "state_district": "West Midlands", 26 | "state": "England", 27 | "postcode": "B72 1LH", 28 | "country": "UK", 29 | "country_code": "gb" 30 | }, 31 | "geojson": { 32 | "type": "Polygon", 33 | "coordinates": [ 34 | [ 35 | [ 36 | -1.8165129, 37 | 52.5487566 38 | ], 39 | [ 40 | -1.8164912, 41 | 52.548824 42 | ], 43 | [ 44 | -1.8164684, 45 | 52.5488213 46 | ], 47 | [ 48 | -1.8164598, 49 | 52.5488481 50 | ], 51 | [ 52 | -1.8163463, 53 | 52.5488346 54 | ], 55 | [ 56 | -1.8163716, 57 | 52.5487561 58 | ], 59 | [ 60 | -1.8164289, 61 | 52.5487629 62 | ], 63 | [ 64 | -1.8164339, 65 | 52.5487473 66 | ], 67 | [ 68 | -1.8165129, 69 | 52.5487566 70 | ] 71 | ] 72 | ] 73 | } 74 | } 75 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-open-geocoder 2 | 3 | Open Street Map API client for geocoding and reverse geocoding 4 | 5 | ---- 6 | 7 | 8 | [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/joaquimserafim/node-open-geocoder)![Code Coverage 100%](https://img.shields.io/badge/code%20coverage-100%25-green.svg?style=flat-square)[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg?style=flat-square)](https://github.com/joaquimserafim/node-open-geocoder/blob/master/LICENSE)[![NodeJS](https://img.shields.io/badge/node-6.1.x-brightgreen.svg?style=flat-square)](https://github.com/joaquimserafim/node-open-geocoder/blob/master/package.json#L48) 9 | 10 | [![JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 11 | 12 | 13 | 14 | 15 | ### about Open Street Map API 16 | 17 | OpenStreetMap has an Editing API for fetching and saving raw geodata from/to the OpenStreetMap database. 18 | 19 | API v0.6 is the current version of the OSM Editing API deployed 17-21 April 2009. 20 | 21 | [Usage Policy](http://wiki.openstreetmap.org/wiki/Nominatim_usage_policy) for Open Street Map. 22 | 23 | 24 | ### api 25 | 26 | `const openGeocoder = require('node-open-geocoder')` 27 | 28 | `openGeocoder([options])` 29 | 30 | * **options** 31 | - url, string, OpenStreetMap URL, default to `nominatim.openstreetmap.org` 32 | - port, integer, OpenStreetMap port, default to `80` 33 | - timeout, integer, client timeout, default to `10000` mls 34 | - userAgent, string, OpenStreetMap needs to receive this header, default to node-open-geocoder 35 | 36 | #### geocode 37 | 38 | `openGeocoder.geocode(addr, [options])` 39 | 40 | [more info about](http://wiki.openstreetmap.org/wiki/Nominatim#Search) the *address format* 41 | 42 | * **addr**, string, ex: `'135 pilkington avenue, birmingham'` 43 | * **options**, object, ex: `{addressdetails: 1, polygon_geojson: 1}` 44 | - addressdetails: Include a breakdown of the address into elements, can be 0 | 1 45 | - the second prop define the type of polygon and can be define only one: 46 | * polygon_geojson: Output geometry of results in geojson format. 47 | * polygon_kml : Output geometry of results in kml format. 48 | * polygon_svg : Output geometry of results in svg format. 49 | * polygon_text : Output geometry of results as a WKT. 50 | 51 | 52 | ##### example 53 | ```js 54 | const openGeocoder = require('node-open-geocoder'); 55 | 56 | openGeocoder() 57 | .geocode('135 pilkington avenue, birmingham') 58 | .end((err, res) => {}) 59 | ``` 60 | 61 | #### reverse 62 | 63 | `openGeocoder.reverse(longitude, latitude)` 64 | 65 | ##### example 66 | ```js 67 | const openGeocoder = require('node-open-geocoder'); 68 | 69 | openGeocoder() 70 | .reverse(-8.945406, 38.575078) 71 | .end((err, res) => {}) 72 | ``` 73 | 74 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | eslint 3 | no-multi-spaces: ["error", {exceptions: {"VariableDeclarator": true}}] 4 | padded-blocks: ["error", {"classes": "always"}] 5 | max-len: ["error", 80] 6 | */ 7 | 'use strict' 8 | 9 | const https = require('https') 10 | const parse = require('json-parse-safe') 11 | const isValidCoordinates = require('is-valid-coordinates') 12 | const isObject = require('is.object') 13 | const qs = require('qs') 14 | const getPropValue = require('get-property-value') 15 | 16 | class Geocoder { 17 | 18 | constructor (options) { 19 | this.format = '&format=json' 20 | this.httpOptions = { 21 | hostname: getPropValue(options, 'url') || 'nominatim.openstreetmap.org', 22 | basePath: getPropValue(options, 'basePath') || '', 23 | port: getPropValue(options, 'port') || 443, 24 | agent: false, 25 | headers: { 26 | 'User-Agent': getPropValue(options, 'userAgent') || 'node-open-geocoder' 27 | } 28 | } 29 | this.timeout = getPropValue(options, 'timeout') || 10000 30 | } 31 | 32 | geocode (addr, options) { 33 | const callOptions = isObject(options) 34 | ? `&${qs.stringify(options)}` 35 | : '&addressdetails=1&polygon_geojson=1' 36 | 37 | this.httpOptions.path = '/search?q=' + 38 | encodeURI(addr.replace(/ /g, '+')) + 39 | callOptions + 40 | this.format 41 | 42 | return this 43 | } 44 | 45 | reverse (lon, lat) { 46 | if (!isValidCoordinates(lon, lat)) { 47 | softOverrideMethod(this, 'end', (cb) => { 48 | cb(new Error('Invalid coordinates!')) 49 | }) 50 | } else { 51 | this.httpOptions.path = 52 | `/reverse?lon=${lon}&lat=${lat}${this.format}&addressdetails=1` 53 | } 54 | 55 | return this 56 | } 57 | 58 | end (cb) { 59 | const req = https.get(this.httpOptions, responseHandler.bind(this, cb)) 60 | 61 | req.setTimeout(this.timeout, timeoutCb) 62 | 63 | req.once('error', onError) 64 | 65 | function timeoutCb () { 66 | this.timedout = true 67 | this.abort() 68 | } 69 | 70 | function onError (err) { 71 | err = this.timedout 72 | ? timeoutError(err.message, this.socket.timeoutMs) 73 | : err 74 | 75 | cb(err) 76 | } 77 | } 78 | 79 | } 80 | 81 | module.exports = function geocoderExports (url) { 82 | return new Geocoder(url) 83 | } 84 | 85 | // 86 | // help functions 87 | // 88 | 89 | function responseHandler (handler, res) { 90 | const statusCode = res.statusCode 91 | const contentType = res.headers['content-type'] 92 | 93 | let error 94 | 95 | if (statusCode !== 200) { 96 | error = new Error(`Request Failed.\nStatus Code: ${statusCode}`) 97 | } else if (!/^application\/json/.test(contentType)) { 98 | error = new Error('Invalid content-type.\n' + 99 | `Expected application/json but received ${contentType}`) 100 | } 101 | 102 | if (error) { 103 | res.resume() 104 | return handler(error) 105 | } 106 | 107 | res.setEncoding('utf8') 108 | 109 | let rawData = '' 110 | 111 | res 112 | .on('data', onData) 113 | .once('end', onEnd) 114 | 115 | function onData (chunk) { 116 | rawData += chunk 117 | } 118 | 119 | function onEnd () { 120 | const json = parse(rawData) 121 | 122 | return json.error 123 | ? handler(json.error) 124 | : handler(null, json.value) 125 | } 126 | } 127 | 128 | function timeoutError (reason, timeout) { 129 | const err = new Error(`${reason} ${timeout}ms exceeded`) 130 | err.timeout = timeout 131 | err.code = 'ECONNABORTED' 132 | 133 | return err 134 | } 135 | 136 | function softOverrideMethod (self, methodName, newMethod) { 137 | self[methodName] = newMethod 138 | } 139 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | eslint 3 | no-multi-spaces: ["error", {exceptions: {"VariableDeclarator": true}}] 4 | padded-blocks: ["error", {"classes": "always"}] 5 | max-len: ["error", 80] 6 | */ 7 | 'use strict' 8 | 9 | const mocha = require('mocha') 10 | const nock = require('nock') 11 | const expect = require('chai').expect 12 | const repeat = require('repeat-element') 13 | 14 | const it = mocha.it 15 | const describe = mocha.describe 16 | const afterEach = mocha.afterEach 17 | 18 | const openGeocoder = require('..') 19 | 20 | const openMapUrl = 'https://nominatim.openstreetmap.org:443' 21 | 22 | const mocks = { 23 | gecode_200: require('./mocks/geocode_200'), 24 | reverse_200: require('./mocks/reverse_200') 25 | } 26 | 27 | var scope 28 | 29 | describe('node-open-geocoder', () => { 30 | 31 | afterEach((done) => { 32 | if (scope) { 33 | expect(scope.isDone()).to.be.equal(true) 34 | } 35 | 36 | done() 37 | }) 38 | 39 | it('should throw an error when gets an ECONNREFUSED', (done) => { 40 | openGeocoder({ url: 'localhost' }) 41 | .geocode('135 pilkington avenue, birmingham') 42 | .end((err, res) => { 43 | expect(err).to.be.an('error') 44 | expect(err.message).to.be 45 | .equal('connect ECONNREFUSED 127.0.0.1:443') 46 | expect(err.code).to.be.equal('ECONNREFUSED') 47 | expect(res).to.be.equal(undefined) 48 | done() 49 | }) 50 | }) 51 | 52 | it('should throw an error when OpenStreetMap returns an error', (done) => { 53 | // let's disable the network for the remaining tests 54 | nock.disableNetConnect() 55 | 56 | scope = nock(openMapUrl) 57 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 58 | .get('/search') 59 | .query({ 60 | q: '135 pilkington avenue, birmingham', 61 | addressdetails: 1, 62 | polygon_geojson: 1, 63 | format: 'json' 64 | }) 65 | .reply(400) 66 | 67 | openGeocoder() 68 | .geocode('135 pilkington avenue, birmingham') 69 | .end((err, res) => { 70 | expect(err).to.be.an('error') 71 | expect(err.message).to.be.equal('Request Failed.\nStatus Code: 400') 72 | expect(res).to.be.equal(undefined) 73 | done() 74 | }) 75 | }) 76 | 77 | it('should throw an error when OpenStreetMap returns an ' + 78 | 'invalid JSON payload', 79 | (done) => { 80 | scope = nock(openMapUrl) 81 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 82 | .get('/search') 83 | .query({ 84 | q: '135 pilkington avenue, birmingham', 85 | addressdetails: 1, 86 | polygon_geojson: 1, 87 | format: 'json' 88 | }) 89 | .reply(200, 'bad json') 90 | 91 | openGeocoder() 92 | .geocode('135 pilkington avenue, birmingham') 93 | .end((err, res) => { 94 | expect(err).to.be.an('error') 95 | expect(err.message).to.be.deep 96 | .equal('Unexpected token b in JSON at position 0') 97 | done() 98 | }) 99 | } 100 | ) 101 | 102 | it('should throw an error when OpenStreetMap returns an ' + 103 | 'bad content-type', 104 | (done) => { 105 | scope = nock(openMapUrl) 106 | .defaultReplyHeaders({ 'Content-Type': 'text/html' }) 107 | .get('/search') 108 | .query({ 109 | q: '135 pilkington avenue, birmingham', 110 | addressdetails: 1, 111 | polygon_geojson: 1, 112 | format: 'json' 113 | }) 114 | .reply(200, 'bad json') 115 | 116 | openGeocoder() 117 | .geocode('135 pilkington avenue, birmingham') 118 | .end((err, res) => { 119 | expect(err).to.be.an('error') 120 | expect(err.message).to.be.deep 121 | .equal('Invalid content-type.\nExpected application/json ' + 122 | 'but received text/html') 123 | done() 124 | }) 125 | } 126 | ) 127 | 128 | it('should be possible to use a different port', (done) => { 129 | scope = nock(openMapUrl.replace('443', '8000')) 130 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 131 | .get('/search') 132 | .query({ 133 | q: '135 pilkington avenue, birmingham', 134 | addressdetails: 1, 135 | polygon_geojson: 1, 136 | format: 'json' 137 | }) 138 | .reply(200, mocks.gecode_200) 139 | 140 | openGeocoder({ port: 8000 }) 141 | .geocode('135 pilkington avenue, birmingham') 142 | .end((err, res) => { 143 | expect(err).to.be.equal(null) 144 | expect(res).to.be.deep.equal(mocks.gecode_200) 145 | done() 146 | }) 147 | }) 148 | 149 | it('should returns a valid response for a valid gecode', (done) => { 150 | scope = nock(openMapUrl) 151 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 152 | .get('/search') 153 | .query({ 154 | q: '135 pilkington avenue, birmingham', 155 | addressdetails: 1, 156 | polygon_geojson: 1, 157 | format: 'json' 158 | }) 159 | .reply(200, mocks.gecode_200) 160 | 161 | openGeocoder() 162 | .geocode('135 pilkington avenue, birmingham') 163 | .end((err, res) => { 164 | expect(err).to.be.equal(null) 165 | expect(res).to.be.deep.equal(mocks.gecode_200) 166 | done() 167 | }) 168 | }) 169 | 170 | it('should returns a valid response for a valid gecode with ' + 171 | 'different geocoding options', 172 | (done) => { 173 | scope = nock(openMapUrl) 174 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 175 | .get('/search') 176 | .query({ 177 | q: '135 pilkington avenue, birmingham', 178 | addressdetails: 0, 179 | format: 'json' 180 | }) 181 | .reply(200, mocks.gecode_200) 182 | 183 | openGeocoder() 184 | .geocode('135 pilkington avenue, birmingham', { addressdetails: 0 }) 185 | .end((err, res) => { 186 | expect(err).to.be.equal(null) 187 | expect(res).to.be.deep.equal(mocks.gecode_200) 188 | done() 189 | }) 190 | } 191 | ) 192 | 193 | it('should throw an error for bad coordinateswhen doing a reverse gecode', 194 | (done) => { 195 | openGeocoder() 196 | .reverse('-8.945406', 38.575078) 197 | .end((err, res) => { 198 | expect(err).to.be.an('error') 199 | expect(err.message).to.be.deep.equal('Invalid coordinates!') 200 | done() 201 | }) 202 | } 203 | ) 204 | 205 | it('should returns a valid response for a valid reverse gecode', 206 | (done) => { 207 | scope = nock(openMapUrl) 208 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 209 | .get('/reverse') 210 | .query({ 211 | lon: -8.945406, 212 | lat: 38.575078, 213 | addressdetails: 1, 214 | format: 'json' 215 | }) 216 | .reply(200, mocks.reverse_200) 217 | 218 | openGeocoder() 219 | .reverse(-8.945406, 38.575078) 220 | .end((err, res) => { 221 | expect(err).to.be.equal(null) 222 | expect(res).to.be.deep.equal(mocks.reverse_200) 223 | done() 224 | }) 225 | } 226 | ) 227 | 228 | it('should return a valid response with a big payload', 229 | (done) => { 230 | const bigPayload = repeat(mocks.reverse_200, 500) 231 | 232 | scope = nock(openMapUrl) 233 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 234 | .get('/reverse') 235 | .query({ 236 | lon: -8.945406, 237 | lat: 38.575078, 238 | addressdetails: 1, 239 | format: 'json' 240 | }) 241 | .reply(200, bigPayload) 242 | 243 | openGeocoder() 244 | .reverse(-8.945406, 38.575078) 245 | .end((err, res) => { 246 | expect(err).to.be.equal(null) 247 | expect(res).to.be.deep.equal(bigPayload) 248 | done() 249 | }) 250 | } 251 | ) 252 | 253 | it('should accept the `user-agent` to be defined through args `options`', 254 | (done) => { 255 | scope = nock(openMapUrl, { reqheaders: { 'user-agent': 'HelloWorld' } }) 256 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 257 | .get('/reverse') 258 | .query({ 259 | lon: -8.945406, 260 | lat: 38.575078, 261 | addressdetails: 1, 262 | format: 'json' 263 | }) 264 | .reply(200, mocks.reverse_200) 265 | 266 | openGeocoder({ userAgent: 'HelloWorld' }) 267 | .reverse(-8.945406, 38.575078) 268 | .end((err, res) => { 269 | expect(err).to.be.equal(null) 270 | expect(res).to.be.deep.equal(mocks.reverse_200) 271 | done() 272 | }) 273 | } 274 | ) 275 | 276 | it('should throw an error when OpenStreetMap returns a bad payload', 277 | (done) => { 278 | scope = nock(openMapUrl) 279 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 280 | .get('/reverse') 281 | .query({ 282 | lon: -8.945406, 283 | lat: 38.575078, 284 | addressdetails: 1, 285 | format: 'json' 286 | }) 287 | .reply(200) 288 | 289 | openGeocoder() 290 | .reverse(-8.945406, 38.575078) 291 | .end((err, res) => { 292 | expect(err).to.be.an('error') 293 | expect(err.message).to.be.deep 294 | .equal('Unexpected end of JSON input') 295 | done() 296 | }) 297 | } 298 | ) 299 | 300 | it('should throw an error when gets a socket timeout', 301 | (done) => { 302 | scope = nock(openMapUrl) 303 | .defaultReplyHeaders({ 'Content-Type': 'application/json' }) 304 | .get('/reverse') 305 | .query({ 306 | lon: -8.945406, 307 | lat: 38.575078, 308 | addressdetails: 1, 309 | format: 'json' 310 | }) 311 | .socketDelay(100) 312 | .reply(200) 313 | 314 | openGeocoder({ timeout: 50 }) 315 | .reverse(-8.945406, 38.575078) 316 | .end((err, res) => { 317 | expect(err).to.be.an('error') 318 | expect(err.message).to.be.equal('socket hang up 50ms exceeded') 319 | expect(err.code).to.be.equal('ECONNABORTED') 320 | expect(err.timeout).to.be.equal(50) 321 | done() 322 | }) 323 | } 324 | ) 325 | }) 326 | --------------------------------------------------------------------------------