diff --git a/addons/api/addon-test-support/handlers/client-daemon-search.js b/addons/api/addon-test-support/handlers/client-daemon-search.js new file mode 100644 index 000000000..baa34e577 --- /dev/null +++ b/addons/api/addon-test-support/handlers/client-daemon-search.js @@ -0,0 +1,62 @@ +import sinon from 'sinon'; + +/** + * This test helper can be used to help setup your sinon stubs in your tests. + * Must be called after setupMirage. + * + * ```js + * import setupStubs from 'api/test-support/handlers/client-daemon-search'; + * + * module('Acceptance | my test', function(hooks) { + * setupApplicationTest(hooks); + * setupMirage(hooks); + * setupStubs(hooks); + * + * // add your actual tests here + * }); + * ``` + * + * This will setup a stub for the IPC service and include helper methods to + * help setup what the client daemon search command returns. + * + * @module Test Helpers + * @public + */ +export default function setupStubs(hooks) { + hooks.beforeEach(function () { + const ipcService = this.owner.lookup('service:ipc'); + this.ipcStub = sinon.stub(ipcService, 'invoke'); + + /** + * We aim to still use mirage data when stubbing out the client daemon + * search. However, the handler expects the search data to be raw JSON from + * the API which will get serialized. We add back the scope object for it to + * get correctly normalized and other fields such as "attributes" from the + * API will already be hoisted and will not be re-normalized from mirage data. + * + * This convenience method can take in multiple types, + * e.g. this.stubClientDaemonSearch('targets', 'sessions'). + * This will have sinon stub out each call so the first call to search will + * provide all the models from the first parameter you specify, + * the second call will provide all the models from the second argument, etc. + * @param types + */ + this.stubClientDaemonSearch = (...types) => { + types.forEach((type, i) => { + this.ipcStub + .withArgs('searchClientDaemon') + .onCall(i) + .returns({ + [type]: this.server.db[type].map((model) => ({ + ...model, + scope: this.server.db.scopes.find(model.scopeId), + })), + }); + }); + }; + }); + + hooks.afterEach(function () { + sinon.restore(); + }); +} diff --git a/addons/api/addon/handlers/client-daemon-handler.js b/addons/api/addon/handlers/client-daemon-handler.js index 0b736ed58..95d7563d5 100644 --- a/addons/api/addon/handlers/client-daemon-handler.js +++ b/addons/api/addon/handlers/client-daemon-handler.js @@ -1,6 +1,5 @@ import { inject as service } from '@ember/service'; import { getOwner, setOwner } from '@ember/application'; -import ArrayProxy from '@ember/array/proxy'; import { pluralize } from 'ember-inflector'; import { generateMQLExpression } from '../utils/mqlQuery'; @@ -111,17 +110,20 @@ export default class ClientDaemonHandler { ); // Return the raw data if we don't push to the store. - let records = normalizedPayload.data; + let records = normalizedPayload.data.map((record) => ({ + ...record.attributes, + id: record.id, + })); if (pushToStore) { records = store.push(normalizedPayload); } - // Use a proxy array to store the metadata so the store's `query` - // array requirement is satisfied. - return ArrayProxy.create({ - content: records, - meta: { totalItems: results?.length ?? 0 }, - }); + // Set a meta property on the array to store the total items of the results. + // This isn't conventional but is better than returning an ArrayProxy + // or EmberArray since the ember store query method asserts it has to be an array + // so we can't just return an object. + records.meta = { totalItems: results?.length ?? 0 }; + return records; } default: return next(context.request); diff --git a/addons/rose/addon/components/rose/pagination/index.hbs b/addons/rose/addon/components/rose/pagination/index.hbs new file mode 100644 index 000000000..254848ea4 --- /dev/null +++ b/addons/rose/addon/components/rose/pagination/index.hbs @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/addons/rose/addon/components/rose/pagination/index.js b/addons/rose/addon/components/rose/pagination/index.js new file mode 100644 index 000000000..d3638fa52 --- /dev/null +++ b/addons/rose/addon/components/rose/pagination/index.js @@ -0,0 +1,19 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; + +export default class RosePaginationComponent extends Component { + @service router; + + /** + * Returns the query params needed for the pagination component. + * @returns {function(number, number): {pageSize: number, page: number}} + */ + get paginationQueryParams() { + return (page, pageSize) => { + return { + page, + pageSize, + }; + }; + } +} diff --git a/addons/rose/app/components/rose/pagination.js b/addons/rose/app/components/rose/pagination.js new file mode 100644 index 000000000..3c9e714bb --- /dev/null +++ b/addons/rose/app/components/rose/pagination.js @@ -0,0 +1 @@ +export { default } from 'rose/components/rose/pagination'; 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 e9eb2313d..7656ac777 100644 --- a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js @@ -21,9 +21,11 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle // =attributes - queryParams = ['search']; + queryParams = ['search', 'page', 'pageSize']; @tracked search; + @tracked page = 1; + @tracked pageSize = 10; @tracked searchItems = ['Project 1', 'Project 2', 'Project 3']; // =methods @@ -138,5 +140,6 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle handleSearchInput(event) { const { value } = event.target; this.search = value; + this.page = 1; } } diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions.js b/ui/desktop/app/routes/scopes/scope/projects/sessions.js index f15e08917..ba02cbd35 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/sessions.js +++ b/ui/desktop/app/routes/scopes/scope/projects/sessions.js @@ -53,7 +53,7 @@ export default class ScopesScopeProjectsSessionsRoute extends Route { this.router.currentRoute.name === 'scopes.scope.projects.sessions.session.index' ) { - await this.router.replaceWith('scopes.scope.projects.targets'); + this.router.replaceWith('scopes.scope.projects.targets'); } } diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js index f3e70524f..9243f2361 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js @@ -23,6 +23,12 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { refreshModel: true, replace: true, }, + page: { + refreshModel: true, + }, + pageSize: { + refreshModel: true, + }, }; // =methods @@ -39,10 +45,15 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { * * @returns {Promise<{totalItems: number, targets: [TargetModel]}>} */ - async model({ search = '' }) { + async model({ search, page, pageSize }) { // TODO: Filter targets by scope we're in manually // const { id: scope_id } = this.modelFor('scopes.scope'); - const queryOptions = { query: { search } }; + + const queryOptions = { + query: { search }, + page, + pageSize, + }; let targets = await this.store.query('target', queryOptions); const totalItems = targets.meta?.totalItems; diff --git a/ui/desktop/app/styles/app.scss b/ui/desktop/app/styles/app.scss index 9f676b770..c65465b48 100644 --- a/ui/desktop/app/styles/app.scss +++ b/ui/desktop/app/styles/app.scss @@ -686,16 +686,20 @@ $chevron-down: url("data:image/svg+xml;utf-8, - + {{#if (or @model.targets this.search)}} -
- - + + + - - + - - - - {{#each this.searchItems as |item|}} - - {{item}} - - {{/each}} - - - - {{t 'actions.yes'}} - {{t 'actions.no'}} - - - - {{t 'resources.target.types.tcp'}} - {{t 'resources.target.types.ssh'}} - - -
+ + {{#each this.searchItems as |item|}} + + {{item}} + + {{/each}} + + + + {{t 'actions.yes'}} + {{t 'actions.no'}} + + + + {{t 'resources.target.types.tcp'}} + {{t 'resources.target.types.ssh'}} + + {{#if @model.targets}} + + {{/if}} {{#if this.noResults}} diff --git a/ui/desktop/electron-app/src/ipc/handlers.js b/ui/desktop/electron-app/src/ipc/handlers.js index 60f61a27e..5cd7c59fa 100644 --- a/ui/desktop/electron-app/src/ipc/handlers.js +++ b/ui/desktop/electron-app/src/ipc/handlers.js @@ -146,7 +146,7 @@ handle('searchClientDaemon', async (request) => * Check to see if the client daemon is running. We use the presence of a * socket path as a proxy for whether the daemon is running. */ -handle('isClientDaemonRunning', async (request) => +handle('isClientDaemonRunning', async () => Boolean(clientDaemonManager.socketPath), ); diff --git a/ui/desktop/tests/acceptance/projects/sessions/index-test.js b/ui/desktop/tests/acceptance/projects/sessions/index-test.js index a071d20a8..18fc711b3 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/index-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/index-test.js @@ -15,17 +15,18 @@ import { authenticateSession, invalidateSession, } from 'ember-simple-auth/test-support'; -import sinon from 'sinon'; import { STATUS_SESSION_ACTIVE, STATUS_SESSION_PENDING, STATUS_SESSION_CANCELING, STATUS_SESSION_TERMINATED, } from 'api/models/session'; +import setupStubs from 'api/test-support/handlers/client-daemon-search'; -module('Acceptance | projects | sessions', function (hooks) { +module('Acceptance | projects | sessions | index', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupStubs(hooks); const instances = { scopes: { @@ -40,12 +41,6 @@ module('Acceptance | projects | sessions', function (hooks) { session: null, }; - const stubs = { - global: null, - org: null, - ipcService: null, - }; - const urls = { scopes: { global: null, @@ -77,17 +72,16 @@ module('Acceptance | projects | sessions', function (hooks) { // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); - stubs.global = { id: 'global', type: 'global' }; + const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); - stubs.org = { id: instances.scopes.org.id, type: 'org' }; + const orgScope = { id: instances.scopes.org.id, type: 'org' }; instances.scopes.project = this.server.create('scope', { type: 'project', - scope: stubs.org, + scope: orgScope, }); - stubs.project = { id: instances.scopes.project.id, type: 'project' }; instances.authMethods.global = this.server.create('auth-method', { scope: instances.scopes.global, @@ -120,12 +114,7 @@ module('Acceptance | projects | sessions', function (hooks) { this.owner.register('service:browser/window', WindowMockIPC); setDefaultClusterUrl(this); - const ipcService = this.owner.lookup('service:ipc'); - stubs.ipcService = sinon.stub(ipcService, 'invoke'); - }); - - hooks.afterEach(function () { - sinon.restore(); + this.ipcStub.withArgs('isClientDaemonRunning').returns(false); }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { @@ -299,7 +288,7 @@ module('Acceptance | projects | sessions', function (hooks) { test('cancelling a session shows success alert', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop'); + this.ipcStub.withArgs('stop'); await visit(urls.sessions); await click('tbody tr:first-child td:last-child button'); @@ -309,7 +298,7 @@ module('Acceptance | projects | sessions', function (hooks) { test('cancelling a session keeps you on the sessions list screen', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop'); + this.ipcStub.withArgs('stop'); await visit(urls.sessions); await click('tbody tr:first-child td:last-child button'); @@ -329,7 +318,7 @@ module('Acceptance | projects | sessions', function (hooks) { test('cancelling a session with ipc error shows notification', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop').throws(); + this.ipcStub.withArgs('stop').throws(); await visit(urls.sessions); await click('tbody tr:first-child td:last-child button'); diff --git a/ui/desktop/tests/acceptance/projects/sessions/session-test.js b/ui/desktop/tests/acceptance/projects/sessions/session-test.js index e69858e0c..332905aa1 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/session-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/session-test.js @@ -12,13 +12,14 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; -import sinon from 'sinon'; import WindowMockIPC from '../../../helpers/window-mock-ipc'; import { STATUS_SESSION_ACTIVE } from 'api/models/session'; +import setupStubs from 'api/test-support/handlers/client-daemon-search'; module('Acceptance | projects | sessions | session', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupStubs(hooks); const TARGET_CONNECT_BUTTON = '[data-test-target-detail-connect-button]'; @@ -35,12 +36,6 @@ module('Acceptance | projects | sessions | session', function (hooks) { session: null, }; - const stubs = { - global: null, - org: null, - ipcService: null, - }; - const urls = { scopes: { global: null, @@ -70,17 +65,17 @@ module('Acceptance | projects | sessions | session', function (hooks) { // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); - stubs.global = { id: 'global', type: 'global' }; + const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); - stubs.org = { id: instances.scopes.org.id, type: 'org' }; + const orgScope = { id: instances.scopes.org.id, type: 'org' }; instances.scopes.project = this.server.create('scope', { type: 'project', - scope: stubs.org, + scope: orgScope, }); - stubs.project = { id: instances.scopes.project.id, type: 'project' }; + instances.hostCatalog = this.server.create('host-catalog', { scope: instances.scopes.project, }); @@ -119,12 +114,10 @@ module('Acceptance | projects | sessions | session', function (hooks) { this.owner.register('service:browser/window', WindowMockIPC); setDefaultClusterUrl(this); - const ipcService = this.owner.lookup('service:ipc'); - stubs.ipcService = sinon.stub(ipcService, 'invoke'); + this.ipcStub.withArgs('isClientDaemonRunning').returns(false); }); hooks.afterEach(function () { - sinon.restore(); // reset onUncaughtException to original state QUnit.onUncaughtException = originalUncaughtException; }); @@ -140,8 +133,8 @@ module('Acceptance | projects | sessions | session', function (hooks) { test('visiting session with no credentials', async function (assert) { assert.expect(4); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', @@ -162,8 +155,8 @@ module('Acceptance | projects | sessions | session', function (hooks) { test('visiting a session that does not have permissions to read a host', async function (assert) { assert.expect(1); this.server.get('/hosts/:id', () => new Response(403)); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, host_id: 'h_123', address: 'a_123', @@ -185,8 +178,8 @@ module('Acceptance | projects | sessions | session', function (hooks) { }; this.server.get('/sessions/:id', () => new Response(403)); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, host_id: 'h_123', address: 'a_123', @@ -220,7 +213,6 @@ module('Acceptance | projects | sessions | session', function (hooks) { test('cancelling a session shows success alert', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop'); await visit(urls.session); await click('[data-test-session-detail-cancel-button]'); @@ -230,7 +222,6 @@ module('Acceptance | projects | sessions | session', function (hooks) { test('cancelling a session takes you to the targets list screen', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop'); await visit(urls.session); await click('[data-test-session-detail-cancel-button]'); @@ -250,7 +241,7 @@ module('Acceptance | projects | sessions | session', function (hooks) { test('cancelling a session with ipc error shows notification', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('stop').throws(); + this.ipcStub.withArgs('stop').throws(); await visit(urls.session); await click('[data-test-session-detail-cancel-button]'); diff --git a/ui/desktop/tests/acceptance/projects/targets/index-test.js b/ui/desktop/tests/acceptance/projects/targets/index-test.js index 62dd7db73..264b96689 100644 --- a/ui/desktop/tests/acceptance/projects/targets/index-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/index-test.js @@ -12,19 +12,19 @@ import { } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import sinon from 'sinon'; import WindowMockIPC from '../../../helpers/window-mock-ipc'; import { authenticateSession, invalidateSession, currentSession, } from 'ember-simple-auth/test-support'; +import setupStubs from 'api/test-support/handlers/client-daemon-search'; module('Acceptance | projects | targets | index', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupStubs(hooks); let getTargetCount; @@ -51,12 +51,6 @@ module('Acceptance | projects | targets | index', function (hooks) { session: null, }; - const stubs = { - global: null, - org: null, - ipcService: null, - }; - const urls = { scopes: { global: null, @@ -87,17 +81,16 @@ module('Acceptance | projects | targets | index', function (hooks) { // Generate scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); - stubs.global = { id: 'global', type: 'global' }; + const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); - stubs.org = { id: instances.scopes.org.id, type: 'org' }; + const orgScope = { id: instances.scopes.org.id, type: 'org' }; instances.scopes.project = this.server.create('scope', { type: 'project', - scope: stubs.org, + scope: orgScope, }); - stubs.project = { id: instances.scopes.project.id, type: 'project' }; // Generate resources instances.authMethods.global = this.server.create('auth-method', { @@ -137,12 +130,8 @@ module('Acceptance | projects | targets | index', function (hooks) { this.owner.register('service:browser/window', WindowMockIPC); setDefaultClusterUrl(this); - const ipcService = this.owner.lookup('service:ipc'); - stubs.ipcService = sinon.stub(ipcService, 'invoke'); - }); - - hooks.afterEach(function () { - sinon.restore(); + this.ipcStub.withArgs('isClientDaemonRunning').returns(true); + this.stubClientDaemonSearch('targets'); }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { @@ -182,7 +171,9 @@ module('Acceptance | projects | targets | index', function (hooks) { test('visiting targets list view with no targets', async function (assert) { assert.expect(1); - this.server.get('/targets', () => new Response(200)); + this.server.db.targets.remove(); + this.stubClientDaemonSearch('targets'); + await visit(urls.projects); await click(`[href="${urls.targets}"]`); @@ -194,6 +185,8 @@ module('Acceptance | projects | targets | index', function (hooks) { assert.expect(1); instances.target.authorized_actions = instances.target.authorized_actions.filter((item) => item !== 'read'); + this.stubClientDaemonSearch('targets'); + await visit(urls.projects); await click(`[href="${urls.targets}"]`); @@ -223,6 +216,8 @@ module('Acceptance | projects | targets | index', function (hooks) { instances.target.authorized_actions.filter( (item) => item !== 'authorize-session', ); + this.stubClientDaemonSearch('targets'); + await visit(urls.projects); await click(`[href="${urls.targets}"]`); @@ -237,8 +232,8 @@ module('Acceptance | projects | targets | index', function (hooks) { test('user is redirected to target details page when unable to connect from list view if they have read and authorize-session permissions', async function (assert) { assert.expect(3); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').rejects(); + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').rejects(); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; @@ -255,8 +250,9 @@ module('Acceptance | projects | targets | index', function (hooks) { assert.expect(2); instances.target.authorized_actions = instances.target.authorized_actions.filter((item) => item !== 'read'); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.stubClientDaemonSearch('targets'); + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', @@ -278,8 +274,9 @@ module('Acceptance | projects | targets | index', function (hooks) { assert.expect(5); instances.target.authorized_actions = instances.target.authorized_actions.filter((item) => item !== 'read'); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').rejects(); + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').rejects(); + this.stubClientDaemonSearch('targets'); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; diff --git a/ui/desktop/tests/acceptance/projects/targets/target-test.js b/ui/desktop/tests/acceptance/projects/targets/target-test.js index e77bf9b66..9c66d7aef 100644 --- a/ui/desktop/tests/acceptance/projects/targets/target-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/target-test.js @@ -7,13 +7,14 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import sinon from 'sinon'; import { authenticateSession } from 'ember-simple-auth/test-support'; import WindowMockIPC from '../../../helpers/window-mock-ipc'; +import setupStubs from 'api/test-support/handlers/client-daemon-search'; module('Acceptance | projects | targets | target', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupStubs(hooks); const TARGET_CONNECT_BUTTON = '[data-test-target-detail-connect-button]'; const TARGET_QUICK_CONNECT_BUTTON = '[data-test-host-quick-connect]'; @@ -40,12 +41,6 @@ module('Acceptance | projects | targets | target', function (hooks) { targetWithTwoHosts: null, }; - const stubs = { - global: null, - org: null, - ipcService: null, - }; - const urls = { scopes: { global: null, @@ -72,17 +67,16 @@ module('Acceptance | projects | targets | target', function (hooks) { // Generate scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); - stubs.global = { id: 'global', type: 'global' }; + const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); - stubs.org = { id: instances.scopes.org.id, type: 'org' }; + const orgScope = { id: instances.scopes.org.id, type: 'org' }; instances.scopes.project = this.server.create('scope', { type: 'project', - scope: stubs.org, + scope: orgScope, }); - stubs.project = { id: instances.scopes.project.id, type: 'project' }; // Generate resources instances.authMethods.global = this.server.create('auth-method', { @@ -129,18 +123,14 @@ module('Acceptance | projects | targets | target', function (hooks) { this.owner.register('service:browser/window', WindowMockIPC); setDefaultClusterUrl(this); - const ipcService = this.owner.lookup('service:ipc'); - stubs.ipcService = sinon.stub(ipcService, 'invoke'); - }); - - hooks.afterEach(function () { - sinon.restore(); + this.ipcStub.withArgs('isClientDaemonRunning').returns(true); + this.stubClientDaemonSearch('targets'); }); test('user can connect to a target with an address', async function (assert) { assert.expect(3); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'localhost', port: 'p_123', @@ -159,7 +149,7 @@ module('Acceptance | projects | targets | target', function (hooks) { test('handles cli error on connect', async function (assert) { assert.expect(4); - stubs.ipcService.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('cliExists').returns(true); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; await visit(urls.targets); @@ -175,8 +165,8 @@ module('Acceptance | projects | targets | target', function (hooks) { test('handles connect error', async function (assert) { assert.expect(4); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').rejects(); + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').rejects(); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; await visit(urls.targets); @@ -192,7 +182,7 @@ module('Acceptance | projects | targets | target', function (hooks) { test('user can retry on error', async function (assert) { assert.expect(1); - stubs.ipcService.withArgs('cliExists').rejects(); + this.ipcStub.withArgs('cliExists').rejects(); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; @@ -208,8 +198,8 @@ module('Acceptance | projects | targets | target', function (hooks) { test('user can connect to a target with one host', async function (assert) { assert.expect(2); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', @@ -228,8 +218,8 @@ module('Acceptance | projects | targets | target', function (hooks) { test('user can connect to a target with two hosts using host modal', async function (assert) { assert.expect(3); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', @@ -275,8 +265,8 @@ module('Acceptance | projects | targets | target', function (hooks) { test('user can connect to a target without read permissions for host-set', async function (assert) { assert.expect(1); this.server.get('/host-sets/:id', () => new Response(403)); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', @@ -294,8 +284,8 @@ module('Acceptance | projects | targets | target', function (hooks) { test('user can connect to a target without read permissions for host', async function (assert) { assert.expect(1); this.server.get('/hosts/:id', () => new Response(403)); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123', diff --git a/ui/desktop/tests/acceptance/scopes-test.js b/ui/desktop/tests/acceptance/scopes-test.js index 6cf788a89..e8c5489e2 100644 --- a/ui/desktop/tests/acceptance/scopes-test.js +++ b/ui/desktop/tests/acceptance/scopes-test.js @@ -9,17 +9,18 @@ import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import sinon from 'sinon'; import { currentSession, authenticateSession, invalidateSession, } from 'ember-simple-auth/test-support'; import WindowMockIPC from '../helpers/window-mock-ipc'; +import setupStubs from 'api/test-support/handlers/client-daemon-search'; module('Acceptance | scopes', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupStubs(hooks); const APP_STATE_TITLE = '.hds-application-state__title'; @@ -36,12 +37,6 @@ module('Acceptance | scopes', function (hooks) { session: null, }; - const stubs = { - global: null, - org: null, - ipcService: null, - }; - const urls = { index: '/', clusterUrl: '/cluster-url', @@ -78,21 +73,20 @@ module('Acceptance | scopes', function (hooks) { // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); - stubs.global = { id: 'global', type: 'global' }; + const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); instances.scopes.org2 = this.server.create('scope', { type: 'org', - scope: stubs.global, + scope: globalScope, }); - stubs.org = { id: instances.scopes.org.id, type: 'org' }; + const orgScope = { id: instances.scopes.org.id, type: 'org' }; instances.scopes.project = this.server.create('scope', { type: 'project', - scope: stubs.org, + scope: orgScope, }); - stubs.project = { id: instances.scopes.project.id, type: 'project' }; instances.authMethods.global = this.server.create('auth-method', { scope: instances.scopes.global, @@ -134,12 +128,8 @@ module('Acceptance | scopes', function (hooks) { this.owner.register('service:browser/window', WindowMockIPC); setDefaultClusterUrl(this); - const ipcService = this.owner.lookup('service:ipc'); - stubs.ipcService = sinon.stub(ipcService, 'invoke'); - }); - - hooks.afterEach(function () { - sinon.restore(); + this.ipcStub.withArgs('isClientDaemonRunning').returns(true); + this.stubClientDaemonSearch('targets'); }); test('visiting index', async function (assert) { @@ -190,6 +180,7 @@ module('Acceptance | scopes', function (hooks) { test('can navigate among org scopes via header navigation', async function (assert) { assert.expect(3); + this.stubClientDaemonSearch('targets', 'targets', 'targets', 'targets'); await visit(urls.targets); await click('.rose-header-nav .rose-dropdown a:nth-of-type(2)'); @@ -224,7 +215,8 @@ module('Acceptance | scopes', function (hooks) { test('visiting empty targets', async function (assert) { assert.expect(1); - this.server.get('/targets', () => new Response(200)); + this.server.db.targets.remove(); + this.stubClientDaemonSearch('targets'); await visit(urls.targets); @@ -233,8 +225,8 @@ module('Acceptance | scopes', function (hooks) { test.skip('connecting to a target', async function (assert) { assert.expect(3); - stubs.ipcService.withArgs('cliExists').returns(true); - stubs.ipcService.withArgs('connect').returns({ + this.ipcStub.ipcService.withArgs('cliExists').returns(true); + this.ipcStub.ipcService.withArgs('connect').returns({ session_id: instances.session.id, address: 'a_123', port: 'p_123',