Remove renderer url (#3194)

* feat: 🎸 Refactor how we store cluster URL
This commit is contained in:
Zhihe Li authored and GitHub committed 2026-05-20 16:36:16 -04:00
1 parent 3d257afd49
commit 7dadc53fe1
26 files changed
+139 -217

No files matched your search

@@ -133,6 +133,8 @@ export const electronTest = test.extend({
electronPage: async ({ electronApp }, use) => {
const page = await electronApp.firstWindow();
await page.evaluate(() => localStorage.clear());
// Reset the cluster URL stored by electron
await page.evaluate(() => window.desktop.cluster.resetClusterUrl());
await page.context().tracing.start({ screenshots: true, snapshots: true });
await use(page);
+5 -4
View File
@@ -45,7 +45,7 @@ export default class ApplicationController extends Controller {
* @type {object}
*/
get currentScope() {
if (this.scope.org.isOrg) {
if (this.scope.org?.isOrg) {
return { name: this.scope.org.displayName, icon: 'org' };
} else {
return { name: this.intl.t('titles.global'), icon: 'globe' };
@@ -100,13 +100,14 @@ export default class ApplicationController extends Controller {
}
/**
* Disconnects from clusterUrl and invalidates session, thereby resetting
* the client and reloading to the onboarding clusterUrl screen.
* Invalidates the session and navigates to the cluster-url route.
* The cluster URL is preserved so that upon reload the user is directed
* back to the authentication screen for the same cluster.
*/
@action
disconnect() {
this.clusterUrl.resetClusterUrl();
this.session.invalidate();
this.router.replaceWith('cluster-url');
}
@action
@@ -1,34 +0,0 @@
/**
* Copyright IBM Corp. 2021, 2026
* SPDX-License-Identifier: BUSL-1.1
*/
import { getOwner } from '@ember/application';
export function initialize(registry) {
// Lookup the application route
const ApplicationRoute = registry.resolveRegistration
? registry.resolveRegistration('route:application')
: registry.resolve('route:application');
ApplicationRoute.reopen({
init() {
this._super(...arguments);
const originalBeforeModel = this.beforeModel;
// Override the beforeModel hook and initialize the clusterUrl
this.beforeModel = async function () {
const clusterUrl = getOwner(this).lookup('service:clusterUrl');
return clusterUrl.updateClusterUrl().then(
() => originalBeforeModel.apply(this, arguments),
() => originalBeforeModel.apply(this, arguments),
);
};
},
});
}
export default {
after: 'ember-simple-auth',
initialize,
};
+8 -6
View File
@@ -15,6 +15,7 @@ export default class ApplicationRoute extends Route {
@service session;
@service clusterUrl;
@service intl;
@service store;
@service rdp;
@service terminal;
@@ -25,19 +26,20 @@ export default class ApplicationRoute extends Route {
*/
routeIfUnauthenticated = 'index';
/**
* Check that the clusterUrl specified in the renderer matches the clusterUrl
* reported by the main process. If they differ, update the main process
* clusterUrl so that the renderer's CSP can be rewritten to allow requests.
*/
@notifyError(({ message }) => message, {
catch: true,
log: { origin: 'ApplicationRoute:beforeModel' },
})
async beforeModel() {
this.intl.setLocale(['en-us']);
const clusterUrl = await this.clusterUrl.getClusterUrl();
if (clusterUrl) {
const adapter = this.store.adapterFor('application');
adapter.host = clusterUrl;
}
await this.session.setup();
await this.clusterUrl.updateClusterUrl();
const theme = this.session.get('data.theme');
/* eslint-disable-next-line ember/no-controller-access-in-routes */
const controller = this.controllerFor(this.routeName);
+2 -2
View File
@@ -41,9 +41,9 @@ export default class ClusterUrlRoute extends Route {
* Adds the existing clusterUrl, if any, to the controller scope.
* @param {Controller} controller
*/
setupController(controller) {
async setupController(controller) {
super.setupController(...arguments);
let clusterUrl = this.clusterUrl.rendererClusterUrl;
let clusterUrl = await this.clusterUrl.getClusterUrl();
// If clusterUrl is unset and this is a development environment,
// auto-set the clusterUrl field of the UI for better DX.
// The controller URL is almost always the same as the current window when
+3 -3
View File
@@ -18,12 +18,12 @@ export default class IndexRoute extends Route {
/**
* If no clusterUrl is specified yet, redirects to cluster-url, otherwise scopes.
*/
redirect() {
async beforeModel() {
// cleanup terminal view when navigating to index route - on window reload
this.terminal.cleanup();
const rendererClusterUrl = this.clusterUrl.rendererClusterUrl;
if (!rendererClusterUrl) {
const clusterUrl = await this.clusterUrl.getClusterUrl();
if (!clusterUrl) {
this.router.replaceWith('cluster-url');
} else {
this.router.replaceWith('scopes');
@@ -17,8 +17,11 @@ export default class ScopesScopeAuthenticateRoute extends Route {
// =methods
beforeModel() {
if (this.session.isAuthenticated) {
async beforeModel() {
const clusterUrl = await this.clusterUrl.getClusterUrl();
if (!clusterUrl) {
this.router.replaceWith('cluster-url');
} else if (this.session.isAuthenticated) {
this.router.replaceWith('scopes.scope.index');
}
}
@@ -64,17 +67,13 @@ export default class ScopesScopeAuthenticateRoute extends Route {
};
}
redirect() {
if (!this.clusterUrl.rendererClusterUrl) this.router.replaceWith('index');
}
/**
* Adds the existing clusterUrl, if any, to the controller scope.
* @param {Controller} controller
*/
setupController(controller) {
async setupController(controller) {
super.setupController(...arguments);
const clusterUrl = this.clusterUrl.rendererClusterUrl;
const clusterUrl = await this.clusterUrl.getClusterUrl();
controller.setProperties({ clusterUrl });
}
}
@@ -22,7 +22,7 @@ export default class ScopesScopeProjectsSettingsIndexRoute extends Route {
const logLevel = await window.desktop.logging.getLogLevel();
const logPath = await window.desktop.logging.getLogPath();
const serverInformation = this.clusterUrl.rendererClusterUrl;
const serverInformation = await this.clusterUrl.getClusterUrl();
const { isLinux } = await window.desktop.system.checkOS();
return {
@@ -18,7 +18,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
// =services
@service abilities;
@service clusterUrl;
@service resourceFilterStore;
@service router;
@service session;
+6 -34
View File
@@ -12,7 +12,6 @@ export default class ClusterUrlService extends Service {
// =services
@service store;
@service storage;
// =attributes
@@ -23,35 +22,10 @@ export default class ClusterUrlService extends Service {
return this.store.adapterFor('application');
}
/**
* @type {?string}
*/
get rendererClusterUrl() {
return this.storage.getItem('cluster-url');
}
/**
* @param {?string} clusterUrl
*/
set rendererClusterUrl(clusterUrl) {
this.storage.setItem('cluster-url', clusterUrl);
}
/**
* @type {Promise{?string}}
*/
get mainClusterUrl() {
return window.desktop.cluster.getClusterUrl();
}
// =methods
/**
* Sets the main clusterUrl equal to the current renderer clusterUrl (if any).
*/
async updateClusterUrl() {
const rendererClusterUrl = this.rendererClusterUrl;
if (rendererClusterUrl) await this.setClusterUrl(rendererClusterUrl);
async getClusterUrl() {
return window.desktop.cluster.getClusterUrl();
}
/**
@@ -73,26 +47,24 @@ export default class ClusterUrlService extends Service {
clusterUrl = clusterUrl.trim();
clusterUrl = clusterUrl.replace(/\/*$/, '');
this.adapter.host = clusterUrl;
this.rendererClusterUrl = clusterUrl;
if (clusterUrl !== (await this.mainClusterUrl)) {
if (clusterUrl !== (await this.getClusterUrl())) {
await window.desktop.cluster.setClusterUrl(clusterUrl);
}
} catch (e) {
this.adapter.host = originalHost;
this.rendererClusterUrl = null;
throw e;
}
}
/**
* Resets the clusterUrl.
* Resets the clusterUrl in both the electron store and the adapter host.
*/
@notifyError(({ message }) => message, {
catch: true,
log: { origin: 'resetClusterUrl' },
})
async resetClusterUrl() {
this.rendererClusterUrl = null;
await window.desktop.cluster.resetClusterUrl();
this.adapter.host = undefined;
return window.desktop.cluster.resetClusterUrl();
}
}
+1 -1
View File
@@ -33,7 +33,7 @@ handle('getClusterUrl', () => runtimeSettings.clusterUrl);
handle('setClusterUrl', async (requestOrigin) => {
const clusterUrl = sanitizer.urlValidate(requestOrigin);
await runtimeSettings.validateClusterUrl(clusterUrl);
await runtimeSettings.setClusterUrl(clusterUrl);
await runtimeSettings.triggerClusterUrlUpdate(clusterUrl);
});
/**
@@ -4,26 +4,25 @@
*/
const { netRequest } = require('../helpers/request-promise.js');
const store = require('../services/electron-store-manager');
// Runtime settings are any configuration, especially user-specified,
// set up at runtime. For example, the Boundary clusterUrl is a runtime setting.
class RuntimeSettings {
// Internal private clusterUrl is exposed via getter/setter below.
#clusterUrl = undefined;
/**
* The user-specified Boundary clusterUrl, which should be allowed by CSP.
* @type {?string}
*/
get clusterUrl() {
return this.#clusterUrl;
return store.get('clusterUrl');
}
async setClusterUrl(clusterUrl) {
if (this.#clusterUrl !== clusterUrl) {
this.#clusterUrl = clusterUrl;
await this.triggerClusterUrlChanged();
}
/**
* Sets the user specified Boundary clusterUrl.
* @param value {string}
*/
set clusterUrl(value) {
store.set('clusterUrl', value);
}
/**
@@ -51,8 +50,8 @@ class RuntimeSettings {
/**
* Sets the clusterUrl to null.
*/
async resetClusterUrl() {
await this.setClusterUrl(null);
resetClusterUrl() {
store.delete('clusterUrl');
}
// Quick and dirty event handler pattern to enable the application to respond
@@ -63,11 +62,14 @@ class RuntimeSettings {
this.#clusterUrlWatchers.push(fn);
}
async triggerClusterUrlChanged() {
// We currently only store one function but this will execute serially
// which may or may not be the desired behavior if there ever is more
for (const fn of this.#clusterUrlWatchers) {
await fn(this.#clusterUrl);
async triggerClusterUrlUpdate(clusterUrl) {
if (this.clusterUrl !== clusterUrl) {
this.clusterUrl = clusterUrl;
for (const fn of this.#clusterUrlWatchers) {
// We currently only store one function but this will execute serially
// which may or may not be the desired behavior if there ever is more
await fn(this.clusterUrl);
}
}
}
}
@@ -71,12 +71,6 @@ module('Acceptance | authentication', function (hooks) {
const MODAL_CANCEL_BTN = '.hds-modal__footer .hds-button--color-secondary';
const HEADER_DROPDOWN_BTN = '.app-header__utility-actions button';
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
await invalidateSession();
@@ -130,7 +124,7 @@ module('Acceptance | authentication', function (hooks) {
urls.targets = `${urls.projects}/targets`;
urls.sessions = `${urls.projects}/sessions`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
});
hooks.afterEach(async function () {
@@ -147,8 +141,17 @@ module('Acceptance | authentication', function (hooks) {
test('visiting authenticate route without clusterUrl redirects to clusterUrl index', async function (assert) {
assert.expect(1);
this.owner.lookup('service:clusterUrl').rendererClusterUrl = null;
await visit(urls.authenticate.global);
window.desktop.cluster.getClusterUrl.resolves(null);
try {
await visit(urls.authenticate.global);
} catch (e) {
if (e.message === 'TransitionAborted') {
// Ignore the expected transition abort error caused by the redirect in beforeModel
} else {
throw e;
}
}
assert.strictEqual(currentURL(), urls.clusterUrl);
});
@@ -8,13 +8,11 @@ import { visit, currentURL, fillIn, click, find } from '@ember/test-helpers';
import { setupApplicationTest } from 'desktop/tests/helpers';
import sinon from 'sinon';
import { invalidateSession } from 'ember-simple-auth/test-support';
import { setupBrowserFakes } from 'ember-browser-services/test-support';
import { setupDesktopContextBridgeApiMock } from '../helpers/desktop-context-bridge-api-mock';
import config from '../../config/environment';
module('Acceptance | clusterUrl', function (hooks) {
setupApplicationTest(hooks);
setupBrowserFakes(hooks, { window: true });
const currentOrigin = window.location.origin;
@@ -108,7 +106,15 @@ module('Acceptance | clusterUrl', function (hooks) {
test('visiting index without a clusterUrl specified redirects to clusterUrl route', async function (assert) {
assert.expect(2);
await visit(urls.index);
try {
await visit(urls.index);
} catch (e) {
if (e.message === 'TransitionAborted') {
// Ignore the expected transition abort error caused by the redirect in beforeModel
} else {
throw e;
}
}
assert.notOk(window.desktop.clusterUrl);
assert.strictEqual(currentURL(), urls.clusterUrl);
+1 -7
View File
@@ -49,12 +49,6 @@ module('Acceptance | projects', function (hooks) {
projects: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
instances.scopes.global = this.server.schema.scopes.find('global');
instances.authMethods.global = this.server.schema.authMethods.first();
@@ -87,7 +81,7 @@ module('Acceptance | projects', function (hooks) {
urls.authenticate.methods.global = `${urls.authenticate.global}/${instances.authMethods.global.id}`;
urls.projects = `${urls.scopes.org}/projects`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
});
test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) {
@@ -72,12 +72,6 @@ module('Acceptance | projects | sessions | index', function (hooks) {
session: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
// create scopes
instances.scopes.global = this.server.schema.scopes.find('global');
@@ -148,8 +142,7 @@ module('Acceptance | projects | sessions | index', function (hooks) {
urls.sessions2 = `${urls.projects2}/sessions`;
urls.session = `${urls.projects}/sessions/${instances.session.id}`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
@@ -58,12 +58,6 @@ module('Acceptance | projects | sessions | session', function (hooks) {
rdpSession: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
let originalUncaughtException = QUnit.onUncaughtException;
hooks.beforeEach(async function () {
@@ -139,7 +133,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
urls.session = `${urls.projects}/sessions/${instances.session.id}`;
urls.rdpSession = `${urls.projects}/sessions/${instances.rdpSession.id}`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
// mock RDP service calls
this.rdpService = this.owner.lookup('service:rdp');
@@ -69,12 +69,6 @@ module('Acceptance | projects | settings | index', function (hooks) {
const RDP_RECOMMENDED_CLIENT = '[data-test-recommended-rdp-client]';
const RDP_RECOMMENDED_CLIENT_LINK = '[data-test-recommended-rdp-client] a';
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
// Generate scopes
instances.scopes.global = this.server.schema.scopes.find('global');
@@ -96,7 +90,7 @@ module('Acceptance | projects | settings | index', function (hooks) {
urls.projects = `${urls.scopes.org}/projects`;
urls.settings = `${urls.projects}/settings`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
// mock RDP client data
window.desktop.rdp.getRdpClients.resolves([
@@ -90,12 +90,6 @@ module('Acceptance | projects | targets | index', function (hooks) {
sessions: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
// bypass mirage config that expects recursive to be passed in as queryParam
this.server.get('/targets', ({ targets }) => targets.all());
@@ -165,7 +159,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
// Generate resource counter
getTargetCount = () => this.server.schema.targets.all().models.length;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
@@ -77,12 +77,6 @@ module('Acceptance | projects | targets | target', function (hooks) {
targetWithManyHosts: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
// bypass mirage config that expects recursive to be passed in as queryParam
this.server.get('/targets', ({ targets }) => targets.all());
@@ -150,8 +144,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
urls.targetWithTwoHosts = `${urls.targets}/${instances.targetWithTwoHosts.id}`;
urls.targetWithManyHosts = `${urls.targets}/${instances.targetWithManyHosts.id}`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
+1 -8
View File
@@ -73,12 +73,6 @@ module('Acceptance | scopes', function (hooks) {
target: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
// bypass mirage config that expects recursive to be passed in as queryParam
this.server.get('/targets', ({ targets }) => targets.all());
@@ -135,8 +129,7 @@ module('Acceptance | scopes', function (hooks) {
urls.globalTargets = `${urls.globalProjects}/targets`;
urls.target = `${urls.targets}/${instances.target.id}`;
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
@@ -16,25 +16,17 @@ module('Integration | Component | settings-card/server', function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks, 'en-us');
const setDefaultClusterUrl = (test) => {
const windowOrigin = 'hashicorp.cloud';
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
setDefaultClusterUrl(this);
test('it displays HCP cluster url in server information section', async function (assert) {
await render(hbs`<SettingsCard::Server/>`);
this.set('model', { serverInformation: 'boundary.hashicorp.cloud' });
await render(hbs`<SettingsCard::Server @model={{this.model}}/>`);
assert.dom(SERVER_NAME).hasText('HashiCorp Cloud Platform');
assert.dom(SERVER_URL).hasText('hashicorp.cloud');
assert.dom(SERVER_URL).hasText('boundary.hashicorp.cloud');
});
test('it displays non HCP cluster url in server information section', async function (assert) {
this.owner.lookup('service:clusterUrl').rendererClusterUrl =
'everythingelse';
await render(hbs`<SettingsCard::Server/>`);
assert.dom(SERVER_NAME).hasText('Self-Managed');
this.set('model', { serverInformation: 'everythingelse' });
await render(hbs`<SettingsCard::Server @model={{this.model}}/>`);
assert.dom(SERVER_NAME).hasText('Self-managed');
assert.dom(SERVER_URL).hasText('everythingelse');
});
});
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { authenticateSession } from 'ember-simple-auth/test-support';
import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-bridge-api-mock';
import sinon from 'sinon';
module('Unit | Controller | application', function (hooks) {
setupTest(hooks);
@@ -40,18 +41,19 @@ module('Unit | Controller | application', function (hooks) {
assert.strictEqual(session.data.theme, 'light');
});
test('disconnect action de-authenticates a user and resets cluster url', async function (assert) {
test('disconnect action de-authenticates a user and keeps last cluster url', async function (assert) {
const url = 'http://localhost:9200';
await clusterUrl.setClusterUrl(url);
assert.true(session.isAuthenticated);
assert.strictEqual(clusterUrl.rendererClusterUrl, url);
assert.strictEqual(await window.desktop.cluster.getClusterUrl(), url);
assert.strictEqual(clusterUrl.adapter.host, url);
sinon.stub(controller.router, 'replaceWith');
await controller.disconnect();
assert.false(session.isAuthenticated);
assert.notOk(clusterUrl.rendererClusterUrl);
assert.notOk(await window.desktop.cluster.getClusterUrl());
assert.strictEqual(await window.desktop.cluster.getClusterUrl(), url);
assert.strictEqual(clusterUrl.adapter.host, url);
});
});
@@ -12,16 +12,13 @@ import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-
module('Unit | Controller | cluster-url', function (hooks) {
setupTest(hooks);
setupIntl(hooks, 'en-us');
setupDesktopContextBridgeApiMock(hooks);
let controller;
let clusterUrl;
setupDesktopContextBridgeApiMock(hooks);
hooks.beforeEach(async function () {
await authenticateSession({});
controller = this.owner.lookup('controller:cluster-url');
clusterUrl = this.owner.lookup('service:cluster-url');
});
test('it exists', function (assert) {
@@ -30,12 +27,10 @@ module('Unit | Controller | cluster-url', function (hooks) {
test('setClusterUrl action de-authenticates a user and resets cluster url', async function (assert) {
const newClusterUrl = 'http://localhost:9200';
assert.notEqual(clusterUrl.rendererClusterUrl, newClusterUrl);
assert.notEqual(window.desktop.clusterUrl, newClusterUrl);
await controller.setClusterUrl(newClusterUrl);
assert.strictEqual(clusterUrl.rendererClusterUrl, newClusterUrl);
assert.strictEqual(window.desktop.clusterUrl, newClusterUrl);
});
});
@@ -35,12 +35,6 @@ module(
targets: null,
};
const setDefaultClusterUrl = (test) => {
const windowOrigin = window.location.origin;
const clusterUrl = test.owner.lookup('service:clusterUrl');
clusterUrl.rendererClusterUrl = windowOrigin;
};
hooks.beforeEach(async function () {
controller = this.owner.lookup(
'controller:scopes/scope/authenticate/method/index',
@@ -58,7 +52,7 @@ module(
urls.targets = '/scopes/global/projects/targets';
setDefaultClusterUrl(this);
window.desktop.cluster.getClusterUrl.resolves(window.location.origin);
});
test('it exists', function (assert) {
@@ -17,37 +17,69 @@ module('Unit | Service | clusterUrl', function (hooks) {
service = this.owner.lookup('service:clusterUrl');
});
test('resets clusterUrl on error', async function (assert) {
assert.expect(4);
test('keeps clusterUrl on error', async function (assert) {
assert.expect(3);
await service.setClusterUrl(window.location.origin);
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
window.desktop.cluster.setClusterUrl.rejects();
service.setClusterUrl('invalid-origin').catch(() => {
assert.notOk(service.rendererClusterUrl);
try {
await service.setClusterUrl('http://other-origin');
} catch {
assert.strictEqual(service.adapter.host, window.location.origin);
});
assert.true(
window.desktop.cluster.setClusterUrl.calledWithExactly(
'http://other-origin',
),
);
}
});
test('drops trailing slashes from clusterUrl on setClusterUrl', async function (assert) {
assert.expect(4);
await service.setClusterUrl(`${window.location.origin}/`);
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
assert.true(
window.desktop.cluster.setClusterUrl.calledWithExactly(
window.location.origin,
),
);
await service.setClusterUrl(`${window.location.origin}//////`);
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
assert.true(
window.desktop.cluster.setClusterUrl.calledWithExactly(
window.location.origin,
),
);
});
test('trim spaces from clusterUrl on setClusterUrl', async function (assert) {
assert.expect(4);
await service.setClusterUrl(` ${window.location.origin}/ `);
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
assert.true(
window.desktop.cluster.setClusterUrl.calledWithExactly(
window.location.origin,
),
);
await service.setClusterUrl(` ${window.location.origin} `);
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
assert.true(
window.desktop.cluster.setClusterUrl.calledWithExactly(
window.location.origin,
),
);
});
test('resetClusterUrl clears adapter host and invokes IPC', async function (assert) {
await service.setClusterUrl(window.location.origin);
assert.strictEqual(service.adapter.host, window.location.origin);
await service.resetClusterUrl();
assert.strictEqual(service.adapter.host, undefined);
assert.true(window.desktop.cluster.resetClusterUrl.calledOnce);
});
});