refactor: 💡 use shared mocks in desktop

This commit is contained in:
Randall Morey committed 2020-11-23 14:26:01 -05:00
1 parent 0c6187e707
commit f5b477d9c2
42 files changed
+4 -854

No files matched your search

+4
View File
@@ -28,6 +28,10 @@ module.exports = function (environment) {
isElectron: Boolean(process.env.EMBER_CLI_ELECTRON),
'ember-cli-mirage': {
directory: '../../addons/api/mirage'
},
api: {
host: API_HOST,
namespace: 'v1',
-149
View File
@@ -1,149 +0,0 @@
import Ember from 'ember';
import config from '../config/environment';
import { authHandler, deauthHandler } from './route-handlers/auth';
import { pickRandomStatusString } from './factories/session';
import { Response } from 'miragejs';
export default function() {
// make this `http://localhost:8080`, for example, if your API is on a different server
// this.urlPrefix = '';
// make this `/api`, for example, if your API is namespaced
this.namespace = config.api.namespace;
// Allow any configured API host to passthrough, which is useful in
// development for testing against a locally running backend.
if (config.api.host) this.passthrough(`${config.api.host}/**`);
this.passthrough();
// Scope resources
this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => {
// Default parent scope is global
if (!scope_id) scope_id = 'global';
const results = scopes.where(scope => {
return scope.scope ? scope.scope.id === scope_id : false
});
return results;
});
this.get('/scopes/:id');
// Auth & IAM resources
this.get('/auth-methods', ({ authMethods }, { queryParams: { scope_id: scopeId } }) => {
return authMethods.where({ scopeId });
});
this.get('/auth-methods/:id');
// Auth Method Accounts
this.get('/accounts', ({ accounts }, { queryParams: { auth_method_id: authMethodId } }) => {
return accounts.where({ authMethodId });
});
this.get('/accounts/:id');
this.post('/accounts/:idMethod', function ({ accounts }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
const method = idMethod.split(':')[1];
const account = accounts.find(id);
const updatedAttrs = {
version: attrs.version,
attributes: account.attributes
};
// Set new password
if (method === 'change-password') {
updatedAttrs.attributes.password = attrs.new_password;
}
return account.update(updatedAttrs);
});
// Authenticate/deauthenticate routes
this.post('/auth-methods/:id_method', authHandler);
this.post('/scopes/:id_method', deauthHandler);
// Other resources
// host-catalog
this.get('/host-catalogs', ({ hostCatalogs }, { queryParams: { scope_id: scopeId } }) => {
return hostCatalogs.where({ scopeId });
});
this.get('/host-catalogs/:id');
// host
this.get('/hosts', function ({ hosts }, { queryParams: { host_catalog_id: hostCatalogId } }) {
return hosts.where({ hostCatalogId });
});
this.get('/hosts/:id');
// host-set
this.get('/host-sets', function ({ hostSets }, { queryParams: { host_catalog_id: hostCatalogId } }) {
return hostSets.where({ hostCatalogId });
});
this.get('/host-sets/:id');
// target
this.get('/targets', function ({ targets }, { queryParams: { scope_id } }) {
return targets.where(target => target.scopeId === scope_id);
});
this.get('/targets/:id');
// session
this.get('/sessions', function ({ sessions }, { queryParams: { scope_id } }) {
// To simulate changes to `session.status` that may occur in the backend,
// we quietly randomize the value of the field on GET.
// But only if not in testing mode.
// In tests, we need deterministic statuses.
if (!Ember.testing) {
sessions.where(session => session.scopeId === scope_id)
.models
.forEach(session => session.update({
status: pickRandomStatusString()
}));
}
return sessions.where(session => session.scopeId === scope_id)
});
this.get('/sessions/:id', function ({ sessions }, { params: { id } }) {
const session = sessions.find(id);
return session.update({
status: pickRandomStatusString()
});
});
this.post('/sessions/:idMethod', function ({ sessions }, { params: { idMethod } }) {
const attrs = this.normalizedRequestAttrs();
const id = idMethod.split(':')[0];
const method = idMethod.split(':')[1];
const session = sessions.find(id);
const updatedAttrs = {
version: attrs.version
};
if (method === 'cancel') {
updatedAttrs.status = 'canceling';
}
return session.update(updatedAttrs);
});
// auth-tokens
this.get('/auth-tokens/:id', function () {
// Set to 401 or 404 to simulate token invalidation, which will
// cause the session to fail restoration and logout the user.
return new Response(200);
});
/* Uncomment the following line and the Response import above
* Then change the response code to simulate error responses.
* this.get('/scopes', () => new Response(505));
*
* Update error payload to simulate specific error responses.
*/
// this.get('/host-catalogs', () => new Response(404));
// this.get('/scopes/:id', () => new Response(505, {}, {
// errors: [{
// status: 505,
// message: 'HTTP version not supported.',
// }]
// }));
}
-3
View File
@@ -1,3 +0,0 @@
import factory from '../generated/factories/account';
export default factory.extend();
@@ -1,20 +0,0 @@
import factory from '../generated/factories/auth-method';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
id: (i) => `auth-method-id-${i}`,
/**
* Adds accounts (with associated users) to auth method.
*/
withAccountsAndUsers: trait({
afterCreate(authMethod, server) {
const { scope } = authMethod;
server.createList('user', 5, { scope }).map(user => {
const { id } = server.create('account', { scope, authMethod });
user.update({ accountIds: [id] });
});
}
})
});
@@ -1,14 +0,0 @@
import factory from '../generated/factories/host-catalog';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
withChildren: trait({
afterCreate(hostCatalog, server) {
const { scope } = hostCatalog;
const hosts = server.createList('host', 10, { scope, hostCatalog });
server.createList('host-set', 3, { scope, hostCatalog, hosts });
}
})
});
-5
View File
@@ -1,5 +0,0 @@
import factory from '../generated/factories/host-set';
export default factory.extend({
id: (i) => `host-set-id-${i}`
});
-5
View File
@@ -1,5 +0,0 @@
import factory from '../generated/factories/host';
export default factory.extend({
id: (i) => `host-id-${i}`
});
-35
View File
@@ -1,35 +0,0 @@
import factory from '../generated/factories/scope';
import { trait } from 'ember-cli-mirage';
export default factory.extend({
type: 'global',
/**
* Generates realistic-ish IDs while still being deterministic.
*/
id(i) {
let id = 'tv6fv3yz8p';
if (i % 3) id = 'xk1i4dk50v';
if (i % 2) id = 's9n819zgko';
return `s_${id}${i}`;
},
withChildren: trait({
afterCreate(record, server) {
if (record.type === 'global') {
server.createList('scope', 1, {
type: 'org',
scope: { id: record.id, type: record.type }
}, 'withChildren');
}
if (record.type === 'org') {
server.createList('scope', 3, {
type: 'project',
scope: { id: record.id, type: record.type }
});
}
}
})
});
-46
View File
@@ -1,46 +0,0 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
import { random } from 'faker';
const statusStrings = [
'pending',
'active',
'canceling',
'terminated'
];
export const pickRandomStatusString = () => random.arrayElement(statusStrings);
export default factory.extend({
id: (i) => `session-id-${i}`,
status: pickRandomStatusString,
/**
* Sessions generated with this trait will automatically form
* associations to user, target, host set, and host (if available) within
* the current scope.
*/
withAssociations: trait({
afterCreate(record, server) {
const scopeId = record.scopeId;
if (record.scope.type === 'project') {
const orgScopeId = record.scope?.scope.id;
const user =
server.schema.users.where({ scopeId: orgScopeId }).models[0];
if (user) record.update({ user });
}
const target = server.schema.targets.where({ scopeId }).models[0];
if (target) {
record.update({ target });
const hostSet = target.hostSets.models[0];
if (hostSet) {
record.update({ hostSet });
const host = hostSet.hosts.models[0];
if (host) record.update({ host });
}
}
}
})
});
-46
View File
@@ -1,46 +0,0 @@
import factory from '../generated/factories/target';
import { trait } from 'ember-cli-mirage';
import { random } from 'faker';
const randomBoolean = (chance = 0.5) => Math.random() < chance;
const hostSetChance = 0.3;
export default factory.extend({
/**
* -1 means "unlimited" and we want to generate these on occassion.
*/
session_connection_limit: () => random.arrayElement([-1, random.number()]),
/**
* Generates attributes fields by type.
*/
afterCreate(target) {
if (target.type === 'tcp') {
target.update({
attributes: {
default_port: random.number()
}
});
}
},
/**
* Randomly selects existing host sets to assign to target.
*/
withRandomHostSets: trait({
afterCreate(target, server) {
let randomlySelectedHostSets;
randomlySelectedHostSets = server.schema.hostSets
// BLERG: fun fact, for no reason at all, the element passed
// into a where function is not a full model instance, as you might
// expect at this level of abstraction, but appears to be a serialized
// representation of the model instance. It's very confusing since
// the result set of `where` _is a collection of model instances_.
.where(hostSet => hostSet.scopeId === target.scope.id).models
.filter(() => randomBoolean(hostSetChance));
target.update({hostSets: randomlySelectedHostSets});
}
})
});
-5
View File
@@ -1,5 +0,0 @@
import factory from '../generated/factories/user';
export default factory.extend({
id: (i) => `user-${i}`
});
@@ -1,17 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedAccountFactory
*/
export default Factory.extend({
type: 'password',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
attributes: () => ({ login_name: random.words() }),
});
@@ -1,16 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedAuthMethodModelFactory
*/
export default Factory.extend({
type: 'password',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -1,16 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedHostCatalogModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -1,17 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedHostSetModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
size: () => random.number(),
});
@@ -1,17 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date, internet } from 'faker';
/**
* GeneratedHostModelFactory
*/
export default Factory.extend({
type: 'static',
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
attributes: () => { return { address: internet.ipv6() } },
});
@@ -1,15 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedOrgModelFactory
*
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
});
@@ -1,16 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedProjectModelFactory
* Project contains all fields related to a Project resource
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -1,15 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedScopeModelFactory
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
@@ -1,14 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedSessionModelFactory
*/
export default Factory.extend({
type: 'tcp',
created_time: () => date.recent(),
updated_time: () => date.recent(),
version: () => random.number(),
});
@@ -1,19 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedTargetModelFactory
*/
export default Factory.extend({
type: 'tcp',
name: () => random.words(),
session_max_seconds: () => random.number(),
session_connection_limit: () => random.number(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
version: () => random.number(),
});
@@ -1,16 +0,0 @@
import { Factory } from 'ember-cli-mirage';
import { random, date } from 'faker';
/**
* GeneratedUserModelFactory
* User contains all fields related to a User resource
*/
export default Factory.extend({
name: () => random.words(),
description: () => random.words(),
created_time: () => date.recent(),
updated_time: () => date.recent(),
disabled: () => random.boolean(),
});
-8
View File
@@ -1,8 +0,0 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
authMethod: belongsTo()
});
-7
View File
@@ -1,7 +0,0 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo()
});
-8
View File
@@ -1,8 +0,0 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostSets: hasMany()
});
-10
View File
@@ -1,10 +0,0 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
target: belongsTo(),
hostCatalog: belongsTo(),
hosts: hasMany()
});
-8
View File
@@ -1,8 +0,0 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostCatalog: belongsTo()
});
-11
View File
@@ -1,11 +0,0 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
user: belongsTo(),
target: belongsTo(),
host: belongsTo(),
hostSet: belongsTo(),
});
-8
View File
@@ -1,8 +0,0 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
hostSets: hasMany()
});
-8
View File
@@ -1,8 +0,0 @@
import { Model, belongsTo, hasMany } from 'ember-cli-mirage';
export default Model.extend({
scope: belongsTo(),
accounts: hasMany('account')
});
-53
View File
@@ -1,53 +0,0 @@
// import config from '../../config/environment';
// import { v1 } from 'ember-uuid';
import { Response } from 'miragejs';
// /**
// * Simulates a cookie-based response to authentication requests. Any user/pass
// * values are allowed *except* when `username` equals `error`, in which case a
// * simulated error response is returned.
// * @param {object} schema
// * @param {object} request
// * @return {Response}
// */
// export default function authenticateHandler(schema, request) {
// const payload = JSON.parse(request.requestBody);
// if (payload.credentials.login_name === 'error') {
// return new Response(400);
// } else {
// const cookieName = config.auth.passwordCookieName;
// const cookieValue = v1();
// const cookiePath = config.rootURL;
// document.cookie = `${cookieName}=${cookieValue};path=${cookiePath}`;
// return new Response(200);
// }
// }
export function authHandler({ scopes, authMethods }, request) {
const payload = JSON.parse(request.requestBody);
if (payload.credentials.login_name === 'error') {
return new Response(400);
} else {
const id = request.params.id_method.split(':')[0];
const authMethod = authMethods.find(id);
const scope = scopes.find(authMethod.scopeId);
const scopeAttrs =
this.serialize(scopes.find(scope.id));
return new Response(200, {}, {
scope: scopeAttrs,
id: 'token123',
token: 'thetokenstring',
account_id: '1',
user_id: 'user123',
auth_method_id: 'authmethod123',
created_time: '',
updated_time: '',
last_used_time: '',
expiration_time: ''
});
}
}
export function deauthHandler() {
return new Response(200);
}
-23
View File
@@ -1,23 +0,0 @@
export default function(server) {
// Scopes
const globalScope = server.create('scope', {
id: 'global',
type: 'global',
name: 'Global'
});
const orgScope = server.createList('scope', 3, {
type: 'org',
scope: { id: globalScope.id, type: globalScope.type }
}, 'withChildren')[0];
// Auth
server.createList('auth-method', 1, { scope: globalScope }, 'withAccountsAndUsers');
server.createList('auth-method', 3, { scope: orgScope }, 'withAccountsAndUsers');
// Other resources
server.schema.scopes.where({type: 'project'}).models.forEach(scope => {
server.createList('host-catalog', 2, { scope }, 'withChildren');
server.createList('target', 2, { scope }, 'withRandomHostSets');
server.createList('session', 4, { scope }, 'withAssociations');
});
}
-12
View File
@@ -1,12 +0,0 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'account',
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.auth_method_id = model.authMethodId;
delete json.attributes?.password;
return json;
}
});
@@ -1,84 +0,0 @@
import { RestSerializer } from 'ember-cli-mirage';
import { underscore } from '@ember/string';
import { isArray } from '@ember/array';
/**
* Manages serialization/normalization of data into and out of the mock API.
*/
export default RestSerializer.extend({
// =attributes
/**
* False to disable automatically rooting payloads under a key.
* This is handled by the `serialize()` method, see below.
* @type {boolean}
*/
root: false,
/**
* @type {boolean}
*/
embed: true,
// =methods
/**
* Generates an underscored key for the attribute.
* @param {string} attr
* @return {string}
*/
keyForAttribute(attr) {
return underscore(attr);
},
keyForRelationshipId(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_id`;
},
/**
* Serialize items into a root key `items`.
* @return {object}
*/
serialize() {
let json = RestSerializer.prototype.serialize.apply(this, arguments);
// If array, root it under a standard `items` key
if (isArray(json)) json = {items: json};
return json;
},
/**
* Serialize scope into the payload, if set.
* @param {object} model
* @return {object}
*/
_hashForModel(model) {
const json = RestSerializer.prototype._hashForModel.apply(this, arguments);
if (model.scope) {
json.scope = {
id: model.scope.id,
type: model.scope.type
};
}
return json;
},
/**
* Adds a root to the payload, since `root: false` doesn't appear to affect
* the normalize method.
* @param {object} json
* @return {object}
*/
normalize(json) {
let newJSON = json;
if (this.modelName) {
newJSON = {};
newJSON[this.modelName] = json;
}
const value = RestSerializer.prototype.normalize.apply(this, [newJSON]);
return value;
}
});
@@ -1,5 +0,0 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'auth-method'
});
@@ -1,5 +0,0 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'host-catalog',
});
-22
View File
@@ -1,22 +0,0 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'host-set',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.host_catalog_id = model.hostCatalogId;
if (model.hostIds?.length) {
json.host_ids = model.hostIds;
}
return json;
}
});
-5
View File
@@ -1,5 +0,0 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'host'
});
-5
View File
@@ -1,5 +0,0 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
modelName: 'scope'
});
-22
View File
@@ -1,22 +0,0 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'session',
keyForRelationshipId(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_id`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
json.user_id = model.userId;
json.target_id = model.targetId;
json.host_set_id = model.hostSetId;
json.host_id = model.hostId;
return json;
}
});
-26
View File
@@ -1,26 +0,0 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'target',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
const { hostSets } = this.schema;
if (model.hostSetIds?.length) {
json.host_sets = model.hostSetIds.map(host_set_id => {
const hostSet = hostSets.find(host_set_id);
const host_catalog_id = hostSet?.hostCatalog?.id;
return { id: host_set_id, host_catalog_id };
});
}
return json;
}
});
-18
View File
@@ -1,18 +0,0 @@
import ApplicationSerializer from './application';
import { underscore } from '@ember/string';
export default ApplicationSerializer.extend({
modelName: 'user',
keyForRelationshipIds(relationshipName) {
return `${this._container.inflector.singularize(
underscore(relationshipName)
)}_ids`;
},
_hashForModel(model) {
const json = ApplicationSerializer.prototype._hashForModel.apply(this, arguments);
if (model.accountIds.length) json.account_ids = model.accountIds;
return json;
}
});