ui: Remove jQuery from the production build (#8088)
* ui: Split up client/http and replace $.ajax This splits the client/http service more in the following ways: 1. Connections are now split out into its own service 2. The transport is now split out into its own service that returns a listener based http transport 3. Various string parsing/stringifying functions are now split out into utils * Remove jQuery from our production build * Move the coverage serving to the server.js file * Self review amends * Add X-Requested-With header * Move some files around, externalize some functions * Move connection tracking to use native Set * Ensure HTTP parsing doesn't encode headers In the future this will change to deal with all HTTP parsing in one place, hence the commented out METHOD_PARSING etc * Start to fix up integration tests to use requestParams
This commit is contained in:
parent
088e1d5693
commit
f50438e76f
|
@ -81,6 +81,9 @@ export default Adapter.extend({
|
|||
// });
|
||||
},
|
||||
error: function(err) {
|
||||
if (err instanceof TypeError) {
|
||||
throw err;
|
||||
}
|
||||
const errors = [
|
||||
{
|
||||
status: `${err.statusCode}`,
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import Service, { inject as service } from '@ember/service';
|
||||
|
||||
export default Service.extend({
|
||||
dom: service('dom'),
|
||||
env: service('env'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
this.connections = new Set();
|
||||
this.addVisibilityChange();
|
||||
},
|
||||
willDestroy: function() {
|
||||
this._listeners.remove();
|
||||
this.purge();
|
||||
this._super(...arguments);
|
||||
},
|
||||
addVisibilityChange: function() {
|
||||
// when the user hides the tab, abort all connections
|
||||
this._listeners.add(this.dom.document(), {
|
||||
visibilitychange: e => {
|
||||
if (e.target.hidden) {
|
||||
this.purge();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
whenAvailable: function(e) {
|
||||
// if the user has hidden the tab (hidden browser/tab switch)
|
||||
// any aborted errors should restart
|
||||
const doc = this.dom.document();
|
||||
if (doc.hidden) {
|
||||
return new Promise(resolve => {
|
||||
const remove = this._listeners.add(doc, {
|
||||
visibilitychange: function(event) {
|
||||
remove();
|
||||
// we resolve with the event that comes from
|
||||
// whenAvailable not visibilitychange
|
||||
resolve(e);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve(e);
|
||||
},
|
||||
purge: function() {
|
||||
[...this.connections].forEach(function(connection) {
|
||||
// Cancelled
|
||||
connection.abort(0);
|
||||
});
|
||||
this.connections = new Set();
|
||||
},
|
||||
acquire: function(request) {
|
||||
this.connections.add(request);
|
||||
if (this.connections.size > this.env.var('CONSUL_HTTP_MAX_CONNECTIONS')) {
|
||||
const connection = this.connections.values().next().value;
|
||||
this.connections.delete(connection);
|
||||
// Too Many Requests
|
||||
connection.abort(429);
|
||||
}
|
||||
},
|
||||
release: function(request) {
|
||||
this.connections.delete(request);
|
||||
},
|
||||
});
|
|
@ -1,15 +1,13 @@
|
|||
/*global $*/
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
import { get, set } from '@ember/object';
|
||||
import { get } from '@ember/object';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
import { CACHE_CONTROL, CONTENT_TYPE } from 'consul-ui/utils/http/headers';
|
||||
|
||||
import { HEADERS_TOKEN as CONSUL_TOKEN } from 'consul-ui/utils/http/consul';
|
||||
|
||||
import { env } from 'consul-ui/env';
|
||||
import getObjectPool from 'consul-ui/utils/get-object-pool';
|
||||
import Request from 'consul-ui/utils/http/request';
|
||||
import createURL from 'consul-ui/utils/createURL';
|
||||
import createURL from 'consul-ui/utils/http/create-url';
|
||||
import createHeaders from 'consul-ui/utils/http/create-headers';
|
||||
import createQueryParams from 'consul-ui/utils/http/create-query-params';
|
||||
|
||||
// reopen EventSources if a user changes tab
|
||||
export const restartWhenAvailable = function(client) {
|
||||
|
@ -26,250 +24,188 @@ export const restartWhenAvailable = function(client) {
|
|||
throw e;
|
||||
};
|
||||
};
|
||||
class HTTPError extends Error {
|
||||
constructor(statusCode, message) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
const dispose = function(request) {
|
||||
if (request.headers()[CONTENT_TYPE.toLowerCase()] === 'text/event-stream') {
|
||||
const xhr = request.connection();
|
||||
// unsent and opened get aborted
|
||||
// headers and loading means wait for it
|
||||
// to finish for the moment
|
||||
if (xhr.readyState) {
|
||||
switch (xhr.readyState) {
|
||||
case 0:
|
||||
case 1:
|
||||
xhr.abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return request;
|
||||
};
|
||||
// TODO: Potentially url should check if any of the params
|
||||
// passed to it are undefined (null is fine). We could then get rid of the
|
||||
// multitude of checks we do throughout the adapters
|
||||
// right now createURL converts undefined to '' so we need to check thats not needed
|
||||
// anywhere (todo written here for visibility)
|
||||
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();
|
||||
const stringifyQueryParams = createQueryParams(encodeURIComponent);
|
||||
const parseURL = createURL(encodeURIComponent, stringifyQueryParams);
|
||||
const parseHeaders = createHeaders();
|
||||
|
||||
const parseBody = function(strs, ...values) {
|
||||
let body = {};
|
||||
const doubleBreak = strs.reduce(function(prev, item, i) {
|
||||
// Ensure each line has no whitespace either end, including empty lines
|
||||
item = item
|
||||
.split('\n')
|
||||
.map(item => item.trim())
|
||||
.join('\n');
|
||||
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];
|
||||
};
|
||||
|
||||
const CLIENT_HEADERS = [CACHE_CONTROL];
|
||||
export default Service.extend({
|
||||
dom: service('dom'),
|
||||
connections: service('client/connections'),
|
||||
transport: service('client/transports/xhr'),
|
||||
settings: service('settings'),
|
||||
init: function() {
|
||||
this._super(...arguments);
|
||||
this._listeners = this.dom.listeners();
|
||||
const maxConnections = env('CONSUL_HTTP_MAX_CONNECTIONS');
|
||||
set(this, 'connections', getObjectPool(dispose, maxConnections));
|
||||
if (typeof maxConnections !== 'undefined') {
|
||||
set(this, 'maxConnections', maxConnections);
|
||||
// when the user hides the tab, abort all connections
|
||||
this._listeners.add(this.dom.document(), {
|
||||
visibilitychange: e => {
|
||||
if (e.target.hidden) {
|
||||
this.connections.purge();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
willDestroy: function() {
|
||||
this._listeners.remove();
|
||||
this.connections.purge();
|
||||
set(this, 'connections', undefined);
|
||||
this._super(...arguments);
|
||||
},
|
||||
url: function() {
|
||||
return url(...arguments);
|
||||
return parseURL(...arguments);
|
||||
},
|
||||
body: function(strs, ...values) {
|
||||
let body = {};
|
||||
const doubleBreak = strs.reduce(function(prev, item, i) {
|
||||
// Ensure each line has no whitespace either end, including empty lines
|
||||
item = item
|
||||
.split('\n')
|
||||
.map(item => item.trim())
|
||||
.join('\n');
|
||||
if (item.indexOf('\n\n') !== -1) {
|
||||
return i;
|
||||
body: function() {
|
||||
return parseBody(...arguments);
|
||||
},
|
||||
requestParams: function(strs, ...values) {
|
||||
// first go to the end and remove/parse the http body
|
||||
const [body, ...urlVars] = this.body(...arguments);
|
||||
// with whats left get the method off the front
|
||||
const [method, ...urlParts] = this.url(strs, ...urlVars).split(' ');
|
||||
// with whats left use the rest of the line for the url
|
||||
// with whats left after the line, use for the headers
|
||||
const [url, ...headerParts] = urlParts.join(' ').split('\n');
|
||||
const params = {
|
||||
url: url.trim(),
|
||||
method: method.trim(),
|
||||
headers: {
|
||||
[CONTENT_TYPE]: 'application/json; charset=utf-8',
|
||||
...parseHeaders(headerParts),
|
||||
},
|
||||
body: null,
|
||||
data: body,
|
||||
};
|
||||
// Remove and save things that shouldn't be sent in the request
|
||||
params.clientHeaders = CLIENT_HEADERS.reduce(function(prev, item) {
|
||||
if (typeof params.headers[item] !== 'undefined') {
|
||||
prev[item.toLowerCase()] = params.headers[item];
|
||||
delete params.headers[item];
|
||||
}
|
||||
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;
|
||||
}, {});
|
||||
if (typeof body !== 'undefined') {
|
||||
// Only read add HTTP body if we aren't GET
|
||||
// Right now we do this to avoid having to put data in the templates
|
||||
// for write-like actions
|
||||
// potentially we should change things so you _have_ to do that
|
||||
// as doing it this way is a little magical
|
||||
if (params.method !== 'GET') {
|
||||
if (params.headers[CONTENT_TYPE].indexOf('json') !== -1) {
|
||||
params.body = JSON.stringify(params.data);
|
||||
} else {
|
||||
if (
|
||||
(typeof params.data === 'string' && params.data.length > 0) ||
|
||||
Object.keys(params.data).length > 0
|
||||
) {
|
||||
params.body = params.data;
|
||||
}
|
||||
}
|
||||
}, body);
|
||||
} else {
|
||||
const str = stringifyQueryParams(params.data);
|
||||
if (str.length > 0) {
|
||||
if (params.url.indexOf('?') !== -1) {
|
||||
params.url = `${params.url}&${str}`;
|
||||
} else {
|
||||
params.url = `${params.url}?${str}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [body, ...values];
|
||||
// temporarily reset the headers/content-type so it works the same
|
||||
// as previously, should be able to remove this once the data layer
|
||||
// rewrite is over and we can assert sending via form-encoded is fine
|
||||
// also see adapters/kv content-types in requestForCreate/UpdateRecord
|
||||
// also see https://github.com/hashicorp/consul/issues/3804
|
||||
params.headers[CONTENT_TYPE] = 'application/json; charset=utf-8';
|
||||
return params;
|
||||
},
|
||||
request: function(cb) {
|
||||
const client = this;
|
||||
return cb(function(strs, ...values) {
|
||||
// first go to the end and remove/parse the http body
|
||||
const [body, ...urlVars] = client.body(...arguments);
|
||||
// with whats left get the method off the front
|
||||
const [method, ...urlParts] = client.url(strs, ...urlVars).split(' ');
|
||||
// with whats left use the rest of the line for the url
|
||||
// with whats left after the line, use for the headers
|
||||
const [url, ...headerParts] = urlParts.join(' ').split('\n');
|
||||
|
||||
return client.settings.findBySlug('token').then(function(token) {
|
||||
const requestHeaders = createHeaders(headerParts);
|
||||
const headers = {
|
||||
// default to application/json
|
||||
...{
|
||||
[CONTENT_TYPE]: 'application/json; charset=utf-8',
|
||||
},
|
||||
// add any application level headers
|
||||
...{
|
||||
const params = client.requestParams(...arguments);
|
||||
return client.settings.findBySlug('token').then(token => {
|
||||
const options = {
|
||||
...params,
|
||||
headers: {
|
||||
[CONSUL_TOKEN]: typeof token.SecretID === 'undefined' ? '' : token.SecretID,
|
||||
...params.headers,
|
||||
},
|
||||
// but overwrite or add to those from anything in the specific request
|
||||
...requestHeaders,
|
||||
};
|
||||
// We use cache-control in the response
|
||||
// but we don't want to send it, but we artificially
|
||||
// tag it onto the response below if it is set on the request
|
||||
delete headers[CACHE_CONTROL];
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
const options = {
|
||||
url: url.trim(),
|
||||
method: method,
|
||||
contentType: headers[CONTENT_TYPE],
|
||||
// type: 'json',
|
||||
complete: function(xhr, textStatus) {
|
||||
client.complete(this.id);
|
||||
const request = client.transport.request(options);
|
||||
return new Promise((resolve, reject) => {
|
||||
const remove = client._listeners.add(request, {
|
||||
open: e => {
|
||||
client.acquire(e.target);
|
||||
},
|
||||
success: function(response, status, xhr) {
|
||||
const headers = createHeaders(xhr.getAllResponseHeaders().split('\n'));
|
||||
if (typeof requestHeaders[CACHE_CONTROL] !== 'undefined') {
|
||||
// if cache-control was on the request, artificially tag
|
||||
// it back onto the response, also see comment above
|
||||
headers[CACHE_CONTROL] = requestHeaders[CACHE_CONTROL];
|
||||
}
|
||||
const respond = function(cb) {
|
||||
return cb(headers, response);
|
||||
message: e => {
|
||||
const headers = {
|
||||
...Object.entries(e.data.headers).reduce(function(prev, [key, value], i) {
|
||||
if (!CLIENT_HEADERS.includes(key)) {
|
||||
prev[key] = value;
|
||||
}
|
||||
return prev;
|
||||
}, {}),
|
||||
...params.clientHeaders,
|
||||
};
|
||||
// TODO: nextTick ?
|
||||
resolve(respond);
|
||||
const respond = function(cb) {
|
||||
return cb(headers, e.data.response);
|
||||
};
|
||||
next(() => resolve(respond));
|
||||
},
|
||||
error: function(xhr, textStatus, err) {
|
||||
let error;
|
||||
if (err instanceof Error) {
|
||||
error = err;
|
||||
} else {
|
||||
let status = xhr.status;
|
||||
// TODO: Not sure if we actually need this, but ember-data checks it
|
||||
if (textStatus === 'abort') {
|
||||
status = 0;
|
||||
}
|
||||
if (textStatus === 'timeout') {
|
||||
status = 408;
|
||||
}
|
||||
error = new HTTPError(status, xhr.responseText);
|
||||
}
|
||||
//TODO: nextTick ?
|
||||
reject(error);
|
||||
error: e => {
|
||||
next(() => reject(e.error));
|
||||
},
|
||||
converters: {
|
||||
'text json': function(response) {
|
||||
try {
|
||||
return $.parseJSON(response);
|
||||
} catch (e) {
|
||||
return response;
|
||||
}
|
||||
},
|
||||
close: e => {
|
||||
client.release(e.target);
|
||||
remove();
|
||||
},
|
||||
};
|
||||
if (typeof body !== 'undefined') {
|
||||
// Only read add HTTP body if we aren't GET
|
||||
// Right now we do this to avoid having to put data in the templates
|
||||
// for write-like actions
|
||||
// potentially we should change things so you _have_ to do that
|
||||
// as doing it this way is a little magical
|
||||
if (method !== 'GET' && headers[CONTENT_TYPE].indexOf('json') !== -1) {
|
||||
options.data = JSON.stringify(body);
|
||||
} else {
|
||||
// TODO: Does this need urlencoding? Assuming jQuery does this
|
||||
options.data = body;
|
||||
}
|
||||
}
|
||||
// temporarily reset the headers/content-type so it works the same
|
||||
// as previously, should be able to remove this once the data layer
|
||||
// rewrite is over and we can assert sending via form-encoded is fine
|
||||
// also see adapters/kv content-types in requestForCreate/UpdateRecord
|
||||
// also see https://github.com/hashicorp/consul/issues/3804
|
||||
options.contentType = 'application/json; charset=utf-8';
|
||||
headers[CONTENT_TYPE] = options.contentType;
|
||||
//
|
||||
options.beforeSend = function(xhr) {
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]));
|
||||
}
|
||||
this.id = client.acquire(options, xhr);
|
||||
};
|
||||
return $.ajax(options);
|
||||
});
|
||||
request.fetch();
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
abort: function(id = null) {
|
||||
this.connections.purge();
|
||||
},
|
||||
whenAvailable: function(e) {
|
||||
// if we are using a connection limited protocol and the user has hidden the tab (hidden browser/tab switch)
|
||||
// any aborted errors should restart
|
||||
const doc = this.dom.document();
|
||||
if (typeof this.maxConnections !== 'undefined' && doc.hidden) {
|
||||
return new Promise(resolve => {
|
||||
const remove = this._listeners.add(doc, {
|
||||
visibilitychange: function(event) {
|
||||
remove();
|
||||
// we resolve with the event that comes from
|
||||
// whenAvailable not visibilitychange
|
||||
resolve(e);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve(e);
|
||||
return this.connections.whenAvailable(e);
|
||||
},
|
||||
acquire: function(options, xhr) {
|
||||
const request = new Request(options.method, options.url, { body: options.data || {} }, xhr);
|
||||
return this.connections.acquire(request, request.getId());
|
||||
abort: function() {
|
||||
return this.connections.purge(...arguments);
|
||||
},
|
||||
complete: function() {
|
||||
acquire: function() {
|
||||
return this.connections.acquire(...arguments);
|
||||
},
|
||||
release: function() {
|
||||
return this.connections.release(...arguments);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import Service from '@ember/service';
|
||||
|
||||
import createHeaders from 'consul-ui/utils/http/create-headers';
|
||||
import createXHR from 'consul-ui/utils/http/xhr';
|
||||
import Request from 'consul-ui/utils/http/request';
|
||||
import HTTPError from 'consul-ui/utils/http/error';
|
||||
|
||||
const xhr = createXHR(createHeaders());
|
||||
|
||||
export default Service.extend({
|
||||
xhr: function(options) {
|
||||
return xhr(options);
|
||||
},
|
||||
request: function(params) {
|
||||
const request = new Request(params.method, params.url, { body: params.data || {} });
|
||||
const options = {
|
||||
...params,
|
||||
beforeSend: function(xhr) {
|
||||
request.open(xhr);
|
||||
},
|
||||
converters: {
|
||||
'text json': function(response) {
|
||||
try {
|
||||
return JSON.parse(response);
|
||||
} catch (e) {
|
||||
return response;
|
||||
}
|
||||
},
|
||||
},
|
||||
success: function(headers, response, status, statusText) {
|
||||
// Response-ish
|
||||
request.respond({
|
||||
headers: headers,
|
||||
response: response,
|
||||
status: status,
|
||||
statusText: statusText,
|
||||
});
|
||||
},
|
||||
error: function(headers, response, status, statusText, err) {
|
||||
let error;
|
||||
if (err instanceof Error) {
|
||||
error = err;
|
||||
} else {
|
||||
error = new HTTPError(status, response);
|
||||
}
|
||||
request.error(error);
|
||||
},
|
||||
complete: function(status) {
|
||||
request.close();
|
||||
},
|
||||
};
|
||||
request.fetch = () => {
|
||||
this.xhr(options);
|
||||
};
|
||||
return request;
|
||||
},
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
export default function(encode) {
|
||||
return function(strs, ...values) {
|
||||
return strs
|
||||
.map(function(item, i) {
|
||||
let val = typeof values[i] === 'undefined' ? '' : values[i];
|
||||
switch (true) {
|
||||
case typeof val === 'string':
|
||||
val = encode(val);
|
||||
break;
|
||||
case Array.isArray(val):
|
||||
val = val
|
||||
.map(function(item) {
|
||||
return `${encode(item)}`;
|
||||
}, '')
|
||||
.join('/');
|
||||
break;
|
||||
case typeof val === 'object':
|
||||
val = Object.keys(val)
|
||||
.reduce(function(prev, key) {
|
||||
if (val[key] === null) {
|
||||
return prev.concat(`${encode(key)}`);
|
||||
} else if (typeof val[key] !== 'undefined') {
|
||||
return prev.concat(`${encode(key)}=${encode(val[key])}`);
|
||||
}
|
||||
return prev;
|
||||
}, [])
|
||||
.join('&');
|
||||
break;
|
||||
}
|
||||
return `${item}${val}`;
|
||||
})
|
||||
.join('')
|
||||
.trim();
|
||||
};
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
export default function(dispose = function() {}, max, objects = []) {
|
||||
return {
|
||||
acquire: function(obj, id) {
|
||||
// TODO: what should happen if an ID already exists
|
||||
// should we ignore and release both? Or prevent from acquiring? Or generate a unique ID?
|
||||
// what happens if we can't get an id via getId or .id?
|
||||
// could potentially use Set
|
||||
objects.push(obj);
|
||||
if (typeof max !== 'undefined') {
|
||||
if (objects.length > max) {
|
||||
return dispose(objects.shift());
|
||||
}
|
||||
}
|
||||
return id;
|
||||
},
|
||||
// release releases the obj from the pool but **doesn't** dispose it
|
||||
release: function(obj) {
|
||||
let index = -1;
|
||||
let id;
|
||||
if (typeof obj === 'string') {
|
||||
id = obj;
|
||||
} else {
|
||||
id = obj.id;
|
||||
}
|
||||
objects.forEach(function(item, i) {
|
||||
let itemId;
|
||||
if (typeof item.getId === 'function') {
|
||||
itemId = item.getId();
|
||||
} else {
|
||||
itemId = item.id;
|
||||
}
|
||||
if (itemId === id) {
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
if (index !== -1) {
|
||||
return objects.splice(index, 1)[0];
|
||||
}
|
||||
},
|
||||
purge: function() {
|
||||
let obj;
|
||||
const objs = [];
|
||||
while ((obj = objects.shift())) {
|
||||
objs.push(dispose(obj));
|
||||
}
|
||||
return objs;
|
||||
},
|
||||
dispose: function(id) {
|
||||
return dispose(this.release(id));
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export default function() {
|
||||
return 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;
|
||||
}, {});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
export default function(encode) {
|
||||
return function stringify(obj, parent) {
|
||||
return Object.entries(obj)
|
||||
.reduce(function(prev, [key, value], i) {
|
||||
// if the value is undefined do nothing
|
||||
if (typeof value === 'undefined') {
|
||||
return prev;
|
||||
}
|
||||
let prop = encode(key);
|
||||
// if we have a parent, prefix the property with that
|
||||
if (typeof parent !== 'undefined') {
|
||||
prop = `${parent}[${prop}]`;
|
||||
}
|
||||
// if the value is null just print the prop
|
||||
if (value === null) {
|
||||
return prev.concat(prop);
|
||||
}
|
||||
// anything nested, recur
|
||||
if (typeof value === 'object') {
|
||||
return prev.concat(stringify(value, prop));
|
||||
}
|
||||
// anything else print prop=value
|
||||
return prev.concat(`${prop}=${encode(value)}`);
|
||||
}, [])
|
||||
.join('&');
|
||||
};
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// const METHOD_PARSING = 0;
|
||||
const PATH_PARSING = 1;
|
||||
const QUERY_PARSING = 2;
|
||||
const HEADER_PARSING = 3;
|
||||
const BODY_PARSING = 4;
|
||||
export default function(encode, queryParams) {
|
||||
return function(strs, ...values) {
|
||||
// TODO: Potentially url should check if any of the params
|
||||
// passed to it are undefined (null is fine). We could then get rid of the
|
||||
// multitude of checks we do throughout the adapters
|
||||
// right now create-url converts undefined to '' so we need to check thats not needed
|
||||
// anywhere
|
||||
let state = PATH_PARSING;
|
||||
return strs
|
||||
.map(function(item, i, arr) {
|
||||
if (i === 0) {
|
||||
item = item.trimStart();
|
||||
}
|
||||
// if(item.indexOf(' ') !== -1 && state === METHOD_PARSING) {
|
||||
// state = PATH_PARSING;
|
||||
// }
|
||||
if (item.indexOf('?') !== -1 && state === PATH_PARSING) {
|
||||
state = QUERY_PARSING;
|
||||
}
|
||||
if (item.indexOf('\n\n') !== -1) {
|
||||
state = BODY_PARSING;
|
||||
}
|
||||
if (item.indexOf('\n') !== -1 && state !== BODY_PARSING) {
|
||||
state = HEADER_PARSING;
|
||||
}
|
||||
let val = typeof values[i] !== 'undefined' ? values[i] : '';
|
||||
switch (state) {
|
||||
case PATH_PARSING:
|
||||
switch (true) {
|
||||
// encode strings
|
||||
case typeof val === 'string':
|
||||
val = encode(val);
|
||||
break;
|
||||
// split encode and join arrays by `/`
|
||||
case Array.isArray(val):
|
||||
val = val
|
||||
.map(function(item) {
|
||||
return `${encode(item)}`;
|
||||
}, '')
|
||||
.join('/');
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case QUERY_PARSING:
|
||||
switch (true) {
|
||||
case typeof val === 'string':
|
||||
val = encode(val);
|
||||
break;
|
||||
// objects offload to queryParams for encoding
|
||||
case typeof val === 'object':
|
||||
val = queryParams(val);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BODY_PARSING:
|
||||
// ignore body until we parse it here
|
||||
return item.split('\n\n')[0];
|
||||
// case METHOD_PARSING:
|
||||
case HEADER_PARSING:
|
||||
// passthrough/ignore method and headers until we parse them here
|
||||
}
|
||||
return `${item}${val}`;
|
||||
})
|
||||
.join('')
|
||||
.trim();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export default class extends Error {
|
||||
constructor(statusCode, message) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
export default class {
|
||||
constructor(method, url, headers, xhr) {
|
||||
this._xhr = xhr;
|
||||
import EventTarget from 'consul-ui/utils/dom/event-target/rsvp';
|
||||
export default class extends EventTarget {
|
||||
constructor(method, url, headers) {
|
||||
super();
|
||||
this._url = url;
|
||||
this._method = method;
|
||||
this._headers = headers;
|
||||
this._headers = {
|
||||
...headers,
|
||||
'content-type': 'application/json',
|
||||
'x-request-id': `${this._method} ${this._url}?${JSON.stringify(headers.body)}`,
|
||||
};
|
||||
if (typeof this._headers.body.index !== 'undefined') {
|
||||
// this should probably be in a response
|
||||
|
@ -17,13 +17,43 @@ export default class {
|
|||
headers() {
|
||||
return this._headers;
|
||||
}
|
||||
getId() {
|
||||
return this._headers['x-request-id'];
|
||||
open(xhr) {
|
||||
this._xhr = xhr;
|
||||
this.dispatchEvent({ type: 'open' });
|
||||
}
|
||||
abort() {
|
||||
this._xhr.abort();
|
||||
respond(data) {
|
||||
this.dispatchEvent({ type: 'message', data: data });
|
||||
}
|
||||
error(error) {
|
||||
// if the xhr was aborted (status = 0)
|
||||
// and this requests was aborted with a different status
|
||||
// switch the status
|
||||
if (error.statusCode === 0 && typeof this.statusCode !== 'undefined') {
|
||||
error.statusCode = this.statusCode;
|
||||
}
|
||||
this.dispatchEvent({ type: 'error', error: error });
|
||||
}
|
||||
close() {
|
||||
this.dispatchEvent({ type: 'close' });
|
||||
}
|
||||
connection() {
|
||||
return this._xhr;
|
||||
}
|
||||
abort(statusCode = 0) {
|
||||
if (this.headers()['content-type'] === 'text/event-stream') {
|
||||
this.statusCode = statusCode;
|
||||
const xhr = this.connection();
|
||||
// unsent and opened get aborted
|
||||
// headers and loading means wait for it
|
||||
// to finish for the moment
|
||||
if (xhr.readyState) {
|
||||
switch (xhr.readyState) {
|
||||
case 0:
|
||||
case 1:
|
||||
xhr.abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
export default function(parseHeaders, XHR) {
|
||||
return function(options) {
|
||||
const xhr = new (XHR || XMLHttpRequest)();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState === 4) {
|
||||
const headers = parseHeaders(this.getAllResponseHeaders().split('\n'));
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
const response = options.converters['text json'](this.response);
|
||||
options.success(headers, response, this.status, this.statusText);
|
||||
} else {
|
||||
options.error(headers, this.responseText, this.status, this.statusText, this.error);
|
||||
}
|
||||
options.complete(this.status);
|
||||
}
|
||||
};
|
||||
xhr.open(options.method, options.url, true);
|
||||
if (typeof options.headers === 'undefined') {
|
||||
options.headers = {};
|
||||
}
|
||||
const headers = {
|
||||
...options.headers,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
};
|
||||
Object.entries(headers).forEach(([key, value]) => xhr.setRequestHeader(key, value));
|
||||
options.beforeSend(xhr);
|
||||
xhr.send(options.body);
|
||||
return xhr;
|
||||
};
|
||||
}
|
|
@ -12,7 +12,6 @@ const apiDoubleHeaders = require('@hashicorp/api-double/lib/headers');
|
|||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
const express = require('express');
|
||||
//
|
||||
module.exports = {
|
||||
name: 'startup',
|
||||
|
@ -20,9 +19,6 @@ module.exports = {
|
|||
// TODO: see if we can move these into the project specific `/server` directory
|
||||
// instead of inside an addon
|
||||
|
||||
// Serve the coverage folder for easy viewing during development
|
||||
server.app.use('/coverage', express.static('coverage'));
|
||||
|
||||
// TODO: This should all be moved out into ember-cli-api-double
|
||||
// and we should figure out a way to get to the settings here for
|
||||
// so we can set this path name centrally in config
|
||||
|
|
|
@ -52,7 +52,6 @@
|
|||
"devDependencies": {
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
|
||||
"@ember/jquery": "^1.1.0",
|
||||
"@ember/optional-features": "^1.3.0",
|
||||
"@glimmer/component": "^1.0.0",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
const fs = require('fs');
|
||||
const promisify = require('util').promisify;
|
||||
const read = promisify(fs.readFile);
|
||||
const express = require('express');
|
||||
|
||||
module.exports = function(app, options) {
|
||||
// During development the proxy server has no way of
|
||||
|
@ -23,4 +24,6 @@ module.exports = function(app, options) {
|
|||
}
|
||||
next();
|
||||
});
|
||||
// Serve the coverage folder for easy viewing during development
|
||||
app.use('/coverage', express.static('coverage'));
|
||||
};
|
||||
|
|
|
@ -13,30 +13,28 @@ module('Integration | Adapter | kv', function(hooks) {
|
|||
test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:kv');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/kv/${id}?keys&dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/kv/${id}?keys&dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:kv');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/kv/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/kv/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:kv');
|
||||
|
|
|
@ -14,15 +14,14 @@ module('Integration | Adapter | oidc-provider', function(hooks) {
|
|||
test('requestForQuery returns the correct url/method', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:oidc-provider');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/internal/ui/oidc-auth-methods?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/internal/ui/oidc-auth-methods?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test('requestForQueryRecord returns the correct url/method', function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:oidc-provider');
|
||||
|
|
|
@ -20,29 +20,27 @@ module('Integration | Adapter | policy', function(hooks) {
|
|||
test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:policy');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/policies?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/acl/policies?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:policy');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/policy/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/acl/policy/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:policy');
|
||||
|
|
|
@ -13,29 +13,27 @@ module('Integration | Adapter | role', function(hooks) {
|
|||
test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:role');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/roles?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/acl/roles?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:role');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/role/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/acl/role/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:role');
|
||||
|
|
|
@ -13,44 +13,41 @@ module('Integration | Adapter | service', function(hooks) {
|
|||
test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:service');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/internal/ui/services?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/internal/ui/services?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQuery returns the correct url/method when called with gateway when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:service');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const gateway = 'gateway';
|
||||
const expected = `GET /v1/internal/ui/gateway-services-nodes/${gateway}?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/internal/ui/gateway-services-nodes/${gateway}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
gateway: gateway,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:service');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/health/service/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/health/service/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
});
|
||||
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
|
||||
|
|
|
@ -14,30 +14,28 @@ module('Integration | Adapter | session', function(hooks) {
|
|||
const adapter = this.owner.lookup('adapter:session');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const node = 'node-id';
|
||||
const expected = `GET /v1/session/node/${node}?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/session/node/${node}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: node,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:session');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/session/info/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/session/info/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForDeleteRecord returns the correct url/method when the nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:session');
|
||||
|
|
|
@ -13,57 +13,53 @@ module('Integration | Adapter | token', function(hooks) {
|
|||
test(`requestForQuery returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:token');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/tokens?dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/acl/tokens?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQuery returns the correct url/method when a policy is specified when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:token');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/tokens?policy=${id}&dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/acl/tokens?policy=${id}&dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
policy: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQuery returns the correct url/method when a role is specified when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:token');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/tokens?role=${id}&dc=${dc}`;
|
||||
let actual = adapter.requestForQuery(client.url, {
|
||||
const expected = `GET /v1/acl/tokens?role=${id}&dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQuery(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
role: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:token');
|
||||
const client = this.owner.lookup('service:client/http');
|
||||
const expected = `GET /v1/acl/token/${id}?dc=${dc}`;
|
||||
let actual = adapter.requestForQueryRecord(client.url, {
|
||||
const expected = `GET /v1/acl/token/${id}?dc=${dc}${
|
||||
shouldHaveNspace(nspace) ? `&ns=${nspace}` : ``
|
||||
}`;
|
||||
let actual = adapter.requestForQueryRecord(client.requestParams.bind(client), {
|
||||
dc: dc,
|
||||
id: id,
|
||||
ns: nspace,
|
||||
});
|
||||
actual = actual.split('\n');
|
||||
assert.equal(actual.shift().trim(), expected);
|
||||
actual = actual.join('\n').trim();
|
||||
assert.equal(actual, `${shouldHaveNspace(nspace) ? `ns=${nspace}` : ``}`);
|
||||
assert.equal(`${actual.method} ${actual.url}`, expected);
|
||||
});
|
||||
test(`requestForCreateRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
|
||||
const adapter = this.owner.lookup('adapter:token');
|
||||
|
|
|
@ -1,9 +1,36 @@
|
|||
import Application from '../app';
|
||||
import config from '../config/environment';
|
||||
import { setApplication } from '@ember/test-helpers';
|
||||
import { registerWaiter } from '@ember/test';
|
||||
import './helpers/flash-message';
|
||||
import start from 'ember-exam/test-support/start';
|
||||
|
||||
import ClientConnections from 'consul-ui/services/client/connections';
|
||||
|
||||
let activeRequests = 0;
|
||||
registerWaiter(function() {
|
||||
return activeRequests === 0;
|
||||
});
|
||||
ClientConnections.reopen({
|
||||
addVisibilityChange: function() {
|
||||
// for the moment don't listen for tab hiding during testing
|
||||
// TODO: make this controllable from testing so we can fake a tab hide
|
||||
},
|
||||
purge: function() {
|
||||
const res = this._super(...arguments);
|
||||
activeRequests = 0;
|
||||
return res;
|
||||
},
|
||||
acquire: function() {
|
||||
activeRequests++;
|
||||
return this._super(...arguments);
|
||||
},
|
||||
release: function() {
|
||||
const res = this._super(...arguments);
|
||||
activeRequests--;
|
||||
return res;
|
||||
},
|
||||
});
|
||||
const application = Application.create(config.APP);
|
||||
application.inject('component:copy-button', 'clipboard', 'service:clipboard/local-storage');
|
||||
setApplication(application);
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | client/connections', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:client/connections');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Service | client/transports/xhr', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
// Replace this with your real tests.
|
||||
test('it exists', function(assert) {
|
||||
let service = this.owner.lookup('service:client/transports/xhr');
|
||||
assert.ok(service);
|
||||
});
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
import getObjectPool from 'consul-ui/utils/get-object-pool';
|
||||
import { module, skip } from 'qunit';
|
||||
import test from 'ember-sinon-qunit/test-support/test';
|
||||
|
||||
module('Unit | Utility | get object pool', function() {
|
||||
skip('Decide what to do if you add 2 objects with the same id');
|
||||
test('acquire adds objects', function(assert) {
|
||||
const actual = [];
|
||||
const expected = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-123',
|
||||
};
|
||||
const expected2 = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-456',
|
||||
};
|
||||
const pool = getObjectPool(function() {}, 10, actual);
|
||||
pool.acquire(expected, expected.id);
|
||||
assert.deepEqual(actual[0], expected);
|
||||
pool.acquire(expected2, expected2.id);
|
||||
assert.deepEqual(actual[1], expected2);
|
||||
});
|
||||
test('acquire adds objects and returns the id', function(assert) {
|
||||
const arr = [];
|
||||
const expected = 'hi-there-123';
|
||||
const obj = {
|
||||
hi: 'there',
|
||||
id: expected,
|
||||
};
|
||||
const pool = getObjectPool(function() {}, 10, arr);
|
||||
const actual = pool.acquire(obj, expected);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('acquire adds objects, and disposes when there is no room', function(assert) {
|
||||
const actual = [];
|
||||
const expected = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-123',
|
||||
};
|
||||
const expected2 = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-456',
|
||||
};
|
||||
const dispose = this.stub()
|
||||
.withArgs(expected)
|
||||
.returnsArg(0);
|
||||
const pool = getObjectPool(dispose, 1, actual);
|
||||
pool.acquire(expected, expected.id);
|
||||
assert.deepEqual(actual[0], expected);
|
||||
pool.acquire(expected2, expected2.id);
|
||||
assert.deepEqual(actual[0], expected2);
|
||||
assert.ok(dispose.calledOnce);
|
||||
});
|
||||
test('it disposes', function(assert) {
|
||||
const arr = [];
|
||||
const expected = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-123',
|
||||
};
|
||||
const expected2 = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-456',
|
||||
};
|
||||
const dispose = this.stub().returnsArg(0);
|
||||
const pool = getObjectPool(dispose, 2, arr);
|
||||
const id = pool.acquire(expected, expected.id);
|
||||
assert.deepEqual(arr[0], expected);
|
||||
pool.acquire(expected2, expected2.id);
|
||||
assert.deepEqual(arr[1], expected2);
|
||||
const actual = pool.dispose(id);
|
||||
assert.ok(dispose.calledOnce);
|
||||
assert.equal(arr.length, 1, 'object was removed from array');
|
||||
assert.deepEqual(actual, expected, 'returned object is expected object');
|
||||
assert.deepEqual(arr[0], expected2, 'object in the pool is expected object');
|
||||
});
|
||||
test('it purges', function(assert) {
|
||||
const arr = [];
|
||||
const expected = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-123',
|
||||
};
|
||||
const expected2 = {
|
||||
hi: 'there',
|
||||
id: 'hi-there-456',
|
||||
};
|
||||
const dispose = this.stub().returnsArg(0);
|
||||
const pool = getObjectPool(dispose, 2, arr);
|
||||
pool.acquire(expected, expected.id);
|
||||
assert.deepEqual(arr[0], expected);
|
||||
pool.acquire(expected2, expected2.id);
|
||||
assert.deepEqual(arr[1], expected2);
|
||||
const actual = pool.purge();
|
||||
assert.ok(dispose.calledTwice, 'dispose was called on everything');
|
||||
assert.equal(arr.length, 0, 'the pool is empty');
|
||||
assert.deepEqual(actual[0], expected, 'the first purged object is correct');
|
||||
assert.deepEqual(actual[1], expected2, 'the second purged object is correct');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import createHeaders from 'consul-ui/utils/http/create-headers';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | http/create-headers', function() {
|
||||
const parseHeaders = createHeaders();
|
||||
test('it converts lines of header-like strings into an object', function(assert) {
|
||||
const expected = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Consul-Index': '1',
|
||||
};
|
||||
const lines = `
|
||||
Content-Type: application/json
|
||||
X-Consul-Index: 1
|
||||
`.split('\n');
|
||||
const actual = parseHeaders(lines);
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import createQueryParams from 'consul-ui/utils/http/create-query-params';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | http/create-query-params', function() {
|
||||
const stringifyQueryParams = createQueryParams(str => str);
|
||||
test('it turns objects into query params formatted strings', function(assert) {
|
||||
const expected = 'something=here&another=variable';
|
||||
const actual = stringifyQueryParams({
|
||||
something: 'here',
|
||||
another: 'variable',
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it ignores undefined properties', function(assert) {
|
||||
const expected = 'something=here';
|
||||
const actual = stringifyQueryParams({
|
||||
something: 'here',
|
||||
another: undefined,
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it stringifies nested objects', function(assert) {
|
||||
const expected = 'something=here&another[something]=here&another[another][something]=here';
|
||||
const actual = stringifyQueryParams({
|
||||
something: 'here',
|
||||
another: {
|
||||
something: 'here',
|
||||
another: {
|
||||
something: 'here',
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it only adds the property if the value is null', function(assert) {
|
||||
const expected = 'something&another=here';
|
||||
const actual = stringifyQueryParams({
|
||||
something: null,
|
||||
another: 'here',
|
||||
});
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
|
@ -1,37 +1,43 @@
|
|||
import { module, skip } from 'qunit';
|
||||
import test from 'ember-sinon-qunit/test-support/test';
|
||||
import createURL from 'consul-ui/utils/createURL';
|
||||
import createURL from 'consul-ui/utils/http/create-url';
|
||||
import createQueryParams from 'consul-ui/utils/http/create-query-params';
|
||||
|
||||
module('Unit | Utils | createURL', function() {
|
||||
module('Unit | Utils | http/create-url', function() {
|
||||
skip("it isn't isolated enough, mock encodeURIComponent");
|
||||
const url = createURL(encodeURIComponent, createQueryParams(encodeURIComponent));
|
||||
test('it passes the values to encode', function(assert) {
|
||||
const url = createURL(encodeURIComponent);
|
||||
const actual = url`/v1/url?${{ query: 'to encode', 'key with': ' spaces ' }}`;
|
||||
const expected = '/v1/url?query=to%20encode&key%20with=%20spaces%20';
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it adds a query string key without an `=` if the query value is `null`', function(assert) {
|
||||
const url = createURL(encodeURIComponent);
|
||||
const actual = url`/v1/url?${{ 'key with space': null }}`;
|
||||
const expected = '/v1/url?key%20with%20space';
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it returns a string when passing an array', function(assert) {
|
||||
const url = createURL(encodeURIComponent);
|
||||
const actual = url`/v1/url/${['raw values', 'to', 'encode']}`;
|
||||
const expected = '/v1/url/raw%20values/to/encode';
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test('it returns a string when passing a string', function(assert) {
|
||||
const url = createURL(encodeURIComponent);
|
||||
const actual = url`/v1/url/${'raw values to encode'}`;
|
||||
const expected = '/v1/url/raw%20values%20to%20encode';
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test("it doesn't add a query string prop/value is the value is undefined", function(assert) {
|
||||
const url = createURL(encodeURIComponent);
|
||||
const actual = url`/v1/url?${{ key: undefined }}`;
|
||||
const expected = '/v1/url?';
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
test("it doesn't encode headers", function(assert) {
|
||||
const actual = url`
|
||||
/v1/url/${'raw values to encode'}
|
||||
Header: %value
|
||||
`;
|
||||
const expected = `/v1/url/raw%20values%20to%20encode
|
||||
Header: %value`;
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import HttpError from 'consul-ui/utils/http/error';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | http/error', function() {
|
||||
// Replace this with your real tests.
|
||||
test('it works', function(assert) {
|
||||
const result = new HttpError();
|
||||
assert.ok(result);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
import httpXhr from 'consul-ui/utils/http/xhr';
|
||||
import { module, test } from 'qunit';
|
||||
|
||||
module('Unit | Utility | http/xhr', function() {
|
||||
// Replace this with your real tests.
|
||||
test('it works', function(assert) {
|
||||
let result = httpXhr();
|
||||
assert.ok(result);
|
||||
});
|
||||
});
|
|
@ -1026,18 +1026,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6"
|
||||
integrity sha512-VmVq/8saCaPdesQmftPqbFtxJWrzxNGSQ+e8x8LLe3Hjm36pJ04Q8LeORGZkAeOhldoUX9seLGmSaHeXkIqoog==
|
||||
|
||||
"@ember/jquery@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@ember/jquery/-/jquery-1.1.0.tgz#33d062610a5ceaa5c5c8a3187f870d47d6595940"
|
||||
integrity sha512-zePT3LiK4/2bS4xafrbOlwoLJrDFseOZ95OOuVDyswv8RjFL+9lar+uxX6+jxRb0w900BcQSWP/4nuFSK6HXXw==
|
||||
dependencies:
|
||||
broccoli-funnel "^2.0.2"
|
||||
broccoli-merge-trees "^3.0.2"
|
||||
ember-cli-babel "^7.11.1"
|
||||
ember-cli-version-checker "^3.1.3"
|
||||
jquery "^3.4.1"
|
||||
resolve "^1.11.1"
|
||||
|
||||
"@ember/optional-features@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ember/optional-features/-/optional-features-1.3.0.tgz#d7da860417b85a56cec88419f30da5ee1dde2756"
|
||||
|
@ -5100,7 +5088,7 @@ ember-cli-babel@^7.1.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.1.3, ember-cl
|
|||
ensure-posix-path "^1.0.2"
|
||||
semver "^5.5.0"
|
||||
|
||||
ember-cli-babel@^7.11.1, ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.8.0:
|
||||
ember-cli-babel@^7.12.0, ember-cli-babel@^7.13.0, ember-cli-babel@^7.13.2, ember-cli-babel@^7.17.2, ember-cli-babel@^7.18.0, ember-cli-babel@^7.8.0:
|
||||
version "7.18.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.18.0.tgz#e979b73eee00cd93f63452c6170d045e8832f29c"
|
||||
integrity sha512-OLPfYD8wSfCrmGHcUf8zEfySSvbAL+5Qp2RWLycJIMaBZhg+SncKj5kVkL3cPJR5n2hVHPdfmKTQIYjOYl6FnQ==
|
||||
|
|
Loading…
Reference in New Issue