From 0f52b86364c8bf5d33a9fb5579587bacbf21d402 Mon Sep 17 00:00:00 2001
From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
Date: Tue, 22 Oct 2024 13:16:58 -0700
Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20route-actio?=
=?UTF-8?q?n=20usage=20for=20targets=20and=20sessions=20(#2528)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: 💡 remove route-action usage for targets and sessions
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-15040
---
ui/desktop/.template-lintrc.js | 1 -
.../scopes/scope/projects/sessions/index.js | 45 ++++
.../scopes/scope/projects/sessions/session.js | 10 +
.../scopes/scope/projects/targets/index.js | 8 +
.../scopes/scope/projects/targets/target.js | 71 ++++-
.../routes/scopes/scope/projects/sessions.js | 44 ---
.../scopes/scope/projects/targets/target.js | 91 +------
.../scopes/scope/projects/sessions/index.hbs | 4 +-
.../scope/projects/sessions/session.hbs | 2 +-
.../scopes/scope/projects/targets/index.hbs | 4 +-
.../scopes/scope/projects/targets/target.hbs | 4 +-
.../scope/projects/sessions/index-test.js | 131 ++++++++-
.../scope/projects/sessions/session-test.js | 22 ++
.../scope/projects/targets/index-test.js | 250 ++++++++++++++++++
14 files changed, 551 insertions(+), 136 deletions(-)
create mode 100644 ui/desktop/app/controllers/scopes/scope/projects/sessions/session.js
create mode 100644 ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/session-test.js
create mode 100644 ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js
diff --git a/ui/desktop/.template-lintrc.js b/ui/desktop/.template-lintrc.js
index 08600c086..e5b91ba34 100644
--- a/ui/desktop/.template-lintrc.js
+++ b/ui/desktop/.template-lintrc.js
@@ -13,7 +13,6 @@ module.exports = {
'no-bare-strings': true,
'no-curly-component-invocation': { allow: ['app-name'] },
'no-implicit-this': { allow: ['app-name', 'is-loading'] },
- 'no-route-action': false,
},
overrides: [
{
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js b/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
index d6e8c5812..bc879530d 100644
--- a/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
+++ b/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
@@ -7,6 +7,8 @@ import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
+import { loading } from 'ember-loading';
+import { notifySuccess, notifyError } from 'core/decorators/notify';
import orderBy from 'lodash/orderBy';
import {
STATUS_SESSION_ACTIVE,
@@ -19,6 +21,11 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
// =services
@service intl;
+ @service store;
+ @service ipc;
+ @service session;
+ @service router;
+ @service can;
// =attributes
@@ -108,4 +115,42 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
this[filter] = [...selectedItems];
this.page = 1;
}
+
+ // =actions
+
+ /**
+ * Cancels the specified session and notifies user of success or error.
+ * @param {SessionModel}
+ */
+ @action
+ @loading
+ @notifyError(({ message }) => message, { catch: true })
+ @notifySuccess('notifications.canceled-success')
+ async cancelSession(session) {
+ let updatedSession = session;
+ // fetch session from API to verify we have most up to date record
+ if (this.can.can('read session', session)) {
+ updatedSession = await this.store.findRecord('session', session.id, {
+ reload: true,
+ });
+ }
+
+ await updatedSession.cancelSession();
+
+ await this.ipc.invoke('stop', { session_id: session.id });
+ if (
+ this.router.currentRoute.name ===
+ 'scopes.scope.projects.sessions.session.index'
+ ) {
+ this.router.replaceWith('scopes.scope.projects.targets.index');
+ }
+ }
+
+ /**
+ * Refreshes the all data for the current page.
+ */
+ @action
+ refresh() {
+ this.send('refreshAll');
+ }
}
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/sessions/session.js b/ui/desktop/app/controllers/scopes/scope/projects/sessions/session.js
new file mode 100644
index 000000000..3292e6745
--- /dev/null
+++ b/ui/desktop/app/controllers/scopes/scope/projects/sessions/session.js
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import Controller, { inject as controller } from '@ember/controller';
+
+export default class ScopesScopeProjectsSessionsSessionController extends Controller {
+ @controller('scopes/scope/projects/sessions/index') sessions;
+}
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
index 8ee445da6..315289e3e 100644
--- a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
+++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
@@ -304,4 +304,12 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
await this.ipc.invoke('stop', { session_id: session.id });
this.router.refresh();
}
+
+ /**
+ * Refreshes the all data for the current page.
+ */
+ @action
+ refresh() {
+ this.send('refreshAll');
+ }
}
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js
index 1d3faf9f0..f9e9bbbbe 100644
--- a/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js
+++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/target.js
@@ -3,16 +3,25 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import Controller from '@ember/controller';
+import Controller, { inject as controller } from '@ember/controller';
+import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
export default class ScopesScopeProjectsTargetsTargetController extends Controller {
+ @controller('scopes/scope/projects/targets/index') targets;
+
+ // =services
+
+ @service store;
+ @service confirm;
+
// =attributes
queryParams = [{ isConnecting: { type: 'boolean' } }];
@tracked isConnecting = false;
+ isConnectionError = false;
// =methods
@@ -20,4 +29,64 @@ export default class ScopesScopeProjectsTargetsTargetController extends Controll
toggleModal(value) {
this.isConnecting = value;
}
+
+ /**
+ * Connect method that calls parent connect method and handles
+ * connection errors unique to this route
+ * @param {TargetModel} target
+ * @param {HostModel} host
+ */
+ @action
+ async connect(target, host) {
+ try {
+ await this.targets.connect(target, host);
+ } catch (error) {
+ this.isConnectionError = true;
+ this.confirm
+ .confirm(error.message, { isConnectError: true })
+ // Retry
+ .then(() => this.connect(target, host))
+ .catch(() => {
+ // Reset the flag as this was user initiated and we're not
+ // in a transition or have a host modal open
+ this.isConnectionError = false;
+ });
+ }
+ }
+
+ /**
+ * Establish a session to current target.
+ * @param {TargetModel} target
+ * @param {HostModel} host
+ * hostConnect is only called when making a connection with a host and ensures that the host modal is automatically closed in the case of a connection error.
+ */
+ @action
+ async hostConnect(target, host) {
+ await this.connect(target, host);
+
+ if (this.isConnectionError) {
+ this.toggleModal(false);
+ this.isConnectionError = false;
+ }
+ }
+
+ /**
+ * Determine if we show host modal or quick connect based on target attributes.
+ * @param {TargetModel} model
+ * @param {function} toggleModal
+ */
+ @action
+ async preConnect(model) {
+ /**
+ * if hosts length is 1 or less we will try to
+ * connect, even if there is no address on the target and
+ * rely on the CLI to give the user the proper error or if
+ * there are 2 or more hosts we show the modal for host selection
+ */
+ if (model.hosts.length <= 1) {
+ await this.connect(model.target);
+ } else {
+ this.toggleModal(true);
+ }
+ }
}
diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/sessions.js
index 11e5d427b..d6831a6f6 100644
--- a/ui/desktop/app/routes/scopes/scope/projects/sessions.js
+++ b/ui/desktop/app/routes/scopes/scope/projects/sessions.js
@@ -5,18 +5,12 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
-import { action } from '@ember/object';
-import { notifySuccess, notifyError } from 'core/decorators/notify';
-import { loading } from 'ember-loading';
export default class ScopesScopeProjectsSessionsRoute extends Route {
// =services
- @service store;
- @service ipc;
@service session;
@service router;
- @service can;
// =methods
@@ -26,42 +20,4 @@ export default class ScopesScopeProjectsSessionsRoute extends Route {
beforeModel() {
if (!this.session.isAuthenticated) this.router.transitionTo('index');
}
-
- // =actions
-
- /**
- * Cancels the specified session and notifies user of success or error.
- * @param {SessionModel}
- */
- @action
- @loading
- @notifyError(({ message }) => message, { catch: true })
- @notifySuccess('notifications.canceled-success')
- async cancelSession(session) {
- let updatedSession = session;
- // fetch session from API to verify we have most up to date record
- if (this.can.can('read session', session)) {
- updatedSession = await this.store.findRecord('session', session.id, {
- reload: true,
- });
- }
-
- await updatedSession.cancelSession();
-
- await this.ipc.invoke('stop', { session_id: session.id });
- if (
- this.router.currentRoute.name ===
- 'scopes.scope.projects.sessions.session.index'
- ) {
- this.router.replaceWith('scopes.scope.projects.targets.index');
- }
- }
-
- /**
- * refreshes session data.
- */
- @action
- async refreshSessions() {
- return this.refresh();
- }
}
diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets/target.js b/ui/desktop/app/routes/scopes/scope/projects/targets/target.js
index 2dda9a929..76d36b392 100644
--- a/ui/desktop/app/routes/scopes/scope/projects/targets/target.js
+++ b/ui/desktop/app/routes/scopes/scope/projects/targets/target.js
@@ -11,11 +11,6 @@ export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
// =services
@service store;
- @service confirm;
-
- // =attributes
-
- isConnectionError = false;
// =methods
@@ -73,7 +68,11 @@ export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
* rely on the CLI to give the user the proper error
*/
if (isConnecting && model.hosts.length <= 1) {
- await this.connect(model.target);
+ /* eslint-disable-next-line ember/no-controller-access-in-routes */
+ const controller = this.controllerFor(
+ 'scopes.scope.projects.targets.target',
+ );
+ await controller.connect(model.target);
}
}
@@ -83,82 +82,14 @@ export default class ScopesScopeProjectsTargetsTargetRoute extends Route {
*/
@action
didTransition() {
- if (this.isConnectionError) {
- /* eslint-disable-next-line ember/no-controller-access-in-routes */
- const controller = this.controllerFor(
- 'scopes.scope.projects.targets.target',
- );
+ /* eslint-disable-next-line ember/no-controller-access-in-routes */
+ const controller = this.controllerFor(
+ 'scopes.scope.projects.targets.target',
+ );
+ if (controller.get('isConnectionError')) {
controller.set('isConnecting', false);
- this.isConnectionError = false;
+ controller.set('isConnectionError', false);
}
return true;
}
-
- /**
- * Connect method that calls parent connect method and handles
- * connection errors unique to this route
- * @param {TargetModel} target
- * @param {HostModel} host
- */
- @action
- async connect(target, host) {
- /* eslint-disable-next-line ember/no-controller-access-in-routes */
- const parentController = this.controllerFor(
- 'scopes.scope.projects.targets.index',
- );
- try {
- await parentController.connect(target, host);
- } catch (error) {
- this.isConnectionError = true;
- this.confirm
- .confirm(error.message, { isConnectError: true })
- // Retry
- .then(() => this.connect(target, host))
- .catch(() => {
- // Reset the flag as this was user initiated and we're not
- // in a transition or have a host modal open
- this.isConnectionError = false;
- });
- }
- }
-
- /**
- * Establish a session to current target.
- * @param {TargetModel} target
- * @param {HostModel} host
- * hostConnect is only called when making a connection with a host and ensures that the host modal is automatically closed in the case of a connection error.
- */
- @action
- async hostConnect(target, host) {
- await this.connect(target, host);
-
- if (this.isConnectionError) {
- /* eslint-disable-next-line ember/no-controller-access-in-routes */
- const controller = this.controllerFor(
- 'scopes.scope.projects.targets.target',
- );
- controller.set('isConnecting', false);
- this.isConnectionError = false;
- }
- }
-
- /**
- * Determine if we show host modal or quick connect based on target attributes.
- * @param {TargetModel} model
- * @param {function} toggleModal
- */
- @action
- async preConnect(model, toggleModal) {
- /**
- * if hosts length is 1 or less we will try to
- * connect, even if there is no address on the target and
- * rely on the CLI to give the user the proper error or if
- * there are 2 or more hosts we show the modal for host selection
- */
- if (model.hosts.length <= 1) {
- await this.connect(model.target);
- } else {
- toggleModal(true);
- }
- }
}
diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
index ce5ada9e2..b0a82c121 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
@@ -56,7 +56,7 @@
-
+
@@ -143,7 +143,7 @@
@icon='x'
@isIconOnly={{true}}
@color='secondary'
- {{on 'click' (route-action 'cancelSession' B.data)}}
+ {{on 'click' (fn this.cancelSession B.data)}}
data-test-session-cancel-button={{B.data.id}}
/>
{{/if}}
diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/session.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/session.hbs
index 60d369ede..f537c8e58 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/sessions/session.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/session.hbs
@@ -25,7 +25,7 @@
@text={{t 'resources.session.actions.end'}}
@icon='x'
@color='secondary'
- {{on 'click' (route-action 'cancelSession' @model)}}
+ {{on 'click' (fn this.sessions.cancelSession @model)}}
/>
{{/if}}
diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
index b1cc502e1..1e489a397 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
@@ -63,7 +63,7 @@
{{#if @model.isCacheRefreshing}}
-
+
@@ -73,7 +73,7 @@
{{else}}
-
+
{{/if}}
diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs
index d5a17de7d..e05df45a9 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target.hbs
@@ -21,7 +21,7 @@
@text={{t 'resources.session.actions.connect'}}
@icon='entry-point'
@iconPosition='trailing'
- {{on 'click' (route-action 'preConnect' @model this.toggleModal)}}
+ {{on 'click' (fn this.preConnect @model)}}
/>
{{/if}}
@@ -31,7 +31,7 @@
diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js
index f940d05c3..8b6ebd430 100644
--- a/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js
+++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js
@@ -5,18 +5,143 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
+import { setupIntl } from 'ember-intl/test-support';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import setupStubs from 'api/test-support/handlers/cache-daemon-search';
+import { visit } from '@ember/test-helpers';
+import { authenticateSession } from 'ember-simple-auth/test-support';
+import {
+ STATUS_SESSION_ACTIVE,
+ STATUS_SESSION_PENDING,
+ STATUS_SESSION_CANCELING,
+ STATUS_SESSION_TERMINATED,
+} from 'api/models/session';
module(
'Unit | Controller | scopes/scope/projects/sessions/index',
function (hooks) {
setupTest(hooks);
+ setupMirage(hooks);
+ setupStubs(hooks);
+ setupIntl(hooks, 'en-us');
- // TODO: Replace this with your real tests.
- test('it exists', function (assert) {
- let controller = this.owner.lookup(
+ let store;
+ let controller;
+
+ const instances = {
+ scopes: {
+ global: null,
+ org: null,
+ project: null,
+ },
+ target: null,
+ session: null,
+ };
+
+ const urls = {
+ projectScope: null,
+ sessions: null,
+ };
+
+ hooks.beforeEach(async function () {
+ authenticateSession({});
+ store = this.owner.lookup('service:store');
+ controller = this.owner.lookup(
'controller:scopes/scope/projects/sessions/index',
);
+
+ instances.scopes.global = this.server.create('scope', { id: 'global' });
+ instances.scopes.org = this.server.create('scope', {
+ type: 'org',
+ scope: { id: 'global', type: 'global' },
+ });
+ instances.scopes.project = this.server.create('scope', {
+ type: 'project',
+ scope: { id: instances.scopes.org.id, type: 'org' },
+ });
+ instances.target = this.server.create('target', {
+ scope: instances.scopes.project,
+ });
+ instances.session = this.server.create('session', {
+ scope: instances.scopes.project,
+ target: instances.target,
+ status: STATUS_SESSION_ACTIVE,
+ });
+
+ urls.projectScope = `/scopes/${instances.scopes.org.id}/projects`;
+ urls.sessions = `${urls.projectScope}/sessions`;
+
+ this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
+ this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
+ });
+
+ test('it exists', function (assert) {
+ this.stubCacheDaemonSearch();
assert.ok(controller);
});
+
+ test('sortedSessions returns a sessions sorted by status', async function (assert) {
+ this.server.create('session', {
+ scope: instances.scopes.org,
+ target: instances.target,
+ status: STATUS_SESSION_CANCELING,
+ });
+ this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
+ await visit(urls.sessions);
+
+ const sortedSessions = controller.sortedSessions;
+
+ assert.strictEqual(sortedSessions[0].status, STATUS_SESSION_ACTIVE);
+ assert.strictEqual(sortedSessions[1].status, STATUS_SESSION_CANCELING);
+ });
+
+ test('sessionStatusOptions returns expected object', function (assert) {
+ this.stubCacheDaemonSearch();
+
+ assert.deepEqual(controller.sessionStatusOptions, [
+ { id: STATUS_SESSION_ACTIVE, name: 'Active' },
+ { id: STATUS_SESSION_PENDING, name: 'Pending' },
+ { id: STATUS_SESSION_CANCELING, name: 'Canceling' },
+ { id: STATUS_SESSION_TERMINATED, name: 'Terminated' },
+ ]);
+ });
+
+ test('availableScopes returns all existing project scopes', async function (assert) {
+ await visit(urls.sessions);
+ const project = await store.findRecord(
+ 'scope',
+ instances.scopes.project.id,
+ );
+
+ assert.deepEqual(controller.availableScopes, [project]);
+ });
+
+ test('filters returns expected entries', async function (assert) {
+ await visit(urls.sessions);
+
+ assert.ok(controller.filters.allFilters);
+ assert.ok(controller.filters.selectedFilters);
+ });
+
+ test('applyFilter action sets expected values correctly', async function (assert) {
+ this.stubCacheDaemonSearch();
+ const selectedItems = ['yes'];
+ controller.applyFilter('availableSessions', selectedItems);
+
+ assert.strictEqual(controller.page, 1);
+ assert.deepEqual(controller.availableSessions, selectedItems);
+ });
+
+ test('cancelSession action action updates session status', async function (assert) {
+ await visit(urls.sessions);
+
+ const session = await store.findRecord('session', instances.session.id);
+
+ assert.strictEqual(session.status, STATUS_SESSION_ACTIVE);
+
+ await controller.cancelSession(session);
+
+ assert.strictEqual(session.status, STATUS_SESSION_CANCELING);
+ });
},
);
diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/session-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/session-test.js
new file mode 100644
index 000000000..edacb7da9
--- /dev/null
+++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/session-test.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module(
+ 'Unit | Controller | scopes/scope/projects/sessions/session',
+ function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let controller = this.owner.lookup(
+ 'controller:scopes/scope/projects/sessions/session',
+ );
+ assert.ok(controller);
+ assert.ok(controller.sessions);
+ });
+ },
+);
diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js
new file mode 100644
index 000000000..6bd50c6e2
--- /dev/null
+++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js
@@ -0,0 +1,250 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+import { setupIntl } from 'ember-intl/test-support';
+import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
+import setupStubs from 'api/test-support/handlers/cache-daemon-search';
+import { waitUntil, visit } from '@ember/test-helpers';
+import { authenticateSession } from 'ember-simple-auth/test-support';
+import {
+ STATUS_SESSION_ACTIVE,
+ STATUS_SESSION_PENDING,
+ STATUS_SESSION_CANCELING,
+} from 'api/models/session';
+import { TYPE_TARGET_SSH, TYPE_TARGET_TCP } from 'api/models/target';
+
+module(
+ 'Unit | Controller | scopes/scope/projects/targets/index',
+ function (hooks) {
+ setupTest(hooks);
+ setupMirage(hooks);
+ setupStubs(hooks);
+ setupIntl(hooks, 'en-us');
+
+ let store;
+ let controller;
+
+ const instances = {
+ scopes: {
+ global: null,
+ org: null,
+ project: null,
+ },
+ target: null,
+ session: null,
+ };
+
+ const urls = {
+ projectScope: null,
+ targets: null,
+ };
+
+ hooks.beforeEach(async function () {
+ authenticateSession({});
+ store = this.owner.lookup('service:store');
+ controller = this.owner.lookup(
+ 'controller:scopes/scope/projects/targets/index',
+ );
+
+ instances.scopes.global = this.server.create('scope', { id: 'global' });
+ instances.scopes.org = this.server.create('scope', {
+ type: 'org',
+ scope: { id: 'global', type: 'global' },
+ });
+ instances.scopes.project = this.server.create('scope', {
+ type: 'project',
+ scope: { id: instances.scopes.org.id, type: 'org' },
+ });
+ instances.target = this.server.create('target', {
+ scope: instances.scopes.org,
+ });
+ instances.session = this.server.create('session', {
+ scope: instances.scopes.org,
+ target: instances.target,
+ status: STATUS_SESSION_ACTIVE,
+ });
+
+ urls.projectScope = `/scopes/${instances.scopes.org.id}/projects`;
+ urls.targets = `${urls.projectScope}/targets`;
+
+ this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
+ this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
+ });
+
+ test('it exists', function (assert) {
+ this.stubCacheDaemonSearch();
+ assert.ok(controller);
+ assert.ok(controller.refresh);
+ });
+
+ test('noResults returns falsy when targets exist', async function (assert) {
+ await visit(urls.targets);
+
+ assert.notOk(controller.noResults);
+ });
+
+ test('noResults returns truthy when no targets exist but there is a search term', async function (assert) {
+ this.server.schema.targets.all().destroy();
+ this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
+ controller.search = 'target that does not exist';
+ await visit(urls.targets);
+
+ assert.ok(controller.noResults);
+ });
+
+ test('noTargets returns truthy when no targets exist', async function (assert) {
+ this.server.schema.targets.all().destroy();
+ this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
+ await visit(urls.targets);
+
+ assert.ok(controller.noTargets);
+ });
+
+ test('noTargets returns falsy when targets exist', async function (assert) {
+ await visit(urls.targets);
+
+ assert.notOk(controller.noTargets);
+ });
+
+ test('availableScopes returns all existing project scopes', async function (assert) {
+ await visit(urls.targets);
+ const project = await store.findRecord(
+ 'scope',
+ instances.scopes.project.id,
+ );
+
+ assert.deepEqual(controller.availableScopes, [project]);
+ });
+
+ test('availableSessionOptions returns expected object', function (assert) {
+ this.stubCacheDaemonSearch();
+
+ assert.deepEqual(controller.availableSessionOptions, [
+ { id: 'yes', name: 'Has active sessions' },
+ { id: 'no', name: 'No active sessions' },
+ ]);
+ });
+
+ test('sortedTargetSessions returns a limit of 10 items sorted by created time descending', async function (assert) {
+ this.server.create('session', {
+ scope: instances.scopes.org,
+ target: instances.target,
+ status: STATUS_SESSION_ACTIVE,
+ });
+ controller.selectedTarget = await store.findRecord(
+ 'target',
+ instances.target.id,
+ );
+ this.stubCacheDaemonSearch('sessions', 'targets', 'aliases');
+ await visit(urls.targets);
+
+ const sortedSessions = controller.sortedTargetSessions;
+
+ assert.true(sortedSessions.length <= 10);
+ assert.true(
+ sortedSessions[0].created_time >= sortedSessions[1].created_time,
+ );
+ assert.false(controller.showFlyoutViewMoreLink);
+ });
+
+ test('viewMoreLinkQueryParams returns expected object', async function (assert) {
+ this.stubCacheDaemonSearch();
+ controller.selectedTarget = instances.target;
+
+ assert.deepEqual(controller.viewMoreLinkQueryParams, {
+ targets: [instances.target.id],
+ status: [STATUS_SESSION_ACTIVE, STATUS_SESSION_PENDING],
+ });
+ });
+
+ test('targetTypeOptions returns expected target types', async function (assert) {
+ this.stubCacheDaemonSearch();
+
+ assert.deepEqual(controller.targetTypeOptions, [
+ { id: TYPE_TARGET_TCP, name: 'Generic TCP' },
+ { id: TYPE_TARGET_SSH, name: 'SSH' },
+ ]);
+ });
+
+ test('filters returns expected entries', async function (assert) {
+ await visit(urls.targets);
+
+ assert.ok(controller.filters.allFilters);
+ assert.ok(controller.filters.selectedFilters);
+ });
+
+ test('handleSearchInput action sets expected values correctly', async function (assert) {
+ this.stubCacheDaemonSearch();
+ const searchValue = 'test';
+ controller.handleSearchInput({ target: { value: searchValue } });
+ await waitUntil(() => controller.search === searchValue);
+
+ assert.strictEqual(controller.page, 1);
+ assert.strictEqual(controller.search, searchValue);
+ });
+
+ test('toggleSessionsFlyout action sets expected value correctly', async function (assert) {
+ this.stubCacheDaemonSearch();
+ controller.sessionsFlyoutActive = false;
+
+ assert.false(controller.sessionsFlyoutActive);
+
+ controller.toggleSessionsFlyout();
+
+ assert.true(controller.sessionsFlyoutActive);
+
+ controller.toggleSessionsFlyout();
+
+ assert.false(controller.sessionsFlyoutActive);
+ });
+
+ test('applyFilter action sets expected values correctly', async function (assert) {
+ this.stubCacheDaemonSearch();
+ const selectedItems = ['yes'];
+ controller.applyFilter('availableSessions', selectedItems);
+
+ assert.strictEqual(controller.page, 1);
+ assert.deepEqual(controller.availableSessions, selectedItems);
+ });
+
+ test('selectTarget action sets expected value correctly', async function (assert) {
+ this.stubCacheDaemonSearch();
+
+ assert.notOk(controller.selectedTarget);
+
+ controller.selectTarget(instances.target);
+
+ assert.deepEqual(controller.selectedTarget, instances.target);
+ });
+
+ test('connect action creates a session with correct info and cancelSession action updates session status', async function (assert) {
+ const attrs = {
+ session_id: instances.session.id,
+ address: 'a_123',
+ port: 'p_123',
+ };
+ this.ipcStub.withArgs('cliExists').returns(true);
+ this.ipcStub.withArgs('connect').returns(attrs);
+ await visit(urls.targets);
+ const target = await store.findRecord('target', instances.target.id);
+ const session = await store.findRecord('session', instances.session.id);
+
+ assert.notOk(session.proxy_address);
+ assert.notOk(session.proxy_port);
+
+ await controller.connect(target);
+
+ assert.strictEqual(session.proxy_address, attrs.address);
+ assert.strictEqual(session.proxy_port, attrs.port);
+
+ await controller.cancelSession(session);
+ await visit(urls.projectScope);
+
+ assert.strictEqual(session.status, STATUS_SESSION_CANCELING);
+ });
+ },
+);