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); + }); + }, +);