From d12d333ad9762de8acc467ad81bb56a6dde51744 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 15:46:21 -0600 Subject: [PATCH 01/16] Test util.addQueryParams() --- test/api/util.test.js | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/api/util.test.js b/test/api/util.test.js index 8c5cc7e..39d8ffc 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -6,6 +6,58 @@ var util = require('../../api/util'); describe('util', function() { + describe('addQueryParams()', function() { + + it('adds params from a query object', function() { + + var cases = [{ + url: 'http://example.com/', + query: { + foo: 'bar' + }, + expect: 'http://example.com/?foo=bar' + }, { + url: 'http://example.com/?foo=bam', + query: { + baz: 'bar' + }, + expect: 'http://example.com/?foo=bam&baz=bar' + }, { + url: 'http://example.com/?foo=bam', + query: { + foo: 'bar' + }, + expect: 'http://example.com/?foo=bar' + }, { + url: 'http://example.com/#anchor', + query: { + foo: 'bar' + }, + expect: 'http://example.com/?foo=bar#anchor' + }, { + url: 'http://example.com/?bam=baz#anchor', + query: { + foo: 'bar' + }, + expect: 'http://example.com/?bam=baz&foo=bar#anchor' + }, { + url: 'http://example.com/?foo=bam#anchor', + query: { + foo: 'bar' + }, + expect: 'http://example.com/?foo=bar#anchor' + }]; + + var add = util.addQueryParams; + for (var i = 0, ii = cases.length; i < ii; ++i) { + var c = cases[i]; + assert.equal(add(c.url, c.query), c.expect, 'case ' + i); + } + + }); + + }); + describe('augmentSceneLinks()', function() { var scene; From 2e6f52ab564b9dc7aad9d103abf2120a329074f3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 16:11:32 -0600 Subject: [PATCH 02/16] Report on coverage --- .gitignore | 1 + .travis.yml | 1 + package.json | 5 ++++- readme.md | 13 +++++++++++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index d49756d..8718db7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules/ /build/ +/.nyc_output/ diff --git a/.travis.yml b/.travis.yml index b198879..0e469f7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,3 +2,4 @@ language: node_js node_js: - "0.12" - "iojs-v2" +after_success: npm run coverage diff --git a/package.json b/package.json index 7f83ec2..670a727 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "main": "api/index.js", "scripts": { "pretest": "eslint bin examples api cli test", - "test": "mocha --recursive test", + "test": "nyc mocha --recursive test", + "coverage": "nyc report --reporter=text-lcov | coveralls", "test-debug": "mocha --debug-brk --recursive test", "start": "watchy --watch bin,examples,api,cli,test -- npm test", "postpublish": "npm run publish-doc", @@ -25,12 +26,14 @@ }, "devDependencies": { "chai": "^3.0.0", + "coveralls": "^2.11.3", "eslint": "^0.22.1", "eslint-config-planet": "^2.0.0", "gh-pages": "^0.3.1", "jsdoc": "^3.3.2", "minami": "^1.1.0", "mocha": "^2.2.5", + "nyc": "^3.1.0", "sinon": "^1.15.3", "watchy": "^0.6.2" }, diff --git a/readme.md b/readme.md index f15574f..963ad16 100644 --- a/readme.md +++ b/readme.md @@ -38,7 +38,7 @@ To enable this every time you start a new shell, you can append the output of `p The CLI will be fully documented when it is a bit more stable. For now, you can get a preview of what's available with this video: -[![screen shot](https://raw.githubusercontent.com/wiki/planetlabs/planet-client-js/planet-client.png)](https://vimeo.com/134018559) +[![screen shot][video-image]][video-url] ### Contributing @@ -56,7 +56,9 @@ During development, you can start a file watcher that runs the linter and tests npm start -[![Current Status](https://travis-ci.org/planetlabs/planet-client-js.svg?branch=master)](https://travis-ci.org/planetlabs/planet-client-js) + +[![Build Status][travis-image]][travis-url] +[![Coverage Status][coveralls-image]][coveralls-url] ### License @@ -65,3 +67,10 @@ During development, you can start a file watcher that runs the linter and tests Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) (the "License"); you may not use this file except in compliance with the License. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See [the License](http://www.apache.org/licenses/LICENSE-2.0) for the specific language governing permissions and limitations under the License. + +[video-url]: https://vimeo.com/134018559 +[video-image]: https://raw.githubusercontent.com/wiki/planetlabs/planet-client-js/planet-client.png +[travis-url]: https://travis-ci.org/planetlabs/planet-client-js +[travis-image]: https://img.shields.io/travis/planetlabs/planet-client-js.svg +[coveralls-url]: https://coveralls.io/github/planetlabs/planet-client-js +[coveralls-image]: https://img.shields.io/coveralls/planetlabs/planet-client-js.svg From 1dde1ddd4865bc7b29aa14300223400c6662abbc Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 16:25:30 -0600 Subject: [PATCH 03/16] Test util.augmentQuadLinks() --- api/util.js | 3 ++ test/api/util.test.js | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/api/util.js b/api/util.js index d65a888..665c85d 100644 --- a/api/util.js +++ b/api/util.js @@ -54,6 +54,9 @@ function augmentQuadLinks(quad) { if (links.full) { links.full = addQueryParams(links.full, {'api_key': key}); } + if (links.thumbnail) { + links.thumbnail = addQueryParams(links.thumbnail, {'api_key': key}); + } } return quad; diff --git a/test/api/util.test.js b/test/api/util.test.js index 39d8ffc..ebf4262 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -119,4 +119,69 @@ describe('util', function() { }); + describe('augmentQuadLinks()', function() { + var quad; + + beforeEach(function() { + quad = util.assign({}, { + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-78.925781239, 39.0959629318], + [-78.925781239, 38.9594087879], + [-78.749999989, 38.9594087879], + [-78.749999989, 39.0959629318], + [-78.925781239, 39.0959629318] + ] + ] + }, + type: 'Feature', + id: 'L15-0575E-1265N', + properties: { + updated: '2015-07-20T13:39:49.550576+00:00', + 'num_input_scenes': 28, + links: { + self: 'https://example.com/mosaics/one/quads/two', + full: 'https://example.com/mosaics/one/quads/two/full', + thumbnail: 'https://example.com/mosaics/one/quads/two/thumb', + mosaic: 'https://example.com/mosaics/one', + scenes: 'https://example.com/mosaics/one/quads/two/scenes/' + }, + 'percent_covered': 100 + } + }); + }); + + afterEach(function() { + authStore.clear(); + }); + + it('adds a API key from stored token to data URLs', function() { + // {api_key: 'my-api-key'} + var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcGlfa2V5Ijoib' + + 'XktYXBpLWtleSJ9.sYcuJzdUThIsvJGNymbobOh-nY6ZKFEqXTqwZS-4QvE'; + authStore.setToken(token); + var key = authStore.getKey(); + + var augmented = util.augmentQuadLinks(quad); + assert.equal(augmented.properties.links.full, + 'https://example.com/mosaics/one/quads/two/full?api_key=' + key); + assert.equal(augmented.properties.links.thumbnail, + 'https://example.com/mosaics/one/quads/two/thumb?api_key=' + key); + }); + + it('adds a stored API key to data URLs', function() { + var key = 'my-key'; + authStore.setKey(key); + + var augmented = util.augmentQuadLinks(quad); + assert.equal(augmented.properties.links.full, + 'https://example.com/mosaics/one/quads/two/full?api_key=' + key); + assert.equal(augmented.properties.links.thumbnail, + 'https://example.com/mosaics/one/quads/two/thumb?api_key=' + key); + }); + + }); + }); From 5a704bdf74aadc2b6e1a55947b7ac0a7be59ae48 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 16:51:17 -0600 Subject: [PATCH 04/16] Test util.augmentMosaicLinks() --- test/api/util.test.js | 64 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/test/api/util.test.js b/test/api/util.test.js index ebf4262..f7a799b 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -184,4 +184,68 @@ describe('util', function() { }); + describe('augmentMosaicLinks()', function() { + var mosaic; + + beforeEach(function() { + mosaic = util.assign({}, { + name: 'color_balance_mosaic', + links: { + quads: 'https://example.com/mosaics/one/quads/', + self: 'https://example.com/mosaics/one', + tiles: 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png', + quadmap: 'https://example.com/mosaics/one/quad-map.png' + }, + 'first_acquired': '2014-03-20T15:57:11+00:00', + datatype: 'byte', + 'quad_size': 4096, + title: 'A Mosaic', + 'coordinate_system': 'EPSG:3857', + geometry: { + type: 'Polygon', + coordinates: [ + [[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]] + ] + }, + 'last_acquired': '2015-07-20T02:11:31.947579+00:00', + 'scene_type': 'ortho', + 'quad_pattern': 'L{glevel:d}-{tilex:04d}E-{tiley:04d}N', + level: 15, + resolution: 4.77731426716 + }); + }); + + afterEach(function() { + authStore.clear(); + }); + + it('adds a API key from stored token to data URLs', function() { + // {api_key: 'my-api-key'} + var token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcGlfa2V5Ijoib' + + 'XktYXBpLWtleSJ9.sYcuJzdUThIsvJGNymbobOh-nY6ZKFEqXTqwZS-4QvE'; + authStore.setToken(token); + var key = authStore.getKey(); + + var augmented = util.augmentMosaicLinks(mosaic); + assert.equal(augmented.links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png?api_key=' + + key); + assert.equal(augmented.links.quadmap, + 'https://example.com/mosaics/one/quad-map.png?api_key=' + key); + }); + + it('adds a stored API key to data URLs', function() { + var key = 'my-key'; + authStore.setKey(key); + + var augmented = util.augmentMosaicLinks(mosaic); + assert.equal(augmented.links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png?api_key=' + + key); + assert.equal(augmented.links.quadmap, + 'https://example.com/mosaics/one/quad-map.png?api_key=' + key); + }); + + }); + }); From 606dd85b67acab303f842871aa2e3261b33f9f73 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 16:59:59 -0600 Subject: [PATCH 05/16] Test util.assign() --- test/api/util.test.js | 70 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/api/util.test.js b/test/api/util.test.js index f7a799b..b1b4af1 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -248,4 +248,74 @@ describe('util', function() { }); + describe('assign()', function() { + + it('assigns source properties to target object', function() { + var source = { + foo: 'bar' + }; + var target = { + num: 42 + }; + + util.assign(target, source); + assert.deepEqual(target, { + foo: 'bar', + num: 42 + }); + }); + + it('returns the target object', function() { + var target = {}; + + var got = util.assign(target, {foo: 'bar'}); + assert.equal(got, target); + }); + + it('overwrites target properties', function() { + var target = { + foo: 'bar' + }; + + util.assign(target, {foo: 'bam'}); + assert.equal(target.foo, 'bam'); + }); + + it('works with multiple sources', function() { + var target = { + foo: 'bar' + }; + var source1 = { + foo1: 'bar1' + }; + var source2 = { + foo2: 'bar2' + }; + + util.assign(target, source1, source2); + assert.deepEqual(target, { + foo: 'bar', + foo1: 'bar1', + foo2: 'bar2' + }); + }); + + it('prefers later sources, does not modify earlier ones', function() { + var target = { + foo: 'bar' + }; + var source1 = { + foo: 'bam' + }; + var source2 = { + foo: 'baz' + }; + + util.assign(target, source1, source2); + assert.deepEqual(target, {foo: 'baz'}); + assert.deepEqual(source1, {foo: 'bam'}); + }); + + }); + }); From 642b22f67d7e47eab621f34a5a747db757783905 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 17:03:17 -0600 Subject: [PATCH 06/16] More page tests --- test/api/page.test.js | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/api/page.test.js b/test/api/page.test.js index 6c1eaaf..bb956aa 100644 --- a/test/api/page.test.js +++ b/test/api/page.test.js @@ -75,4 +75,65 @@ describe('Page', function() { }); + describe('#prev()', function() { + + it('is only assigned if data includes prev link', function() { + var query = { + foo: 'bar', + num: '42' + }; + + var data = { + links: { + next: url.format({ + query: query + }) + } + }; + + var page = new Page(data, spy); + assert.isNull(page.prev); + }); + + it('calls the factory with a query from the prev url', function() { + var query = { + foo: 'bar', + num: '42' + }; + + var data = { + links: { + prev: url.format({ + query: query + }) + } + }; + + var page = new Page(data, spy); + assert.typeOf(page.prev, 'function'); + page.prev(); + assert.equal(spy.callCount, 1); + var call = spy.getCall(0); + assert.deepEqual(call.args[0], query); + }); + + it('passes along options as second arg to factory', function() { + var data = { + links: { + prev: 'http://example.com' + } + }; + + var page = new Page(data, spy); + assert.typeOf(page.prev, 'function'); + + var options = {}; + page.prev(options); + assert.equal(spy.callCount, 1); + var call = spy.getCall(0); + assert.equal(call.args[1], options); + }); + + }); + }); From 97e66524fb946cfe77956048ee579763f05239ac Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 17:10:52 -0600 Subject: [PATCH 07/16] More auth store tests --- test/api/auth-store.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/api/auth-store.test.js b/test/api/auth-store.test.js index c65ba98..c89bc66 100644 --- a/test/api/auth-store.test.js +++ b/test/api/auth-store.test.js @@ -23,6 +23,17 @@ describe('authStore', function() { assert.equal(authStore.getKey(), 'my-api-key'); }); + it('throws if the token does not contain an api_key claim', function() { + // {foo: 'bar'} + var bogus = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmb28iOiJiYXIifQ.' + + 'yPmf5QFV26W-3ldVCrsvRdnecy7QjA0fnCWCDLDZ-M4'; + + function call() { + authStore.setToken(bogus); + } + assert.throws(call, Error, 'Expected api_key in token payload'); + }); + }); describe('getToken()', function() { From 812007529433f15a4842b05ade5b0be8437cef9a Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 17:18:39 -0600 Subject: [PATCH 08/16] Additional tests for auth.login() --- test/api/auth.test.js | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/api/auth.test.js b/test/api/auth.test.js index 339d9f0..1bbf8ec 100644 --- a/test/api/auth.test.js +++ b/test/api/auth.test.js @@ -9,6 +9,7 @@ var sinon = require('sinon'); var auth = require('../../api/auth'); var authStore = require('../../api/auth-store'); +var errors = require('../../api/errors'); describe('auth', function() { @@ -89,6 +90,51 @@ describe('auth', function() { response.emit('end'); }); + it('rejects if body does not contain a token', function(done) { + var response = new stream.Readable(); + response.statusCode = 200; + var body = {foo: 'bar'}; + + var email = 'user@email.com'; + var password = 'psswd'; + auth.login(email, password).then(function(success) { + done(new Error('Expected rejection')); + }, function(err) { + assert.instanceOf(err, errors.UnexpectedResponse); + done(); + }); + + assert.equal(https.request.callCount, 1); + var args = https.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', JSON.stringify(body)); + response.emit('end'); + }); + + it('rejects if body contains a bogus token', function(done) { + var response = new stream.Readable(); + response.statusCode = 200; + var body = {token: 'bogus'}; + + var email = 'user@email.com'; + var password = 'psswd'; + auth.login(email, password).then(function(success) { + done(new Error('Expected rejection')); + }, function(err) { + assert.instanceOf(err, errors.UnexpectedResponse); + done(); + }); + + assert.equal(https.request.callCount, 1); + var args = https.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', JSON.stringify(body)); + response.emit('end'); + }); }); describe('setKey()', function() { From 6a81c14b2816034bf17b5d0822092a810f51d90b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 17:56:57 -0600 Subject: [PATCH 09/16] Additional request tests --- api/request.js | 2 +- test/api/request.test.js | 184 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 1 deletion(-) diff --git a/api/request.js b/api/request.js index fd0e89c..7de8311 100644 --- a/api/request.js +++ b/api/request.js @@ -129,7 +129,7 @@ function createResponseHandler(resolve, reject, info) { var body = null; var err = null; if (status === 401) { - err = new errors.Unauthorized('Unauthorized', response, body); + err = new errors.Unauthorized('Unauthorized', response, data); } else if (!(status >= 200 && status < 300)) { err = new errors.UnexpectedResponse('Unexpected response status: ' + status, response, data); diff --git a/test/api/request.test.js b/test/api/request.test.js index 028ce5b..e7ec590 100644 --- a/test/api/request.test.js +++ b/test/api/request.test.js @@ -11,6 +11,7 @@ var authStore = require('../../api/auth-store'); var assign = require('../../api/util').assign; var errors = require('../../api/errors'); var req = require('../../api/request'); +var util = require('../../api/util'); chai.config.truncateThreshold = 0; var assert = chai.assert; @@ -96,6 +97,40 @@ describe('request', function() { response.emit('end'); }); + it('follows location header on 302', function(done) { + var firstResponse = new stream.Readable(); + firstResponse.statusCode = 302; + firstResponse.headers = { + location: 'https://redirect.com' + }; + + var secondResponse = new stream.Readable(); + secondResponse.statusCode = 200; + var body = { + foo: 'bar' + }; + + var promise = request({ + url: 'https://example.com' + }); + promise.then(function(obj) { + assert.equal(obj.response, secondResponse); + assert.deepEqual(obj.body, body); + done(); + }, done); + + assert.equal(https.request.callCount, 1); + var firstCallback = https.request.getCall(0).args[1]; + firstCallback(firstResponse); + firstResponse.emit('end'); + + assert.equal(https.request.callCount, 2); + var secondCallback = https.request.getCall(1).args[1]; + secondCallback(secondResponse); + secondResponse.emit('data', JSON.stringify(body)); + secondResponse.emit('end'); + }); + it('resolves before parsing body if stream is true', function(done) { var response = new stream.Readable(); response.statusCode = 200; @@ -122,6 +157,32 @@ describe('request', function() { response.emit('end'); }); + it('rejects on non 2xx if stream is true', function(done) { + var response = new stream.Readable(); + response.statusCode = 502; + var body = 'too much request'; + + var promise = request({ + url: 'http://example.com', + stream: true + }); + promise.then(function(obj) { + done(new Error('Expected rejection')); + }, function(err) { + assert.instanceOf(err, errors.UnexpectedResponse); + assert.include(err.message, 'Unexpected response status: 502'); + done(); + }); + + assert.equal(http.request.callCount, 1); + var args = http.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', JSON.stringify(body)); + response.emit('end'); + }); + it('rejects for invalid JSON in successful response', function(done) { var response = new stream.Readable(); response.statusCode = 200; @@ -146,6 +207,54 @@ describe('request', function() { response.emit('end'); }); + it('rejects for non 2xx response', function(done) { + var response = new stream.Readable(); + response.statusCode = 500; + var body = 'server error'; + + var promise = request({url: 'http://example.com'}); + promise.then(function(obj) { + done(new Error('Expected promise to be rejected')); + }, function(err) { + assert.instanceOf(err, errors.UnexpectedResponse); + assert.include(err.message, 'Unexpected response status: 500'); + assert.equal(err.body, body); + done(); + }).catch(done); + + assert.equal(http.request.callCount, 1); + var args = http.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', body); + response.emit('end'); + }); + + it('rejects with Unauthorized for 401', function(done) { + var response = new stream.Readable(); + response.statusCode = 401; + var body = 'unauthorized'; + + var promise = request({url: 'http://example.com'}); + promise.then(function(obj) { + done(new Error('Expected promise to be rejected')); + }, function(err) { + assert.instanceOf(err, errors.Unauthorized); + assert.include(err.message, 'Unauthorized'); + assert.equal(err.body, body); + done(); + }).catch(done); + + assert.equal(http.request.callCount, 1); + var args = http.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', body); + response.emit('end'); + }); + it('accepts a terminator for aborting requests', function(done) { var promise = request({ url: 'http//example.com', @@ -162,6 +271,62 @@ describe('request', function() { }); }); + it('calls request.xhr.abort() if request.abort is absent', function(done) { + var promise = request({ + url: 'http//example.com', + terminator: function(abort) { + setTimeout(abort, 10); + } + }); + + delete mockRequest.abort; + mockRequest.xhr = { + abort: sinon.spy() + }; + + promise.then(function() { + done(new Error('Expected promise to be rejected')); + }).catch(function(err) { + assert.instanceOf(err, errors.AbortedRequest); + assert.equal(mockRequest.xhr.abort.callCount, 1); + done(); + }); + }); + + it('allows termination on partial response', function(done) { + var response = new stream.Readable(); + response.statusCode = 200; + var body = 'partial body'; + + var promise = request({ + url: 'http//example.com', + terminator: function(abort) { + setTimeout(abort, 10); + } + }); + + var rejected = false; + promise.then(function() { + done(new Error('Expected promise to be rejected')); + }).catch(function(err) { + rejected = true; + assert.instanceOf(err, errors.AbortedRequest); + assert.equal(mockRequest.abort.callCount, 1); + }); + + assert.equal(http.request.callCount, 1); + var args = http.request.getCall(0).args; + assert.lengthOf(args, 2); + var callback = args[1]; + callback(response); + response.emit('data', body); + setTimeout(function() { + response.emit('end'); + assert.equal(rejected, true); + done(); + }, 20); + }); + }); describe('get()', function() { @@ -210,6 +375,25 @@ describe('request', function() { assert.deepEqual(parseConfig(config), options); }); + it('adds user provided headers', function() { + var config = { + url: 'http://example.com', + headers: { + foo: 'bar' + } + }; + var options = { + protocol: 'http:', + hostname: 'example.com', + port: '80', + method: 'GET', + path: '/', + headers: util.assign({}, defaultHeaders, config.headers) + }; + + assert.deepEqual(parseConfig(config), options); + }); + it('uses the correct default port for https', function() { var config = { url: 'https://example.com' From 5f5d5a062b694873b91b36fad8e91227eb49fcde Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 19:05:24 -0600 Subject: [PATCH 10/16] Test scenes requests --- test/api/auth.test.js | 3 - test/api/scenes.test.js | 245 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 test/api/scenes.test.js diff --git a/test/api/auth.test.js b/test/api/auth.test.js index 1bbf8ec..fefad4b 100644 --- a/test/api/auth.test.js +++ b/test/api/auth.test.js @@ -40,9 +40,6 @@ describe('auth', function() { https.request = httpsRequest; mockRequest = null; authStore.clear(); - }); - - afterEach(function() { auth.logout(); }); diff --git a/test/api/scenes.test.js b/test/api/scenes.test.js new file mode 100644 index 0000000..f544dbb --- /dev/null +++ b/test/api/scenes.test.js @@ -0,0 +1,245 @@ +/* eslint-env mocha */ + +var assert = require('chai').assert; + +var Page = require('../../api/page'); +var auth = require('../../api/auth'); +var request = require('../../api/request'); +var scenes = require('../../api/scenes'); +var urls = require('../../api/urls'); +var util = require('../../api/util'); + +describe('scenes', function() { + + var get = request.get; + afterEach(function() { + request.get = get; + auth.logout(); + }); + + describe('get()', function() { + + var scene; + beforeEach(function() { + scene = util.assign({}, { + properties: { + links: { + 'full': 'http://example.com/#hash', + 'square_thumbnail': 'http://example.com/?foo=bar', + 'thumbnail': 'http://example.com/thumb' + }, + data: { + products: { + foo: { + bar: 'http://example.com/foo/bar' + } + } + } + } + }); + }); + + it('requests a scene by type and id', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: scene + }); + }; + + var promise = scenes.get({type: 'foo', id: 'bar'}); + assert.lengthOf(calls, 1); + var arg = calls[0]; + assert.equal(arg.url, urls.join(urls.SCENES, 'foo', 'bar')); + + promise.then(function(got) { + assert.deepEqual(got, scene); + done(); + }).catch(done); + }); + + it('defaults to ortho if only id is provided', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: scene + }); + }; + + var promise = scenes.get('bar'); + assert.equal(calls[0].url, urls.join(urls.SCENES, 'ortho', 'bar')); + + promise.then(function(got) { + assert.deepEqual(got, scene); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: scene + }); + }; + + var promise = scenes.get('bar'); + + promise.then(function(got) { + assert.equal(got.properties.links.full, + 'http://example.com/?api_key=my-key#hash'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: scene + }); + }; + + var promise = scenes.get('bar', {augmentLinks: false}); + + promise.then(function(got) { + assert.equal(got.properties.links.full, + 'http://example.com/#hash'); + done(); + }).catch(done); + }); + + }); + + describe('search()', function() { + + var scene; + beforeEach(function() { + scene = util.assign({}, { + properties: { + links: { + 'full': 'http://example.com/#hash', + 'square_thumbnail': 'http://example.com/?foo=bar', + 'thumbnail': 'http://example.com/thumb' + }, + data: { + products: { + foo: { + bar: 'http://example.com/foo/bar' + } + } + } + } + }); + }); + + it('queries the scenes collection', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + var query = { + 'acquired.lte': new Date().toISOString(), + type: 'landsat' + }; + + var promise = scenes.search(query); + + var arg = calls[0]; + assert.equal(arg.url, urls.join(urls.SCENES, 'landsat', '')); + assert.deepEqual(arg.query, query); + + promise.then(function(got) { + assert.instanceOf(got, Page); + assert.lengthOf(got.data.features, 1); + assert.deepEqual(got.data.features[0], scene); + done(); + }).catch(done); + }); + + it('defaults to ortho if no type provided', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + var query = { + 'acquired.lte': new Date().toISOString() + }; + + var promise = scenes.search(query); + + var arg = calls[0]; + assert.equal(arg.url, urls.join(urls.SCENES, 'ortho', '')); + assert.deepEqual(arg.query, query); + + promise.then(function(got) { + assert.instanceOf(got, Page); + assert.lengthOf(got.data.features, 1); + assert.deepEqual(got.data.features[0], scene); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + scenes.search({foo: 'bar'}).then(function(got) { + assert.equal(got.data.features[0].properties.links.full, + 'http://example.com/?api_key=my-key#hash'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + scenes.search({foo: 'bar'}, {augmentLinks: false}).then(function(got) { + assert.equal(got.data.features[0].properties.links.full, + 'http://example.com/#hash'); + done(); + }).catch(done); + }); + + }); + +}); From 8a31eee023c462dd5f4c8f589bb2c2dd80f28fc5 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 22:24:58 -0600 Subject: [PATCH 11/16] Additional tests for find-scenes --- cli/find-scenes.js | 3 + test/cli/find-scenes.test.js | 206 +++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) diff --git a/cli/find-scenes.js b/cli/find-scenes.js index 2872d4e..759ee18 100644 --- a/cli/find-scenes.js +++ b/cli/find-scenes.js @@ -205,5 +205,8 @@ exports.description = 'Find scenes'; exports.main = main; exports.options = options; +exports.fetch = fetch; exports.parseAcquired = parseAcquired; exports.parseWhere = parseWhere; +exports.resolveIntersects = resolveIntersects; +exports.resolveQuery = resolveQuery; diff --git a/test/cli/find-scenes.test.js b/test/cli/find-scenes.test.js index cce82c4..bbd28bb 100644 --- a/test/cli/find-scenes.test.js +++ b/test/cli/find-scenes.test.js @@ -1,10 +1,91 @@ /* eslint-env mocha */ var assert = require('chai').assert; +var Page = require('../../api/page'); var findScenes = require('../../cli/find-scenes'); +var scenes = require('../../api/scenes'); +var util = require('../../cli/util'); describe('cli/find-scenes', function() { + describe('fetch()', function() { + + var numPages = 10; + var fetched = 0; + + function makePage() { + ++fetched; + var more = fetched < numPages; + var data = { + features: ['first feature', 'second feature'], + links: { + next: more ? 'http://example.com/more' : null + } + }; + return new Page(data, factory); + } + + function factory() { + return Promise.resolve(makePage()); + } + + beforeEach(function() { + fetched = 0; + }); + + it('concatenates pages of features', function(done) { + var promise = Promise.resolve(makePage()); + + findScenes.fetch(promise, [], 100).then(function(features) { + assert.lengthOf(features, 20); + done(); + }).catch(done); + }); + + it('stops when the limit is reached', function(done) { + var promise = Promise.resolve(makePage()); + + findScenes.fetch(promise, [], 11).then(function(features) { + assert.lengthOf(features, 11); + done(); + }).catch(done); + }); + + }); + + describe('main()', function() { + + var search = scenes.search; + afterEach(function() { + scenes.search = search; + }); + + it('calls scenes.search() with a query', function(done) { + var calls = []; + var features = []; + + scenes.search = function() { + calls.push(arguments); + var data = { + features: features, + links: {} + }; + var page = new Page(data, scenes.search); + return Promise.resolve(page); + }; + + var opts = { + type: 'landsat', + limit: 250 + }; + findScenes.main(opts).then(function(str) { + assert.typeOf(str, 'string'); + done(); + }).catch(done); + }); + + }); + describe('parseWhere()', function() { var parse = findScenes.parseWhere; @@ -125,4 +206,129 @@ describe('cli/find-scenes', function() { }); + describe('resolveIntersects()', function() { + + var orig = {}; + beforeEach(function() { + for (var key in util) { + orig[key] = util[key]; + } + }); + + afterEach(function() { + for (var key in orig) { + util[key] = orig[key]; + } + orig = {}; + }); + + var resolveIntersects = findScenes.resolveIntersects; + + it('resolves to null for falsey values', function(done) { + resolveIntersects('').then(function(val) { + assert.isNull(val); + done(); + }).catch(done); + }); + + it('resolves stdin for @-', function(done) { + util.stdin = function() { + return Promise.resolve('read stdin'); + }; + + resolveIntersects('@-').then(function(val) { + assert.equal(val, 'read stdin'); + done(); + }).catch(done); + }); + + it('resolves stdin for @-', function(done) { + util.stdin = function() { + return Promise.resolve('read stdin'); + }; + + resolveIntersects('@-').then(function(val) { + assert.equal(val, 'read stdin'); + done(); + }).catch(done); + }); + + it('reads a file for other @', function(done) { + util.readFile = function(name) { + return Promise.resolve('read ' + name); + }; + + resolveIntersects('@foo.txt').then(function(val) { + assert.equal(val, 'read foo.txt'); + done(); + }).catch(done); + }); + + it('resolves to the value for all other', function(done) { + resolveIntersects('POINT(1 1)').then(function(val) { + assert.equal(val, 'POINT(1 1)'); + done(); + }).catch(done); + }); + + }); + + describe('resolveQuery()', function() { + + var resolveQuery = findScenes.resolveQuery; + + it('resolves to a query given command options', function(done) { + var opts = { + intersects: 'POINT(1 1)', + type: 'landsat', + limit: 300 + }; + + resolveQuery(opts).then(function(query) { + assert.deepEqual(query, { + intersects: 'POINT(1 1)', + type: 'landsat', + count: 300 + }); + done(); + }).catch(done); + }); + + it('generates a query given acquired option', function(done) { + var opts = { + acquired: '2000..', + type: 'ortho', + limit: 250 + }; + + resolveQuery(opts).then(function(query) { + assert.deepEqual(query, { + 'acquired.gte': '2000-01-01T00:00:00.000Z', + type: 'ortho', + count: 250 + }); + done(); + }).catch(done); + }); + + it('generates a query given where options', function(done) { + var opts = { + where: ['acquired.gt=2000', 'gsd.gt=10'], + type: 'ortho', + limit: 250 + }; + + resolveQuery(opts).then(function(query) { + assert.deepEqual(query, { + 'acquired.gt': '2000', + 'gsd.gt': '10', + type: 'ortho', + count: 250 + }); + done(); + }).catch(done); + }); + + }); + }); From 2006618c69ec9ab177857e451f2f484bc79aaf79 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 22:25:19 -0600 Subject: [PATCH 12/16] Tests for cli/util --- test/cli/util.test.js | 145 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 test/cli/util.test.js diff --git a/test/cli/util.test.js b/test/cli/util.test.js new file mode 100644 index 0000000..b7726e0 --- /dev/null +++ b/test/cli/util.test.js @@ -0,0 +1,145 @@ +/* eslint-env mocha */ +var EventEmitter = require('events').EventEmitter; +var fs = require('fs'); + +var assert = require('chai').assert; + +var util = require('../../cli/util'); + +describe('cli/util', function() { + + describe('choicesHelp()', function() { + + var choicesHelp = util.choicesHelp; + + it('concatenates choices', function() { + assert.equal(choicesHelp(['foo', 'bar']), ' (foo or bar)'); + }); + + it('adds commas', function() { + assert.equal(choicesHelp(['foo', 'bar', 'bam']), ' (foo, bar, or bam)'); + }); + + }); + + describe('readFile()', function() { + + var orig = {}; + beforeEach(function() { + for (var key in fs) { + orig[key] = fs[key]; + } + }); + + afterEach(function() { + for (var key in orig) { + fs[key] = orig[key]; + } + orig = {}; + }); + + it('calls fs.readFile()', function(done) { + fs.readFile = function(name, options, callback) { + callback(null, 'called fs.readFile'); + }; + + util.readFile('foo.txt').then(function(text) { + assert.equal(text, 'called fs.readFile'); + done(); + }).catch(done); + }); + + it('rejects on ENOENT', function(done) { + var error = new Error('not found'); + error.code = 'ENOENT'; + + fs.readFile = function(name, options, callback) { + callback(error); + }; + + util.readFile('foo.txt').then(function(text) { + done(new Error('Expected rejection')); + }).catch(function(err) { + assert.instanceOf(err, Error); + assert.include(err.message, 'File not found'); + done(); + }).catch(done); + }); + + it('rejects on EACCES', function(done) { + var error = new Error('permission'); + error.code = 'EACCES'; + + fs.readFile = function(name, options, callback) { + callback(error); + }; + + util.readFile('foo.txt').then(function(text) { + done(new Error('Expected rejection')); + }).catch(function(err) { + assert.instanceOf(err, Error); + assert.include(err.message, 'Permission denied'); + done(); + }).catch(done); + }); + + it('rejects on EISDIR', function(done) { + var error = new Error('directory'); + error.code = 'EISDIR'; + + fs.readFile = function(name, options, callback) { + callback(error); + }; + + util.readFile('foo.txt').then(function(text) { + done(new Error('Expected rejection')); + }).catch(function(err) { + assert.instanceOf(err, Error); + assert.include(err.message, 'Got a directory instead of a file'); + done(); + }).catch(done); + }); + + it('rejects on any error', function(done) { + var error = new Error('any error'); + + fs.readFile = function(name, options, callback) { + callback(error); + }; + + util.readFile('foo.txt').then(function(text) { + done(new Error('Expected rejection')); + }).catch(function(err) { + assert.instanceOf(err, Error); + assert.include(err.message, 'any error'); + done(); + }).catch(done); + }); + + }); + + describe('stdin()', function() { + + var stdin = process.stdin; + afterEach(function() { + process.stdin = stdin; + }); + + it('resolves to text read from stdin', function(done) { + process.stdin = new EventEmitter(); + process.stdin.read = function() { + return 'read from stdin'; + }; + + util.stdin().then(function(text) { + assert.equal(text, 'read from stdin'); + done(); + }).catch(done); + + process.stdin.emit('readable'); + process.stdin.emit('end'); + }); + + }); + +}); From 0956ade8807081fda2d3927809630dd7f0e2ea1b Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 22:29:41 -0600 Subject: [PATCH 13/16] Catch all rejected promises --- test/api/auth.test.js | 6 +++--- test/api/request.test.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/api/auth.test.js b/test/api/auth.test.js index fefad4b..bf5477e 100644 --- a/test/api/auth.test.js +++ b/test/api/auth.test.js @@ -76,7 +76,7 @@ describe('auth', function() { assert.equal(authStore.getToken(), token); assert.equal(authStore.getKey(), 'my-api-key'); done(); - }, done); + }).catch(done); assert.equal(https.request.callCount, 1); var args = https.request.getCall(0).args; @@ -99,7 +99,7 @@ describe('auth', function() { }, function(err) { assert.instanceOf(err, errors.UnexpectedResponse); done(); - }); + }).catch(done); assert.equal(https.request.callCount, 1); var args = https.request.getCall(0).args; @@ -122,7 +122,7 @@ describe('auth', function() { }, function(err) { assert.instanceOf(err, errors.UnexpectedResponse); done(); - }); + }).catch(done); assert.equal(https.request.callCount, 1); var args = https.request.getCall(0).args; diff --git a/test/api/request.test.js b/test/api/request.test.js index e7ec590..153f46f 100644 --- a/test/api/request.test.js +++ b/test/api/request.test.js @@ -86,7 +86,7 @@ describe('request', function() { assert.equal(obj.response, response); assert.deepEqual(obj.body, body); done(); - }, done); + }).catch(done); assert.equal(http.request.callCount, 1); var args = http.request.getCall(0).args; @@ -117,7 +117,7 @@ describe('request', function() { assert.equal(obj.response, secondResponse); assert.deepEqual(obj.body, body); done(); - }, done); + }).catch(done); assert.equal(https.request.callCount, 1); var firstCallback = https.request.getCall(0).args[1]; @@ -146,7 +146,7 @@ describe('request', function() { assert.equal(obj.response, response); assert.isNull(obj.body); done(); - }, done); + }).catch(done); assert.equal(http.request.callCount, 1); var args = http.request.getCall(0).args; @@ -172,7 +172,7 @@ describe('request', function() { assert.instanceOf(err, errors.UnexpectedResponse); assert.include(err.message, 'Unexpected response status: 502'); done(); - }); + }).catch(done); assert.equal(http.request.callCount, 1); var args = http.request.getCall(0).args; From 5bdbcfb4fdbee4d1dd348e56ae47adb8e0313dc2 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 22:31:50 -0600 Subject: [PATCH 14/16] Descriptive names --- test/api/auth-store.test.js | 2 +- test/api/auth.test.js | 2 +- test/api/errors.test.js | 2 +- test/api/page.test.js | 2 +- test/api/request.test.js | 2 +- test/api/scenes.test.js | 2 +- test/api/urls.test.js | 2 +- test/api/util.test.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/api/auth-store.test.js b/test/api/auth-store.test.js index c89bc66..2721401 100644 --- a/test/api/auth-store.test.js +++ b/test/api/auth-store.test.js @@ -3,7 +3,7 @@ var assert = require('chai').assert; var authStore = require('../../api/auth-store'); -describe('authStore', function() { +describe('api/auth-store', function() { afterEach(function() { authStore.clear(); }); diff --git a/test/api/auth.test.js b/test/api/auth.test.js index bf5477e..a221ef0 100644 --- a/test/api/auth.test.js +++ b/test/api/auth.test.js @@ -11,7 +11,7 @@ var auth = require('../../api/auth'); var authStore = require('../../api/auth-store'); var errors = require('../../api/errors'); -describe('auth', function() { +describe('api/auth', function() { var httpRequest = http.request; var httpsRequest = https.request; diff --git a/test/api/errors.test.js b/test/api/errors.test.js index 75d9f0f..fa16e0d 100644 --- a/test/api/errors.test.js +++ b/test/api/errors.test.js @@ -3,7 +3,7 @@ var assert = require('chai').assert; var errors = require('../../api/errors'); -describe('errors', function() { +describe('api/errors', function() { describe('ResponseError', function() { it('is a generic response error', function() { diff --git a/test/api/page.test.js b/test/api/page.test.js index bb956aa..83a228e 100644 --- a/test/api/page.test.js +++ b/test/api/page.test.js @@ -6,7 +6,7 @@ var sinon = require('sinon'); var Page = require('../../api/page'); -describe('Page', function() { +describe('api/page', function() { var spy; beforeEach(function() { diff --git a/test/api/request.test.js b/test/api/request.test.js index 153f46f..7738473 100644 --- a/test/api/request.test.js +++ b/test/api/request.test.js @@ -16,7 +16,7 @@ var util = require('../../api/util'); chai.config.truncateThreshold = 0; var assert = chai.assert; -describe('request', function() { +describe('api/request', function() { var httpRequest = http.request; var httpsRequest = https.request; diff --git a/test/api/scenes.test.js b/test/api/scenes.test.js index f544dbb..d529e39 100644 --- a/test/api/scenes.test.js +++ b/test/api/scenes.test.js @@ -9,7 +9,7 @@ var scenes = require('../../api/scenes'); var urls = require('../../api/urls'); var util = require('../../api/util'); -describe('scenes', function() { +describe('api/scenes', function() { var get = request.get; afterEach(function() { diff --git a/test/api/urls.test.js b/test/api/urls.test.js index 7042e69..4fdb8ad 100644 --- a/test/api/urls.test.js +++ b/test/api/urls.test.js @@ -4,7 +4,7 @@ var assert = require('chai').assert; var urls = require('../../api/urls'); -describe('urls', function() { +describe('api/urls', function() { describe('join()', function() { diff --git a/test/api/util.test.js b/test/api/util.test.js index b1b4af1..8c0041d 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -4,7 +4,7 @@ var assert = require('chai').assert; var authStore = require('../../api/auth-store'); var util = require('../../api/util'); -describe('util', function() { +describe('api/util', function() { describe('addQueryParams()', function() { From dc8e63a9b6afe569533634af6277e28a14bee029 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 22:47:50 -0600 Subject: [PATCH 15/16] Test mosaic requests --- test/api/mosaics.test.js | 185 +++++++++++++++++++++++++++++++++++++++ test/api/util.test.js | 2 +- 2 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 test/api/mosaics.test.js diff --git a/test/api/mosaics.test.js b/test/api/mosaics.test.js new file mode 100644 index 0000000..1a44f07 --- /dev/null +++ b/test/api/mosaics.test.js @@ -0,0 +1,185 @@ +/* eslint-env mocha */ + +var assert = require('chai').assert; + +var Page = require('../../api/page'); +var auth = require('../../api/auth'); +var request = require('../../api/request'); +var mosaics = require('../../api/mosaics'); +var urls = require('../../api/urls'); +var util = require('../../api/util'); + +describe('api/mosaics', function() { + + var mosaic; + beforeEach(function() { + mosaic = util.assign({}, { + name: 'one', + links: { + quads: 'https://example.com/mosaics/one/quads/', + self: 'https://example.com/mosaics/one', + tiles: 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png', + quadmap: 'https://example.com/mosaics/one/quad-map.png' + }, + 'first_acquired': '2014-03-20T15:57:11+00:00', + datatype: 'byte', + 'quad_size': 4096, + title: 'A Mosaic', + 'coordinate_system': 'EPSG:3857', + geometry: { + type: 'Polygon', + coordinates: [ + [[-180, -90], [-180, 90], [180, 90], [180, -90], [-180, -90]] + ] + }, + 'last_acquired': '2015-07-20T02:11:31.947579+00:00', + 'scene_type': 'ortho', + 'quad_pattern': 'L{glevel:d}-{tilex:04d}E-{tiley:04d}N', + level: 15, + resolution: 4.77731426716 + }); + }); + + var get = request.get; + afterEach(function() { + request.get = get; + auth.logout(); + }); + + describe('get()', function() { + + it('requests a mosiac by id', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: mosaic + }); + }; + + var promise = mosaics.get('one'); + assert.lengthOf(calls, 1); + var arg = calls[0]; + assert.equal(arg.url, urls.join(urls.MOSAICS, 'one')); + + promise.then(function(got) { + assert.deepEqual(got, mosaic); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: mosaic + }); + }; + + var promise = mosaics.get('one'); + + promise.then(function(got) { + assert.equal(got.links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png' + + '?api_key=my-key'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: mosaic + }); + }; + + var promise = mosaics.get('one', {augmentLinks: false}); + + promise.then(function(got) { + assert.equal(got.links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png'); + done(); + }).catch(done); + }); + + }); + + describe('search()', function() { + + it('queries the mosaics collection', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: { + mosaics: [mosaic], + links: {} + } + }); + }; + + var query = { + count: 1 + }; + + var promise = mosaics.search(query); + + var arg = calls[0]; + assert.equal(arg.url, urls.MOSAICS); + assert.deepEqual(arg.query, query); + + promise.then(function(got) { + assert.instanceOf(got, Page); + assert.lengthOf(got.data.mosaics, 1); + assert.deepEqual(got.data.mosaics[0], mosaic); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + mosaics: [mosaic], + links: {} + } + }); + }; + + mosaics.search({}).then(function(got) { + assert.equal(got.data.mosaics[0].links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png?' + + 'api_key=my-key'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + mosaics: [mosaic], + links: {} + } + }); + }; + + mosaics.search({}, {augmentLinks: false}).then(function(got) { + assert.equal(got.data.mosaics[0].links.tiles, + 'https://s{0-3}.example.com/v0/mosaics/one/{z}/{x}/{y}.png'); + done(); + }).catch(done); + }); + + }); + +}); diff --git a/test/api/util.test.js b/test/api/util.test.js index 8c0041d..bc4b853 100644 --- a/test/api/util.test.js +++ b/test/api/util.test.js @@ -189,7 +189,7 @@ describe('api/util', function() { beforeEach(function() { mosaic = util.assign({}, { - name: 'color_balance_mosaic', + name: 'one', links: { quads: 'https://example.com/mosaics/one/quads/', self: 'https://example.com/mosaics/one', From 34335feabf8927c9b59caa096e5ca8aae1cc015f Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Tue, 4 Aug 2015 23:08:57 -0600 Subject: [PATCH 16/16] Test quads requests --- test/api/quads.test.js | 279 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 test/api/quads.test.js diff --git a/test/api/quads.test.js b/test/api/quads.test.js new file mode 100644 index 0000000..870c0b8 --- /dev/null +++ b/test/api/quads.test.js @@ -0,0 +1,279 @@ +/* eslint-env mocha */ + +var assert = require('chai').assert; + +var Page = require('../../api/page'); +var auth = require('../../api/auth'); +var request = require('../../api/request'); +var quads = require('../../api/quads'); +var urls = require('../../api/urls'); +var util = require('../../api/util'); + +describe('api/mosaics', function() { + + var quad; + + beforeEach(function() { + quad = util.assign({}, { + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-78.925781239, 39.0959629318], + [-78.925781239, 38.9594087879], + [-78.749999989, 38.9594087879], + [-78.749999989, 39.0959629318], + [-78.925781239, 39.0959629318] + ] + ] + }, + type: 'Feature', + id: 'L15-0575E-1265N', + properties: { + updated: '2015-07-20T13:39:49.550576+00:00', + 'num_input_scenes': 28, + links: { + self: 'https://example.com/mosaics/one/quads/two', + full: 'https://example.com/mosaics/one/quads/two/full', + thumbnail: 'https://example.com/mosaics/one/quads/two/thumb', + mosaic: 'https://example.com/mosaics/one', + scenes: 'https://example.com/mosaics/one/quads/two/scenes/' + }, + 'percent_covered': 100 + } + }); + }); + + var get = request.get; + afterEach(function() { + request.get = get; + auth.logout(); + }); + + describe('get()', function() { + + it('gets a mosaic quad', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: quad + }); + }; + + var promise = quads.get('my-mosaic', 'my-quad'); + assert.lengthOf(calls, 1); + var arg = calls[0]; + assert.equal(arg.url, + urls.join(urls.MOSAICS, 'my-mosaic', 'quads', 'my-quad')); + + promise.then(function(got) { + assert.deepEqual(got, quad); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: quad + }); + }; + + var promise = quads.get('my-mosaic', 'my-quad'); + + promise.then(function(got) { + assert.equal(got.properties.links.full, + 'https://example.com/mosaics/one/quads/two/full' + + '?api_key=my-key'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: quad + }); + }; + + var promise = quads.get('my-mosaic', 'my-quad', {augmentLinks: false}); + + promise.then(function(got) { + assert.equal(got.properties.links.full, + 'https://example.com/mosaics/one/quads/two/full'); + done(); + }).catch(done); + }); + + }); + + describe('search()', function() { + + it('queries a mosaic quads collection', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: { + features: [quad], + links: {} + } + }); + }; + + var query = { + count: 1 + }; + + var promise = quads.search('my-mosaic', query); + + var arg = calls[0]; + assert.equal(arg.url, urls.join(urls.MOSAICS, 'my-mosaic', 'quads', '')); + assert.deepEqual(arg.query, query); + + promise.then(function(got) { + assert.instanceOf(got, Page); + assert.lengthOf(got.data.features, 1); + assert.deepEqual(got.data.features[0], quad); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [quad], + links: {} + } + }); + }; + + quads.search('my-mosaic', {}).then(function(got) { + assert.equal(got.data.features[0].properties.links.full, + 'https://example.com/mosaics/one/quads/two/full' + + '?api_key=my-key'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [quad], + links: {} + } + }); + }; + + quads.search('my-mosaic', {}, {augmentLinks: false}).then(function(got) { + assert.equal(got.data.features[0].properties.links.full, + 'https://example.com/mosaics/one/quads/two/full'); + done(); + }).catch(done); + }); + + }); + + describe('scenes()', function() { + + var scene; + beforeEach(function() { + scene = util.assign({}, { + properties: { + links: { + 'full': 'http://example.com/#hash', + 'square_thumbnail': 'http://example.com/?foo=bar', + 'thumbnail': 'http://example.com/thumb' + }, + data: { + products: { + foo: { + bar: 'http://example.com/foo/bar' + } + } + } + } + }); + }); + + it('gets scenes that make up a quad', function(done) { + var calls = []; + + request.get = function(config) { + calls.push(config); + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + var promise = quads.scenes('my-mosaic', 'my-quad'); + + var arg = calls[0]; + assert.equal(arg.url, urls.join( + urls.MOSAICS, 'my-mosaic', 'quads', 'my-quad', 'scenes', '')); + + promise.then(function(got) { + assert.lengthOf(got.features, 1); + assert.deepEqual(got.features[0], scene); + done(); + }).catch(done); + }); + + it('augments links if key is set', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + quads.scenes('my-mosaic', 'my-quad').then(function(got) { + assert.equal(got.features[0].properties.links.full, + 'http://example.com/?api_key=my-key#hash'); + done(); + }).catch(done); + }); + + it('can be told not to augment links', function(done) { + auth.setKey('my-key'); + + request.get = function(config) { + return Promise.resolve({ + body: { + features: [scene], + links: {} + } + }); + }; + + quads.scenes('my-mosaic', 'my-quad', {augmentLinks: false}) + .then(function(got) { + assert.equal(got.features[0].properties.links.full, + 'http://example.com/#hash'); + done(); + }).catch(done); + }); + + }); + +});