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:
John Cowen 2020-02-18 16:43:52 +00:00 committed by John Cowen
parent 51bee897b7
commit 9974f32561
8 changed files with 170 additions and 55 deletions

View File

@ -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;
} }
}, },

View File

@ -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) {

View File

@ -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);
},
});

View File

@ -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);
};
}

View File

@ -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);
});
}); });

View File

@ -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
);
});
}); });

View File

@ -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);
});
});

View File

@ -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"