Merge pull request #121 from hashicorp/feature-org-scope-crud

Feature org scope crud
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2020-08-18 18:32:48 -04:00
commit 5ce721099c
36 files changed
+647 -113

No files matched your search

+44 -1
View File
@@ -37,9 +37,52 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator {
* @param {string} scopeID
* @return {string}
*/
buildDeauthEndpointURL({ org_id: scopeID }) {
buildDeauthEndpointURL({ scope: { id: scopeID } }) {
const adapter = this.store.adapterFor('application');
const options = { adapterOptions: { method: 'deauthenticate' } };
return adapter.buildURL('scope', scopeID, options, 'findRecord');
}
/**
* Intercepts the authenticate response, if any, and assigns the returned
* token to all future requests via `addTokenToAuthorization`.
* Returns the response data as normal.
* @override
*/
authenticate() {
return super.authenticate(...arguments)
.then(data => {
const token = data?.token;
if (token) this.addTokenToAuthorization(token);
return data;
});
}
/**
* When restoring the session (say, on load), assigns the session token
* to all future requests via `addTokenToAuthorization`.
* @override
* @param {object} data
* @return {object}
*/
restore(data) {
const token = data?.token;
if (token) this.addTokenToAuthorization(token);
return super.restore(data);
}
/**
* Assigns a token string all future requests via the `Authorization` header
* on the application adapter prototype. Only application and adapters that
* extend the application adapter receive this header (which is most).
* @param {string} token
*/
addTokenToAuthorization(token) {
const adapterPrototype =
this.store.adapterFor('application').constructor.prototype;
const headers = adapterPrototype?.headers;
if (!headers) adapterPrototype.headers = {};
adapterPrototype.headers.Authorization = null;
if (token) adapterPrototype.headers.Authorization = `Bearer ${token}`;
}
}
+49
View File
@@ -0,0 +1,49 @@
<Rose::Form
@onSubmit={{@submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
as |form|
>
<form.input
@name="name"
@type="name"
@value={{@model.name}}
@label={{t "form.label.name"}}
@error={{@model.errors.name}}
readonly={{false}}
as |field|>
{{#if @model.errors.name}}
<field.errors as |errors|>
{{#each @model.errors.name as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.input>
<form.textarea
@name="description"
@type="description"
@value={{@model.description}}
@label={{t "form.label.description"}}
@error={{@model.errors.description}}
disabled={{@model.isSaving}}
readonly={{false}}
as |field|>
{{#if @model.errors.description}}
<field.errors as |errors|>
{{#each @model.errors.description as |error|}}
<errors.message>{{error.message}}</errors.message>
{{/each}}
</field.errors>
{{/if}}
</form.textarea>
<form.actions
@disabled={{if @model.cannotSave @model.cannotSave}}
@submitText={{t "actions.save"}}
@cancelText={{t "actions.cancel"}} />
</Rose::Form>
@@ -0,0 +1,21 @@
import Controller from '@ember/controller';
import { sort } from '@ember/object/computed';
export default class ScopesScopeAuthenticateController extends Controller {
// =attributes
/**
* @type {Array}
*/
@sort('model.scopes', (a, b) => {
if (a.isGlobal) {
return -1;
} else if (b.isGlobal) {
return 1;
}
return 0;
})
sortedScopes;
}
@@ -0,0 +1,17 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeOrgsController extends Controller {
// =services
@service intl;
// =attributes
/**
* @type {string}
*/
get breadCrumb() {
return this.intl.t('resources.orgs');
}
}
@@ -0,0 +1,17 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeOrgsNewController extends Controller {
// =services
@service intl;
// =attributes
/**
* @type {string}
*/
get breadCrumb() {
return this.intl.t('titles.new');
}
}
+20
View File
@@ -9,17 +9,35 @@ export default class Router extends EmberRouter {
Router.map(function () {
this.route('scopes', function () {
this.route('scope', { path: ':scope_id' }, function () {
this.route('authenticate', function () {
this.route('method', { path: ':auth_method_id' });
});
this.route('orgs', function() {
this.route('new');
});
this.route('projects', function() {
this.route('project', { path: ':project_id' }, function() {
this.route('host-catalogs', function() {
this.route('new');
this.route('host-catalog', { path: ':host_catalog_id' }, function() {});
});
});
this.route('new');
});
this.route('users', function () {
this.route('user', { path: ':user_id' }, function () {});
this.route('new');
});
this.route('groups', function () {
this.route('group', { path: ':group_id' }, function () {});
this.route('new');
});
this.route('roles', function () {
this.route('role', { path: ':role_id' }, function () {
this.route('grants');
@@ -27,6 +45,7 @@ Router.map(function () {
});
this.route('new');
});
this.route('projects', function() {
this.route('project', { path: ':project_id' }, function() {
this.route('host-catalogs', function() {
@@ -40,6 +59,7 @@ Router.map(function () {
this.route('auth-method', { path: ':auth_method_id' }, function() {});
this.route('new');
});
});
});
});
+3 -3
View File
@@ -15,15 +15,15 @@ export default class ScopesIndexRoute extends Route {
* @param {[ScopeModel]} model
* @param {?ScopeModel} model.firstObject
*/
redirect({ firstObject }) {
redirect() {
const authenticatedScopeID = get(
this.session,
'data.authenticated.scope.id'
);
if (authenticatedScopeID) {
this.transitionTo('scopes.scope', authenticatedScopeID);
} else if (firstObject) {
this.transitionTo('scopes.scope', firstObject.id);
} else {
this.transitionTo('scopes.scope', 'global');
}
}
}
+16 -3
View File
@@ -1,6 +1,12 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ScopesScopeRoute extends Route {
// =services
@service intl;
// =methods
/**
@@ -14,11 +20,18 @@ export default class ScopesScopeRoute extends Route {
* @return {Promise{ScopeModel}}
*/
model({ scope_id: id }) {
// Since only global and org scopes are authenticatable, we can infer type
// from ID because global has a fixed ID.
const type = id === 'global' ? 'global' : 'org';
return this.store.findRecord('scope', id).catch(() => {
const maybeExistingScope = this.store.peekRecord('scope', id);
// TODO it's unclear if creating a temporary unsaved scope is reliable
// we may need to revert to a POJO.
return maybeExistingScope || this.store.createRecord('scope', { id });
const scopeOptions = { id, type };
if (type === 'global') {
scopeOptions.name = this.intl.t('titles.global');
}
return maybeExistingScope ||
this.store.createRecord('scope', scopeOptions);
});
}
}
@@ -7,9 +7,17 @@ export default class ScopesScopeAuthMethodsRoute extends Route {
@service intl;
@service notify;
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
/**
* Load all auth-methods under current scope
* @return {Promise[AuthMethodModel]}
@@ -7,12 +7,6 @@ export default class ScopesScopeAuthenticateRoute extends Route {
@service session;
// =attributes
// TODO: this assumes we're in an org scope, but the "landing"
// for global scope is probably different.
routeIfAlreadyAuthenticated = 'scopes.scope.projects';
// =methods
/**
@@ -37,7 +31,12 @@ export default class ScopesScopeAuthenticateRoute extends Route {
redirect() {
if (this.session.isAuthenticated) {
this.transitionTo(this.routeIfAlreadyAuthenticated);
const scope = this.modelFor('scopes.scope');
if (scope.isGlobal) {
this.transitionTo('scopes.scope.orgs');
} else {
this.transitionTo('scopes.scope.projects');
}
}
}
}
@@ -9,10 +9,6 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
@service notify;
@service intl;
// =attributes
routeAfterAuthentication = 'scopes.scope.projects';
// =methods
/**
@@ -55,7 +51,11 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route {
requestCookies,
{ scope, authMethod }
);
this.transitionTo(this.routeAfterAuthentication);
if (scope.isGlobal) {
this.transitionTo('scopes.scope.orgs');
} else {
this.transitionTo('scopes.scope.projects');
}
} catch (e) {
const errorMessage = this.intl.t('errors.titles.authentication-failed');
this.notify.error(errorMessage, { closeAfter: null });
@@ -7,9 +7,17 @@ export default class ScopesScopeGroupsRoute extends Route {
@service intl;
@service notify;
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
/**
* Load all groups under current scope
* @return {Promise[GroupModel]}
+64
View File
@@ -0,0 +1,64 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class ScopesScopeOrgsRoute extends Route {
// =services
@service intl;
@service notify;
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
/**
* Loads all scopes under the global scope.
* @return {Promise{[ScopeModel]}}
*/
async model() {
return this.store.query('scope', { scope_id: 'global' });
}
// =actions
/**
* Rollback changes on org.
* @param {ScopeModel} org
*/
@action
cancel(org) {
const { isNew } = org;
org.rollbackAttributes();
if (isNew) this.transitionTo('scopes.scope.orgs');
}
/**
* Handle save org.
* @param {ScopeModel} org
* @param {Event} e
*/
@action
async save(org) {
const { isNew } = org;
try {
await org.save();
this.refresh();
this.notify.success(
this.intl.t(isNew ? 'notify.create-success' : 'notify.save-success')
);
this.transitionTo('scopes.scope', org);
} catch (error) {
// TODO: replace with translated strings
this.notify.error(error.message, { closeAfter: null });
}
}
}
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default class ScopesScopeOrgsIndexRoute extends Route {
}
@@ -0,0 +1,18 @@
import Route from '@ember/routing/route';
export default class ScopesScopeOrgsNewRoute extends Route {
// =methods
/**
* Creates a new unsaved org scope belonging to the global scope.
* @return {ScopeModel}
*/
model() {
return this.store.createRecord('scope', {
type: 'org',
scopeID: 'global',
});
}
}
+8
View File
@@ -7,9 +7,17 @@ export default class ScopesScopeRolesRoute extends Route {
@service intl;
@service notify;
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
/**
* Load all roles under current scope.
* @return {Promise{[RoleModel]}}
+8
View File
@@ -7,9 +7,17 @@ export default class ScopesScopeUsersRoute extends Route {
@service intl;
@service notify;
@service session;
// =methods
/**
* If arriving here unauthenticated, redirect to index for further processing.
*/
beforeModel() {
if (!this.session.isAuthenticated) this.transitionTo('index');
}
/**
* Load all users under current scope.
* @return {Promise{[UserModel]}}
+1 -33
View File
@@ -1,38 +1,6 @@
import LocalStorageSessionStore from 'auth/session-stores/local-storage';
import { inject as service } from '@ember/service';
import { get } from '@ember/object';
//import config from '../config/environment';
export default class ApplicationSessionStore extends LocalStorageSessionStore {
@service store;
/**
* If the auth sessionData includes a token, apply it as a bearer token in the
* application adapter's Authorization header. Otherwise clear the header.
* @param {object} sessionData
* @return {object}
*/
persist(sessionData) {
this.addTokenToAuthorization(sessionData);
//this.addScopeToSession(sessionData);
return super.persist(...arguments);
}
/**
* If the session has a full token, it is added as a bearer token to the
* `Authorization` header for all API requests.
* @param {object} sessionData
*/
addTokenToAuthorization(sessionData) {
const token = get(sessionData, 'authenticated.token');
const adapter = this.store.adapterFor('application');
const headers = get(adapter, 'headers');
if (!headers) adapter.headers = {};
adapter.headers.Authorization = null;
if (token) adapter.headers.Authorization = `Bearer ${token}`;
}
}
export default class ApplicationSessionStore extends LocalStorageSessionStore {}
// import CookieSessionStore from 'auth/session-stores/cookie';
// import config from '../config/environment';
+2
View File
@@ -1,6 +1,7 @@
<Rose::Layout::Sidebar as |sidebarLayout|>
{{#if this.session.isAuthenticated}}
{{#unless @model.isGlobal}}
<sidebarLayout.sidebar>
<Rose::Nav::Sidebar @title={{t "titles.general"}} as |nav|>
<nav.link @route="scopes.scope.projects">
@@ -26,6 +27,7 @@
{{outlet "sidebar"}}
</sidebarLayout.sidebar>
{{/unless}}
{{/if}}
<sidebarLayout.body>
@@ -14,7 +14,7 @@
@text={{t "actions.choose-different-org"}}
@icon="user-organization"
as |dropdown|>
{{#each @model.scopes as |scope|}}
{{#each this.sortedScopes as |scope|}}
<dropdown.link @route="scopes.scope.authenticate" @model={{scope}}>
{{scope.displayName}}
</dropdown.link>
@@ -0,0 +1,3 @@
{{title (t "resources.orgs")}}
{{outlet}}
@@ -0,0 +1,56 @@
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
<h2>{{t "resources.orgs"}}</h2>
</page.header>
<page.actions>
{{#if @model}}
<Rose::LinkButton
@route="scopes.scope.orgs.new"
@style="primary"
>
{{t 'titles.new'}}
</Rose::LinkButton>
{{/if}}
</page.actions>
<page.body>
{{#if @model}}
<Rose::Cards as |cards|>
{{#each @model as |org|}}
<cards.link
@title={{org.name}}
@subtitle={{org.id}}
@description={{org.description}}
@route="scopes.scope"
@model={{org}}
/>
{{/each}}
</Rose::Cards>
{{else}}
<Rose::Layout::Centered>
<Rose::Message @title={{t 'titles.orgs-welcome'}} as |message|>
<message.description>
{{t 'descriptions.orgs-welcome'}}
</message.description>
<message.link @route="scopes.scope.orgs.new">
<Rose::Icon @name="plus-circle-outline" />
{{t 'titles.new'}}
</message.link>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
</page.body>
</Rose::Layout::Page>
@@ -0,0 +1,21 @@
{{title (t 'titles.new')}}
<Rose::Layout::Page as |page|>
<page.breadcrumbs>
<BreadCrumbs />
</page.breadcrumbs>
<page.header>
<h2>{{t "titles.new"}}</h2>
</page.header>
<page.body>
<Form::Org
@model={{@model}}
@submit={{route-action "save" @model}}
@cancel={{route-action "cancel" @model}}
/>
</page.body>
</Rose::Layout::Page>
+6 -1
View File
@@ -2,7 +2,12 @@ export default function(server) {
// Scope resources
const globalScope = server.create('scope', { id: 'global' }); // creates a global scope
// creates a global scope
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 }
+26 -18
View File
@@ -23,8 +23,11 @@ module('Acceptance | authentication', function (hooks) {
let scope;
let authMethod;
let authMethodID;
let globalAuthenticateURL;
let authenticateURL;
let authMethodAuthenticateURL;
let authMethodGlobalAuthenticateURL;
let orgsURL;
let projectsURL;
hooks.beforeEach(function () {
@@ -45,21 +48,14 @@ module('Acceptance | authentication', function (hooks) {
authMethodID = authMethod.id;
scopesURL = `/scopes`;
orgScopeURL = `/scopes/${orgScopeID}`;
globalAuthenticateURL = `/scopes/global/authenticate`;
authenticateURL = `/scopes/${orgScopeID}/authenticate`;
authMethodGlobalAuthenticateURL = `/scopes/global/authenticate/${authMethodID}`;
authMethodAuthenticateURL = `/scopes/${orgScopeID}/authenticate/${authMethodID}`;
orgsURL = `/scopes/global/orgs`;
projectsURL = `/scopes/${orgScopeID}/projects`;
});
test('visiting scopes when there are none shows a message', async function (assert) {
assert.expect(2);
this.server.get('/scopes', () => new Response(404));
await visit(scopesURL);
await a11yAudit();
assert.equal(currentURL(), scopesURL);
assert.ok(find('.rose-message'));
});
test('visiting auth methods authenticate route redirects to first auth method', async function (assert) {
assert.expect(1);
await visit(authenticateURL);
@@ -96,19 +92,19 @@ module('Acceptance | authentication', function (hooks) {
assert.equal(currentURL(), authMethodAuthenticateURL);
});
test('visiting any authentication parent route while unauthenticated redirects to first authenticate method', async function (assert) {
test('visiting any authentication parent route while unauthenticated redirects to first global authenticate method', async function (assert) {
assert.expect(3);
await visit(indexURL);
assert.equal(currentURL(), authMethodAuthenticateURL);
assert.equal(currentURL(), authMethodGlobalAuthenticateURL);
await visit(scopesURL);
assert.equal(currentURL(), authMethodAuthenticateURL);
assert.equal(currentURL(), authMethodGlobalAuthenticateURL);
assert.notOk(currentSession().isAuthenticated);
});
test('visiting projects while unauthenticated redirects to first authenticate method', async function (assert) {
test('visiting projects while unauthenticated redirects to first global authenticate method', async function (assert) {
assert.expect(2);
await visit(projectsURL);
assert.equal(currentURL(), authMethodAuthenticateURL);
assert.equal(currentURL(), authMethodGlobalAuthenticateURL);
assert.notOk(currentSession().isAuthenticated);
});
@@ -119,7 +115,7 @@ module('Acceptance | authentication', function (hooks) {
assert.equal(currentURL(), authMethodAuthenticateURL);
});
test('visiting any authentication parent route while already authenticated redirects to projects', async function (assert) {
test('visiting any authentication parent route while already authenticated with an org redirects to projects', async function (assert) {
assert.expect(6);
authenticateSession({ scope });
await visit(indexURL);
@@ -135,6 +131,18 @@ module('Acceptance | authentication', function (hooks) {
assert.ok(currentSession().isAuthenticated);
});
test('visiting index or scopes routes while already authenticated with global redirects to orgs', async function (assert) {
assert.expect(4);
authenticateSession({ scope: { id: globalScope.id, type: globalScope.type } });
await visit(indexURL);
assert.equal(currentURL(), orgsURL);
await visit(scopesURL);
assert.equal(currentURL(), orgsURL);
await visit(globalAuthenticateURL);
assert.equal(currentURL(), orgsURL);
assert.ok(currentSession().isAuthenticated);
});
test('failed authentication shows a notification message', async function (assert) {
assert.expect(3);
await visit(authMethodAuthenticateURL);
@@ -157,7 +165,7 @@ module('Acceptance | authentication', function (hooks) {
assert.ok(currentSession().isAuthenticated);
});
test('deauthentication redirects to first authenticate method', async function (assert) {
test('deauthentication redirects to first global authenticate method', async function (assert) {
assert.expect(3);
await visit(authMethodAuthenticateURL);
await fillIn('[name="identification"]', 'test');
@@ -166,6 +174,6 @@ module('Acceptance | authentication', function (hooks) {
assert.ok(currentSession().isAuthenticated);
await click('.rose-button-header-dropdown');
assert.notOk(currentSession().isAuthenticated);
assert.equal(currentURL(), authMethodAuthenticateURL);
assert.equal(currentURL(), authMethodGlobalAuthenticateURL);
});
});
+76
View File
@@ -0,0 +1,76 @@
import { module, test } from 'qunit';
//import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
//import a11yAudit from 'ember-a11y-testing/test-support/audit';
//import { Response } from 'miragejs';
import {
//authenticateSession,
// These are left here intentionally for future reference.
//currentSession,
//invalidateSession,
} from 'ember-simple-auth/test-support';
module('Acceptance | org', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
// let org;
// let orgID;
// let orgsURL;
// let newOrgURL;
// let existingOrg;
// let existingOrgURL;
// let getOrgScopesCount;
// let getFirstOrgScope;
// let initialOrgScopesCount;
//
// hooks.beforeEach(function () {
// authenticateSession({});
//
// });
test('visiting orgs', async function (assert) {
assert.expect(0);
});
test('visiting orgs within a org scope redirects to the parent org scope', async function (assert) {
assert.expect(0);
});
test('can create new orgs', async function (assert) {
assert.expect(0);
});
test('can cancel create new orgs', async function (assert) {
assert.expect(0);
});
test('saving a new org with invalid fields displays error messages', async function (assert) {
assert.expect(0);
});
test('can save changes to existing org', async function (assert) {
assert.expect(0);
});
test('can cancel changes to existing org', async function (assert) {
assert.expect(0);
});
test('can delete org', async function (assert) {
assert.expect(0);
});
test('saving an existing org with invalid fields displays error messages', async function (assert) {
assert.expect(0);
});
test('errors are displayed when save org fails', async function (assert) {
assert.expect(0);
});
test('errors are displayed when delete org fails', async function (assert) {
assert.expect(0);
});
});
@@ -0,0 +1,23 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | form/org', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.set('myAction', function(val) { ... });
this.submit = () => {};
this.cancel = () => {};
await render(hbs`
<Form::Org
@submit={{this.submit}}
@cancel={{this.cancel}}
/>`);
assert.ok(find('form'));
});
});
@@ -0,0 +1,44 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
module('Unit | Authenticator | password', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
let store;
let applicationAdapter;
let session;
let authenticator;
hooks.beforeEach(function () {
store = this.owner.lookup('service:store');
applicationAdapter = store.adapterFor('application');
session = this.owner.lookup('service:session');
authenticator = this.owner.lookup('authenticator:password');
session.invalidate();
});
hooks.afterEach(function () {
session.invalidate();
});
test('it adds an authorization header to application adapter on restore', async function (assert) {
assert.expect(1);
authenticator.restore({ token: 'token1234' });
assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234');
});
test('it adds an authorization header to application adapter on authenticate', async function (assert) {
assert.expect(1);
const creds = { identification: 'foo', password: 'bar' };
const scope = { id: 'global' };
const authMethod = { id: 'paum_1234' };
await authenticator.authenticate(
creds,
null,
{ scope, authMethod }
);
assert.equal(applicationAdapter.headers.Authorization, 'Bearer thetokenstring');
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/authenticate', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/authenticate');
assert.ok(controller);
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/orgs', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/orgs');
assert.ok(controller);
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | scopes/scope/orgs/new', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:scopes/scope/orgs/new');
assert.ok(controller);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/orgs', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:scopes/scope/orgs');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/orgs/index', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:scopes/scope/orgs/index');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | scopes/scope/orgs/new', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:scopes/scope/orgs/new');
assert.ok(route);
});
});
@@ -1,41 +0,0 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Session Store | application', function (hooks) {
setupTest(hooks);
let store;
let applicationAdapter;
let sessionStore;
hooks.beforeEach(function () {
store = this.owner.lookup('service:store');
applicationAdapter = store.adapterFor('application');
sessionStore = this.owner.lookup('session-store:application');
sessionStore.clear();
});
hooks.afterEach(function () {
sessionStore.clear();
});
test('it does not add an authorization header to application adapter on persist if no token is present', async function (assert) {
assert.expect(1);
sessionStore.persist({ authenticated: { token: null } });
assert.notOk(applicationAdapter.headers.Authorization);
});
test('it adds an authorization header to application adapter on persist', async function (assert) {
assert.expect(1);
sessionStore.persist({ authenticated: { token: 'token1234' } });
assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234');
});
test('it adds an authorization header to application adapter which already has headers on persist', async function (assert) {
assert.expect(2);
applicationAdapter.headers = { foo: 'bar' };
sessionStore.persist({ authenticated: { token: 'token1234' } });
assert.equal(applicationAdapter.headers.foo, 'bar');
assert.equal(applicationAdapter.headers.Authorization, 'Bearer token1234');
});
});
+3
View File
@@ -27,6 +27,8 @@ resources:
roles: Roles
group: Group
groups: Groups
orgs: Orgs
org: Org
projects: Projects
project: Project
host-catalog: Host Catalog
@@ -51,6 +53,7 @@ titles:
grants: Permissions
new-grant: New Grant
principals: Principals
global: Global
descriptions:
host-catalogs: Overview of collections of hosts and host sets.
empty-set: There are no items to display yet. You may be able to add items or try back later.