featureservice
Advanced tools
Comparing version 1.0.0 to 1.1.0
@@ -5,2 +5,17 @@ # Change Log | ||
## [1.1.0] - 2015-08-10 | ||
### Added | ||
* New fixtures and integration tests for paging | ||
* Support for paging layers from server version 10.0 | ||
* New fixtures and tests for decoding | ||
### Changed | ||
* Refactored paging strategy | ||
* Moved feature request decoding into isolated function | ||
### Fixed | ||
* Catch errors that come on 200 responses | ||
* Errors are reported correctly up the chain | ||
* Retries for all errors | ||
## [1.0.0] - 2015-08-07 | ||
@@ -55,2 +70,3 @@ ### Added | ||
[1.1.0]: https://github.com/chelm/featureservice/ompare/v1.0.0...v1.1.0 | ||
[1.0.0]: https://github.com/chelm/featureservice/ompare/v0.2.0...v1.0.0 | ||
@@ -57,0 +73,0 @@ [0.2.0]: https://github.com/chelm/featureservice/ompare/v0.1.0...v0.2.0 |
317
index.js
@@ -24,4 +24,6 @@ var queue = require('async').queue | ||
// protects us from urls registered with layers already in the url | ||
var layer = url.split('/').pop() | ||
if (parseInt(layer, 0) >= 0) { | ||
var end = url.split('/').pop() | ||
var layer | ||
if (parseInt(end, 10) >= 0) { | ||
layer = end | ||
var len = ('' + layer).length | ||
@@ -32,4 +34,4 @@ url = url.substring(0, url.length - ((len || 2) + 1)) | ||
this.url = url | ||
this.options = options | ||
this.layer = options.layer || 0 | ||
this.options = options || {} | ||
this.layer = layer || this.options.layer || 0 | ||
this.timeOut = 1.5 * 60 * 1000 | ||
@@ -169,2 +171,5 @@ var concurrency = this.url.split('//')[1].match(/^service/) ? 16 : 4 | ||
this.request(this.url + '/' + this.layer + '/query?where=1=1&returnIdsOnly=true&f=json', function (err, json) { | ||
if (err || !json.objectIds) return callback(new Error('Request for object ids failed' + err)) | ||
// TODO: is this really necessary | ||
json.objectIds.sort(function (a, b) { return a - b }) | ||
callback(err, json.objectIds) | ||
@@ -175,2 +180,28 @@ }) | ||
/** | ||
* Gets and derives layer metadata from two sources | ||
* @param {function} callback - called with an error or a metadata object | ||
*/ | ||
FeatureService.prototype.metadata = function (callback) { | ||
// TODO memoize this | ||
this.layerInfo(function (err, layer) { | ||
if (err) return callback(new Error(err || 'Unable to get layer metadata')) | ||
var oid = this.getObjectIdField(layer) | ||
var size = layer.maxRecordCount | ||
// TODO flatten this | ||
var metadata = {layer: layer, oid: oid, size: size} | ||
// 10.0 servers don't support count requests | ||
// they also do not show current version on the layer | ||
if (!layer.currentVersion) return callback(null, metadata) | ||
this.featureCount(function (err, json) { | ||
if (err) return callback(err) | ||
if (json.count < 1) return callback(new Error('Service returned count of 0')) | ||
metadata.count = json.count | ||
callback(null, metadata) | ||
}) | ||
}.bind(this)) | ||
} | ||
/** | ||
* Build an array pages that will cover every feature in the service | ||
@@ -180,90 +211,52 @@ * @param {object} callback - called when the service info comes back | ||
FeatureService.prototype.pages = function (callback) { | ||
this.featureCount(function (err, json) { | ||
if (err) { | ||
return callback(err) | ||
} | ||
this.metadata(function (err, meta) { | ||
if (err) return callback(err) | ||
var size = meta.size | ||
var layer = meta.layer | ||
this.options.objectIdField = meta.oid | ||
size = Math.min(parseInt(size, 10), 1000) || 1000 | ||
var nPages = Math.ceil(meta.count / size) | ||
var count = json.count | ||
// if the service supports paging, we can use offset to build pages | ||
var canPage = layer.advancedQueryCapabilities && layer.advancedQueryCapabilities.supportsPagination | ||
if (canPage) return callback(null, this._offsetPages(nPages, size)) | ||
if (count === 0) { | ||
return callback('Service returned a count of zero') | ||
// if the service supports statistics, we can request the maximum and minimum id to build pages | ||
if (layer.supportsStatistics) { | ||
this._getIdRangeFromStats(meta, function (err, stats) { | ||
// if this worked then we can pagination using where clauses | ||
if (!err) return callback(null, this._rangePages(stats, size)) | ||
// if it failed, try to request all the ids and split them into pages | ||
this.layerIds(function (err, ids) { | ||
// either this works or we give up | ||
if (err) return callback(err) | ||
return callback(null, this._idPages(ids, size)) | ||
}.bind(this)) | ||
}.bind(this)) | ||
} else { | ||
// this is the last thing we can try | ||
this.layerIds(function (err, ids) { | ||
if (err) return callback(err) | ||
callback(null, this._idPages(ids, size)) | ||
}.bind(this)) | ||
} | ||
}.bind(this)) | ||
} | ||
// get layer info | ||
this.layerInfo(function (err, serviceInfo) { | ||
if (err || !serviceInfo) { | ||
return callback(err || 'Unable to get layer metadata') | ||
} | ||
/** | ||
* Get the max and min object id | ||
* @param {object} meta - layer metadata, holds information needed to request oid stats | ||
* @param {function} callback - returns with an error or objectID stats | ||
*/ | ||
FeatureService.prototype._getIdRangeFromStats = function (meta, callback) { | ||
this.options.objectIdField = this.getObjectIdField(serviceInfo) | ||
// figure out what kind of pages we can build | ||
var maxCount = Math.min(parseInt(serviceInfo.maxRecordCount, 0), 1000) || 1000 | ||
// build legit offset based page requests | ||
if (serviceInfo.advancedQueryCapabilities && | ||
serviceInfo.advancedQueryCapabilities.supportsPagination) { | ||
var nPages = Math.ceil(count / maxCount) | ||
return callback(null, this._offsetPages(nPages, maxCount)) | ||
} | ||
// build where clause based pages | ||
if (serviceInfo.supportsStatistics) { | ||
this.statistics(serviceInfo.objectIdField, ['min', 'max'], function (err, stats) { | ||
if (err) { | ||
return callback(err) | ||
} | ||
try { | ||
if (stats.error) { | ||
try { | ||
var idUrl = this.url + '/' + (this.layer || 0) + '/query?where=1=1&returnIdsOnly=true&f=json' | ||
this.request(idUrl, function (err, idJson) { | ||
if (err) { | ||
return callback(err) | ||
} | ||
var minID, maxID | ||
if (idJson.error) { | ||
// DMF: if grabbing objectIDs fails fall back to guessing based on 0 and count | ||
minID = 0 | ||
maxID = count | ||
} else { | ||
idJson.objectIds.sort(function (a, b) { return a - b }) | ||
minID = idJson.objectIds[0] | ||
maxID = idJson.objectIds[idJson.objectIds.length - 1] | ||
} | ||
return callback(null, this._objectIdPages(minID, maxID, maxCount)) | ||
}.bind(this)) | ||
} catch (e) { | ||
return callback(e) | ||
} | ||
} else { | ||
var names, minId, maxId | ||
var attrs = stats.features[0].attributes | ||
if (stats && stats.fieldAliases) { | ||
names = Object.keys(stats.fieldAliases) | ||
} | ||
minId = attrs.min_oid || attrs.MIN_OID || attrs[names[0]] | ||
maxId = attrs.max_oid || attrs.MAX_OID || attrs[names[1]] | ||
return callback(null, this._objectIdPages(minId, maxId, maxCount)) | ||
} | ||
} catch (e) { | ||
return callback(e) | ||
} | ||
}.bind(this)) | ||
} else { | ||
if (count < 1000000) { | ||
this.layerIds(function (err, ids) { | ||
callback(err, this._idPages(ids, 250)) | ||
}.bind(this)) | ||
} else { | ||
// default to sequential objectID paging starting from zero | ||
return callback(null, this._objectIdPages(0, count, maxCount)) | ||
} | ||
} | ||
}.bind(this)) | ||
}.bind(this)) | ||
this.statistics(meta.oid, ['min', 'max'], function (reqErr, stats) { | ||
if (reqErr || stats.error) return callback(new Error('statistics request failed')) | ||
var attrs = stats.features[0].attributes | ||
// dmf: what's up with this third strategy? | ||
var names = stats && stats.fieldAliases ? Object.keys(stats.fieldAliases) : null | ||
var min = attrs.min || attrs.MIN || attrs[names[0]] | ||
var max = attrs.max || attrs.MAX || attrs[names[1]] | ||
callback(null, {min: min, max: max}) | ||
}) | ||
} | ||
@@ -276,13 +269,9 @@ | ||
FeatureService.prototype.featureCount = function (callback) { | ||
var countUrl = this.url + '/' + (this.options.layer || 0) | ||
countUrl += '/query?where=1=1&returnIdsOnly=true&returnCountOnly=true&f=json' | ||
var countUrl = this.url + '/' + (this.layer || 0) | ||
countUrl += '/query?where=1=1&returnCountOnly=true&f=json' | ||
this.request(countUrl, function (err, json) { | ||
if (err) { | ||
return callback(err) | ||
} | ||
if (err) return callback(err) | ||
if (json.error) { | ||
return callback(json.error.message + ': ' + countUrl, null) | ||
} | ||
if (json.error) return callback(json.error.message + ': ' + countUrl, null) | ||
@@ -322,15 +311,15 @@ callback(null, json) | ||
*/ | ||
FeatureService.prototype._idPages = function (ids, maxCount) { | ||
FeatureService.prototype._idPages = function (ids, size) { | ||
var reqs = [] | ||
var where | ||
var pageUrl | ||
var oidField = this.options.objectIdField || 'objectId' | ||
var pages = (ids.length / size) | ||
var objId = this.options.objectIdField || 'objectId' | ||
var pages = (ids.length / maxCount) | ||
for (var i = 0; i < pages + 1; i++) { | ||
var pageIds = ids.splice(0, maxCount) | ||
var pageIds = ids.splice(0, size) | ||
if (pageIds.length) { | ||
where = objId + ' in (' + pageIds.join(',') + ')' | ||
pageUrl = this.url + '/' + (this.options.layer || 0) + '/query?outSR=4326&where=' + where + '&f=json&outFields=*' | ||
var pageMin = pageIds[0] | ||
pageMin = pageMin === 0 ? pageMin : pageMin - 1 | ||
var pageMax = pageIds.pop() | ||
var where = [oidField, ' > ', pageMin, ' AND ', 'oidField', '<=', pageMax].join('') | ||
var pageUrl = this.url + '/' + (this.options.layer || 0) + '/query?outSR=4326&where=' + where + '&f=json&outFields=*' | ||
pageUrl += '&geometry=&returnGeometry=true&geometryPrecision=10' | ||
@@ -352,3 +341,3 @@ reqs.push({req: pageUrl}) | ||
*/ | ||
FeatureService.prototype._objectIdPages = function (min, max, maxRecordCount) { | ||
FeatureService.prototype._rangePages = function (stats, size) { | ||
var reqs = [] | ||
@@ -362,3 +351,3 @@ var pageUrl | ||
var url = this.url | ||
var pages = Math.max((max === maxRecordCount) ? max : Math.ceil((max - min) / maxRecordCount), 1) | ||
var pages = Math.max((stats.max === size) ? stats.max : Math.ceil((stats.max - stats.min) / size), 1) | ||
@@ -369,7 +358,7 @@ for (var i = 0; i < pages; i++) { | ||
if (i === pages - 1) { | ||
pageMax = max | ||
pageMax = stats.max | ||
} else { | ||
pageMax = min + (maxRecordCount * (i + 1)) - 1 | ||
pageMax = stats.min + (size * (i + 1)) - 1 | ||
} | ||
pageMin = min + (maxRecordCount * i) | ||
pageMin = stats.min + (size * i) | ||
where = objId + '<=' + pageMax + '+AND+' + objId + '>=' + pageMin | ||
@@ -420,3 +409,3 @@ pageUrl = url + '/' + (this.options.layer || 0) + '/query?outSR=4326&where=' + where + '&f=json&outFields=*' | ||
// make an http or https request based on the protocol | ||
// make an http or https request based on the protocol | ||
var req = ((url_parts.protocol === 'https:') ? https : http).request(opts, function (response) { | ||
@@ -429,35 +418,11 @@ var data = [] | ||
response.on('error', function (err) { | ||
catchErrors(task, err, uri, cb) | ||
self._catchErrors(task, err, uri, cb) | ||
}) | ||
response.on('end', function () { | ||
try { | ||
var json | ||
var buffer = Buffer.concat(data) | ||
var encoding = response.headers['content-encoding'] | ||
// TODO all this shit is ugly -- make it less shitty (less try/catch) | ||
if (encoding === 'gzip') { | ||
zlib.gunzip(buffer, function (e, result) { | ||
try { | ||
json = JSON.parse(result.toString().replace(/NaN/g, 'null')) | ||
cb(null, json) | ||
} catch (e) { | ||
catchErrors(task, e, uri, cb) | ||
} | ||
}) | ||
} else if (encoding === 'deflate') { | ||
try { | ||
json = JSON.parse(zlib.inflateSync(buffer).toString()) | ||
cb(null, json) | ||
} catch (e) { | ||
catchErrors(task, e, uri, cb) | ||
} | ||
} else { | ||
json = JSON.parse(buffer.toString().replace(/NaN/g, 'null')) | ||
cb(null, json) | ||
} | ||
} catch(e) { | ||
catchErrors(task, e, uri, cb) | ||
} | ||
// TODO: move this into a function call decode | ||
self._decode(response, data, function (err, json) { | ||
if (err) return self._catchErrors(task, err, uri, cb) | ||
cb(null, json) | ||
}) | ||
}) | ||
@@ -470,3 +435,3 @@ }) | ||
var err = JSON.stringify({message: 'The request timed out after ' + self.timeOut / 1000 + ' seconds.'}) | ||
catchErrors(task, err, uri, cb) | ||
self._catchErrors(task, err, uri, cb) | ||
}) | ||
@@ -476,3 +441,3 @@ | ||
req.on('error', function (err) { | ||
catchErrors(task, err, uri, cb) | ||
self._catchErrors(task, err, uri, cb) | ||
}) | ||
@@ -482,32 +447,58 @@ | ||
} catch(e) { | ||
catchErrors(task, e, uri, cb) | ||
self._catchErrors(task, e, uri, cb) | ||
} | ||
} | ||
// Catch any errors and either retry the request or fail it | ||
var catchErrors = function (task, e, url, cb) { | ||
if (task.retry && task.retry === 3) { | ||
try { | ||
var jsonErr = JSON.parse(e) | ||
this._abortPaging('Failed to request a page of features', url, jsonErr.message, jsonErr.code, cb) | ||
} catch (parseErr) { | ||
this._abortPaging('Failed to request a page of features', url, parseErr, null, cb) | ||
} | ||
return | ||
} | ||
// immediately kill | ||
if (!task.retry) { | ||
task.retry = 1 | ||
/* Decodes a response for features | ||
* @param {object} res - the response received from the GIS Server | ||
* @param {array} data - an array of chunks received from the server | ||
* @param {function} callback - calls back with either an error or the decoded feature json | ||
*/ | ||
FeatureService.prototype._decode = function (res, data, callback) { | ||
var json | ||
var encoding = res.headers['content-encoding'] | ||
if (!data.length > 0) return callback(new Error('Empty reply from the server')) | ||
try { | ||
var buffer = Buffer.concat(data) | ||
if (encoding === 'gzip') { | ||
json = JSON.parse(zlib.gunzipSync(buffer).toString().replace(/NaN/g, 'null')) | ||
} else if (encoding === 'deflate') { | ||
json = JSON.parse(zlib.inflateSync(buffer).toString()) | ||
} else { | ||
task.retry++ | ||
json = JSON.parse(buffer.toString().replace(/NaN/g, 'null')) | ||
} | ||
} catch (e) { | ||
callback(e) | ||
} | ||
// ArcGIS Server responds 200 on errors so we have to inspect the payload | ||
if (json.error) return callback(json.error) | ||
// everything has worked so callback with the decoded JSON | ||
callback(null, json) | ||
} | ||
console.log('Re-requesting page', task.req, task.retry) | ||
/* Catches an errors during paging and handles retry logic | ||
* @param {object} task - the currently executing job | ||
* @param {object} e - the error in application logic or from a failed request to a server | ||
* @param {string} url - the url of the last request for pages | ||
* @param {function} cb - callback passed through to the abort paging function | ||
*/ | ||
FeatureService.prototype._catchErrors = function (task, e, url, cb) { | ||
if (!e.message) e.message = e.toString() | ||
if (task.retry && task.retry === 3) return this._abortPaging('Failed to request a page of features', url, e.message, e.code, cb) | ||
setTimeout(function () { | ||
this._requestFeatures(task, cb) | ||
}.bind(this), task.retry * 1000) | ||
// initiate the count or increment it | ||
if (!task.retry) { | ||
task.retry = 1 | ||
} else { | ||
task.retry++ | ||
} | ||
}.bind(this) | ||
console.log('Re-requesting page', task.req, task.retry) | ||
setTimeout(function () { | ||
this._requestFeatures(task, cb) | ||
}.bind(this), task.retry * 1000) | ||
} | ||
module.exports = FeatureService |
{ | ||
"name": "featureservice", | ||
"description": "Get all features from an Esri Feature Service", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"author": "Chris Helm", | ||
@@ -6,0 +6,0 @@ "bugs": { |
@@ -6,2 +6,3 @@ var sinon = require('sinon') | ||
var fs = require('fs') | ||
var zlib = require('zlib') | ||
@@ -11,2 +12,5 @@ var service = new FeatureService('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/0', {}) | ||
var layerInfo = JSON.parse(fs.readFileSync('./test/fixtures/layerInfo.json')) | ||
var layerFixture = JSON.parse(fs.readFileSync('./test/fixtures/layer.json')) | ||
var idFixture = JSON.parse(fs.readFileSync('./test/fixtures/objectIds.json')) | ||
var countFixture = JSON.parse(fs.readFileSync('./test/fixtures/count.json')) | ||
@@ -22,7 +26,6 @@ test('get the objectId', function (t) { | ||
var pages | ||
var min = 0 | ||
var max = 2000 | ||
pages = service._objectIdPages(min, max, max / 2) | ||
var stats = {min: 0, max: 2000} | ||
pages = service._rangePages(stats, stats.max / 2) | ||
t.equal(pages.length, 2) | ||
pages = service._objectIdPages(min, max, max / 4) | ||
pages = service._rangePages(stats, stats.max / 4) | ||
t.equal(pages.length, 4) | ||
@@ -55,23 +58,10 @@ t.end() | ||
test('builds pages for the service', function (t) { | ||
var url = 'http://maps.indiana.edu/ArcGIS/rest/services/Infrastructure/Railroads_Rail_Crossings_INDOT/MapServer' | ||
var indiana = new FeatureService(url, {}) | ||
indiana.pages(function (err, pages) { | ||
t.equal(err, null) | ||
t.equal(pages.length, 156) | ||
t.end() | ||
}) | ||
}) | ||
test('stub setup', function (t) { | ||
test('get the metadata for a layer on the service', function (t) { | ||
sinon.stub(service, 'request', function (url, callback) { | ||
callback(null, {body: '{}'}) | ||
callback(null, layerFixture) | ||
}) | ||
t.end() | ||
}) | ||
test('get the metadata for a layer on the service', function (t) { | ||
service.layerInfo(function (err, metadata) { | ||
t.equal(err, null) | ||
t.equal(service.request.calledWith('http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/0?f=json'), true) | ||
service.request.restore() | ||
t.end() | ||
@@ -82,2 +72,5 @@ }) | ||
test('get all the object ids for a layer on the service', function (t) { | ||
sinon.stub(service, 'request', function (url, callback) { | ||
callback(null, idFixture) | ||
}) | ||
service.layerIds(function (err, metadata) { | ||
@@ -87,2 +80,3 @@ t.equal(err, null) | ||
t.equal(service.request.calledWith(expected), true) | ||
service.request.restore() | ||
t.end() | ||
@@ -93,6 +87,10 @@ }) | ||
test('get all feature count for a layer on the service', function (t) { | ||
sinon.stub(service, 'request', function (url, callback) { | ||
callback(null, countFixture) | ||
}) | ||
service.featureCount(function (err, metadata) { | ||
t.equal(err, null) | ||
var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/0/query?where=1=1&returnIdsOnly=true&returnCountOnly=true&f=json' | ||
var expected = 'http://koop.dc.esri.com/socrata/seattle/2tje-83f6/FeatureServer/0/query?where=1=1&returnCountOnly=true&f=json' | ||
t.equal(service.request.calledWith(expected), true) | ||
service.request.restore() | ||
t.end() | ||
@@ -116,5 +114,208 @@ }) | ||
test('teardown', function (t) { | ||
service.request.restore() | ||
t.end() | ||
test('decoding something that is gzipped', function (t) { | ||
var json = JSON.stringify(JSON.parse(fs.readFileSync('./test/fixtures/uncompressed.json'))) | ||
var gzipped = zlib.gzipSync(json) | ||
var data = [gzipped] | ||
var res = {headers: {'content-encoding': 'gzip'}} | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
service._decode(res, data, function (err, json) { | ||
t.equal(json.features.length, 2000) | ||
t.end() | ||
}) | ||
}) | ||
test('decoding something that is deflated', function (t) { | ||
var json = JSON.stringify(JSON.parse((fs.readFileSync('./test/fixtures/uncompressed.json')))) | ||
var deflated = zlib.deflateSync(json) | ||
var data = [deflated] | ||
var res = {headers: {'content-encoding': 'deflate'}} | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
service._decode(res, data, function (err, json) { | ||
t.equal(json.features.length, 2000) | ||
t.end() | ||
}) | ||
}) | ||
test('decoding something that is not compressed', function (t) { | ||
var uncompressed = JSON.stringify(JSON.parse(fs.readFileSync('./test/fixtures/uncompressed.json'))) | ||
var data = [new Buffer(uncompressed)] | ||
var res = {headers: {}} | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
service._decode(res, data, function (err, json) { | ||
t.equal(json.features.length, 2000) | ||
t.end() | ||
}) | ||
}) | ||
test('decoding an empty response', function (t) { | ||
var empty = [] | ||
var res = {headers: {'content-encoding': 'gzip'}} | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
service._decode(res, empty, function (err, json) { | ||
t.notEqual(typeof err, 'undefined') | ||
t.end() | ||
}) | ||
}) | ||
test('should callback with an error when decoding json with an error in the response', function (t) { | ||
var data = { | ||
error: { | ||
code: 400, | ||
message: 'Invalid or missing input parameters.', | ||
details: [] | ||
} | ||
} | ||
var res = {headers: {}} | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
service._decode(res, [new Buffer(JSON.stringify(data))], function (err, json) { | ||
t.notEqual(typeof err, 'undefined') | ||
t.equal(err.code, 400) | ||
t.equal(err.message, 'Invalid or missing input parameters.') | ||
t.end() | ||
}) | ||
}) | ||
test('catching errors with a json payload', function (t) { | ||
var service = new FeatureService('http://service.com/mapserver/2') | ||
var task = {retry: 3} | ||
var error = { | ||
code: 400, | ||
message: 'Invalid or missing input parameters.', | ||
details: [] | ||
} | ||
var url = 'http://url.com' | ||
sinon.stub(service, '_abortPaging', function (msg, url, eMsg, eCode, cb) { | ||
var info = { | ||
message: msg, | ||
request: url, | ||
response: eMsg, | ||
code: eCode | ||
} | ||
cb(info) | ||
}) | ||
service._catchErrors(task, error, url, function (info) { | ||
t.equal(info.message, 'Failed to request a page of features') | ||
t.equal(info.code, error.code) | ||
t.equal(info.request, url) | ||
t.equal(info.response, error.message) | ||
service._abortPaging.restore() | ||
t.end() | ||
}) | ||
}) | ||
// feature request integration tests | ||
test('requesting a page of features', function (t) { | ||
var page = fs.readFileSync('./test/fixtures/page.json') | ||
var fixture = nock('http://servicesqa.arcgis.com') | ||
fixture.get('/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&geometryPrecision=') | ||
.reply(200, page) | ||
var service = new FeatureService('http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0') | ||
var task = {req: 'http://servicesqa.arcgis.com/97KLIFOSt5CxbiRI/arcgis/rest/services/QA_data_simple_point_5000/FeatureServer/0/query?outSR=4326&f=json&outFields=*&where=1=1&resultOffset=1000&resultRecordCount=1000&geometry=&returnGeometry=true&geometryPrecision='} | ||
service._requestFeatures(task, function (err, json) { | ||
t.equal(json.features.length, 1000) | ||
t.end() | ||
}) | ||
}) | ||
// paging integration tests | ||
test('building pages for a service that supports pagination', function (t) { | ||
var countPaging = JSON.parse(fs.readFileSync('./test/fixtures/countPaging.json')) | ||
var layerPaging = JSON.parse(fs.readFileSync('./test/fixtures/layerPaging.json')) | ||
var fixture = nock('http://maps.indiana.edu') | ||
fixture.get('/ArcGIS/rest/services/Infrastructure/Railroads_Rail_Crossings_INDOT/MapServer/0/query?where=1=1&returnCountOnly=true&f=json') | ||
.reply(200, countPaging) | ||
fixture.get('/ArcGIS/rest/services/Infrastructure/Railroads_Rail_Crossings_INDOT/MapServer/0?f=json') | ||
.reply(200, layerPaging) | ||
var service = new FeatureService('http://maps.indiana.edu/ArcGIS/rest/services/Infrastructure/Railroads_Rail_Crossings_INDOT/MapServer/0', {}) | ||
service.pages(function (err, pages) { | ||
t.equal(err, null) | ||
t.equal(pages.length, 156) | ||
t.end() | ||
}) | ||
}) | ||
test('building pages from a layer that does not support pagination', function (t) { | ||
var layerNoPaging = JSON.parse(fs.readFileSync('./test/fixtures/layerNoPaging.json')) | ||
var countNoPaging = JSON.parse(fs.readFileSync('./test/fixtures/countNoPaging.json')) | ||
var statsNoPaging = JSON.parse(fs.readFileSync('./test/fixtures/statsNoPaging.json')) | ||
var fixture = nock('http://maps2.dcgis.dc.gov') | ||
fixture.get('/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/50/query?where=1=1&returnCountOnly=true&f=json') | ||
.reply(200, countNoPaging) | ||
fixture.get('/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/50?f=json') | ||
.reply(200, layerNoPaging) | ||
fixture.get('/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/50/query?f=json&outFields=&outStatistics=%5B%7B%22statisticType%22:%22min%22,%22onStatisticField%22:%22OBJECTID_1%22,%22outStatisticFieldName%22:%22min_OBJECTID_1%22%7D,%7B%22statisticType%22:%22max%22,%22onStatisticField%22:%22OBJECTID_1%22,%22outStatisticFieldName%22:%22max_OBJECTID_1%22%7D%5D') | ||
.reply(200, statsNoPaging) | ||
var service = new FeatureService('http://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Transportation_WebMercator/MapServer/50') | ||
service.pages(function (err, pages) { | ||
t.equal(err, null) | ||
t.equal(pages.length, 1) | ||
t.end() | ||
}) | ||
}) | ||
test('building pages from a layer where statistics fail', function (t) { | ||
var layerStatsFail = JSON.parse(fs.readFileSync('./test/fixtures/layerStatsFail.json')) | ||
var countStatsFail = JSON.parse(fs.readFileSync('./test/fixtures/countStatsFail.json')) | ||
var idsStatsFail = JSON.parse(fs.readFileSync('./test/fixtures/idsStatsFail.json')) | ||
var statsFail = JSON.parse(fs.readFileSync('./test/fixtures/statsFail.json')) | ||
var fixture = nock('http://maps2.dcgis.dc.gov') | ||
fixture.get('/dcgis/rest/services/FEEDS/CDW_Feeds/MapServer/8/query?where=1=1&returnIdsOnly=true&f=json') | ||
.reply(200, idsStatsFail) | ||
fixture.get('/dcgis/rest/services/FEEDS/CDW_Feeds/MapServer/8/query?where=1=1&returnCountOnly=true&f=json') | ||
.reply(200, countStatsFail) | ||
fixture.get('/dcgis/rest/services/FEEDS/CDW_Feeds/MapServer/8?f=json') | ||
.reply(200, layerStatsFail) | ||
fixture.get('/dcgis/rest/services/FEEDS/CDW_Feeds/MapServer/8/query?f=json&outFields=&outStatistics=%5B%7B%22statisticType%22:%22min%22,%22onStatisticField%22:%22ESRI_OID%22,%22outStatisticFieldName%22:%22min_ESRI_OID%22%7D,%7B%22statisticType%22:%22max%22,%22onStatisticField%22:%22ESRI_OID%22,%22outStatisticFieldName%22:%22max_ESRI_OID%22%7D%5D') | ||
.reply(200, statsFail) | ||
var service = new FeatureService('http://maps2.dcgis.dc.gov/dcgis/rest/services/FEEDS/CDW_Feeds/MapServer/8') | ||
service.pages(function (err, pages) { | ||
t.equal(err, null) | ||
t.equal(pages.length, 4) | ||
t.end() | ||
}) | ||
}) | ||
test('building pages for a version 10.0 server', function (t) { | ||
var layer10 = JSON.parse(fs.readFileSync('./test/fixtures/layer10.0.json')) | ||
var ids10 = JSON.parse(fs.readFileSync('./test/fixtures/ids10.0.json')) | ||
var fixture = nock('http://sampleserver3.arcgisonline.com') | ||
fixture.get('/ArcGIS/rest/services/Fire/Sheep/FeatureServer/2?f=json').reply(200, layer10) | ||
fixture.get('/ArcGIS/rest/services/Fire/Sheep/FeatureServer/2/query?where=1=1&returnIdsOnly=true&f=json').reply(200, ids10) | ||
var service = new FeatureService('http://sampleserver3.arcgisonline.com/ArcGIS/rest/services/Fire/Sheep/FeatureServer/2') | ||
service.pages(function (err, pages) { | ||
t.equal(err, null) | ||
t.equal(pages.length, 1) | ||
t.end() | ||
}) | ||
}) | ||
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
532142
26
1443
5