ui: HTTP Body testing (#7290)
* ui: Test Coverage Reporting (#7027) * Serve up the /coverage folder whilst developing * Upgrade ember-cli-api-double now it supports passthrough per url * ui: make env into a service and use it where we need to be injectable * Give http/client a body method so we can use it on its own for testing * Add test helper for testing with and without nspaces enabled * Add two tests, one thay needs nspaces and one that doesn't * Keep cleaning up client/http, whilst figuring out a immutable bug * Convert tests to new return format ([actual, leftovers])
This commit is contained in:
parent
51bee897b7
commit
9974f32561
|
@ -1,14 +1,13 @@
|
||||||
import Adapter from './http';
|
import Adapter from './http';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { env } from 'consul-ui/env';
|
|
||||||
|
|
||||||
export const DATACENTER_QUERY_PARAM = 'dc';
|
export const DATACENTER_QUERY_PARAM = 'dc';
|
||||||
export const NSPACE_QUERY_PARAM = 'ns';
|
export const NSPACE_QUERY_PARAM = 'ns';
|
||||||
export default Adapter.extend({
|
export default Adapter.extend({
|
||||||
repo: service('settings'),
|
|
||||||
client: service('client/http'),
|
client: service('client/http'),
|
||||||
|
env: service('env'),
|
||||||
formatNspace: function(nspace) {
|
formatNspace: function(nspace) {
|
||||||
if (env('CONSUL_NSPACES_ENABLED')) {
|
if (this.env.env('CONSUL_NSPACES_ENABLED')) {
|
||||||
return nspace !== '' ? { [NSPACE_QUERY_PARAM]: nspace } : undefined;
|
return nspace !== '' ? { [NSPACE_QUERY_PARAM]: nspace } : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,21 @@ import getObjectPool from 'consul-ui/utils/get-object-pool';
|
||||||
import Request from 'consul-ui/utils/http/request';
|
import Request from 'consul-ui/utils/http/request';
|
||||||
import createURL from 'consul-ui/utils/createURL';
|
import createURL from 'consul-ui/utils/createURL';
|
||||||
|
|
||||||
|
// reopen EventSources if a user changes tab
|
||||||
|
export const restartWhenAvailable = function(client) {
|
||||||
|
return function(e) {
|
||||||
|
// setup the aborted connection restarting
|
||||||
|
// this should happen here to avoid cache deletion
|
||||||
|
const status = get(e, 'errors.firstObject.status');
|
||||||
|
if (status === '0') {
|
||||||
|
// Any '0' errors (abort) should possibly try again, depending upon the circumstances
|
||||||
|
// whenAvailable returns a Promise that resolves when the client is available
|
||||||
|
// again
|
||||||
|
return client.whenAvailable(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
};
|
||||||
|
};
|
||||||
class HTTPError extends Error {
|
class HTTPError extends Error {
|
||||||
constructor(statusCode, message) {
|
constructor(statusCode, message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
@ -37,6 +52,15 @@ const dispose = function(request) {
|
||||||
// right now createURL converts undefined to '' so we need to check thats not needed
|
// right now createURL converts undefined to '' so we need to check thats not needed
|
||||||
// anywhere (todo written here for visibility)
|
// anywhere (todo written here for visibility)
|
||||||
const url = createURL(encodeURIComponent);
|
const url = createURL(encodeURIComponent);
|
||||||
|
const createHeaders = function(lines) {
|
||||||
|
return lines.reduce(function(prev, item) {
|
||||||
|
const temp = item.split(':');
|
||||||
|
if (temp.length > 1) {
|
||||||
|
prev[temp[0].trim()] = temp[1].trim();
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
dom: service('dom'),
|
dom: service('dom'),
|
||||||
settings: service('settings'),
|
settings: service('settings'),
|
||||||
|
@ -58,64 +82,65 @@ export default Service.extend({
|
||||||
url: function() {
|
url: function() {
|
||||||
return url(...arguments);
|
return url(...arguments);
|
||||||
},
|
},
|
||||||
|
body: function(strs, ...values) {
|
||||||
|
let body = {};
|
||||||
|
const doubleBreak = strs.reduce(function(prev, item, i) {
|
||||||
|
if (item.indexOf('\n\n') !== -1) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, -1);
|
||||||
|
if (doubleBreak !== -1) {
|
||||||
|
// This merges request bodies together, so you can specify multiple bodies
|
||||||
|
// in the request and it will merge them together.
|
||||||
|
// Turns out we never actually do this, so it might be worth removing as it complicates
|
||||||
|
// matters slightly as we assumed post bodies would be an object.
|
||||||
|
// This actually works as it just uses the value of the first object, if its an array
|
||||||
|
// it concats
|
||||||
|
body = values.splice(doubleBreak).reduce(function(prev, item, i) {
|
||||||
|
switch (true) {
|
||||||
|
case Array.isArray(item):
|
||||||
|
if (i === 0) {
|
||||||
|
prev = [];
|
||||||
|
}
|
||||||
|
return prev.concat(item);
|
||||||
|
case typeof item !== 'string':
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
...item,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}, body);
|
||||||
|
}
|
||||||
|
return [body, ...values];
|
||||||
|
},
|
||||||
request: function(cb) {
|
request: function(cb) {
|
||||||
const client = this;
|
const client = this;
|
||||||
return cb(function(strs, ...values) {
|
return cb(function(strs, ...values) {
|
||||||
let body = {};
|
// first go to the end and remove/parse the http body
|
||||||
const doubleBreak = strs.reduce(function(prev, item, i) {
|
const [body, ...urlVars] = client.body(...arguments);
|
||||||
if (item.indexOf('\n\n') !== -1) {
|
// with whats left get the method off the front
|
||||||
return i;
|
const [method, ...urlParts] = client.url(strs, ...urlVars).split(' ');
|
||||||
}
|
// with whats left use the rest of the line for the url
|
||||||
return prev;
|
// with whats left after the line, use for the headers
|
||||||
}, -1);
|
const [url, ...headerParts] = urlParts.join(' ').split('\n');
|
||||||
if (doubleBreak !== -1) {
|
|
||||||
// This merges request bodies together, so you can specify multiple bodies
|
|
||||||
// in the request and it will merge them together.
|
|
||||||
// Turns out we never actually do this, so it might be worth removing as it complicates
|
|
||||||
// matters slightly as we assumed post bodies would be an object.
|
|
||||||
// This actually works as it just uses the value of the first object, if its an array
|
|
||||||
// it concats
|
|
||||||
body = values.splice(doubleBreak).reduce(function(prev, item, i) {
|
|
||||||
switch (true) {
|
|
||||||
case Array.isArray(item):
|
|
||||||
if (i === 0) {
|
|
||||||
prev = [];
|
|
||||||
}
|
|
||||||
return prev.concat(item);
|
|
||||||
case typeof item !== 'string':
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
...item,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}, body);
|
|
||||||
}
|
|
||||||
let temp = url(strs, ...values).split(' ');
|
|
||||||
const method = temp.shift();
|
|
||||||
let rest = temp.join(' ');
|
|
||||||
temp = rest.split('\n');
|
|
||||||
const path = temp.shift().trim();
|
|
||||||
const createHeaders = function(lines) {
|
|
||||||
return lines.reduce(function(prev, item) {
|
|
||||||
const temp = item.split(':');
|
|
||||||
if (temp.length > 1) {
|
|
||||||
prev[temp[0].trim()] = temp[1].trim();
|
|
||||||
}
|
|
||||||
return prev;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
const headers = {
|
const headers = {
|
||||||
|
// default to application/json
|
||||||
...{
|
...{
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
},
|
},
|
||||||
|
// add any application level headers
|
||||||
...get(client, 'settings').findHeaders(),
|
...get(client, 'settings').findHeaders(),
|
||||||
...createHeaders(temp),
|
// but overwrite or add to those from anything in the specific request
|
||||||
|
...createHeaders(headerParts),
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
const options = {
|
const options = {
|
||||||
url: path,
|
url: url.trim(),
|
||||||
method: method,
|
method: method,
|
||||||
contentType: headers['Content-Type'],
|
contentType: headers['Content-Type'],
|
||||||
// type: 'json',
|
// type: 'json',
|
||||||
|
@ -127,7 +152,7 @@ export default Service.extend({
|
||||||
const respond = function(cb) {
|
const respond = function(cb) {
|
||||||
return cb(headers, response);
|
return cb(headers, response);
|
||||||
};
|
};
|
||||||
//TODO: nextTick ?
|
// TODO: nextTick ?
|
||||||
resolve(respond);
|
resolve(respond);
|
||||||
},
|
},
|
||||||
error: function(xhr, textStatus, err) {
|
error: function(xhr, textStatus, err) {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import Service from '@ember/service';
|
||||||
|
import { env } from 'consul-ui/env';
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
env: function(key) {
|
||||||
|
return env(key);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Service from '@ember/service';
|
||||||
|
export default function(type) {
|
||||||
|
return function(cb, withNspaces, withoutNspaces, container, assert) {
|
||||||
|
let CONSUL_NSPACES_ENABLED = true;
|
||||||
|
container.owner.register(
|
||||||
|
'service:env',
|
||||||
|
Service.extend({
|
||||||
|
env: function() {
|
||||||
|
return CONSUL_NSPACES_ENABLED;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const adapter = container.owner.lookup(`adapter:${type}`);
|
||||||
|
const serializer = container.owner.lookup(`serializer:${type}`);
|
||||||
|
const client = container.owner.lookup('service:client/http');
|
||||||
|
let actual;
|
||||||
|
|
||||||
|
actual = cb(adapter, serializer, client);
|
||||||
|
assert.deepEqual(actual[0], withNspaces);
|
||||||
|
|
||||||
|
CONSUL_NSPACES_ENABLED = false;
|
||||||
|
actual = cb(adapter, serializer, client);
|
||||||
|
assert.deepEqual(actual[0], withoutNspaces);
|
||||||
|
};
|
||||||
|
}
|
|
@ -12,4 +12,16 @@ module('Integration | Adapter | coordinate', function(hooks) {
|
||||||
});
|
});
|
||||||
assert.equal(actual, expected);
|
assert.equal(actual, expected);
|
||||||
});
|
});
|
||||||
|
test('requestForQuery returns the correct body', function(assert) {
|
||||||
|
const adapter = this.owner.lookup('adapter:coordinate');
|
||||||
|
const client = this.owner.lookup('service:client/http');
|
||||||
|
const expected = {
|
||||||
|
index: 1,
|
||||||
|
};
|
||||||
|
const [actual] = adapter.requestForQuery(client.body, {
|
||||||
|
dc: dc,
|
||||||
|
index: 1,
|
||||||
|
});
|
||||||
|
assert.deepEqual(actual, expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupTest } from 'ember-qunit';
|
import { setupTest } from 'ember-qunit';
|
||||||
|
import getNspaceRunner from 'consul-ui/tests/helpers/get-nspace-runner';
|
||||||
|
|
||||||
|
const nspaceRunner = getNspaceRunner('discovery-chain');
|
||||||
module('Integration | Adapter | discovery-chain', function(hooks) {
|
module('Integration | Adapter | discovery-chain', function(hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
const dc = 'dc-1';
|
const dc = 'dc-1';
|
||||||
|
@ -24,4 +26,25 @@ module('Integration | Adapter | discovery-chain', function(hooks) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
test('requestForQueryRecord returns the correct body', function(assert) {
|
||||||
|
return nspaceRunner(
|
||||||
|
(adapter, serializer, client) => {
|
||||||
|
return adapter.requestForQueryRecord(client.body, {
|
||||||
|
id: id,
|
||||||
|
dc: dc,
|
||||||
|
ns: 'team-1',
|
||||||
|
index: 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
ns: 'team-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
this,
|
||||||
|
assert
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupTest } from 'ember-qunit';
|
||||||
|
|
||||||
|
module('Unit | Service | env', function(hooks) {
|
||||||
|
setupTest(hooks);
|
||||||
|
|
||||||
|
// Replace this with your real tests.
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let service = this.owner.lookup('service:env');
|
||||||
|
assert.ok(service);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1154,9 +1154,9 @@
|
||||||
integrity sha512-8OcgesUjWQ8AjaXzbz3tGJQn1kM0sN6pLidGM7isNPUyYmIjIEXQzaeUQYzsfv0N2Ko9ZuOXYUsaBl8IK1KGow==
|
integrity sha512-8OcgesUjWQ8AjaXzbz3tGJQn1kM0sN6pLidGM7isNPUyYmIjIEXQzaeUQYzsfv0N2Ko9ZuOXYUsaBl8IK1KGow==
|
||||||
|
|
||||||
"@hashicorp/ember-cli-api-double@^3.0.0":
|
"@hashicorp/ember-cli-api-double@^3.0.0":
|
||||||
version "3.0.2"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-3.0.2.tgz#4f66f22e4b54293c46fe16dc24568267526c8acd"
|
resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-3.0.0.tgz#2a9e8e475c8ac9780221f46584297640870f9f1c"
|
||||||
integrity sha512-NmcA+jBcBO8tzCfbqWzrQdHvTUeaj71Gdu9phxaULMtM9Zw7BZtHlvb2P1ivknGGw92w9Se50pDNjkB57ww22A==
|
integrity sha512-3jpkkB0jsVWbpI3ySsBJ5jmSb1bPkJu8nZTh9YvIiQDhH76zqHY7AXi35oSV4M83f5qYtBLJjDSdwrf0zJ9Dlg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hashicorp/api-double" "^1.6.1"
|
"@hashicorp/api-double" "^1.6.1"
|
||||||
array-range "^1.0.1"
|
array-range "^1.0.1"
|
||||||
|
@ -6871,7 +6871,7 @@ growly@^1.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
|
||||||
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
|
||||||
|
|
||||||
handlebars@^4.0.11, handlebars@^4.3.1:
|
handlebars@^4.0.11:
|
||||||
version "4.7.3"
|
version "4.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee"
|
||||||
integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==
|
integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg==
|
||||||
|
@ -6893,6 +6893,17 @@ handlebars@^4.0.13, handlebars@^4.0.4, handlebars@^4.1.2:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
uglify-js "^3.1.4"
|
uglify-js "^3.1.4"
|
||||||
|
|
||||||
|
handlebars@^4.3.1:
|
||||||
|
version "4.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.2.tgz#01127b3840156a0927058779482031afe0e730d7"
|
||||||
|
integrity sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==
|
||||||
|
dependencies:
|
||||||
|
neo-async "^2.6.0"
|
||||||
|
optimist "^0.6.1"
|
||||||
|
source-map "^0.6.1"
|
||||||
|
optionalDependencies:
|
||||||
|
uglify-js "^3.1.4"
|
||||||
|
|
||||||
handlebars@~4.1.2:
|
handlebars@~4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
|
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67"
|
||||||
|
|
Loading…
Reference in New Issue