From 95160a73226abd838f9693c340bcc54a86c438d9 Mon Sep 17 00:00:00 2001 From: Rich Gwozdz Date: Sun, 10 Mar 2024 18:47:05 -0700 Subject: [PATCH] feat: set exceededTransferLimit in metadata (#947) * feat: set exceededTransferLimit in metadata --- .changeset/big-pets-search.md | 5 + .changeset/kind-students-move.md | 5 + .changeset/twenty-gifts-fly.md | 5 + packages/featureserver/coverage-unit.svg | 8 +- packages/featureserver/coverage.svg | 8 +- .../src/query/render-features.js | 8 +- .../src/query/render-features.spec.js | 318 ++++++++++++++---- packages/featureserver/src/route.spec.js | 29 +- .../test/integration/route.spec.js | 193 ++++++----- packages/winnow/coverage-unit.svg | 14 +- .../src/normalize-query-options/limit.js | 2 +- packages/winnow/src/query/standard-query.js | 24 +- .../winnow/src/query/standard-query.spec.js | 77 +++-- .../winnow/test/integration/to-esri.spec.js | 6 +- test/geoservice-query.spec.js | 84 +++-- test/provider-data/pass-through.geojson | 63 ++++ 16 files changed, 613 insertions(+), 236 deletions(-) create mode 100644 .changeset/big-pets-search.md create mode 100644 .changeset/kind-students-move.md create mode 100644 .changeset/twenty-gifts-fly.md create mode 100644 test/provider-data/pass-through.geojson diff --git a/.changeset/big-pets-search.md b/.changeset/big-pets-search.md new file mode 100644 index 000000000..7b0f8a7ee --- /dev/null +++ b/.changeset/big-pets-search.md @@ -0,0 +1,5 @@ +--- +"@koopjs/featureserver": patch +--- + +- allow exceededTransferLimit to be set by provider metadata diff --git a/.changeset/kind-students-move.md b/.changeset/kind-students-move.md new file mode 100644 index 000000000..831e15252 --- /dev/null +++ b/.changeset/kind-students-move.md @@ -0,0 +1,5 @@ +--- +"@koopjs/koop-core": minor +--- + +allow exceededTransferLimit in GeoServices output-plugin to be set by provider metadata diff --git a/.changeset/twenty-gifts-fly.md b/.changeset/twenty-gifts-fly.md new file mode 100644 index 000000000..2c118a509 --- /dev/null +++ b/.changeset/twenty-gifts-fly.md @@ -0,0 +1,5 @@ +--- +"@koopjs/winnow": major +--- + +- change collection.metadata.limitExceeded to collection.metadata.exceededTransferLimit diff --git a/packages/featureserver/coverage-unit.svg b/packages/featureserver/coverage-unit.svg index 607e1dd64..4fe52837a 100644 --- a/packages/featureserver/coverage-unit.svg +++ b/packages/featureserver/coverage-unit.svg @@ -1,5 +1,5 @@ - - coverage: 95.34% + + coverage: 95.68% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/packages/featureserver/coverage.svg b/packages/featureserver/coverage.svg index e8b0e04d1..bc121038d 100644 --- a/packages/featureserver/coverage.svg +++ b/packages/featureserver/coverage.svg @@ -1,5 +1,5 @@ - - coverage: 98.17% + + coverage: 98.42% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/packages/featureserver/src/query/render-features.js b/packages/featureserver/src/query/render-features.js index 44823b93f..d5cb6d76b 100644 --- a/packages/featureserver/src/query/render-features.js +++ b/packages/featureserver/src/query/render-features.js @@ -22,7 +22,7 @@ const featureResponseTemplate = { * @param {object} params * @return {object} formatted features data */ -function renderFeaturesResponse(data = {}, params = {}) { +function renderFeaturesResponse(data, params) { const template = _.cloneDeep(featureResponseTemplate); const { @@ -30,14 +30,16 @@ function renderFeaturesResponse(data = {}, params = {}) { objectIdFieldName: objectIdFieldNameDefault, } = template; - const { metadata: { limitExceeded, transform, idField, hasZ } = {} } = data; + const { + metadata: { exceededTransferLimit = false, transform, idField, hasZ } = {}, + } = data; const computedProperties = { geometryType: params.geometryType, spatialReference: getOutputSpatialReference(data, params), fields: QueryFields.create({ ...data, ...params }), features: data.features || [], - exceededTransferLimit: !!limitExceeded, + exceededTransferLimit, objectIdFieldName: idField || objectIdFieldNameDefault, uniqueIdField: { ...uniqueIdFieldDefault, diff --git a/packages/featureserver/src/query/render-features.spec.js b/packages/featureserver/src/query/render-features.spec.js index 5962094b0..e0971eabe 100644 --- a/packages/featureserver/src/query/render-features.spec.js +++ b/packages/featureserver/src/query/render-features.spec.js @@ -1,4 +1,4 @@ -const should = require('should') // eslint-disable-line +const should = require('should'); // eslint-disable-line const sinon = require('sinon'); const proxyquire = require('proxyquire'); @@ -8,8 +8,8 @@ const createQueryFieldsSpy = sinon.spy(function () { const fields = { QueryFields: { - create: createQueryFieldsSpy - } + create: createQueryFieldsSpy, + }, }; const normalizeSpatialReferenceSpy = sinon.spy(function () { @@ -24,8 +24,8 @@ const stub = { '../helpers/fields': fields, '../helpers': { getCollectionCrs: getCollectionCrsSpy, - normalizeSpatialReference: normalizeSpatialReferenceSpy - } + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, }; const { renderFeaturesResponse } = proxyquire('./render-features', stub); @@ -43,31 +43,33 @@ describe('renderFeaturesResponse', () => { features: [ { attributes: { - OBJECTID: 1138516379 + OBJECTID: 1138516379, }, geometry: { x: -104.9476, - y: 39.9448 - } + y: 39.9448, + }, }, { attributes: { - OBJECTID: 1954528849 + OBJECTID: 1954528849, }, geometry: { x: -104.8424, - y: 39.9137 - } - } - ] + y: 39.9137, + }, + }, + ], }; - const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint' }); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -76,10 +78,45 @@ describe('renderFeaturesResponse', () => { spatialReference: { wkid: 1234 }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, + }); + createQueryFieldsSpy.callCount.should.equal(1); + createQueryFieldsSpy.firstCall.args.should.deepEqual([ + { ...json, geometryType: 'esriGeometryPoint' }, + ]); + getCollectionCrsSpy.callCount.should.equal(1); + getCollectionCrsSpy.firstCall.args.should.deepEqual([json]); + normalizeSpatialReferenceSpy.callCount.should.equal(1); + normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual(['crs']); + }); + + it('should convert json with no features', () => { + const json = { + type: 'FeatureCollection', + }; + + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + }); + result.should.deepEqual({ + objectIdFieldName: 'OBJECTID', + uniqueIdField: { + name: 'OBJECTID', + isSystemMaintained: true, + }, + geometryType: 'esriGeometryPoint', + globalIdFieldName: '', + hasZ: false, + hasM: false, + spatialReference: { wkid: 1234 }, + fields: 'fields', + features: [], + exceededTransferLimit: false, }); createQueryFieldsSpy.callCount.should.equal(1); - createQueryFieldsSpy.firstCall.args.should.deepEqual([{ ...json, geometryType: 'esriGeometryPoint' }]); + createQueryFieldsSpy.firstCall.args.should.deepEqual([ + { ...json, geometryType: 'esriGeometryPoint' }, + ]); getCollectionCrsSpy.callCount.should.equal(1); getCollectionCrsSpy.firstCall.args.should.deepEqual([json]); normalizeSpatialReferenceSpy.callCount.should.equal(1); @@ -89,40 +126,42 @@ describe('renderFeaturesResponse', () => { it('should convert json with metadata to Geoservices JSON', () => { const json = { metadata: { - limitExceeded: true, + exceededTransferLimit: true, transform: 'transform', idField: 'hello_world', - hasZ: true + hasZ: true, }, type: 'FeatureCollection', features: [ { attributes: { - hello_world: 1138516379 + hello_world: 1138516379, }, geometry: { x: -104.9476, - y: 39.9448 - } + y: 39.9448, + }, }, { attributes: { - hello_world: 1954528849 + hello_world: 1954528849, }, geometry: { x: -104.8424, - y: 39.9137 - } - } - ] + y: 39.9137, + }, + }, + ], }; - const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint' }); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + }); result.should.deepEqual({ objectIdFieldName: 'hello_world', uniqueIdField: { name: 'hello_world', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -132,10 +171,12 @@ describe('renderFeaturesResponse', () => { fields: 'fields', features: json.features, exceededTransferLimit: true, - transform: 'transform' + transform: 'transform', }); createQueryFieldsSpy.callCount.should.equal(1); - createQueryFieldsSpy.firstCall.args.should.deepEqual([{ ...json, geometryType: 'esriGeometryPoint' }]); + createQueryFieldsSpy.firstCall.args.should.deepEqual([ + { ...json, geometryType: 'esriGeometryPoint' }, + ]); getCollectionCrsSpy.callCount.should.equal(1); getCollectionCrsSpy.firstCall.args.should.deepEqual([json]); normalizeSpatialReferenceSpy.callCount.should.equal(1); @@ -155,8 +196,8 @@ describe('renderFeaturesResponse', () => { '../helpers/fields': fields, '../helpers': { getCollectionCrs: getCollectionCrsSpy, - normalizeSpatialReference: normalizeSpatialReferenceSpy - } + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, }; const { renderFeaturesResponse } = proxyquire('./render-features', stub); @@ -166,14 +207,14 @@ describe('renderFeaturesResponse', () => { features: [ { attributes: { - OBJECTID: 1138516379 + OBJECTID: 1138516379, }, geometry: { x: -104.9476, - y: 39.9448 - } - } - ] + y: 39.9448, + }, + }, + ], }; afterEach(function () { @@ -183,35 +224,52 @@ describe('renderFeaturesResponse', () => { }); it('should acquire from default', () => { - const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint' }); + const getCollectionCrsSpy = sinon.spy(function () { + return; + }); + + const stub = { + '../helpers/fields': fields, + '../helpers': { + getCollectionCrs: getCollectionCrsSpy, + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, + }; + const { renderFeaturesResponse } = proxyquire('./render-features', stub); + + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', hasZ: false, hasM: false, - spatialReference: { wkid: 'crs' }, + spatialReference: { wkid: 4326 }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(1); getCollectionCrsSpy.firstCall.args.should.deepEqual([json]); normalizeSpatialReferenceSpy.callCount.should.equal(1); - normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual(['crs']); + normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual([4326]); }); it('should acquire from collection', () => { - const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint' }); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -220,7 +278,7 @@ describe('renderFeaturesResponse', () => { spatialReference: { wkid: 'crs' }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(1); getCollectionCrsSpy.firstCall.args.should.deepEqual([json]); @@ -231,13 +289,13 @@ describe('renderFeaturesResponse', () => { it('should acquire from sourceSR', () => { const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint', - sourceSR: 9999 + sourceSR: 9999, }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -246,7 +304,7 @@ describe('renderFeaturesResponse', () => { spatialReference: { wkid: 9999 }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(0); normalizeSpatialReferenceSpy.callCount.should.equal(1); @@ -257,13 +315,13 @@ describe('renderFeaturesResponse', () => { const result = renderFeaturesResponse(json, { geometryType: 'esriGeometryPoint', sourceSR: 9999, - inputCrs: 8888 + inputCrs: 8888, }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -272,7 +330,7 @@ describe('renderFeaturesResponse', () => { spatialReference: { wkid: 8888 }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(0); normalizeSpatialReferenceSpy.callCount.should.equal(1); @@ -284,13 +342,13 @@ describe('renderFeaturesResponse', () => { geometryType: 'esriGeometryPoint', sourceSR: 9999, inputCrs: 8888, - outSR: 7777 + outSR: 7777, }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', @@ -299,7 +357,7 @@ describe('renderFeaturesResponse', () => { spatialReference: { wkid: 7777 }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(0); normalizeSpatialReferenceSpy.callCount.should.equal(1); @@ -312,28 +370,170 @@ describe('renderFeaturesResponse', () => { sourceSR: 9999, inputCrs: 8888, outSR: 7777, - outputCrs: 3857 + outputCrs: 3857, }); result.should.deepEqual({ objectIdFieldName: 'OBJECTID', uniqueIdField: { name: 'OBJECTID', - isSystemMaintained: true + isSystemMaintained: true, }, geometryType: 'esriGeometryPoint', globalIdFieldName: '', hasZ: false, hasM: false, spatialReference: { - wkid: 3857 + wkid: 3857, }, fields: 'fields', features: json.features, - exceededTransferLimit: false + exceededTransferLimit: false, }); getCollectionCrsSpy.callCount.should.equal(0); normalizeSpatialReferenceSpy.callCount.should.equal(1); normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual([3857]); }); + + it('should use latestWkid', () => { + const normalizeSpatialReferenceSpy = sinon.spy(function (latestWkid) { + return { latestWkid }; + }); + + const getCollectionCrsSpy = sinon.spy(function () { + return 'crs'; + }); + + const stub = { + '../helpers/fields': fields, + '../helpers': { + getCollectionCrs: getCollectionCrsSpy, + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, + }; + + const { renderFeaturesResponse } = proxyquire('./render-features', stub); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + sourceSR: 9999, + inputCrs: 8888, + outSR: 7777, + outputCrs: 3857, + }); + result.should.deepEqual({ + objectIdFieldName: 'OBJECTID', + uniqueIdField: { + name: 'OBJECTID', + isSystemMaintained: true, + }, + geometryType: 'esriGeometryPoint', + globalIdFieldName: '', + hasZ: false, + hasM: false, + spatialReference: { + latestWkid: 3857, + }, + fields: 'fields', + features: json.features, + exceededTransferLimit: false, + }); + getCollectionCrsSpy.callCount.should.equal(0); + normalizeSpatialReferenceSpy.callCount.should.equal(1); + normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual([3857]); + }); + + it('should use wkid and latestWkid', () => { + const normalizeSpatialReferenceSpy = sinon.spy(function (latestWkid) { + return { wkid: latestWkid, latestWkid }; + }); + + const getCollectionCrsSpy = sinon.spy(function () { + return 'crs'; + }); + + const stub = { + '../helpers/fields': fields, + '../helpers': { + getCollectionCrs: getCollectionCrsSpy, + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, + }; + + const { renderFeaturesResponse } = proxyquire('./render-features', stub); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + sourceSR: 9999, + inputCrs: 8888, + outSR: 7777, + outputCrs: 3857, + }); + result.should.deepEqual({ + objectIdFieldName: 'OBJECTID', + uniqueIdField: { + name: 'OBJECTID', + isSystemMaintained: true, + }, + geometryType: 'esriGeometryPoint', + globalIdFieldName: '', + hasZ: false, + hasM: false, + spatialReference: { + wkid: 3857, + latestWkid: 3857, + }, + fields: 'fields', + features: json.features, + exceededTransferLimit: false, + }); + getCollectionCrsSpy.callCount.should.equal(0); + normalizeSpatialReferenceSpy.callCount.should.equal(1); + normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual([3857]); + }); + + it('should use wkt', () => { + const normalizeSpatialReferenceSpy = sinon.spy(function (wkt) { + return { wkt }; + }); + + const getCollectionCrsSpy = sinon.spy(function () { + return 'crs'; + }); + + const stub = { + '../helpers/fields': fields, + '../helpers': { + getCollectionCrs: getCollectionCrsSpy, + normalizeSpatialReference: normalizeSpatialReferenceSpy, + }, + }; + + const { renderFeaturesResponse } = proxyquire('./render-features', stub); + const result = renderFeaturesResponse(json, { + geometryType: 'esriGeometryPoint', + sourceSR: 9999, + inputCrs: 8888, + outSR: 7777, + outputCrs: 'wkt-here', + }); + result.should.deepEqual({ + objectIdFieldName: 'OBJECTID', + uniqueIdField: { + name: 'OBJECTID', + isSystemMaintained: true, + }, + geometryType: 'esriGeometryPoint', + globalIdFieldName: '', + hasZ: false, + hasM: false, + spatialReference: { + wkt: 'wkt-here', + }, + fields: 'fields', + features: json.features, + exceededTransferLimit: false, + }); + getCollectionCrsSpy.callCount.should.equal(0); + normalizeSpatialReferenceSpy.callCount.should.equal(1); + normalizeSpatialReferenceSpy.firstCall.args.should.deepEqual(['wkt-here']); + }); }); }); diff --git a/packages/featureserver/src/route.spec.js b/packages/featureserver/src/route.spec.js index acc3e589e..6db6187ef 100644 --- a/packages/featureserver/src/route.spec.js +++ b/packages/featureserver/src/route.spec.js @@ -15,7 +15,10 @@ describe('Route module unit tests', () => { const route = proxyquire('./route', { './query': querySpy, - './response-handlers': { generalResponseHandler: responseHandlerSpy, queryResponseHandler: responseHandlerSpy }, + './response-handlers': { + generalResponseHandler: responseHandlerSpy, + queryResponseHandler: responseHandlerSpy, + }, }); it('should use query handler and return 200', () => { @@ -41,7 +44,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, { features: [] }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -75,7 +78,7 @@ describe('Route module unit tests', () => { details: ['Fool bar'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); afterEach(() => { @@ -122,7 +125,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, { restInfo: true }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -166,7 +169,7 @@ describe('Route module unit tests', () => { details: ['Fool bar'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -214,7 +217,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, { serverInfo: true }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -258,7 +261,7 @@ describe('Route module unit tests', () => { details: ['Fool bar'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -304,7 +307,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, { layersInfo: true }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -346,7 +349,7 @@ describe('Route module unit tests', () => { details: ['Fool bar'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -394,7 +397,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, 'layer-metadata', - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -438,7 +441,7 @@ describe('Route module unit tests', () => { details: ['Fool bar'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -486,7 +489,7 @@ describe('Route module unit tests', () => { responseHandlerSpy.firstCall.args.should.deepEqual([ {}, 'layer-metadata', - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); @@ -522,7 +525,7 @@ describe('Route module unit tests', () => { details: ['Not Found'], }, }, - { resultRecordCount: 2000 } + { resultRecordCount: 2000 }, ]); }); }); diff --git a/packages/featureserver/test/integration/route.spec.js b/packages/featureserver/test/integration/route.spec.js index 0584921a5..c7de6b32b 100644 --- a/packages/featureserver/test/integration/route.spec.js +++ b/packages/featureserver/test/integration/route.spec.js @@ -13,7 +13,7 @@ let data; const serverHander = (req, res) => { FeatureServer.route(req, res, { description: 'test', - layers: [data, data] + layers: [data, data], }); }; const handler = (req, res) => FeatureServer.route(req, res, data); @@ -31,10 +31,10 @@ describe('Routing feature server requests', () => { }); describe('Server Info', () => { - it('should properly route and handle a server info request to /FeatureServer`', done => { + it('should properly route and handle a server info request to /FeatureServer`', (done) => { request(app) .get('/FeatureServer?f=json') - .expect(res => { + .expect((res) => { res.body.serviceDescription.should.equal('test'); res.body.layers.length.should.equal(2); Array.isArray(res.body.tables).should.equal(true); @@ -43,10 +43,10 @@ describe('Routing feature server requests', () => { .expect(200, done); }); - it('should properly route and handle a server info request to /FeatureServer/`', done => { + it('should properly route and handle a server info request to /FeatureServer/`', (done) => { request(app) .get('/FeatureServer/?f=json') - .expect(res => { + .expect((res) => { res.body.serviceDescription.should.equal('test'); res.body.layers.length.should.equal(2); Array.isArray(res.body.tables).should.equal(true); @@ -55,10 +55,10 @@ describe('Routing feature server requests', () => { .expect(200, done); }); - it('should properly route and handle a server info request to /FeatureServer/info`', done => { + it('should properly route and handle a server info request to /FeatureServer/info`', (done) => { request(app) .get('/FeatureServer/info?f=json') - .expect(res => { + .expect((res) => { res.body.serviceDescription.should.equal('test'); res.body.layers.length.should.equal(2); Array.isArray(res.body.tables).should.equal(true); @@ -69,10 +69,10 @@ describe('Routing feature server requests', () => { }); describe('Layers', () => { - it('should properly route and handle a layers request`', done => { + it('should properly route and handle a layers request`', (done) => { request(app) .get('/FeatureServer/layers?f=json') - .expect(res => { + .expect((res) => { res.body.layers.length.should.equal(1); res.body.tables.length.should.equal(0); res.body.layers[0].name.should.equal('Snow'); @@ -83,15 +83,15 @@ describe('Routing feature server requests', () => { }); describe('Layer Info', () => { - it('should properly route and handle a layer info request of form /FeatureServer/:layerId`', done => { + it('should properly route and handle a layer info request of form /FeatureServer/:layerId`', (done) => { request(app) .get('/FeatureServer/3?f=json') - .expect(res => { + .expect((res) => { res.body.type.should.equal('Feature Layer'); res.body.name.should.equal('Snow'); res.body.id.should.equal(3); res.body.fields - .filter(f => { + .filter((f) => { return f.name === 'OBJECTID'; }) .length.should.equal(1); @@ -101,15 +101,15 @@ describe('Routing feature server requests', () => { .expect(200, done); }); - it('should properly route and handle a layer info request of form /FeatureServer/:layerId/`', done => { + it('should properly route and handle a layer info request of form /FeatureServer/:layerId/`', (done) => { request(app) .get('/FeatureServer/3/?f=json') - .expect(res => { + .expect((res) => { res.body.type.should.equal('Feature Layer'); res.body.name.should.equal('Snow'); res.body.id.should.equal(3); res.body.fields - .filter(f => { + .filter((f) => { return f.name === 'OBJECTID'; }) .length.should.equal(1); @@ -119,15 +119,15 @@ describe('Routing feature server requests', () => { .expect(200, done); }); - it('should properly route and handle a layer info request of form /FeatureServer/:layerId/info`', done => { + it('should properly route and handle a layer info request of form /FeatureServer/:layerId/info`', (done) => { request(app) .get('/FeatureServer/3/info?f=json') - .expect(res => { + .expect((res) => { res.body.type.should.equal('Feature Layer'); res.body.name.should.equal('Snow'); res.body.id.should.equal(3); res.body.fields - .filter(f => { + .filter((f) => { return f.name === 'OBJECTID'; }) .length.should.equal(1); @@ -141,14 +141,14 @@ describe('Routing feature server requests', () => { beforeEach(() => { data = _.cloneDeep(noGeom); }); - it('should properly route and handle the layer with no geometry', done => { + it('should properly route and handle the layer with no geometry', (done) => { request(app) .get('/FeatureServer/3?f=json') - .expect(res => { + .expect((res) => { res.body.type.should.equal('Table'); res.body.id.should.equal(3); res.body.fields - .filter(f => { + .filter((f) => { return f.name === 'OBJECTID'; }) .length.should.equal(1); @@ -160,16 +160,16 @@ describe('Routing feature server requests', () => { }); describe('Method not supported', () => { - it('should return an informative error', done => { + it('should return an informative error', (done) => { request(app) .get('/FeatureServer/0/foobarbaz') - .expect(res => { + .expect((res) => { res.body.should.deepEqual({ error: { code: 400, message: 'Method not supported', - details: ['Method not supported'] - } + details: ['Method not supported'], + }, }); }) .expect('Content-Type', /json/) @@ -186,10 +186,10 @@ describe('Routing feature server requests', () => { this.secondOBJECTID = response.features[1].attributes.OBJECTID; }); - it('should properly route and handle a query', done => { + it('should properly route and handle a query', (done) => { request(app) .get('/FeatureServer/0/query?f=json&where=1%3D1') - .expect(res => { + .expect((res) => { res.body.features.length.should.equal(417); res.body.exceededTransferLimit.should.equal(false); }) @@ -201,8 +201,10 @@ describe('Routing feature server requests', () => { data.metadata.maxRecordCount = 2; request(app) .get('/FeatureServer/0/query?f=json&where=1%3D1') - .expect(res => { - res.body.features[1].attributes.OBJECTID.should.equal(this.secondOBJECTID); + .expect((res) => { + res.body.features[1].attributes.OBJECTID.should.equal( + this.secondOBJECTID, + ); res.body.features.length.should.equal(2); res.body.exceededTransferLimit.should.equal(true); }) @@ -210,29 +212,20 @@ describe('Routing feature server requests', () => { .expect(200, done); }); - it('should respect resultRecordCount', done => { - request(app) - .get('/FeatureServer/0/query?f=json&where=1%3D1&resultRecordCount=10') - .expect(res => { - res.body.features.length.should.equal(10); - res.body.exceededTransferLimit.should.equal(true); - }) - .expect('Content-Type', /json/) - .expect(200, done); - }); - it('should ignore empty query parameters', function (done) { request(app) .get('/FeatureServer/0/query?f=json&orderByFields=') - .expect(res => { - res.body.features[1].attributes.OBJECTID.should.equal(this.secondOBJECTID); + .expect((res) => { + res.body.features[1].attributes.OBJECTID.should.equal( + this.secondOBJECTID, + ); res.body.features.length.should.equal(417); }) .expect('Content-Type', /json/) .expect(200, done); }); - it('should handle when a provider passes in statistics', done => { + it('should handle when a provider passes in statistics', (done) => { data = require('./fixtures/provider-statistics.json'); request(app) .get( @@ -242,9 +235,9 @@ describe('Routing feature server requests', () => { 'inSR=102100&' + 'spatialRel=esriSpatialRelIntersects&' + 'outStatistics=[{"onStatisticField":"OBJECTID","statisticType":"min","outStatisticFieldName":"min_2"},{"onStatisticField":"OBJECTID","statisticType":"max","outStatisticFieldName":"max_2"},{"onStatisticField":"OBJECTID","statisticType":"count","outStatisticFieldName":"count_2"}]&' + - 'where=1=1' + 'where=1=1', ) - .expect(res => { + .expect((res) => { res.body.features[0].attributes.min_2.should.equal(0); res.body.features[0].attributes.max_2.should.equal(57611); res.body.features[0].attributes.count_2.should.equal(75343); @@ -263,7 +256,7 @@ describe('Routing feature server requests', () => { data = _.cloneDeep({ type: 'FeatureCollection', metadata: { - name: 'GDeltGKG' + name: 'GDeltGKG', }, statistics: { classBreaks: [ @@ -275,71 +268,95 @@ describe('Routing feature server requests', () => { [307, 360], [360, 558], [558, 799], - [799, 2000] - ] - } + [799, 2000], + ], + }, }); }); - it('should properly route and handle when a provider passes in class breaks statistics', done => { + it('should properly route and handle when a provider passes in class breaks statistics', (done) => { request(app) .get('/FeatureServer/3/generateRenderer?') - .expect(res => { + .expect((res) => { res.body.type.should.equal('classBreaks'); res.body.classBreakInfos.length.should.equal(9); - res.body.classBreakInfos[0].symbol.color.should.deepEqual([0, 255, 0]); + res.body.classBreakInfos[0].symbol.color.should.deepEqual([ + 0, 255, 0, + ]); res.body.classBreakInfos[0].label.should.equal('80-147'); - res.body.classBreakInfos[4].symbol.color.should.deepEqual([0, 255, 255]); - res.body.classBreakInfos[8].symbol.color.should.deepEqual([0, 0, 255]); + res.body.classBreakInfos[4].symbol.color.should.deepEqual([ + 0, 255, 255, + ]); + res.body.classBreakInfos[8].symbol.color.should.deepEqual([ + 0, 0, 255, + ]); }) .expect('Content-Type', /json/) .expect(200, done); }); - it('should ignore options when statistics are passed in', done => { + it('should ignore options when statistics are passed in', (done) => { request(app) - .get('/FeatureServer/3/generateRenderer?' + - 'classificationDef={' + - '"type": "classBreaksDef",' + - '"classificationField": "daily snow total",' + - '"classificationMethod": "esriClassifyEqualInterval",' + - '"breakCount": 9}&' + - 'where=&' + - 'gdbVersion=&' + - 'f=json') - .expect(res => { + .get( + '/FeatureServer/3/generateRenderer?' + + 'classificationDef={' + + '"type": "classBreaksDef",' + + '"classificationField": "daily snow total",' + + '"classificationMethod": "esriClassifyEqualInterval",' + + '"breakCount": 9}&' + + 'where=&' + + 'gdbVersion=&' + + 'f=json', + ) + .expect((res) => { res.body.type.should.equal('classBreaks'); res.body.classBreakInfos.length.should.equal(9); - res.body.classBreakInfos[0].symbol.color.should.deepEqual([0, 255, 0]); + res.body.classBreakInfos[0].symbol.color.should.deepEqual([ + 0, 255, 0, + ]); res.body.classBreakInfos[0].label.should.equal('80-147'); - res.body.classBreakInfos[4].symbol.color.should.deepEqual([0, 255, 255]); - res.body.classBreakInfos[8].symbol.color.should.deepEqual([0, 0, 255]); + res.body.classBreakInfos[4].symbol.color.should.deepEqual([ + 0, 255, 255, + ]); + res.body.classBreakInfos[8].symbol.color.should.deepEqual([ + 0, 0, 255, + ]); }) .expect('Content-Type', /json/) .expect(200, done); }); }); - it('should properly route and handle a generate renderer request', done => { + it('should properly route and handle a generate renderer request', (done) => { request(app) - .get('/FeatureServer/3/generateRenderer?' + - 'classificationDef={' + - '"type": "classBreaksDef",' + - '"classificationField": "daily snow total",' + - '"classificationMethod": "esriClassifyEqualInterval",' + - '"breakCount": 7,' + - '"colorRamp": {' + + .get( + '/FeatureServer/3/generateRenderer?' + + 'classificationDef={' + + '"type": "classBreaksDef",' + + '"classificationField": "daily snow total",' + + '"classificationMethod": "esriClassifyEqualInterval",' + + '"breakCount": 7,' + + '"colorRamp": {' + '"type": "algorithmic",' + '"fromColor": [0, 100, 0, 255],' + '"toColor": [0, 0, 255, 255],' + '"algorithm": "esriHSVAlgorithm"}' + - '}&' + - 'where=latitude < 39 AND latitude > 38.5&' + - 'f=json') - .expect(res => { + '}&' + + 'where=latitude < 39 AND latitude > 38.5&' + + 'f=json', + ) + .expect((res) => { res.body.type.should.equal('classBreaks'); res.body.classBreakInfos.length.should.equal(7); - res.body.classBreakInfos[0].symbol.color.should.deepEqual([0, 100, 0]); - res.body.classBreakInfos[0].label.should.equal('0-0.7571428571428571'); - res.body.classBreakInfos[3].symbol.color.should.deepEqual([0, 177, 178]); - res.body.classBreakInfos[6].symbol.color.should.deepEqual([0, 0, 255]); + res.body.classBreakInfos[0].symbol.color.should.deepEqual([ + 0, 100, 0, + ]); + res.body.classBreakInfos[0].label.should.equal( + '0-0.7571428571428571', + ); + res.body.classBreakInfos[3].symbol.color.should.deepEqual([ + 0, 177, 178, + ]); + res.body.classBreakInfos[6].symbol.color.should.deepEqual([ + 0, 0, 255, + ]); }) .expect('Content-Type', /json/) .expect(200, done); @@ -351,12 +368,14 @@ describe('Routing feature server requests', () => { beforeEach(() => { data = _.cloneDeep(relatedData); }); - it('should properly route and handle return related records result', done => { + it('should properly route and handle return related records result', (done) => { request(app) .get('/FeatureServer/0/queryRelatedRecords?') - .expect(res => { + .expect((res) => { res.body.relatedRecordGroups.length.should.equal(1); - res.body.relatedRecordGroups[0].relatedRecords.length.should.equal(11); + res.body.relatedRecordGroups[0].relatedRecords.length.should.equal( + 11, + ); }) .expect('Content-Type', /json/) .expect(200, done); diff --git a/packages/winnow/coverage-unit.svg b/packages/winnow/coverage-unit.svg index 2f18fe687..0971dec99 100644 --- a/packages/winnow/coverage-unit.svg +++ b/packages/winnow/coverage-unit.svg @@ -1,20 +1,20 @@ - - coverage: 96.6% + + coverage: 96.68% - + - - + + \ No newline at end of file diff --git a/packages/winnow/src/normalize-query-options/limit.js b/packages/winnow/src/normalize-query-options/limit.js index 8b3123f03..522baf159 100644 --- a/packages/winnow/src/normalize-query-options/limit.js +++ b/packages/winnow/src/normalize-query-options/limit.js @@ -12,7 +12,7 @@ function normalizeLimit(options) { logManager.logger.debug('"limit" option is not an integer; skipping'); return; } - // If there is a limit, add 1 to it so we can later calculate a limitExceeded. The result set will be resized accordingly, post SQL + // If there is a limit, add 1 to it so we can later calculate a exceededTransferLimit. The result set will be resized accordingly, post SQL return limit ? limit + 1 : undefined; } diff --git a/packages/winnow/src/query/standard-query.js b/packages/winnow/src/query/standard-query.js index c783f5a84..9e2fc45fb 100644 --- a/packages/winnow/src/query/standard-query.js +++ b/packages/winnow/src/query/standard-query.js @@ -3,31 +3,33 @@ const { filterAndTransform } = require('../filter-and-transform'); const { params: createSqlParams } = require('../sql-query-builder'); const packageFeatures = require('./package-features'); -function standardQuery (features, sqlString, options = {}) { +function standardQuery(features, sqlString, options = {}) { const { limit } = options; const params = createSqlParams(features, options); const filtered = filterAndTransform(sqlString, params); - if (options.skipLimitHandling || !options.limit) { + // 1) For GeoService API queries there is always a limit + // 2) options.limit is incremented by one in normalizeOptions.js; if filtered.length === options.limit, original limit option has been exceeded + if ( + options.skipLimitHandling || + !options.limit || + filtered.length !== limit + ) { return packageFeatures(filtered, options); } - // options.limit is incremented by one in normalizeOptions.js; if filtered.length === options.limit, original limit option has been exceeded - const limitExceeded = filtered.length === limit; - - if (limitExceeded) { - return conformToLimit(filtered, options); - } + modifyForLimit(filtered, options); return packageFeatures(filtered, options); } -function conformToLimit (features, options) { +function modifyForLimit(features, options) { // Pop off the last feature, so that feature array length is consistent with original limit option features.pop(); - if (options.collection) _.set(options, 'collection.metadata.limitExceeded', true); - return packageFeatures(features, options); + if (options.collection) { + _.set(options, 'collection.metadata.exceededTransferLimit', true); + } } module.exports = standardQuery; diff --git a/packages/winnow/src/query/standard-query.spec.js b/packages/winnow/src/query/standard-query.spec.js index 2733c5996..a6b738bf8 100644 --- a/packages/winnow/src/query/standard-query.spec.js +++ b/packages/winnow/src/query/standard-query.spec.js @@ -3,86 +3,119 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); const modulePath = './standard-query'; -test('standardQuery, no options', t => { +test('standardQuery, no options', (t) => { const filterAndTransformSpy = sinon.spy({ filterAndTransform: () => { return ['feature1', 'feature2']; - } + }, }); const queryBuilderSpy = sinon.spy({ params: () => { return ['params1', 'params2']; - } + }, }); const standardQuery = proxyquire(modulePath, { '../filter-and-transform': filterAndTransformSpy, - '../sql-query-builder': queryBuilderSpy + '../sql-query-builder': queryBuilderSpy, }); - const result = standardQuery(['feature1', 'feature2', 'feature3'], 'SQL statement', { foo: 'bar' }); + const result = standardQuery( + ['feature1', 'feature2', 'feature3'], + 'SQL statement', + ); t.deepEquals(result, ['feature1', 'feature2']); t.ok(queryBuilderSpy.params.calledOnce); - t.deepEquals(queryBuilderSpy.params.firstCall.args, [['feature1', 'feature2', 'feature3'], { foo: 'bar' }]); + t.deepEquals(queryBuilderSpy.params.firstCall.args, [ + ['feature1', 'feature2', 'feature3'], + {}, + ]); t.ok(filterAndTransformSpy.filterAndTransform.calledOnce); - t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, ['SQL statement', ['params1', 'params2']]); + t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, [ + 'SQL statement', + ['params1', 'params2'], + ]); t.end(); }); -test('standardQuery, limit option', t => { +test('standardQuery, limit option', (t) => { const filterAndTransformSpy = sinon.spy({ filterAndTransform: () => { return ['feature1', 'feature2']; - } + }, }); const queryBuilderSpy = sinon.spy({ params: () => { return ['params1', 'params2']; - } + }, }); const standardQuery = proxyquire(modulePath, { '../filter-and-transform': filterAndTransformSpy, - '../sql-query-builder': queryBuilderSpy + '../sql-query-builder': queryBuilderSpy, }); - const result = standardQuery(['feature1', 'feature2', 'feature3'], 'SQL statement', { foo: 'bar', limit: 2 }); + const result = standardQuery( + ['feature1', 'feature2', 'feature3'], + 'SQL statement', + { foo: 'bar', limit: 2 }, + ); t.deepEquals(result, ['feature1']); t.ok(queryBuilderSpy.params.calledOnce); - t.deepEquals(queryBuilderSpy.params.firstCall.args, [['feature1', 'feature2', 'feature3'], { foo: 'bar', limit: 2 }]); + t.deepEquals(queryBuilderSpy.params.firstCall.args, [ + ['feature1', 'feature2', 'feature3'], + { foo: 'bar', limit: 2 }, + ]); t.ok(filterAndTransformSpy.filterAndTransform.calledOnce); - t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, ['SQL statement', ['params1', 'params2']]); + t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, [ + 'SQL statement', + ['params1', 'params2'], + ]); t.end(); }); -test('standardQuery, limit and collection option', t => { +test('standardQuery, limit and collection option', (t) => { const filterAndTransformSpy = sinon.spy({ filterAndTransform: () => { return ['feature1', 'feature2']; - } + }, }); const queryBuilderSpy = sinon.spy({ params: () => { return ['params1', 'params2']; - } + }, }); const standardQuery = proxyquire(modulePath, { '../filter-and-transform': filterAndTransformSpy, - '../sql-query-builder': queryBuilderSpy + '../sql-query-builder': queryBuilderSpy, }); - const result = standardQuery(['feature1', 'feature2', 'feature3'], 'SQL statement', { foo: 'bar', limit: 2, collection: {} }); - t.deepEquals(result, { metadata: { limitExceeded: true }, features: ['feature1'] }); + const result = standardQuery( + ['feature1', 'feature2', 'feature3'], + 'SQL statement', + { foo: 'bar', limit: 2, collection: {} }, + ); + t.deepEquals(result, { + metadata: { exceededTransferLimit: true }, + features: ['feature1'], + }); t.ok(queryBuilderSpy.params.calledOnce); t.deepEquals(queryBuilderSpy.params.firstCall.args, [ ['feature1', 'feature2', 'feature3'], - { foo: 'bar', limit: 2, collection: { metadata: { limitExceeded: true } } } + { + foo: 'bar', + limit: 2, + collection: { metadata: { exceededTransferLimit: true } }, + }, ]); t.ok(filterAndTransformSpy.filterAndTransform.calledOnce); - t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, ['SQL statement', ['params1', 'params2']]); + t.deepEquals(filterAndTransformSpy.filterAndTransform.firstCall.args, [ + 'SQL statement', + ['params1', 'params2'], + ]); t.end(); }); diff --git a/packages/winnow/test/integration/to-esri.spec.js b/packages/winnow/test/integration/to-esri.spec.js index ff108f17c..94fee05f8 100644 --- a/packages/winnow/test/integration/to-esri.spec.js +++ b/packages/winnow/test/integration/to-esri.spec.js @@ -26,7 +26,7 @@ test('With a where option and a limit smaller than the filter', t => { }; const result = Winnow.query(trees, options); const metadata = result.metadata; - t.ok(metadata.limitExceeded); + t.ok(metadata.exceededTransferLimit); t.end(); }); @@ -37,7 +37,7 @@ test('With a where option and a limit larger than the filter', t => { }; const result = Winnow.query(trees, options); const metadata = result.metadata; - t.notOk(metadata.limitExceeded); + t.notOk(metadata.exceededTransferLimit); t.end(); }); @@ -49,7 +49,7 @@ test('With a where option and a limit the same as the the filter', t => { }; const result = Winnow.query(trees, options); const metadata = result.metadata; - t.notOk(metadata.limitExceeded); + t.notOk(metadata.exceededTransferLimit); t.end(); }); diff --git a/test/geoservice-query.spec.js b/test/geoservice-query.spec.js index 2e6efa6ad..59f633121 100644 --- a/test/geoservice-query.spec.js +++ b/test/geoservice-query.spec.js @@ -6,28 +6,21 @@ const mockLogger = { info: () => {}, silly: () => {}, warn: () => {}, - error: () => {} + error: () => {}, }; describe('koop', () => { const koop = new Koop({ logLevel: 'error', logger: mockLogger }); koop.register(provider, { dataDir: './test/provider-data' }); - test('should return true', async () => { - try { - const response = await request(koop.server).get('/file-geojson/rest/services/polygon/FeatureServer/0/query'); - expect(response.status).toBe(200); - } catch (error) { - console.error(error); - throw error; - } - }); describe('Feature Server', () => { describe('/query', () => { describe('objectIds', () => { test('handles empty value', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds='); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds=', + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(3); @@ -40,7 +33,9 @@ describe('koop', () => { describe('using OBJECTID field', () => { test('handles single value', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds=2'); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds=2', + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(1); @@ -53,7 +48,9 @@ describe('koop', () => { test('handles delimited values', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds=2,3'); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?objectIds=2,3', + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(2); @@ -69,7 +66,9 @@ describe('koop', () => { describe('using defined id field', () => { test('handles single value', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-metadata-id/FeatureServer/0/query?objectIds=2'); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-metadata-id/FeatureServer/0/query?objectIds=2', + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(1); @@ -79,10 +78,12 @@ describe('koop', () => { throw error; } }); - + test('handles delimited values', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-metadata-id/FeatureServer/0/query?objectIds=2,3'); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-metadata-id/FeatureServer/0/query?objectIds=2,3', + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(2); @@ -98,15 +99,19 @@ describe('koop', () => { describe('without OBJECTID or idField', () => { let objectIds; beforeAll(async () => { - const response = await request(koop.server).get('/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query'); - objectIds = response.body.features.map(feature => { + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query', + ); + objectIds = response.body.features.map((feature) => { return feature.attributes.OBJECTID; }); }); - + test('handles single value', async () => { try { - const response = await request(koop.server).get(`/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query?objectIds=${objectIds[1]}`); + const response = await request(koop.server).get( + `/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query?objectIds=${objectIds[1]}`, + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(1); @@ -119,7 +124,9 @@ describe('koop', () => { test('handles delimited values', async () => { try { - const response = await request(koop.server).get(`/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query?objectIds=${objectIds[1]},${objectIds[2]}`); + const response = await request(koop.server).get( + `/file-geojson/rest/services/points-wo-objectid/FeatureServer/0/query?objectIds=${objectIds[1]},${objectIds[2]}`, + ); expect(response.status).toBe(200); const { features } = response.body; expect(features.length).toBe(2); @@ -136,10 +143,43 @@ describe('koop', () => { describe('where', () => { test('handle query with "+" as whitespace', async () => { try { - const response = await request(koop.server).get('/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?WHERE=label+is+not+null'); + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?WHERE=label+is+not+null', + ); + expect(response.status).toBe(200); + const { features } = response.body; + expect(features.length).toBe(3); + } catch (error) { + console.error(error); + throw error; + } + }); + }); + + describe('resultRecordCount', () => { + test('should respect resultRecordCount applied from winnow', async () => { + try { + const response = await request(koop.server).get( + '/file-geojson/rest/services/points-w-objectid/FeatureServer/0/query?resultRecordCount=2', + ); expect(response.status).toBe(200); const { features } = response.body; + expect(features.length).toBe(2); + } catch (error) { + console.error(error); + throw error; + } + }); + + test('should respect resultRecordCount applied from passthrough provider', async () => { + try { + const response = await request(koop.server).get( + '/file-geojson/rest/services/pass-through/FeatureServer/0/query?resultRecordCount=3', + ); + expect(response.status).toBe(200); + const { features, exceededTransferLimit } = response.body; expect(features.length).toBe(3); + expect(exceededTransferLimit).toBe(true); } catch (error) { console.error(error); throw error; diff --git a/test/provider-data/pass-through.geojson b/test/provider-data/pass-through.geojson new file mode 100644 index 000000000..a7905ac44 --- /dev/null +++ b/test/provider-data/pass-through.geojson @@ -0,0 +1,63 @@ +{ + "type": "FeatureCollection", + "filtersApplied": { "all": true }, + "metadata": { + "exceededTransferLimit": true, + "fields": [ + { "name": "timestamp", "type": "Date" }, + { "name": "OBJECTID", "type": "Number" }, + { "name": "label", "type": "String" }, + { "name": "category", "type": "String" } + ] + }, + "features": [ + { + "type": "Feature", + "properties": { + "OBJECTID": 1, + "timestamp": "2023-04-10T16:15:30.000Z", + "label": "White Leg", + "category": "pinto" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -80, + 25 + ] + } + }, + { + "type": "Feature", + "properties": { + "OBJECTID": 2, + "timestamp": "2020-04-12T16:15:30.000Z", + "label": "Fireman", + "category": "pinto" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -120, + 45 + ] + } + }, + { + "type": "Feature", + "properties": { + "OBJECTID": 3, + "timestamp": "2015-04-11T16:15:30.000Z", + "label": "Workhorse", + "category": "draft" + }, + "geometry": { + "type": "Point", + "coordinates": [ + -100, + 40 + ] + } + } + ] +} \ No newline at end of file