diff --git a/ui-v2/app/models/coordinate.js b/ui-v2/app/models/coordinate.js index 7d82dcb05e..7449c6c871 100644 --- a/ui-v2/app/models/coordinate.js +++ b/ui-v2/app/models/coordinate.js @@ -9,4 +9,5 @@ export default Model.extend({ [SLUG_KEY]: attr('string'), Coord: attr(), Segment: attr('string'), + Datacenter: attr('string'), }); diff --git a/ui-v2/app/models/dc.js b/ui-v2/app/models/dc.js index d48bc182b0..f7a44df54a 100644 --- a/ui-v2/app/models/dc.js +++ b/ui-v2/app/models/dc.js @@ -9,6 +9,7 @@ export const SLUG_KEY = 'Name'; export default Model.extend({ [PRIMARY_KEY]: attr('string'), [SLUG_KEY]: attr('string'), + // TODO: Are these required? Services: hasMany('service'), Nodes: hasMany('node'), }); diff --git a/ui-v2/app/models/intention.js b/ui-v2/app/models/intention.js index 2937ff2248..8e5b01515d 100644 --- a/ui-v2/app/models/intention.js +++ b/ui-v2/app/models/intention.js @@ -11,6 +11,7 @@ const model = Model.extend({ SourceNS: attr('string'), SourceName: attr('string'), DestinationName: attr('string'), + DestinationNS: attr('string'), Precedence: attr('number'), SourceType: attr('string', { defaultValue: 'consul' }), Action: attr('string', { defaultValue: 'deny' }), diff --git a/ui-v2/app/models/kv.js b/ui-v2/app/models/kv.js index 313cb587a3..f1f9d28c04 100644 --- a/ui-v2/app/models/kv.js +++ b/ui-v2/app/models/kv.js @@ -18,8 +18,8 @@ export default Model.extend({ // preferably removeNull would be done in this layer also as if a property is `null` // default Values don't kick in, which also explains `Tags` elsewhere Value: attr('string'), //, {defaultValue: function() {return '';}} - CreateIndex: attr('string'), - ModifyIndex: attr('string'), + CreateIndex: attr('number'), + ModifyIndex: attr('number'), Session: attr('string'), Datacenter: attr('string'), diff --git a/ui-v2/app/services/session.js b/ui-v2/app/services/session.js index 7f28439911..9260bd003f 100644 --- a/ui-v2/app/services/session.js +++ b/ui-v2/app/services/session.js @@ -9,6 +9,7 @@ export default Service.extend({ dc: dc, }); }, + // TODO: Why Key? Probably should be findBySlug like the others findByKey: function(slug, dc) { return get(this, 'store').queryRecord('session', { id: slug, diff --git a/ui-v2/tests/helpers/api.js b/ui-v2/tests/helpers/api.js index cf41fb80cd..af0d20f5df 100644 --- a/ui-v2/tests/helpers/api.js +++ b/ui-v2/tests/helpers/api.js @@ -27,6 +27,7 @@ export const get = function(_url, options = { headers: { cookie: {} } }) { }, {}), }, { + set: function() {}, send: function(content) { resolve(JSON.parse(content)); }, diff --git a/ui-v2/tests/helpers/measure.js b/ui-v2/tests/helpers/measure.js new file mode 100644 index 0000000000..0ba4f07694 --- /dev/null +++ b/ui-v2/tests/helpers/measure.js @@ -0,0 +1,5 @@ +/* eslint no-console: "off" */ +import getMeasure from 'consul-ui/tests/lib/measure/getMeasure'; +let report; +let len = 1; +export default getMeasure(len, report); diff --git a/ui-v2/tests/helpers/repo.js b/ui-v2/tests/helpers/repo.js new file mode 100644 index 0000000000..02ca51b804 --- /dev/null +++ b/ui-v2/tests/helpers/repo.js @@ -0,0 +1,100 @@ +import { get as httpGet } from 'consul-ui/tests/helpers/api'; +import { get } from '@ember/object'; +import measure from 'consul-ui/tests/helpers/measure'; + +/** Stub an ember-data adapter response using the private method + * + * Allows you to easily specify a HTTP response for the Adapter. The stub only works + * during the 'lifetime' of `cb` and is reset to normal unstubbed functionality afterwards. + * + * Please Note: This overwrites a private ember-data Adapter method, please understand + * the consequences of doing this if you are using it + * + * @param {function} cb - The callback, or test case, to run using the stubbed response + * @param {object} payload - The payload to use as the response + * @param {DS.Adapter} adapter - An instance of an ember-data Adapter + */ +const stubAdapterResponse = function(cb, payload, adapter) { + const payloadClone = JSON.parse(JSON.stringify(payload)); + const ajax = adapter._ajaxRequest; + adapter._ajaxRequest = function(options) { + options.success(payloadClone, '200', { + status: 200, + textStatus: '200', + getAllResponseHeaders: function() { + return ''; + }, + }); + }; + return cb(payload).then(function(result) { + adapter._ajaxRequest = ajax; + return result; + }); +}; +/** `repo` a helper function to faciliate easy integration testing of ember-data Service 'repo' layers + * + * Test performance is also measured using `consul-ui/tests/helpers/measure` and therefore results + * can optionally be sent to a centralized metrics collection stack + * + * @param {string} name - The name of your repo Service (only used for meta purposes) + * @param {string} payload - The method you are testing (only used for meta purposes) + * @param {Service} service - An instance of an ember-data based repo Service + * @param {function} stub - A function that receives a `stub` function allowing you to stub + * an API endpoint with a set of cookies/env vars used by the double + * @param {function} test - Your test case. This function receives an instance of the Service provided + * above as a first and only argument, it should return the result of your test + * @param {function} assert - Your assertion. This receives the result of the previous function as the first + * argument and a function to that receives the stubbed payload giving you an + * opportunity to mutate it before returning for use in your assertion + */ +export default function(name, method, service, stub, test, assert) { + const adapter = get(service, 'store').adapterFor(name.toLowerCase()); + let tags = {}; + const requestHeaders = function(url, cookies = {}) { + const key = Object.keys(cookies).find(function(item) { + return item.indexOf('COUNT') !== -1; + }); + tags = { + count: typeof key !== 'undefined' ? parseInt(cookies[key]) : 1, + }; + return httpGet(url, { + headers: { + cookie: cookies, + }, + }); + }; + const parseResponse = function(response) { + let actual; + if (typeof response.toArray === 'function') { + actual = response.toArray().map(function(item) { + return get(item, 'data'); + }); + } else { + if (typeof response.get === 'function') { + actual = get(response, 'data'); + } else { + actual = response; + } + } + return actual; + }; + return stub(requestHeaders).then(function(payload) { + return stubAdapterResponse( + function(payload) { + return measure( + function() { + return test(service); + }, + `${name}Service.${method}`, + tags + ).then(function(response) { + assert(parseResponse(response), function(cb) { + return cb(payload); + }); + }); + }, + payload, + adapter + ); + }); +} diff --git a/ui-v2/tests/integration/services/acls-test.js b/ui-v2/tests/integration/services/acls-test.js new file mode 100644 index 0000000000..4902b55ef5 --- /dev/null +++ b/ui-v2/tests/integration/services/acls-test.js @@ -0,0 +1,61 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +moduleFor('service:acls', 'Integration | Service | acls', { + // Specify the other units that are required for this test. + needs: ['service:store', 'model:acl', 'adapter:acl', 'serializer:acl', 'service:settings'], +}); +const dc = 'dc-1'; +const id = 'token-name'; +test('findByDatacenter returns the correct data for list endpoint', function(assert) { + return repo( + 'Acl', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/acl/list?dc=${dc}`, { + CONSUL_ACL_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAllByDatacenter(dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); +}); +test('findBySlug returns the correct data for item endpoint', function(assert) { + return repo( + 'Acl', + 'findBySlug', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/acl/info/${id}?dc=${dc}`); + }, + function performTest(service) { + return service.findBySlug(id, dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload[0]; + return Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/coordinates-test.js b/ui-v2/tests/integration/services/coordinates-test.js new file mode 100644 index 0000000000..2fbf162f0b --- /dev/null +++ b/ui-v2/tests/integration/services/coordinates-test.js @@ -0,0 +1,43 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'coordinate'; +moduleFor(`service:${NAME}s`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + ], +}); + +const dc = 'dc-1'; +test('findAllByDatacenter returns the correct data for list endpoint', function(assert) { + return repo( + 'Coordinate', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/coordinate/nodes?dc=${dc}`, { + CONSUL_NODE_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAllByDatacenter(dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.Node}"]`, + }) + ); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/dc-test.js b/ui-v2/tests/integration/services/dc-test.js new file mode 100644 index 0000000000..6a296b7cb0 --- /dev/null +++ b/ui-v2/tests/integration/services/dc-test.js @@ -0,0 +1,45 @@ +import { moduleFor, test } from 'ember-qunit'; +import { skip } from 'qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'dc'; +moduleFor(`service:${NAME}`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + // relationships + 'model:service', + 'model:node', + ], +}); +skip("findBySlug (doesn't interact with the API) but still needs an int test"); +test('findAll returns the correct data for list endpoint', function(assert) { + return repo( + 'Dc', + 'findAll', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/catalog/datacenters`, { + CONSUL_DATACENTER_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAll(); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => ({ Name: item })).sort(function(a, b) { + if (a.Name < b.Name) return -1; + if (a.Name > b.Name) return 1; + return 0; + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/intentions-test.js b/ui-v2/tests/integration/services/intentions-test.js new file mode 100644 index 0000000000..fa04fa4b40 --- /dev/null +++ b/ui-v2/tests/integration/services/intentions-test.js @@ -0,0 +1,73 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'intention'; +moduleFor(`service:${NAME}s`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + ], +}); + +const dc = 'dc-1'; +const id = 'token-name'; +test('findAllByDatacenter returns the correct data for list endpoint', function(assert) { + return repo( + 'Intention', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/connect/intentions?dc=${dc}`, { + CONSUL_INTENTION_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAllByDatacenter(dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + CreatedAt: new Date(item.CreatedAt), + UpdatedAt: new Date(item.UpdatedAt), + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); +}); +test('findBySlug returns the correct data for item endpoint', function(assert) { + return repo( + 'Intention', + 'findBySlug', + this.subject(), + function(stub) { + return stub(`/v1/connect/intentions/${id}?dc=${dc}`); + }, + function(service) { + return service.findBySlug(id, dc); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload; + return Object.assign({}, item, { + CreatedAt: new Date(item.CreatedAt), + UpdatedAt: new Date(item.UpdatedAt), + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/kvs-test.js b/ui-v2/tests/integration/services/kvs-test.js new file mode 100644 index 0000000000..3926fae5c1 --- /dev/null +++ b/ui-v2/tests/integration/services/kvs-test.js @@ -0,0 +1,70 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'kv'; +moduleFor(`service:${NAME}`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + 'service:atob', + ], +}); +const dc = 'dc-1'; +const id = 'key-name'; +test('findAllBySlug returns the correct data for list endpoint', function(assert) { + return repo( + 'Kv', + 'findAllBySlug', + this.subject(), + function retrieveTest(stub) { + return stub(`/v1/kv/${id}?keys&dc=${dc}`, { + CONSUL_KV_COUNT: '1', + }); + }, + function performTest(service) { + return service.findAllBySlug(id, dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => { + return { + Datacenter: dc, + uid: `["${dc}","${item}"]`, + Key: item, + }; + }); + }) + ); + } + ); +}); +test('findAllBySlug returns the correct data for item endpoint', function(assert) { + return repo( + 'Kv', + 'findAllBySlug', + this.subject(), + function(stub) { + return stub(`/v1/kv/${id}?dc=${dc}`); + }, + function(service) { + return service.findBySlug(id, dc); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload[0]; + return Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.Key}"]`, + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/nodes-test.js b/ui-v2/tests/integration/services/nodes-test.js new file mode 100644 index 0000000000..b438eea4df --- /dev/null +++ b/ui-v2/tests/integration/services/nodes-test.js @@ -0,0 +1,73 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'node'; +moduleFor(`service:${NAME}s`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + 'service:coordinates', + 'adapter:coordinate', + 'serializer:coordinate', + 'model:coordinate', + ], +}); + +const dc = 'dc-1'; +const id = 'token-name'; +test('findByDatacenter returns the correct data for list endpoint', function(assert) { + return repo( + 'Node', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/internal/ui/nodes?dc=${dc}`, { + CONSUL_NODE_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAllByDatacenter(dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); +}); +test('findBySlug returns the correct data for item endpoint', function(assert) { + return repo( + 'Node', + 'findBySlug', + this.subject(), + function(stub) { + return stub(`/v1/internal/ui/node/${id}?dc=${dc}`); + }, + function(service) { + return service.findBySlug(id, dc); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload; + return Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/services-test.js b/ui-v2/tests/integration/services/services-test.js new file mode 100644 index 0000000000..7e8b69ccbf --- /dev/null +++ b/ui-v2/tests/integration/services/services-test.js @@ -0,0 +1,82 @@ +import { moduleFor, test } from 'ember-qunit'; +import { skip } from 'qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +moduleFor('service:services', 'Integration | Service | services', { + // Specify the other units that are required for this test. + needs: [ + 'service:store', + 'model:service', + 'adapter:service', + 'serializer:service', + 'service:settings', + ], +}); +const dc = 'dc-1'; +const id = 'token-name'; +test('findByDatacenter returns the correct data for list endpoint', function(assert) { + return repo( + 'Service', + 'findAllByDatacenter', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/internal/ui/services?dc=${dc}`, { + CONSUL_SERVICE_COUNT: '100', + }); + }, + function performTest(service) { + return service.findAllByDatacenter(dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.Name}"]`, + }) + ); + }) + ); + } + ); +}); +skip('findBySlug returns a sane tree'); +test('findBySlug returns the correct data for item endpoint', function(assert) { + return repo( + 'Service', + 'findBySlug', + this.subject(), + function(stub) { + return stub(`/v1/health/service/${id}?dc=${dc}`, { + CONSUL_NODE_COUNT: 1, + }); + }, + function(service) { + return service.findBySlug(id, dc); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + // TODO: So this tree is all 'wrong', it's not having any major impact + // this this tree needs revisting to something that makes more sense + payload = Object.assign( + {}, + { Nodes: payload }, + { + Datacenter: dc, + uid: `["${dc}","${id}"]`, + } + ); + const nodes = payload.Nodes; + const service = payload.Nodes[0]; + service.Nodes = nodes; + service.Tags = payload.Nodes[0].Service.Tags; + + return service; + }) + ); + } + ); +}); diff --git a/ui-v2/tests/integration/services/sessions-test.js b/ui-v2/tests/integration/services/sessions-test.js new file mode 100644 index 0000000000..dadac1df6b --- /dev/null +++ b/ui-v2/tests/integration/services/sessions-test.js @@ -0,0 +1,69 @@ +import { moduleFor, test } from 'ember-qunit'; +import repo from 'consul-ui/tests/helpers/repo'; +const NAME = 'session'; +moduleFor(`service:${NAME}`, `Integration | Service | ${NAME}s`, { + // Specify the other units that are required for this test. + needs: [ + 'service:settings', + 'service:store', + `adapter:${NAME}`, + `serializer:${NAME}`, + `model:${NAME}`, + ], +}); + +const dc = 'dc-1'; +const id = 'node-name'; +test('findByNode returns the correct data for list endpoint', function(assert) { + return repo( + 'Session', + 'findByNode', + this.subject(), + function retrieveStub(stub) { + return stub(`/v1/session/node/${id}?dc=${dc}`, { + CONSUL_SESSION_COUNT: '100', + }); + }, + function performTest(service) { + return service.findByNode(id, dc); + }, + function performAssertion(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + return payload.map(item => + Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }) + ); + }) + ); + } + ); +}); +test('findByKey returns the correct data for item endpoint', function(assert) { + return repo( + 'Session', + 'findByKey', + this.subject(), + function(stub) { + return stub(`/v1/session/info/${id}?dc=${dc}`); + }, + function(service) { + return service.findByKey(id, dc); + }, + function(actual, expected) { + assert.deepEqual( + actual, + expected(function(payload) { + const item = payload[0]; + return Object.assign({}, item, { + Datacenter: dc, + uid: `["${dc}","${item.ID}"]`, + }); + }) + ); + } + ); +}); diff --git a/ui-v2/tests/lib/measure/getMeasure.js b/ui-v2/tests/lib/measure/getMeasure.js new file mode 100644 index 0000000000..5dffe25c6f --- /dev/null +++ b/ui-v2/tests/lib/measure/getMeasure.js @@ -0,0 +1,26 @@ +/* eslint no-console: "off" */ +const log = function(results, measurement, tags) { + console.log(measurement, results, tags); +}; +export default function(len = 10000, report = log, performance = window.performance) { + return function(cb, measurement, tags) { + let actual; + return new Array(len) + .fill(true) + .reduce(function(prev, item, i) { + return prev.then(function(ms) { + return new Promise(function(resolve) { + const start = performance.now(); + cb().then(function(res) { + actual = res; + resolve(ms + (performance.now() - start)); + }); + }); + }); + }, Promise.resolve(0)) + .then(function(total) { + report({ avg: total / len, total: total, count: len }, measurement, tags); + return actual; + }); + }; +} diff --git a/ui-v2/yarn.lock b/ui-v2/yarn.lock index acef1fb1a0..5c91f370f0 100644 --- a/ui-v2/yarn.lock +++ b/ui-v2/yarn.lock @@ -70,8 +70,8 @@ "@glimmer/di" "^0.2.0" "@hashicorp/api-double@^1.3.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.4.0.tgz#17ddad8e55370de0d24151a38c5f029bc207cafe" + version "1.4.1" + resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.4.1.tgz#4f4be42f0e2fec07450415cfe19294654bc7ad00" dependencies: "@gardenhq/o" "^8.0.1" "@gardenhq/tick-control" "^2.0.0" @@ -82,12 +82,12 @@ js-yaml "^3.10.0" "@hashicorp/consul-api-double@^1.4.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.4.1.tgz#547643b98c3a26a1fe1584189bd05e4d9f383966" + version "1.4.3" + resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.4.3.tgz#0d08e167b1163200885636e6d368585004db1c98" "@hashicorp/ember-cli-api-double@^1.3.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.4.0.tgz#4190b30f8b6a51ec33a707c45effede6e93e6b38" + version "1.5.1" + resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.5.1.tgz#92789eaf2073b5871d859700bc696e9552bb835b" dependencies: "@hashicorp/api-double" "^1.3.0" array-range "^1.0.1" @@ -111,6 +111,10 @@ version "10.0.8" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.0.8.tgz#37b4d91d4e958e4c2ba0be2b86e7ed4ff19b0858" +"@xg-wang/whatwg-fetch@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@xg-wang/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#f7b222c012a238e7d6e89ed3d72a1e0edb58453d" + JSONStream@^1.0.3: version "1.3.2" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" @@ -6689,12 +6693,22 @@ miller-rabin@^4.0.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" -mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.7: +mime-db@~1.35.0: + version "1.35.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" + +mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.18" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: mime-db "~1.33.0" +mime-types@~2.1.18: + version "2.1.19" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" + dependencies: + mime-db "~1.35.0" + mime@1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" @@ -7468,9 +7482,10 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" pretender@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pretender/-/pretender-2.0.0.tgz#5adae189f1d5b25f86113f9225df25bed54f4072" + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretender/-/pretender-2.1.1.tgz#5085f0a1272c31d5b57c488386f69e6ca207cb35" dependencies: + "@xg-wang/whatwg-fetch" "^3.0.0" fake-xml-http-request "^2.0.0" route-recognizer "^0.3.3"