ui: Split ember-data service model into service/service-instance (#8557)

This commit is contained in:
John Cowen 2020-08-26 15:24:30 +01:00 committed by GitHub
parent ad9f118d14
commit 1846e6316c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 348 additions and 228 deletions

View File

@ -0,0 +1,23 @@
import Adapter from './application';
// TODO: Update to use this.formatDatacenter()
export default Adapter.extend({
requestForQuery: function(request, { dc, ns, index, id, uri }) {
if (typeof id === 'undefined') {
throw new Error('You must specify an id');
}
return request`
GET /v1/health/service/${id}?${{ dc }}
X-Request-ID: ${uri}
${{
...this.formatNspace(ns),
index,
}}
`;
},
requestForQueryRecord: function(request, { dc, ns, index, id, uri }) {
// query and queryRecord both use the same endpoint
// they are just serialized differently
return this.requestForQuery(...arguments);
},
});

View File

@ -20,7 +20,7 @@
<ConsulInstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
<ConsulInstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
{{/if}}
{{#if (get proxies item.Service.ID)}}
{{#if item.ProxyInstance}}
<dl class="proxy">
<dt>
<Tooltip>

View File

@ -1,9 +1,11 @@
import Controller from '@ember/controller';
import { get } from '@ember/object';
import { alias } from '@ember/object/computed';
import { inject as service } from '@ember/service';
export default Controller.extend({
dom: service('dom'),
notify: service('flashMessages'),
item: alias('items.firstObject'),
actions: {
error: function(e) {
if (e.target.readyState === 1) {

View File

@ -1,9 +1,6 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { alias } from '@ember/object/computed';
export default Controller.extend({
items: alias('item.Nodes'),
queryParams: {
sortBy: 'sort',
search: {
@ -11,12 +8,4 @@ export default Controller.extend({
replace: true,
},
},
keyedProxies: computed('proxies.[]', function() {
const proxies = {};
this.proxies.forEach(item => {
proxies[item.ServiceProxy.DestinationServiceID] = true;
});
return proxies;
}),
});

View File

@ -0,0 +1,31 @@
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import { filter, alias } from '@ember/object/computed';
export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'Node.Node,Service.ID';
export default Model.extend({
[PRIMARY_KEY]: attr('string'),
[SLUG_KEY]: attr('string'),
Datacenter: attr('string'),
// ProxyInstance is the ember-data model relationship
ProxyInstance: belongsTo('Proxy'),
// Proxy is the actual JSON api response
Proxy: attr(),
Node: attr(),
Service: attr(),
Checks: attr(),
SyncTime: attr('number'),
meta: attr(),
Tags: alias('Service.Tags'),
Meta: alias('Service.Meta'),
Namespace: alias('Service.Namespace'),
ServiceChecks: filter('Checks', function(item, i, arr) {
return item.ServiceID !== '';
}),
NodeChecks: filter('Checks', function(item, i, arr) {
return item.ServiceID === '';
}),
});

View File

@ -14,8 +14,8 @@ export default Model.extend({
},
}),
InstanceCount: attr('number'),
ProxyFor: attr(),
Proxy: attr(),
ProxyFor: attr(),
Kind: attr('string'),
ExternalSources: attr(),
GatewayConfig: attr(),

View File

@ -10,7 +10,7 @@ export default Route.extend({
return this.modelFor(parent);
},
afterModel: function(model, transition) {
if (get(model, 'item.Kind') !== 'mesh-gateway') {
if (get(model, 'item.Service.Kind') !== 'mesh-gateway') {
const parent = this.routeName
.split('.')
.slice(0, -1)

View File

@ -10,7 +10,7 @@ export default Route.extend({
return this.modelFor(parent);
},
afterModel: function(model, transition) {
if (get(model, 'item.Kind') !== 'connect-proxy') {
if (get(model, 'item.Service.Kind') !== 'connect-proxy') {
const parent = this.routeName
.split('.')
.slice(0, -1)

View File

@ -13,13 +13,15 @@ export default Route.extend({
slug: params.name,
dc: dc,
nspace: nspace,
item: this.data.source(uri => uri`/${nspace}/${dc}/service/${params.name}`),
items: this.data.source(
uri => uri`/${nspace}/${dc}/service-instances/for-service/${params.name}`
),
urls: this.settings.findBySlug('urls'),
proxies: [],
})
.then(model => {
return ['connect-proxy', 'mesh-gateway', 'ingress-gateway', 'terminating-gateway'].includes(
get(model, 'item.Service.Kind')
get(model, 'items.firstObject.Service.Kind')
)
? model
: hash({
@ -31,7 +33,9 @@ export default Route.extend({
});
})
.then(model => {
return ['ingress-gateway', 'terminating-gateway'].includes(get(model, 'item.Service.Kind'))
return ['ingress-gateway', 'terminating-gateway'].includes(
get(model, 'items.firstObject.Service.Kind')
)
? hash({
...model,
gatewayServices: this.data.source(

View File

@ -0,0 +1,45 @@
import Serializer from './application';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/service-instance';
export default Serializer.extend({
primaryKey: PRIMARY_KEY,
slugKey: SLUG_KEY,
respondForQuery: function(respond, query) {
return this._super(function(cb) {
return respond(function(headers, body) {
if (body.length === 0) {
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
return cb(headers, body);
});
}, query);
},
respondForQueryRecord: function(respond, query) {
return this._super(function(cb) {
return respond(function(headers, body) {
body = body.find(function(item) {
return item.Node.Node === query.node && item.Service.ID === query.serviceId;
});
if (typeof body === 'undefined') {
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
body.Namespace = body.Service.Namespace;
return cb(headers, body);
});
}, query);
},
});

View File

@ -8,7 +8,8 @@ export default Service.extend({
gateways: service('repository/service'),
services: service('repository/service'),
service: service('repository/service'),
['service-instance']: service('repository/service'),
['service-instance']: service('repository/service-instance'),
['service-instances']: service('repository/service-instance'),
proxies: service('repository/proxy'),
['proxy-instance']: service('repository/proxy'),
['discovery-chain']: service('repository/discovery-chain'),
@ -67,6 +68,14 @@ export default Service.extend({
break;
}
break;
case 'service-instances':
[method, ...slug] = rest;
switch (method) {
case 'for-service':
find = configuration => repo.findByService(slug, dc, nspace, configuration);
break;
}
break;
case 'coordinates':
[method, ...slug] = rest;
switch (method) {
@ -102,12 +111,15 @@ export default Service.extend({
case 'token':
find = configuration => repo.self(rest[1], dc);
break;
case 'service':
case 'discovery-chain':
case 'node':
find = configuration => repo.findBySlug(rest[0], dc, nspace, configuration);
break;
case 'service-instance':
// id, node, service
find = configuration =>
repo.findBySlug(rest[0], rest[1], rest[2], dc, nspace, configuration);
break;
case 'proxy-instance':
// id, node, service
find = configuration =>

View File

@ -19,7 +19,20 @@ export default RepositoryService.extend({
query.index = configuration.cursor;
query.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
return this.store.query(this.getModelName(), query).then(items => {
items.forEach(item => {
// swap out the id for the services id
// so we can then assign the proxy to it if it exists
const id = JSON.parse(item.uid);
id.pop();
id.push(item.ServiceProxy.DestinationServiceID);
const service = this.store.peekRecord('service-instance', JSON.stringify(id));
if (service) {
set(service, 'ProxyInstance', item);
}
});
return items;
});
},
findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) {
return this.findAllBySlug(slug, dc, nspace, configuration).then(function(items) {

View File

@ -0,0 +1,33 @@
import RepositoryService from 'consul-ui/services/repository';
const modelName = 'service-instance';
export default RepositoryService.extend({
getModelName: function() {
return modelName;
},
findByService: function(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,
nspace: nspace,
id: slug,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
}
return this.store.query(this.getModelName(), query);
},
findBySlug: function(serviceId, node, service, dc, nspace, configuration = {}) {
const query = {
dc: dc,
ns: nspace,
serviceId: serviceId,
node: node,
id: service,
};
if (typeof configuration.cursor !== 'undefined') {
query.index = configuration.cursor;
query.uri = configuration.uri;
}
return this.store.queryRecord(this.getModelName(), query);
},
});

View File

@ -1,74 +1,9 @@
import RepositoryService from 'consul-ui/services/repository';
import { get, set } from '@ember/object';
const modelName = 'service';
export default RepositoryService.extend({
getModelName: function() {
return modelName;
},
findBySlug: function(slug, dc) {
return this._super(...arguments).then(function(item) {
// TODO: Move this to the Serializer
const nodes = get(item, 'Nodes');
if (nodes.length === 0) {
// TODO: Add an store.error("404", "message") or similar
// or move all this to serializer
const e = new Error();
e.errors = [
{
status: '404',
title: 'Not found',
},
];
throw e;
}
const service = get(nodes, 'firstObject');
// TODO: Use [...new Set()] instead of uniq
const tags = nodes
.reduce(function(prev, item) {
return prev.concat(get(item, 'Service.Tags') || []);
}, [])
.uniq();
set(service, 'Tags', tags);
set(service, 'Nodes', nodes);
set(service, 'meta', get(item, 'meta'));
set(service, 'Namespace', get(item, 'Namespace'));
return service;
});
},
findInstanceBySlug: function(id, node, slug, dc, nspace, configuration) {
return this.findBySlug(slug, dc, nspace, configuration).then(function(item) {
// TODO: Move this to the Serializer
// Loop through all the service instances and pick out the one
// that has the same service id AND node name
// node names are unique per datacenter
const i = item.Nodes.findIndex(function(item) {
return item.Service.ID === id && item.Node.Node === node;
});
if (i !== -1) {
const service = item.Nodes[i].Service;
service.Node = item.Nodes[i].Node;
service.ServiceChecks = item.Nodes[i].Checks.filter(function(item) {
return item.ServiceID != '';
});
service.NodeChecks = item.Nodes[i].Checks.filter(function(item) {
return item.ServiceID == '';
});
set(service, 'meta', get(item, 'meta'));
set(service, 'Namespace', get(item, 'Namespace'));
return service;
}
// TODO: Add an store.error("404", "message") or similar
// or move all this to serializer
const e = new Error();
e.errors = [
{
status: '404',
title: 'Unable to find instance',
},
];
throw e;
});
},
findGatewayBySlug: function(slug, dc, nspace, configuration = {}) {
const query = {
dc: dc,

View File

@ -1,4 +1,4 @@
{{title item.ID}}
{{title item.Service.ID}}
<EventSource @src={{item}} @onerror={{action "error"}} />
<EventSource @src={{proxy}} />
<EventSource @src={{proxyMeta}} />
@ -9,12 +9,12 @@
<BlockSlot @name="breadcrumbs">
<ol>
<li><a data-test-back href={{href-to 'dc.services'}}>All Services</a></li>
<li><a data-test-back href={{href-to 'dc.services.show'}}>Service ({{item.Service}})</a></li>
<li><a data-test-back href={{href-to 'dc.services.show'}}>Service ({{item.Service.Service}})</a></li>
</ol>
</BlockSlot>
<BlockSlot @name="header">
<h1>
{{ item.ID }}
{{ item.Service.ID }}
</h1>
<ConsulExternalSource @item={{item}} />
<ConsulKind @item={{item}} @withInfo={{true}} />
@ -22,7 +22,7 @@
<BlockSlot @name="nav">
<dl>
<dt>Service Name</dt>
<dd><a href="{{href-to 'dc.services.show' item.Service}}">{{item.Service}}</a></dd>
<dd><a href="{{href-to 'dc.services.show' item.Service.Service}}">{{item.Service.Service}}</a></dd>
</dl>
<dl>
<dt>Node Name</dt>
@ -30,7 +30,7 @@
</dl>
</BlockSlot>
<BlockSlot @name="actions">
{{#let (or item.Address item.Node.Address) as |address|}}
{{#let (or item.Service.Address item.Node.Address) as |address|}}
<CopyButton @value={{address}} @name="Address">{{address}}</CopyButton>
{{/let}}
</BlockSlot>
@ -40,7 +40,7 @@
(array
(hash label="Health Checks" href=(href-to "dc.services.instance.healthchecks") selected=(is-href "dc.services.instance.healthchecks"))
(if
(eq item.Kind 'mesh-gateway')
(eq item.Service.Kind 'mesh-gateway')
(hash label="Addresses" href=(href-to "dc.services.instance.addresses") selected=(is-href "dc.services.instance.addresses")) ""
)
(if proxy

View File

@ -1,9 +1,9 @@
<div id="addresses" class="tab-section">
<div role="tabpanel">
{{#if item.TaggedAddresses }}
{{#if item.Service.TaggedAddresses }}
<TabularCollection
data-test-addresses
@items={{object-entries item.TaggedAddresses}} as |taggedAddress index|
@items={{object-entries item.Service.TaggedAddresses}} as |taggedAddress index|
>
<BlockSlot @name="header">
<th>Tag</th>

View File

@ -1,18 +1,18 @@
<div class="tab-section">
<div role="tabpanel">
{{#if (gt proxy.Proxy.Upstreams.length 0)}}
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
<section class="proxy-upstreams">
<h3>Upstreams</h3>
<ConsulUpstreamInstanceList @items={{proxy.Proxy.Upstreams}} @dc={{dc}} @nspace={{nspace}} />
<ConsulUpstreamInstanceList @items={{proxy.Service.Proxy.Upstreams}} @dc={{dc}} @nspace={{nspace}} />
</section>
{{/if}}
{{#if (gt proxy.Proxy.Expose.Paths.length 0)}}
{{#if (gt proxy.Service.Proxy.Expose.Paths.length 0)}}
<section class="proxy-exposed-paths">
<h3>Exposed paths</h3>
<p>
The following list shows individual HTTP paths exposed through Envoy for external services like Prometheus. Read more about this in our documentation.
</p>
<ConsulExposedPathList @items={{proxy.Proxy.Expose.Paths}} @address={{item.Address}} />
<ConsulExposedPathList @items={{proxy.Service.Proxy.Expose.Paths}} @address={{item.Address}} />
</section>
{{/if}}
{{#if (or (gt proxy.ServiceChecks.length 0) (gt proxy.NodeChecks.length 0))}}

View File

@ -1,9 +1,9 @@
{{title item.Service.Service}}
<EventSource @src={{item}} @onerror={{action "error"}} />
<EventSource @src={{items}} @onerror={{action "error"}} />
<EventSource @src={{chain}} />
<EventSource @src={{intentions}} />
<EventSource @src={{proxies}} />
<EventSource @src={{gatewayServices}} />
{{title item.Service.Service}}
<AppView @class="service show">
<BlockSlot @name="notification" as |status type|>
{{partial 'dc/services/notifications'}}

View File

@ -9,7 +9,7 @@
{{/if}}
<ChangeableSet @dispatcher={{searchable 'serviceInstance' items}} @terms={{search}}>
<BlockSlot @name="set" as |filtered|>
<ConsulServiceInstanceList @routeName="dc.services.instance" @items={{filtered}} @proxies={{keyedProxies}}/>
<ConsulServiceInstanceList @routeName="dc.services.instance" @items={{filtered}}/>
</BlockSlot>
<BlockSlot @name="empty">
<EmptyState>

View File

@ -1,8 +1,9 @@
<div id="tags" class="tab-section">
<div role="tabpanel">
{{#if (gt item.Tags.length 0) }}
<TagList @item={{item}} />
{{else}}
{{#let (flatten (map-by "Tags" items)) as |tags|}}
{{#if (gt tags.length 0) }}
<TagList @item={{hash Tags=tags}} />
{{else}}
<EmptyState>
<BlockSlot @name="body">
<p>
@ -10,6 +11,7 @@
</p>
</BlockSlot>
</EmptyState>
{{/if}}
{{/if}}
{{/let}}
</div>
</div>

View File

@ -1,3 +1,4 @@
import { get } from '@ember/object';
export default function(foreignKey, nspaceKey, hash = JSON.stringify) {
return function(primaryKey, slugKey, foreignKeyValue) {
if (foreignKeyValue == null || foreignKeyValue.length < 1) {
@ -6,12 +7,12 @@ export default function(foreignKey, nspaceKey, hash = JSON.stringify) {
return function(item) {
const slugKeys = slugKey.split(',');
const slugValues = slugKeys.map(function(slugKey) {
if (item[slugKey] == null || item[slugKey].length < 1) {
if (get(item, slugKey) == null || get(item, slugKey).length < 1) {
throw new Error('Unable to create fingerprint, missing slug');
}
return item[slugKey];
return get(item, slugKey);
});
const nspaceValue = item[nspaceKey] || 'default';
const nspaceValue = get(item, nspaceKey) || 'default';
return {
...item,
...{

View File

@ -0,0 +1,36 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { env } from '../../../env';
const shouldHaveNspace = function(nspace) {
return typeof nspace !== 'undefined' && env('CONSUL_NSPACES_ENABLED');
};
module('Integration | Adapter | service-instance', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
const id = 'service-name';
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`requestForQueryRecord returns the correct url/method when nspace is ${nspace}`, function(assert) {
const adapter = this.owner.lookup('adapter:service-instance');
const client = this.owner.lookup('service:client/http');
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,
});
assert.equal(`${actual.method} ${actual.url}`, expected);
});
});
test("requestForQueryRecord throws if you don't specify an id", function(assert) {
const adapter = this.owner.lookup('adapter:service-instance');
const client = this.owner.lookup('service:client/http');
assert.throws(function() {
adapter.requestForQueryRecord(client.url, {
dc: dc,
});
});
});
});

View File

@ -0,0 +1,54 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
import {
HEADERS_SYMBOL as META,
HEADERS_DATACENTER as DC,
HEADERS_NAMESPACE as NSPACE,
} from 'consul-ui/utils/http/consul';
module('Integration | Serializer | service-instance', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) {
const serializer = this.owner.lookup('serializer:service-instance');
const id = 'service-name';
const node = 'node-0';
const request = {
url: `/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
};
return get(request.url).then(function(payload) {
payload[0].Node.Node = node;
payload[0].Service.ID = id;
const expected = {
...payload[0],
Datacenter: dc,
[META]: {
[DC.toLowerCase()]: dc,
[NSPACE.toLowerCase()]: payload[0].Service.Namespace || undefinedNspace,
},
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${node}","${id}"]`,
};
const actual = serializer.respondForQueryRecord(
function(cb) {
const headers = {};
const body = payload;
return cb(headers, body);
},
{
dc: dc,
ns: nspace,
id: id,
node: node,
serviceId: id,
}
);
assert.deepEqual(actual, expected);
});
});
});
});

View File

@ -1,11 +1,6 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { get } from 'consul-ui/tests/helpers/api';
import {
HEADERS_SYMBOL as META,
HEADERS_DATACENTER as DC,
HEADERS_NAMESPACE as NSPACE,
} from 'consul-ui/utils/http/consul';
module('Integration | Serializer | service', function(hooks) {
setupTest(hooks);
const dc = 'dc-1';
@ -73,40 +68,5 @@ module('Integration | Serializer | service', function(hooks) {
assert.deepEqual(actual, expected);
});
});
test(`respondForQueryRecord returns the correct data for item endpoint when nspace is ${nspace}`, function(assert) {
const serializer = this.owner.lookup('serializer:service');
const id = 'service-name';
const request = {
url: `/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
};
return get(request.url).then(function(payload) {
const expected = {
Datacenter: dc,
[META]: {
[DC.toLowerCase()]: dc,
[NSPACE.toLowerCase()]: payload[0].Service.Namespace || undefinedNspace,
},
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`,
Name: id,
Nodes: payload,
};
const actual = serializer.respondForQueryRecord(
function(cb) {
const headers = {};
const body = payload;
return cb(headers, body);
},
{
dc: dc,
ns: nspace,
id: id,
}
);
assert.deepEqual(actual, expected);
});
});
});
});

View File

@ -7,89 +7,9 @@ moduleFor(`service:repository/${NAME}`, `Integration | Service | ${NAME}`, {
integration: true,
});
const dc = 'dc-1';
const id = 'token-name';
const now = new Date().getTime();
const undefinedNspace = 'default';
[undefinedNspace, 'team-1', undefined].forEach(nspace => {
test(`findInstanceBySlug calls findBySlug with the correct arguments when nspace is ${nspace}`, function(assert) {
assert.expect(4);
const id = 'id';
const slug = 'slug';
const node = 'node-name';
const datacenter = 'dc-1';
const conf = {
cursor: 1,
};
const service = this.subject();
service.findBySlug = function(slug, dc, nspace, configuration) {
assert.equal(
arguments.length,
4,
'Expected to be called with the correct number of arguments'
);
assert.equal(dc, datacenter);
assert.deepEqual(configuration, conf);
return Promise.resolve({ Nodes: [] });
};
// This will throw an error as we don't resolve any services that match what was requested
// so a 404 is the correct error response
return service.findInstanceBySlug(id, slug, node, datacenter, nspace, conf).catch(function(e) {
assert.equal(e.errors[0].status, '404');
});
});
test(`findBySlug returns the correct data for item endpoint when the nspace is ${nspace}`, function(assert) {
return repo(
'Service',
'findBySlug',
this.subject(),
function(stub) {
return stub(
`/v1/health/service/${id}?dc=${dc}${
typeof nspace !== 'undefined' ? `&ns=${nspace}` : ``
}`,
{
CONSUL_NODE_COUNT: 1,
}
);
},
function(service) {
return service.findBySlug(id, dc, nspace || undefinedNspace);
},
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,
Namespace: payload[0].Service.Namespace || undefinedNspace,
uid: `["${payload[0].Service.Namespace || undefinedNspace}","${dc}","${id}"]`,
}
);
const nodes = payload.Nodes;
const service = payload.Nodes[0];
service.Nodes = nodes;
service.Tags = [...new Set(payload.Nodes[0].Service.Tags)];
service.Namespace = payload.Namespace;
service.meta = {
cacheControl: undefined,
cursor: undefined,
dc: dc,
nspace: payload.Namespace,
};
return service;
})
);
}
);
});
test(`findGatewayBySlug returns the correct data for list endpoint when nspace is ${nspace}`, function(assert) {
get(this.subject(), 'store').serializerFor(NAME).timestamp = function() {
return now;

View File

@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Adapter | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let adapter = this.owner.lookup('adapter:service-instance');
assert.ok(adapter);
});
});

View File

@ -0,0 +1,13 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Model | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let store = this.owner.lookup('service:store');
let model = store.createRecord('service-instance', {});
assert.ok(model);
});
});

View File

@ -0,0 +1,23 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Serializer | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let store = this.owner.lookup('service:store');
let serializer = store.serializerFor('service-instance');
assert.ok(serializer);
});
test('it serializes records', function(assert) {
let store = this.owner.lookup('service:store');
let record = store.createRecord('service-instance', {});
let serializedRecord = record.serialize();
assert.ok(serializedRecord);
});
});

View File

@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Repository | service-instance', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
const repo = this.owner.lookup('service:repository/service-instance');
assert.ok(repo);
});
});