refactor: 💡 window postmessage pattern -> contextbridge boundary api (#3167)
* refactor: 💡 postmessage pattern -> contextbridge boundary api * refactor: 💡 use sinon stub for boundary api mock test helper * refactor: 💡 updated tests * refactor: 💡 renamed boundary mock api * chore: 🤖 addressed feedback * refactor: 💡 updated context bridge name, added nested objects
This commit is contained in:
1 parent
eba17d3c71
commit
392e49bf49
45 files changed
+744
-1062
No files matched your search
@@ -11,7 +11,7 @@ import { underscore } from '@ember/string';
|
||||
|
||||
/**
|
||||
* This test helper can be used to help setup your sinon stubs in your tests.
|
||||
* Must be called after setupMirage.
|
||||
* Must be called after setupMirage and setupDesktopContextBridgeApiMock in tests.
|
||||
*
|
||||
* ```js
|
||||
* import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
@@ -19,14 +19,15 @@ import { underscore } from '@ember/string';
|
||||
* module('Acceptance | my test', function(hooks) {
|
||||
* setupApplicationTest(hooks);
|
||||
* setupMirage(hooks);
|
||||
* setupDesktopContextBridgeApiMock(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 cache daemon search command returns.
|
||||
* This will setup a stub for window.desktop.daemon.searchCacheDaemon and include
|
||||
* helper methods to help setup what the cache daemon search command returns.
|
||||
*
|
||||
* @module Test Helpers
|
||||
* @public
|
||||
@@ -35,9 +36,6 @@ export default function setupStubs(hooks) {
|
||||
let stubTypes = [];
|
||||
|
||||
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 cache daemon
|
||||
* search. However, the handler expects the search data to be raw JSON from
|
||||
@@ -56,34 +54,32 @@ export default function setupStubs(hooks) {
|
||||
stubTypes = types;
|
||||
|
||||
types.forEach((type, i) => {
|
||||
this.ipcStub
|
||||
.withArgs('searchCacheDaemon')
|
||||
.onCall(i)
|
||||
.callsFake(() => {
|
||||
let models;
|
||||
let resourceName;
|
||||
if (typeOf(type) === 'object') {
|
||||
models = type.func();
|
||||
resourceName = type.resource;
|
||||
} else {
|
||||
models = this.server.schema[type].all().models;
|
||||
resourceName = type;
|
||||
}
|
||||
this.searchCacheDaemonStub.onCall(i).callsFake(() => {
|
||||
let models;
|
||||
let resourceName;
|
||||
if (typeOf(type) === 'object') {
|
||||
models = type.func();
|
||||
resourceName = type.resource;
|
||||
} else {
|
||||
models = this.server.schema[type].all().models;
|
||||
resourceName = type;
|
||||
}
|
||||
|
||||
return {
|
||||
[underscore(resourceNames[singularize(resourceName)])]:
|
||||
models.map((model) => {
|
||||
// Use internal serializer to serialize the model correctly
|
||||
// according to our mirage serializers
|
||||
const modelData =
|
||||
this.server.serializerOrRegistry.serialize(model);
|
||||
return {
|
||||
[underscore(resourceNames[singularize(resourceName)])]: models.map(
|
||||
(model) => {
|
||||
// Use internal serializer to serialize the model correctly
|
||||
// according to our mirage serializers
|
||||
const modelData =
|
||||
this.server.serializerOrRegistry.serialize(model);
|
||||
|
||||
// Serialize the data properly to standard JSON as that is what
|
||||
// we're expecting from the cache daemon response
|
||||
return JSON.parse(JSON.stringify(modelData));
|
||||
}),
|
||||
};
|
||||
});
|
||||
// Serialize the data properly to standard JSON as that is what
|
||||
// we're expecting from the cache daemon response
|
||||
return JSON.parse(JSON.stringify(modelData));
|
||||
},
|
||||
),
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -91,22 +87,19 @@ export default function setupStubs(hooks) {
|
||||
hooks.afterEach(function () {
|
||||
// Verify the number of mocks we set up match with how
|
||||
// many times the cache daemon was called with
|
||||
sinon.assert.callCount(
|
||||
this.ipcStub.withArgs('searchCacheDaemon'),
|
||||
stubTypes.length,
|
||||
);
|
||||
sinon.assert.callCount(this.searchCacheDaemonStub, stubTypes.length);
|
||||
|
||||
stubTypes.forEach((type, i) => {
|
||||
// Assert that the stub was called with the resource we're trying to mock
|
||||
// so we don't mistakenly mock the wrong resource
|
||||
const ipcCall = this.ipcStub.withArgs('searchCacheDaemon').getCall(i);
|
||||
const call = this.searchCacheDaemonStub.getCall(i);
|
||||
let resource = type;
|
||||
if (typeOf(type) === 'object') {
|
||||
resource = type.resource;
|
||||
}
|
||||
|
||||
const mappedResourceName = resourceNames[singularize(resource)];
|
||||
sinon.assert.match(ipcCall.args[1], { resource: mappedResourceName });
|
||||
sinon.assert.match(call.args[0], { resource: mappedResourceName });
|
||||
});
|
||||
|
||||
sinon.restore();
|
||||
|
||||
@@ -59,7 +59,6 @@ const fetchControllerData = async (context, next) => {
|
||||
*/
|
||||
export default class CacheDaemonHandler {
|
||||
@service session;
|
||||
@service ipc;
|
||||
|
||||
constructor(context) {
|
||||
setOwner(this, getOwner(context));
|
||||
@@ -70,9 +69,8 @@ export default class CacheDaemonHandler {
|
||||
case 'query': {
|
||||
const { store, data } = context.request;
|
||||
const { type, query, options: { pushToStore = true } = {} } = data;
|
||||
const isCacheDaemonRunning = await this.ipc.invoke(
|
||||
'isCacheDaemonRunning',
|
||||
);
|
||||
const isCacheDaemonRunning =
|
||||
await window.desktop.daemon.isCacheDaemonRunning();
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let { recursive, scope_id, page, pageSize, ...remainingQuery } = query;
|
||||
@@ -119,23 +117,19 @@ export default class CacheDaemonHandler {
|
||||
|
||||
let cacheDaemonResults = {};
|
||||
try {
|
||||
cacheDaemonResults = await this.ipc.invoke(
|
||||
'searchCacheDaemon',
|
||||
remainingQuery,
|
||||
);
|
||||
cacheDaemonResults =
|
||||
await window.desktop.daemon.searchCacheDaemon(remainingQuery);
|
||||
} catch (e) {
|
||||
// If we got a 403, most likely the cache daemon was restarted and our token is no longer valid
|
||||
// I'm not sure if we can get a 401 since we always send a token but we'll handle it in the same way
|
||||
if (e.statusCode === 403 || e.statusCode === 401) {
|
||||
try {
|
||||
await this.ipc.invoke('addTokenToDaemons', {
|
||||
await window.desktop.daemon.addTokenToDaemons({
|
||||
tokenId: auth_token_id,
|
||||
token,
|
||||
});
|
||||
cacheDaemonResults = await this.ipc.invoke(
|
||||
'searchCacheDaemon',
|
||||
remainingQuery,
|
||||
);
|
||||
cacheDaemonResults =
|
||||
await window.desktop.daemon.searchCacheDaemon(remainingQuery);
|
||||
} catch (err) {
|
||||
// If it fails again just fall back to fetching controller data
|
||||
__electronLog?.error(
|
||||
|
||||
@@ -7,7 +7,7 @@ import { createServer, Response } from 'miragejs';
|
||||
import { authHandler, deauthHandler } from './route-handlers/auth';
|
||||
import { targetHandler } from './route-handlers/target';
|
||||
import { pickRandomStatusString } from './factories/session';
|
||||
import initializeMockIPC from './scenarios/ipc';
|
||||
import initializeMockDesktopContextBridgeAPI from './scenarios/ipc/desktop-context-bridge-api';
|
||||
import makeBooleanFilter from './helpers/bexpr-filter';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { asciicasts } from './data/asciicasts';
|
||||
@@ -66,7 +66,7 @@ import workerSerializer from './serializers/worker';
|
||||
|
||||
// mirage scenarios (alphabetical)
|
||||
import defaultScenario from './scenarios/default';
|
||||
import ipcScenario from './scenarios/ipc';
|
||||
import desktopContextBridgeApiScenario from './scenarios/ipc/desktop-context-bridge-api';
|
||||
|
||||
// mirage factories (alphabetical)
|
||||
import accountFactory from './factories/account';
|
||||
@@ -103,7 +103,7 @@ export default function (mirageConfig) {
|
||||
|
||||
scenarios: {
|
||||
default: defaultScenario,
|
||||
ipcScenario: ipcScenario,
|
||||
desktopContextBridgeApiScenario: desktopContextBridgeApiScenario,
|
||||
},
|
||||
|
||||
factories: {
|
||||
@@ -190,7 +190,7 @@ export default function (mirageConfig) {
|
||||
|
||||
// Only routes are defined here
|
||||
function routes() {
|
||||
initializeMockIPC(this, environmentConfig);
|
||||
initializeMockDesktopContextBridgeAPI(this, environmentConfig);
|
||||
|
||||
this.passthrough();
|
||||
// make this `http://localhost:8080`, for example, if your API is on a different server
|
||||
|
||||
@@ -1,296 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
export default function initializeMockIPC(server, config) {
|
||||
const isTesting = config.environment === 'test';
|
||||
|
||||
/**
|
||||
* We strive to make this application runnable in a regular web browser, since
|
||||
* it is a convenient environment for development and testing. But only an
|
||||
* Electron environment has true IPC. Outside of Electron or with mirage,
|
||||
* we mock the handling of the message-based IPC requests originating from the
|
||||
* renderer (the Ember app).
|
||||
*/
|
||||
class MockIPC {
|
||||
async invoke(method, payload) {
|
||||
return await this[method](payload);
|
||||
}
|
||||
|
||||
clusterUrl = null;
|
||||
|
||||
checkOS() {
|
||||
return {
|
||||
isLinux: false,
|
||||
isMac: true,
|
||||
isWindows: false,
|
||||
};
|
||||
}
|
||||
|
||||
getClusterUrl() {
|
||||
return this.clusterUrl;
|
||||
}
|
||||
|
||||
setClusterUrl(clusterUrl) {
|
||||
this.clusterUrl = clusterUrl;
|
||||
return this.clusterUrl;
|
||||
}
|
||||
|
||||
resetClusterUrl() {
|
||||
this.clusterUrl = null;
|
||||
}
|
||||
|
||||
openExternal(href) {
|
||||
window.open(href);
|
||||
}
|
||||
|
||||
cliExists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session instance associated with the specified target.
|
||||
* @param connect_details
|
||||
* @param
|
||||
*/
|
||||
connect({ target_id }) {
|
||||
const target = server.schema.targets.find(target_id);
|
||||
const { scope, type } = target;
|
||||
const newSession = server.schema.sessions.create({
|
||||
scope,
|
||||
target,
|
||||
userId: 'authenticateduser',
|
||||
type,
|
||||
created_time: new Date().toISOString(),
|
||||
expiration_time: faker.date.soon(),
|
||||
});
|
||||
const decoded = {
|
||||
key1: 'value 1',
|
||||
key2: true,
|
||||
key3: 'test',
|
||||
};
|
||||
const raw = btoa(JSON.stringify(decoded));
|
||||
return {
|
||||
address: window.location.hostname,
|
||||
port: faker.internet.port(),
|
||||
protocol: type,
|
||||
session_id: newSession.id,
|
||||
credentials: [
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 1',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 0',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault-generic',
|
||||
},
|
||||
secret: {
|
||||
raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ',
|
||||
decoded: {
|
||||
data: {
|
||||
backslash: 'password\\withslash\\',
|
||||
backslash1: 'password\\fwithslash\\f',
|
||||
password: '123',
|
||||
username: 'test',
|
||||
email: {
|
||||
address: 'test.com',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
created_time: '2024-04-12T18:38:36.226715555Z',
|
||||
custom_metadata: null,
|
||||
deletion_time: '',
|
||||
destroyed: false,
|
||||
version: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
credential_source: {
|
||||
id: 'credjson_7cKBbBEkC3',
|
||||
credential_store_id: 'csst_yzlsot2pum',
|
||||
type: 'static',
|
||||
credential_type: 'json',
|
||||
},
|
||||
secret: {
|
||||
raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ==',
|
||||
decoded: {
|
||||
secret_key: 'QWERTYUIOP',
|
||||
secret_access_key: 'QWERT.YUIOP234567890',
|
||||
nested_secret: {
|
||||
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
||||
complex_nest: {
|
||||
blackslash: 'password\\withslash\\',
|
||||
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credential: {
|
||||
secret_key: 'QWERTYUIOP',
|
||||
secret_access_key: 'QWERT.YUIOP234567890',
|
||||
nested_secret: {
|
||||
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
||||
complex_nest: {
|
||||
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 2',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Credential Library 3',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Credential Library 4',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for window chrome on MacOS
|
||||
*/
|
||||
hasMacOSChrome() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for OS chrome state
|
||||
*/
|
||||
showWindowActions() {
|
||||
if (navigator.userAgent.includes('Mac')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing when attempting to minimize, toggle fullscreen,
|
||||
* and close a browser window
|
||||
* * Do nothing when canceling a session invokes the stop method
|
||||
*/
|
||||
minimizeWindow() {}
|
||||
closeWindow() {}
|
||||
toggleFullscreenWindow() {}
|
||||
stop() {}
|
||||
checkCommand() {
|
||||
return faker.system.filePath();
|
||||
}
|
||||
addTokenToDaemons() {}
|
||||
searchCacheDaemon() {}
|
||||
isCacheDaemonRunning() {
|
||||
return false;
|
||||
}
|
||||
isClientAgentRunning() {
|
||||
return false;
|
||||
}
|
||||
cacheDaemonStatus() {
|
||||
return { version: 'Boundary v1.0.0' };
|
||||
}
|
||||
getCliVersion() {
|
||||
return { versionNumber: '1.0.0' };
|
||||
}
|
||||
getDesktopVersion() {
|
||||
return { desktopVersion: '1.0.0' };
|
||||
}
|
||||
getLogLevel() {
|
||||
return 'info';
|
||||
}
|
||||
getLogPath() {
|
||||
return '~/.config/Boundary/logs/desktop-client.log';
|
||||
}
|
||||
setLogLevel() {}
|
||||
clientAgentStatus() {
|
||||
return { version: '0.0.1-dev', status: 'running' };
|
||||
}
|
||||
pauseClientAgent() {}
|
||||
resumeClientAgent() {}
|
||||
hasRunningSessions() {}
|
||||
stopAll() {}
|
||||
getRdpClients() {
|
||||
return ['windows-app', 'none', 'mstsc'];
|
||||
}
|
||||
getPreferredRdpClient() {
|
||||
return 'windows-app';
|
||||
}
|
||||
getRecommendedRdpClient() {
|
||||
return {
|
||||
name: 'windows-app',
|
||||
link: 'https://apps.apple.com/us/app/windows-app/id1295203466',
|
||||
};
|
||||
}
|
||||
setPreferredRdpClient() {}
|
||||
launchRdpClient() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establishes a mock IPC handler, which mimics the behavior of the Electron
|
||||
* main process, and routes messages to it in a way similar to our
|
||||
* preload.js script.
|
||||
*
|
||||
* Initializes mock IPC only in a non-testing context and when mirage is turned on.
|
||||
* We mock certain functions even in electron (e.g. hasMacOSChrome) when running
|
||||
* locally which will force a certain appearance regardless of platform
|
||||
*/
|
||||
if (config.mirage?.enabled && !isTesting) {
|
||||
const mockIPC = new MockIPC();
|
||||
|
||||
window.addEventListener('message', async function (event) {
|
||||
if (event.origin !== window.location.origin) return;
|
||||
const { method, payload } = event?.data ?? {};
|
||||
if (method) {
|
||||
const response = await mockIPC.invoke(method, payload);
|
||||
event.ports[0].postMessage(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
export default function initializeMockDesktopContextBridgeAPI(server, config) {
|
||||
const isTesting = config.environment === 'test';
|
||||
|
||||
/**
|
||||
* We strive to make this application runnable in a regular web browser, since
|
||||
* it is a convenient environment for development and testing. But only an
|
||||
* Electron environment has the desktop API exposed via contextBridge.
|
||||
* Outside of Electron or with mirage, we mock the desktop API object to
|
||||
* simulate the Electron preload script's exposed API.
|
||||
*/
|
||||
class MockDesktopContextBridgeAPI {
|
||||
clusterUrl = null;
|
||||
|
||||
cluster = {
|
||||
getClusterUrl: () => {
|
||||
return this.clusterUrl;
|
||||
},
|
||||
|
||||
setClusterUrl: (clusterUrl) => {
|
||||
this.clusterUrl = clusterUrl;
|
||||
return this.clusterUrl;
|
||||
},
|
||||
|
||||
resetClusterUrl: () => {
|
||||
this.clusterUrl = null;
|
||||
},
|
||||
};
|
||||
|
||||
system = {
|
||||
checkOS: () => {
|
||||
return {
|
||||
isLinux: false,
|
||||
isMac: true,
|
||||
isWindows: false,
|
||||
};
|
||||
},
|
||||
|
||||
openExternal: (href) => {
|
||||
window.open(href);
|
||||
},
|
||||
|
||||
cliExists: () => {
|
||||
return true;
|
||||
},
|
||||
|
||||
checkCommand: () => {
|
||||
return faker.system.filePath();
|
||||
},
|
||||
|
||||
getCliVersion: () => {
|
||||
return { versionNumber: '1.0.0' };
|
||||
},
|
||||
|
||||
getDesktopVersion: () => {
|
||||
return { desktopVersion: '1.0.0' };
|
||||
},
|
||||
};
|
||||
|
||||
session = {
|
||||
/**
|
||||
* Creates a new session instance associated with the specified target.
|
||||
* @param connect_details
|
||||
* @param
|
||||
*/
|
||||
connectSession: ({ target_id }) => {
|
||||
const target = server.schema.targets.find(target_id);
|
||||
const { scope, type } = target;
|
||||
const newSession = server.schema.sessions.create({
|
||||
scope,
|
||||
target,
|
||||
userId: 'authenticateduser',
|
||||
type,
|
||||
created_time: new Date().toISOString(),
|
||||
expiration_time: faker.date.soon(),
|
||||
});
|
||||
const decoded = {
|
||||
key1: 'value 1',
|
||||
key2: true,
|
||||
key3: 'test',
|
||||
};
|
||||
const raw = btoa(JSON.stringify(decoded));
|
||||
return {
|
||||
address: window.location.hostname,
|
||||
port: faker.internet.port(),
|
||||
protocol: type,
|
||||
session_id: newSession.id,
|
||||
credentials: [
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 1',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 0',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault-generic',
|
||||
},
|
||||
secret: {
|
||||
raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ',
|
||||
decoded: {
|
||||
data: {
|
||||
backslash: 'password\\withslash\\',
|
||||
backslash1: 'password\\fwithslash\\f',
|
||||
password: '123',
|
||||
username: 'test',
|
||||
email: {
|
||||
address: 'test.com',
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
created_time: '2024-04-12T18:38:36.226715555Z',
|
||||
custom_metadata: null,
|
||||
deletion_time: '',
|
||||
destroyed: false,
|
||||
version: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
credential_source: {
|
||||
id: 'credjson_7cKBbBEkC3',
|
||||
credential_store_id: 'csst_yzlsot2pum',
|
||||
type: 'static',
|
||||
credential_type: 'json',
|
||||
},
|
||||
secret: {
|
||||
raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ==',
|
||||
decoded: {
|
||||
secret_key: 'QWERTYUIOP',
|
||||
secret_access_key: 'QWERT.YUIOP234567890',
|
||||
nested_secret: {
|
||||
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
||||
complex_nest: {
|
||||
blackslash: 'password\\withslash\\',
|
||||
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
credential: {
|
||||
secret_key: 'QWERTYUIOP',
|
||||
secret_access_key: 'QWERT.YUIOP234567890',
|
||||
nested_secret: {
|
||||
session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321',
|
||||
complex_nest: {
|
||||
hash: 'qazxswedcvfrtgbnjhyujm.1234567890',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_4cvscMTl0N',
|
||||
name: 'Credential Library 2',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Credential Library 3',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw: btoa('just-a-random-string'),
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_source: {
|
||||
id: 'clvlt_9KWscxpcY7',
|
||||
name: 'Credential Library 4',
|
||||
description: 'Source Description',
|
||||
credential_store_id: 'csvlt_Q1HFGt7Jpm',
|
||||
type: 'vault',
|
||||
},
|
||||
secret: {
|
||||
raw,
|
||||
decoded,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
stopSession: () => {},
|
||||
hasRunningSessions: () => {},
|
||||
stopAllSessions: () => {},
|
||||
};
|
||||
|
||||
windowAction = {
|
||||
/**
|
||||
* Check for window chrome on MacOS
|
||||
*/
|
||||
hasMacOSChrome: () => {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check for OS chrome state
|
||||
*/
|
||||
showWindowActions: () => {
|
||||
if (navigator.userAgent.includes('Mac')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Do nothing when attempting to minimize, toggle fullscreen,
|
||||
* and close a browser window
|
||||
*/
|
||||
minimizeWindow: () => {},
|
||||
closeWindow: () => {},
|
||||
toggleFullscreenWindow: () => {},
|
||||
focusWindow: () => {},
|
||||
};
|
||||
|
||||
daemon = {
|
||||
addTokenToDaemons: () => {},
|
||||
searchCacheDaemon: () => {},
|
||||
isCacheDaemonRunning: () => {
|
||||
return false;
|
||||
},
|
||||
cacheDaemonStatus: () => {
|
||||
return { version: 'Boundary v1.0.0' };
|
||||
},
|
||||
};
|
||||
|
||||
clientAgent = {
|
||||
getClientAgentSessions: () => {},
|
||||
isClientAgentRunning: () => {
|
||||
return false;
|
||||
},
|
||||
clientAgentStatus: () => {
|
||||
return { version: '0.0.1-dev', status: 'running' };
|
||||
},
|
||||
pauseClientAgent: () => {},
|
||||
resumeClientAgent: () => {},
|
||||
};
|
||||
|
||||
logging = {
|
||||
getLogLevel: () => {
|
||||
return 'info';
|
||||
},
|
||||
getLogPath: () => {
|
||||
return '~/.config/Boundary/logs/desktop-client.log';
|
||||
},
|
||||
setLogLevel: () => {},
|
||||
};
|
||||
|
||||
rdp = {
|
||||
getRdpClients: () => {
|
||||
return ['windows-app', 'none', 'mstsc'];
|
||||
},
|
||||
getPreferredRdpClient: () => {
|
||||
return 'windows-app';
|
||||
},
|
||||
getRecommendedRdpClient: () => {
|
||||
return {
|
||||
name: 'windows-app',
|
||||
link: 'https://apps.apple.com/us/app/windows-app/id1295203466',
|
||||
};
|
||||
},
|
||||
setPreferredRdpClient: () => {},
|
||||
launchRdpClient: () => {},
|
||||
};
|
||||
|
||||
app = {
|
||||
onAppQuit: (callback) => {
|
||||
const listenerCallback = () => callback();
|
||||
window.addEventListener('onAppQuit', listenerCallback);
|
||||
return () => {
|
||||
window.removeEventListener('onAppQuit', listenerCallback);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes mock only in a non-testing environment and when mirage is enabled.
|
||||
* In browser mode, creates the API. In Electron mode, the API already exists
|
||||
* from contextBridge and we can't override it
|
||||
*/
|
||||
if (config.mirage?.enabled && !isTesting) {
|
||||
const desktopContextBridgeAPIMock = new MockDesktopContextBridgeAPI();
|
||||
// Only in browser mode, we create the desktop API object not in Electron mode because they already exist from contextBridge
|
||||
if (!window.desktop) {
|
||||
window.desktop = desktopContextBridgeAPIMock;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
||||
@@ -31,10 +30,6 @@ const calculateTerminalContainerHeight = (termContainer) => {
|
||||
};
|
||||
|
||||
export default class SessionTerminalTabsComponent extends Component {
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
|
||||
// =attributes
|
||||
|
||||
id;
|
||||
@@ -50,7 +45,7 @@ export default class SessionTerminalTabsComponent extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isWindows } = await this.ipc.invoke('checkOS');
|
||||
const { isWindows } = await window.desktop.system.checkOS();
|
||||
const xterm = new Terminal(terminalOptions);
|
||||
this.terminal = xterm;
|
||||
const fitAddon = new FitAddon();
|
||||
@@ -83,7 +78,8 @@ export default class SessionTerminalTabsComponent extends Component {
|
||||
this.id = uuidv4();
|
||||
this.#setupTerminal(fitAddon, xterm, termContainer);
|
||||
|
||||
const isSSHCommandAvailable = await this.ipc.invoke('checkCommand', 'ssh');
|
||||
const isSSHCommandAvailable =
|
||||
await window.desktop.system.checkCommand('ssh');
|
||||
const { model } = this.args;
|
||||
|
||||
const { proxy_address, proxy_port, started_desktop_client, target } = model;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { notifyError } from 'core/decorators/notify';
|
||||
|
||||
export default class SettingsCardClientAgentComponent extends Component {
|
||||
// =services
|
||||
@service ipc;
|
||||
@service router;
|
||||
@service loading;
|
||||
|
||||
@@ -59,9 +58,9 @@ export default class SettingsCardClientAgentComponent extends Component {
|
||||
@notifyError(({ message }) => message)
|
||||
async changeClientAgentState() {
|
||||
if (this.isClientAgentStatusRunning) {
|
||||
await this.ipc.invoke('pauseClientAgent');
|
||||
await window.desktop.clientAgent.pauseClientAgent();
|
||||
} else {
|
||||
await this.ipc.invoke('resumeClientAgent');
|
||||
await window.desktop.clientAgent.resumeClientAgent();
|
||||
}
|
||||
// Refresh projects page in order to stop or start
|
||||
// polling when client agent status changes.
|
||||
|
||||
@@ -5,12 +5,8 @@
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default class SettingsCardLogsComponent extends Component {
|
||||
// =services
|
||||
@service ipc;
|
||||
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
@@ -30,6 +26,6 @@ export default class SettingsCardLogsComponent extends Component {
|
||||
*/
|
||||
@action
|
||||
async selectLogLevel({ target: { value } }) {
|
||||
await this.ipc.invoke('setLogLevel', value);
|
||||
await window.desktop.logging.setLogLevel(value);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ export default class ApplicationController extends Controller {
|
||||
|
||||
@service clusterUrl;
|
||||
@service flashMessages;
|
||||
@service ipc;
|
||||
@service session;
|
||||
@service('browser/window') window;
|
||||
@service router;
|
||||
@@ -34,7 +33,7 @@ export default class ApplicationController extends Controller {
|
||||
super(...arguments);
|
||||
// Listen for when user attempts to quit app
|
||||
// Setup removeOnAppQuitListener to destroy the listener afterwards
|
||||
this.removeOnAppQuitListener = this.window.electron?.onAppQuit(() => {
|
||||
this.removeOnAppQuitListener = this.window.desktop?.app.onAppQuit(() => {
|
||||
this.isAppQuitting = true;
|
||||
});
|
||||
}
|
||||
@@ -71,7 +70,7 @@ export default class ApplicationController extends Controller {
|
||||
*/
|
||||
@action
|
||||
async confirmCloseSessions() {
|
||||
await this.ipc.invoke('stopAll');
|
||||
await window.desktop.session.stopAllSessions();
|
||||
if (this.isAppQuitting) {
|
||||
this.isAppQuitting = false;
|
||||
this.close();
|
||||
@@ -88,7 +87,8 @@ export default class ApplicationController extends Controller {
|
||||
*/
|
||||
@action
|
||||
async showModalOrLogout() {
|
||||
const hasRunningSessions = await this.ipc.invoke('hasRunningSessions');
|
||||
const hasRunningSessions =
|
||||
await window.desktop.session.hasRunningSessions();
|
||||
if (hasRunningSessions) {
|
||||
this.isLoggingOut = true;
|
||||
} else {
|
||||
@@ -108,17 +108,17 @@ export default class ApplicationController extends Controller {
|
||||
|
||||
@action
|
||||
minimize() {
|
||||
this.ipc.invoke('minimizeWindow');
|
||||
window.desktop.windowAction.minimizeWindow();
|
||||
}
|
||||
|
||||
@action
|
||||
toggleFullScreen() {
|
||||
this.ipc.invoke('toggleFullscreenWindow');
|
||||
window.desktop.windowAction.toggleFullscreenWindow();
|
||||
}
|
||||
|
||||
@action
|
||||
close() {
|
||||
this.ipc.invoke('closeWindow');
|
||||
window.desktop.windowAction.closeWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,6 @@ export default class ScopesScopeAuthenticateMethodIndexController extends Contro
|
||||
// =services
|
||||
|
||||
@service session;
|
||||
@service ipc;
|
||||
@service intl;
|
||||
@service router;
|
||||
|
||||
@@ -39,7 +38,7 @@ export default class ScopesScopeAuthenticateMethodIndexController extends Contro
|
||||
* @param {string} url
|
||||
*/
|
||||
async openExternalOIDCFlow(url) {
|
||||
await this.ipc.invoke('openExternal', url);
|
||||
await window.desktop.system.openExternal(url);
|
||||
}
|
||||
|
||||
// =actions
|
||||
|
||||
@@ -22,7 +22,6 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
|
||||
|
||||
@service intl;
|
||||
@service store;
|
||||
@service ipc;
|
||||
@service session;
|
||||
@service router;
|
||||
@service abilities;
|
||||
@@ -157,7 +156,7 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
|
||||
|
||||
await updatedSession.cancelSession();
|
||||
|
||||
await this.ipc.invoke('stop', { session_id: session.id });
|
||||
await window.desktop.session.stopSession({ session_id: session.id });
|
||||
if (
|
||||
this.router.currentRoute.name ===
|
||||
'scopes.scope.projects.sessions.session.index'
|
||||
|
||||
@@ -20,7 +20,6 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
// =services
|
||||
|
||||
@service confirm;
|
||||
@service ipc;
|
||||
@service router;
|
||||
@service session;
|
||||
@service store;
|
||||
@@ -189,7 +188,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
@loading
|
||||
async connect(target, host) {
|
||||
// Check for CLI
|
||||
const cliExists = await this.ipc.invoke('cliExists');
|
||||
const cliExists = await window.desktop.system.cliExists();
|
||||
if (!cliExists) throw new Error('Cannot find Boundary CLI.');
|
||||
|
||||
const options = {
|
||||
@@ -201,7 +200,8 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
if (host) options.host_id = host.id;
|
||||
|
||||
// Create target session
|
||||
const connectionDetails = await this.ipc.invoke('connect', options);
|
||||
const connectionDetails =
|
||||
await window.desktop.session.connectSession(options);
|
||||
|
||||
// Associate the connection details with the session
|
||||
let session;
|
||||
@@ -307,7 +307,7 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
|
||||
}
|
||||
|
||||
await updatedSession.cancelSession();
|
||||
await this.ipc.invoke('stop', { session_id: session.id });
|
||||
await window.desktop.session.stopSession({ session_id: session.id });
|
||||
this.router.refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ export default class ApplicationRoute extends Route {
|
||||
|
||||
@service session;
|
||||
@service clusterUrl;
|
||||
@service ipc;
|
||||
@service intl;
|
||||
@service rdp;
|
||||
|
||||
@@ -46,7 +45,7 @@ export default class ApplicationRoute extends Route {
|
||||
// Add token to cache daemon after a successful authentication restoration
|
||||
if (this.session.isAuthenticated) {
|
||||
const sessionData = this.session.data?.authenticated;
|
||||
await this.ipc.invoke('addTokenToDaemons', {
|
||||
await window.desktop.daemon.addTokenToDaemons({
|
||||
tokenId: sessionData?.id,
|
||||
token: sessionData?.token,
|
||||
});
|
||||
@@ -62,10 +61,13 @@ export default class ApplicationRoute extends Route {
|
||||
* Add window frame config on controller.
|
||||
*/
|
||||
async setupController(controller) {
|
||||
controller.set('hasMacOSChrome', await this.ipc.invoke('hasMacOSChrome'));
|
||||
controller.set(
|
||||
'hasMacOSChrome',
|
||||
await window.desktop.windowAction.hasMacOSChrome(),
|
||||
);
|
||||
controller.set(
|
||||
'showWindowActions',
|
||||
await this.ipc.invoke('showWindowActions'),
|
||||
await window.desktop.windowAction.showWindowActions(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ export default class ScopesRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service ipc;
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -65,7 +64,8 @@ export default class ScopesRoute extends Route {
|
||||
if (!this.isPaginationSupported) {
|
||||
const metaDataUrl =
|
||||
'https://api.releases.hashicorp.com/v1/releases/boundary-desktop/1.7.1';
|
||||
const { isWindows, isMac, isLinux } = await this.ipc.invoke('checkOS');
|
||||
const { isWindows, isMac, isLinux } =
|
||||
await window.desktop.system.checkOS();
|
||||
|
||||
try {
|
||||
const metaDataResponse = await fetch(metaDataUrl);
|
||||
|
||||
@@ -33,7 +33,6 @@ export default class ScopesScopeProjectsRoute extends Route {
|
||||
@service router;
|
||||
@service store;
|
||||
@service intl;
|
||||
@service ipc;
|
||||
@service clientAgentSessions;
|
||||
@service flashMessages;
|
||||
|
||||
@@ -51,7 +50,8 @@ export default class ScopesScopeProjectsRoute extends Route {
|
||||
* @return {Promise<ScopeModel>}
|
||||
*/
|
||||
async model() {
|
||||
const isClientAgentRunning = await this.ipc.invoke('isClientAgentRunning');
|
||||
const isClientAgentRunning =
|
||||
await window.desktop.clientAgent.isClientAgentRunning();
|
||||
if (isClientAgentRunning) {
|
||||
this.poller.perform();
|
||||
// start polling task
|
||||
@@ -95,7 +95,7 @@ export default class ScopesScopeProjectsRoute extends Route {
|
||||
const token = sessionData?.token;
|
||||
|
||||
try {
|
||||
await this.ipc.invoke('addTokenToDaemons', {
|
||||
await window.desktop.daemon.addTokenToDaemons({
|
||||
tokenId: auth_token_id,
|
||||
token,
|
||||
});
|
||||
@@ -156,7 +156,7 @@ export default class ScopesScopeProjectsRoute extends Route {
|
||||
}
|
||||
|
||||
window.location.href = `serve://boundary/#/scopes/${orgScope}/projects/sessions/${session.session_authorization.session_id}`;
|
||||
await this.ipc.invoke('focusWindow');
|
||||
await window.desktop.windowAction.focusWindow();
|
||||
};
|
||||
});
|
||||
await timeout(POLL_TIMEOUT_SECONDS * 1000);
|
||||
|
||||
@@ -18,7 +18,6 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
|
||||
@service session;
|
||||
@service store;
|
||||
@service ipc;
|
||||
@service router;
|
||||
|
||||
// =attributes
|
||||
@@ -137,7 +136,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
|
||||
allSessions: this.allSessions,
|
||||
associatedTargets,
|
||||
totalItems,
|
||||
isCacheDaemonRunning: await this.ipc.invoke('isCacheDaemonRunning'),
|
||||
isCacheDaemonRunning: await window.desktop.daemon.isCacheDaemonRunning(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ export default class ScopesScopeProjectsSessionsSessionRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service ipc;
|
||||
@service clientAgentSessions;
|
||||
@service flashMessages;
|
||||
@service intl;
|
||||
@@ -57,7 +56,7 @@ export default class ScopesScopeProjectsSessionsSessionRoute extends Route {
|
||||
// was initiated through the client agent.
|
||||
if (
|
||||
!session.credentials.length &&
|
||||
(await this.ipc.invoke('isClientAgentRunning'))
|
||||
(await window.desktop.clientAgent.isClientAgentRunning())
|
||||
) {
|
||||
try {
|
||||
const clientAgentSession =
|
||||
|
||||
@@ -8,21 +8,20 @@ import { service } from '@ember/service';
|
||||
|
||||
export default class ScopesScopeProjectsSettingsIndexRoute extends Route {
|
||||
// =services
|
||||
@service ipc;
|
||||
@service session;
|
||||
@service clusterUrl;
|
||||
|
||||
// =methods
|
||||
async model() {
|
||||
const { versionNumber: cliVersion } =
|
||||
await this.ipc.invoke('getCliVersion');
|
||||
await window.desktop.system.getCliVersion();
|
||||
|
||||
const { desktopVersion } = await this.ipc.invoke('getDesktopVersion');
|
||||
const { desktopVersion } = await window.desktop.system.getDesktopVersion();
|
||||
const cacheDaemonStatus = await this.getCacheStatus();
|
||||
const clientAgentStatus = await this.getClientAgentStatus();
|
||||
|
||||
const logLevel = await this.ipc.invoke('getLogLevel');
|
||||
const logPath = await this.ipc.invoke('getLogPath');
|
||||
const logLevel = await window.desktop.logging.getLogLevel();
|
||||
const logPath = await window.desktop.logging.getLogPath();
|
||||
const serverInformation = this.clusterUrl.rendererClusterUrl;
|
||||
|
||||
return {
|
||||
@@ -40,7 +39,7 @@ export default class ScopesScopeProjectsSettingsIndexRoute extends Route {
|
||||
let cacheDaemonStatus;
|
||||
let cacheDaemonStatusError = [];
|
||||
try {
|
||||
cacheDaemonStatus = await this.ipc.invoke('cacheDaemonStatus');
|
||||
cacheDaemonStatus = await window.desktop.daemon.cacheDaemonStatus();
|
||||
} catch (e) {
|
||||
cacheDaemonStatusError.push(e);
|
||||
}
|
||||
@@ -66,7 +65,7 @@ export default class ScopesScopeProjectsSettingsIndexRoute extends Route {
|
||||
let clientAgentStatus;
|
||||
let clientAgentStatusError = [];
|
||||
try {
|
||||
clientAgentStatus = await this.ipc.invoke('clientAgentStatus');
|
||||
clientAgentStatus = await window.desktop.clientAgent.clientAgentStatus();
|
||||
} catch (e) {
|
||||
clientAgentStatusError.push(e);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
@service router;
|
||||
@service session;
|
||||
@service store;
|
||||
@service ipc;
|
||||
|
||||
// =attributes
|
||||
|
||||
@@ -113,7 +112,8 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
|
||||
await timeout(250);
|
||||
}
|
||||
|
||||
const isCacheRunningPromise = this.ipc.invoke('isCacheDaemonRunning');
|
||||
const isCacheRunningPromise =
|
||||
window.desktop.daemon.isCacheDaemonRunning();
|
||||
|
||||
const orgScope = this.modelFor('scopes.scope');
|
||||
const projects = this.modelFor('scopes.scope.projects');
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Service, { inject as service } from '@ember/service';
|
||||
import Service from '@ember/service';
|
||||
|
||||
export default class ClientAgentSessionsService extends Service {
|
||||
// =services
|
||||
@service ipc;
|
||||
|
||||
// =attributes
|
||||
#sessionsSet;
|
||||
|
||||
@@ -19,7 +16,7 @@ export default class ClientAgentSessionsService extends Service {
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async getClientAgentSessions() {
|
||||
return this.ipc.invoke('getClientAgentSessions');
|
||||
return window.desktop.clientAgent.getClientAgentSessions();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@ import { notifyError } from 'core/decorators/notify';
|
||||
export default class ClusterUrlService extends Service {
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
@service store;
|
||||
@service storage;
|
||||
|
||||
@@ -42,7 +41,7 @@ export default class ClusterUrlService extends Service {
|
||||
* @type {Promise{?string}}
|
||||
*/
|
||||
get mainClusterUrl() {
|
||||
return this.ipc.invoke('getClusterUrl');
|
||||
return window.desktop.cluster.getClusterUrl();
|
||||
}
|
||||
|
||||
// =methods
|
||||
@@ -76,7 +75,7 @@ export default class ClusterUrlService extends Service {
|
||||
this.adapter.host = clusterUrl;
|
||||
this.rendererClusterUrl = clusterUrl;
|
||||
if (clusterUrl !== (await this.mainClusterUrl)) {
|
||||
await this.ipc.invoke('setClusterUrl', clusterUrl);
|
||||
await window.desktop.cluster.setClusterUrl(clusterUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
this.adapter.host = originalHost;
|
||||
@@ -94,6 +93,6 @@ export default class ClusterUrlService extends Service {
|
||||
})
|
||||
async resetClusterUrl() {
|
||||
this.rendererClusterUrl = null;
|
||||
await this.ipc.invoke('resetClusterUrl');
|
||||
await window.desktop.cluster.resetClusterUrl();
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Service from '@ember/service';
|
||||
import { service } from '@ember/service';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
/**
|
||||
* An IPC request is a promise-like object that issues a request via
|
||||
* `window.postMessage` and resolves to a response received via a response
|
||||
* port. IPC requests are used by an Electron renderer process to communicate
|
||||
* with the main process when the renderer is fully isolated.
|
||||
*
|
||||
* Some of the design goals of this "safe IPC" are to respect the confines
|
||||
* of process isolation and security, without using node integration, while
|
||||
* retaining the ability to run the Ember application within a browser
|
||||
* (sans main process) by using only APIs available in a standard
|
||||
* (non-Electron) browser. This is achieved by posting messages to the
|
||||
* renderer's `window.postMessage` interface. Thus, the renderer requires no
|
||||
* knowledge of the main process or the Electron context. In fact a main
|
||||
* process and Electron context are not even required.
|
||||
*
|
||||
* In an Electron context, the `preload.js` script sets up a `window.postMessage`
|
||||
* listener which acts as proxy layer between the main and renderer processes.
|
||||
* It waits for incoming requests from the renderer and forwards them to main.
|
||||
* Responses from the main process are forwarded back via a response port
|
||||
* (sent with the original request).
|
||||
*
|
||||
* Local development in a non-Electron browser context or test context should
|
||||
* mock up IPC requests. See the ipc service test for an example of mocking.
|
||||
*
|
||||
* TODO: add utilities for quickly mocking IPC responses (like Mirage)
|
||||
*
|
||||
*/
|
||||
export class IPCRequest {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {?MessageChannel}
|
||||
* @private
|
||||
*/
|
||||
#channel = null;
|
||||
|
||||
/**
|
||||
* @type {?Promise}
|
||||
* @private
|
||||
*/
|
||||
#promise = null;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Posts a message to the window object, along with a MessagePort
|
||||
* with which to receive a response. An internal promise is generated which
|
||||
* resolves to the response.
|
||||
* @param {string} method
|
||||
* @param {object} payload
|
||||
* @param {Window} window - A reference to the window object
|
||||
*/
|
||||
constructor(method, payload, window) {
|
||||
this.method = method;
|
||||
this.payload = payload;
|
||||
this.origin = window?.location?.origin;
|
||||
this.#channel = new window.MessageChannel();
|
||||
this.#promise = new Promise((resolve, reject) => {
|
||||
this.#channel.port1.onmessage = (event) => {
|
||||
this.close();
|
||||
if (event.data instanceof Error) {
|
||||
reject(JSON.parse(event.data.message));
|
||||
} else {
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
});
|
||||
window.postMessage(
|
||||
{
|
||||
method: this.method,
|
||||
payload: this.payload,
|
||||
},
|
||||
origin,
|
||||
[this.#channel.port2],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the message ports and drops the channel reference.
|
||||
* This is called upon receiving a response for cleanup purposes.
|
||||
*/
|
||||
close() {
|
||||
this.#channel.port1.close();
|
||||
this.#channel.port2.close();
|
||||
this.#channel = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
then() {
|
||||
return this.#promise.then(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
catch() {
|
||||
return this.#promise.catch(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxies to the underlying promise.
|
||||
*/
|
||||
finally() {
|
||||
return this.#promise.finally(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple factory for generating IPCRequests in Ember.
|
||||
* @example
|
||||
*
|
||||
* class MyClass {
|
||||
* @service ipc;
|
||||
*
|
||||
* makeRequest {
|
||||
* return this.ipc.invoke('getFoobars', { hello: 'world' });
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export default class IpcService extends Service {
|
||||
// =services
|
||||
|
||||
@service('browser/window') window;
|
||||
|
||||
// =methods
|
||||
|
||||
/**
|
||||
* Creates a new IPC request and returns it.
|
||||
* @param {string} method
|
||||
* @param {object} payload
|
||||
* @return {IPCRequest}
|
||||
*/
|
||||
invoke(method, payload) {
|
||||
return new IPCRequest(method, payload, this.window);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import Service from '@ember/service';
|
||||
import { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export const RDP_CLIENT_NONE = 'none';
|
||||
@@ -26,10 +25,6 @@ const windowsRecommendedRdpClient = {
|
||||
};
|
||||
|
||||
export default class RdpService extends Service {
|
||||
// =services
|
||||
|
||||
@service ipc;
|
||||
|
||||
// =properties
|
||||
|
||||
/**
|
||||
@@ -84,7 +79,7 @@ export default class RdpService extends Service {
|
||||
return this.rdpClients;
|
||||
}
|
||||
try {
|
||||
this.rdpClients = await this.ipc.invoke('getRdpClients');
|
||||
this.rdpClients = await window.desktop.rdp.getRdpClients();
|
||||
return this.rdpClients;
|
||||
} catch (error) {
|
||||
__electronLog?.error('Failed to fetch RDP clients', error.message);
|
||||
@@ -103,7 +98,7 @@ export default class RdpService extends Service {
|
||||
if (this.recommendedRdpClient !== null) {
|
||||
return this.recommendedRdpClient;
|
||||
}
|
||||
const { isWindows, isMac } = await this.ipc.invoke('checkOS');
|
||||
const { isWindows, isMac } = await window.desktop.system.checkOS();
|
||||
if (isWindows) {
|
||||
this.recommendedRdpClient = windowsRecommendedRdpClient;
|
||||
} else if (isMac) {
|
||||
@@ -128,7 +123,8 @@ export default class RdpService extends Service {
|
||||
return this.preferredRdpClient;
|
||||
}
|
||||
try {
|
||||
this.preferredRdpClient = await this.ipc.invoke('getPreferredRdpClient');
|
||||
this.preferredRdpClient =
|
||||
await window.desktop.rdp.getPreferredRdpClient();
|
||||
return this.preferredRdpClient;
|
||||
} catch (error) {
|
||||
__electronLog?.error(
|
||||
@@ -148,7 +144,7 @@ export default class RdpService extends Service {
|
||||
*/
|
||||
async setPreferredRdpClient(rdpClient) {
|
||||
try {
|
||||
await this.ipc.invoke('setPreferredRdpClient', rdpClient);
|
||||
await window.desktop.rdp.setPreferredRdpClient(rdpClient);
|
||||
this.preferredRdpClient = rdpClient;
|
||||
return this.preferredRdpClient;
|
||||
} catch (error) {
|
||||
@@ -165,6 +161,6 @@ export default class RdpService extends Service {
|
||||
* @param {string} sessionId - The ID of the active session
|
||||
*/
|
||||
async launchRdpClient(sessionId) {
|
||||
await this.ipc.invoke('launchRdpClient', sessionId);
|
||||
await window.desktop.rdp.launchRdpClient(sessionId);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import { notifyError } from 'core/decorators/notify';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
export default class SessionService extends BaseSessionService {
|
||||
@service ipc;
|
||||
@service store;
|
||||
|
||||
@tracked username;
|
||||
@@ -24,7 +23,7 @@ export default class SessionService extends BaseSessionService {
|
||||
|
||||
if (this.session.isAuthenticated) {
|
||||
const sessionData = this.data?.authenticated;
|
||||
await this.ipc.invoke('addTokenToDaemons', {
|
||||
await window.desktop.daemon.addTokenToDaemons({
|
||||
tokenId: sessionData?.id,
|
||||
token: sessionData?.token,
|
||||
});
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
const { ipcRenderer, contextBridge } = require('electron');
|
||||
|
||||
// Messages must originate from this origin
|
||||
const emberAppOrigin = window.location.origin;
|
||||
|
||||
/**
|
||||
* Exposing terminal creation to an isolated context (Ember)
|
||||
* More information about contextBridge https://www.electronjs.org/docs/latest/api/context-bridge
|
||||
@@ -41,39 +38,82 @@ contextBridge.exposeInMainWorld('terminal', {
|
||||
},
|
||||
});
|
||||
|
||||
process.once('loaded', () => {
|
||||
/**
|
||||
* Ember-land has no access to the renderer or node modules, and thus
|
||||
* cannot call into the main process or make invocations directly.
|
||||
* In order to communicate with the main process, the Ember app uses
|
||||
* the `postMessage` interface to send messages to its browser window.
|
||||
* These messages include message ports with which to receive responses from
|
||||
* the main process.
|
||||
*
|
||||
* This preload script simply wires up the forwarding of
|
||||
* messages received on the window and responses received from main.
|
||||
*/
|
||||
window.addEventListener('message', async function (event) {
|
||||
if (event.origin !== emberAppOrigin) return;
|
||||
const { method, payload } = event?.data ?? {};
|
||||
if (method) {
|
||||
const response = await ipcRenderer.invoke(method, payload);
|
||||
event.ports[0].postMessage(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Listener on electron app when user triggers before-quit event
|
||||
* Exposes `desktop` via Electron ContextBridge to the renderer process. It is grouped into nested objects based on the type of functionality.
|
||||
* use example: window.desktop.cluster.getClusterUrl().
|
||||
*/
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
onAppQuit: (callback) => {
|
||||
// Don't pass in callback directly so users can't access passed in event for security
|
||||
const listenerCallback = () => callback();
|
||||
ipcRenderer.on('onAppQuit', listenerCallback);
|
||||
contextBridge.exposeInMainWorld('desktop', {
|
||||
cluster: {
|
||||
getClusterUrl: () => ipcRenderer.invoke('getClusterUrl'),
|
||||
setClusterUrl: (url) => ipcRenderer.invoke('setClusterUrl', url),
|
||||
resetClusterUrl: () => ipcRenderer.invoke('resetClusterUrl'),
|
||||
},
|
||||
|
||||
return () => {
|
||||
return ipcRenderer.removeListener('onAppQuit', listenerCallback);
|
||||
};
|
||||
system: {
|
||||
openExternal: (href) => ipcRenderer.invoke('openExternal', href),
|
||||
cliExists: () => ipcRenderer.invoke('cliExists'),
|
||||
getCliVersion: () => ipcRenderer.invoke('getCliVersion'),
|
||||
checkCommand: (command) => ipcRenderer.invoke('checkCommand', command),
|
||||
checkOS: () => ipcRenderer.invoke('checkOS'),
|
||||
getDesktopVersion: () => ipcRenderer.invoke('getDesktopVersion'),
|
||||
},
|
||||
|
||||
session: {
|
||||
connectSession: (params) => ipcRenderer.invoke('connect', params),
|
||||
stopSession: (params) => ipcRenderer.invoke('stop', params),
|
||||
stopAllSessions: () => ipcRenderer.invoke('stopAll'),
|
||||
hasRunningSessions: () => ipcRenderer.invoke('hasRunningSessions'),
|
||||
},
|
||||
|
||||
windowAction: {
|
||||
hasMacOSChrome: () => ipcRenderer.invoke('hasMacOSChrome'),
|
||||
showWindowActions: () => ipcRenderer.invoke('showWindowActions'),
|
||||
minimizeWindow: () => ipcRenderer.invoke('minimizeWindow'),
|
||||
toggleFullscreenWindow: () => ipcRenderer.invoke('toggleFullscreenWindow'),
|
||||
closeWindow: () => ipcRenderer.invoke('closeWindow'),
|
||||
focusWindow: () => ipcRenderer.invoke('focusWindow'),
|
||||
},
|
||||
|
||||
daemon: {
|
||||
addTokenToDaemons: (data) => ipcRenderer.invoke('addTokenToDaemons', data),
|
||||
searchCacheDaemon: (request) =>
|
||||
ipcRenderer.invoke('searchCacheDaemon', request),
|
||||
isCacheDaemonRunning: () => ipcRenderer.invoke('isCacheDaemonRunning'),
|
||||
cacheDaemonStatus: () => ipcRenderer.invoke('cacheDaemonStatus'),
|
||||
},
|
||||
|
||||
clientAgent: {
|
||||
getClientAgentSessions: () => ipcRenderer.invoke('getClientAgentSessions'),
|
||||
isClientAgentRunning: () => ipcRenderer.invoke('isClientAgentRunning'),
|
||||
clientAgentStatus: () => ipcRenderer.invoke('clientAgentStatus'),
|
||||
pauseClientAgent: () => ipcRenderer.invoke('pauseClientAgent'),
|
||||
resumeClientAgent: () => ipcRenderer.invoke('resumeClientAgent'),
|
||||
},
|
||||
|
||||
logging: {
|
||||
getLogLevel: () => ipcRenderer.invoke('getLogLevel'),
|
||||
setLogLevel: (logLevel) => ipcRenderer.invoke('setLogLevel', logLevel),
|
||||
getLogPath: () => ipcRenderer.invoke('getLogPath'),
|
||||
},
|
||||
|
||||
rdp: {
|
||||
getRdpClients: () => ipcRenderer.invoke('getRdpClients'),
|
||||
getPreferredRdpClient: () => ipcRenderer.invoke('getPreferredRdpClient'),
|
||||
setPreferredRdpClient: (client) =>
|
||||
ipcRenderer.invoke('setPreferredRdpClient', client),
|
||||
launchRdpClient: (sessionId) =>
|
||||
ipcRenderer.invoke('launchRdpClient', sessionId),
|
||||
},
|
||||
|
||||
app: {
|
||||
onAppQuit: (callback) => {
|
||||
// Don't pass in callback directly so users can't access passed in event for security
|
||||
const listenerCallback = () => callback();
|
||||
ipcRenderer.on('onAppQuit', listenerCallback);
|
||||
|
||||
return () => {
|
||||
return ipcRenderer.removeListener('onAppQuit', listenerCallback);
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -9,6 +9,7 @@ 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';
|
||||
|
||||
module('Acceptance | Application', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -19,9 +20,7 @@ module('Acceptance | Application', function (hooks) {
|
||||
'[data-test-toast-notification] button';
|
||||
const TOAST_DISMISS_BUTTON = '[aria-label="Dismiss"]';
|
||||
|
||||
const stubs = {
|
||||
ipcService: null,
|
||||
};
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
const urls = {
|
||||
clusterUrl: null,
|
||||
@@ -32,9 +31,6 @@ module('Acceptance | Application', function (hooks) {
|
||||
|
||||
urls.clusterUrl = '/cluster-url';
|
||||
|
||||
const ipcService = this.owner.lookup('service:ipc');
|
||||
stubs.ipcService = sinon.stub(ipcService, 'invoke');
|
||||
|
||||
// mock RDP service calls
|
||||
let rdpService = this.owner.lookup('service:rdp');
|
||||
sinon.stub(rdpService, 'initialize').resolves();
|
||||
@@ -46,7 +42,6 @@ module('Acceptance | Application', function (hooks) {
|
||||
|
||||
test('visiting index with chrome disabled hides custom window actions', async function (assert) {
|
||||
assert.expect(3);
|
||||
stubs.ipcService.withArgs('showWindowActions').returns(false);
|
||||
await visit(urls.clusterUrl);
|
||||
assert.notOk(find('.button-window-close'), 'Window close button');
|
||||
assert.notOk(find('.button-window-minimize'), 'Window minimize button');
|
||||
@@ -55,7 +50,7 @@ module('Acceptance | Application', function (hooks) {
|
||||
|
||||
test('visiting index with chrome enabled shows custom window actions', async function (assert) {
|
||||
assert.expect(3);
|
||||
stubs.ipcService.withArgs('showWindowActions').returns(true);
|
||||
window.desktop.windowAction.showWindowActions.resolves(true);
|
||||
await visit(urls.clusterUrl);
|
||||
assert.ok(find('.button-window-close'), 'Window close button');
|
||||
assert.ok(find('.button-window-minimize'), 'Window minimize button');
|
||||
@@ -64,7 +59,7 @@ module('Acceptance | Application', function (hooks) {
|
||||
|
||||
test('visiting index with MacOS chrome enabled', async function (assert) {
|
||||
assert.expect(1);
|
||||
stubs.ipcService.withArgs('hasMacOSChrome').returns(true);
|
||||
window.desktop.windowAction.hasMacOSChrome.resolves(true);
|
||||
await visit(urls.clusterUrl);
|
||||
assert.ok(
|
||||
find('.app-header.header-cushion'),
|
||||
@@ -74,7 +69,7 @@ module('Acceptance | Application', function (hooks) {
|
||||
|
||||
test('visiting index with MacOS chrome disabled', async function (assert) {
|
||||
assert.expect(1);
|
||||
stubs.ipcService.withArgs('hasMacOSChrome').returns(false);
|
||||
window.desktop.windowAction.hasMacOSChrome.resolves(false);
|
||||
await visit(urls.clusterUrl);
|
||||
assert.notOk(
|
||||
find('.app-header.header-cushion'),
|
||||
|
||||
@@ -18,13 +18,13 @@ import {
|
||||
authenticateSession,
|
||||
invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
import WindowMockIPC from '../helpers/window-mock-ipc';
|
||||
import Service from '@ember/service';
|
||||
import { setupDesktopContextBridgeApiMock } from '../helpers/desktop-context-bridge-api-mock';
|
||||
import sinon from 'sinon';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
|
||||
module('Acceptance | authentication', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -80,9 +80,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
await invalidateSession();
|
||||
|
||||
const ipcService = this.owner.lookup('service:ipc');
|
||||
this.ipcStub = sinon.stub(ipcService, 'invoke');
|
||||
|
||||
// create scopes
|
||||
instances.scopes.global = this.server.schema.scopes.find('global');
|
||||
stubs.global = { id: 'global', type: 'global' };
|
||||
@@ -133,8 +130,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
urls.targets = `${urls.projects}/targets`;
|
||||
urls.sessions = `${urls.projects}/sessions`;
|
||||
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
});
|
||||
|
||||
@@ -196,6 +191,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
},
|
||||
},
|
||||
});
|
||||
window.desktop.session.hasRunningSessions.resolves(false);
|
||||
|
||||
assert.expect(3);
|
||||
await visit(urls.authenticate.methods.global);
|
||||
@@ -274,8 +270,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('hasRunningSessions').returns(true);
|
||||
|
||||
await visit(urls.authenticate.methods.global);
|
||||
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
@@ -305,9 +299,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
const stopAllSessions = this.ipcStub.withArgs('stopAll');
|
||||
this.ipcStub.withArgs('hasRunningSessions').returns(true);
|
||||
|
||||
await visit(urls.authenticate.methods.global);
|
||||
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
@@ -324,7 +315,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
await click(MODAL_CONFIRM_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isNotVisible();
|
||||
assert.ok(stopAllSessions.calledOnce);
|
||||
assert.ok(window.desktop.session.stopAllSessions.calledOnce);
|
||||
assert.notOk(currentSession().isAuthenticated);
|
||||
});
|
||||
|
||||
@@ -338,26 +329,6 @@ module('Acceptance | authentication', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
// We need to encapsulate the event listener inside a mocked window service to ensure
|
||||
// the entire event is torn down (including the mocked window), since "window" exists
|
||||
// globally across all tests, and we don't want tests impacting one another
|
||||
const mockElectronEvent = class WindowElectronMock extends Service {
|
||||
electron = {
|
||||
onAppQuit: (callback) => {
|
||||
window.addEventListener('onAppQuit', callback);
|
||||
return () => {
|
||||
window.removeEventListener('onAppQuit', callback);
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
this.owner.register('service:browser/window', mockElectronEvent);
|
||||
const stopAllSessions = this.ipcStub.withArgs('stopAll');
|
||||
const quitApp = this.ipcStub.withArgs('closeWindow');
|
||||
|
||||
this.ipcStub.withArgs('hasRunningSessions').returns(true);
|
||||
|
||||
await visit(urls.authenticate.methods.global);
|
||||
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
@@ -378,7 +349,7 @@ module('Acceptance | authentication', function (hooks) {
|
||||
.includesText('Close sessions before quitting?');
|
||||
|
||||
await click(MODAL_CONFIRM_BTN);
|
||||
assert.ok(stopAllSessions.calledOnce);
|
||||
assert.ok(quitApp.calledOnce);
|
||||
assert.ok(window.desktop.session.stopAllSessions.calledOnce);
|
||||
assert.ok(window.desktop.windowAction.closeWindow.calledOnce);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ 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 WindowMockIPC from '../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../helpers/desktop-context-bridge-api-mock';
|
||||
import config from '../../config/environment';
|
||||
|
||||
module('Acceptance | clusterUrl', function (hooks) {
|
||||
@@ -17,7 +17,6 @@ module('Acceptance | clusterUrl', function (hooks) {
|
||||
setupBrowserFakes(hooks, { window: true });
|
||||
|
||||
const currentOrigin = window.location.origin;
|
||||
let mockIPC;
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -36,7 +35,6 @@ module('Acceptance | clusterUrl', function (hooks) {
|
||||
const stubs = {
|
||||
global: null,
|
||||
org: null,
|
||||
ipcService: null,
|
||||
};
|
||||
|
||||
const urls = {
|
||||
@@ -55,10 +53,7 @@ module('Acceptance | clusterUrl', function (hooks) {
|
||||
projects: null,
|
||||
targets: null,
|
||||
};
|
||||
const setupMockIpc = (test) => {
|
||||
test.owner.register('service:browser/window', WindowMockIPC);
|
||||
mockIPC = test.owner.lookup('service:browser/window').mockIPC;
|
||||
};
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
await invalidateSession();
|
||||
@@ -113,41 +108,37 @@ module('Acceptance | clusterUrl', function (hooks) {
|
||||
|
||||
test('visiting index without a clusterUrl specified redirects to clusterUrl route', async function (assert) {
|
||||
assert.expect(2);
|
||||
setupMockIpc(this);
|
||||
await visit(urls.index);
|
||||
|
||||
assert.notOk(mockIPC.clusterUrl);
|
||||
assert.notOk(window.desktop.clusterUrl);
|
||||
assert.strictEqual(currentURL(), urls.clusterUrl);
|
||||
});
|
||||
|
||||
test('can set clusterUrl', async function (assert) {
|
||||
assert.expect(3);
|
||||
setupMockIpc(this);
|
||||
assert.notOk(mockIPC.clusterUrl);
|
||||
assert.notOk(window.desktop.clusterUrl);
|
||||
await visit(urls.clusterUrl);
|
||||
await fillIn('[name="host"]', currentOrigin);
|
||||
await click('[type="submit"]');
|
||||
assert.strictEqual(currentURL(), urls.authenticate.methods.global);
|
||||
assert.strictEqual(mockIPC.clusterUrl, currentOrigin);
|
||||
assert.strictEqual(window.desktop.clusterUrl, currentOrigin);
|
||||
});
|
||||
|
||||
test('can reset clusterUrl before authentication', async function (assert) {
|
||||
assert.expect(4);
|
||||
setupMockIpc(this);
|
||||
assert.notOk(mockIPC.clusterUrl);
|
||||
assert.notOk(window.desktop.clusterUrl);
|
||||
await visit(urls.clusterUrl);
|
||||
await fillIn('[name="host"]', currentOrigin);
|
||||
await click('[type="submit"]');
|
||||
assert.strictEqual(currentURL(), urls.authenticate.methods.global);
|
||||
assert.strictEqual(mockIPC.clusterUrl, currentOrigin);
|
||||
assert.strictEqual(window.desktop.clusterUrl, currentOrigin);
|
||||
await click('.change-origin a');
|
||||
assert.strictEqual(currentURL(), urls.clusterUrl);
|
||||
});
|
||||
|
||||
test('captures error on clusterUrl update', async function (assert) {
|
||||
assert.expect(2);
|
||||
setupMockIpc(this);
|
||||
assert.notOk(mockIPC.clusterUrl);
|
||||
assert.notOk(window.desktop.clusterUrl);
|
||||
sinon
|
||||
.stub(this.owner.lookup('service:clusterUrl'), 'setClusterUrl')
|
||||
.throws();
|
||||
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
currentSession,
|
||||
invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
import WindowMockIPC from '../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module('Acceptance | projects', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -86,8 +87,6 @@ module('Acceptance | projects', function (hooks) {
|
||||
urls.authenticate.methods.global = `${urls.authenticate.global}/${instances.authMethods.global.id}`;
|
||||
urls.projects = `${urls.scopes.org}/projects`;
|
||||
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'desktop/tests/helpers';
|
||||
import { Response } from 'miragejs';
|
||||
import WindowMockIPC from '../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../helpers/desktop-context-bridge-api-mock';
|
||||
import {
|
||||
currentSession,
|
||||
invalidateSession,
|
||||
@@ -24,6 +24,7 @@ import sinon from 'sinon';
|
||||
|
||||
module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
const APP_STATE_TITLE =
|
||||
@@ -147,11 +148,9 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
urls.sessions2 = `${urls.projects2}/sessions`;
|
||||
urls.session = `${urls.projects}/sessions/${instances.session.id}`;
|
||||
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
|
||||
|
||||
// mock RDP service calls
|
||||
@@ -441,7 +440,6 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('stop');
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.sessions}"]`);
|
||||
@@ -462,7 +460,6 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('stop');
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.sessions}"]`);
|
||||
@@ -502,7 +499,7 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('stop').throws();
|
||||
window.desktop.session.stopSession.throws();
|
||||
await visit(urls.projects);
|
||||
|
||||
await click(`[href="${urls.sessions}"]`);
|
||||
@@ -572,7 +569,8 @@ module('Acceptance | projects | sessions | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(false);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(false);
|
||||
|
||||
this.stubCacheDaemonSearch();
|
||||
const sessionsCount = this.server.schema.sessions.all().models.length;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { module, test } from 'qunit';
|
||||
import { visit, currentURL, click } from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'desktop/tests/helpers';
|
||||
import { Response } from 'miragejs';
|
||||
import WindowMockIPC from '../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../helpers/desktop-context-bridge-api-mock';
|
||||
import { STATUS_SESSION_ACTIVE } from 'api/models/session';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
@@ -19,6 +19,7 @@ import { RDP_CLIENT_WINDOWS_APP, RDP_CLIENT_NONE } from 'desktop/services/rdp';
|
||||
|
||||
module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
const TARGET_CONNECT_BUTTON = '[data-test-target-detail-connect-button]';
|
||||
@@ -137,11 +138,8 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
urls.sessions = `${urls.projects}/sessions`;
|
||||
urls.session = `${urls.projects}/sessions/${instances.session.id}`;
|
||||
urls.rdpSession = `${urls.projects}/sessions/${instances.rdpSession.id}`;
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(false);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
// mock RDP service calls
|
||||
this.rdpService = this.owner.lookup('service:rdp');
|
||||
@@ -181,8 +179,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(4);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -210,8 +207,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -283,8 +279,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -341,8 +336,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
@@ -378,8 +372,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
});
|
||||
|
||||
// First RDP Session visit should show the toast notification
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
protocol: 'rdp',
|
||||
});
|
||||
@@ -426,8 +419,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
|
||||
assert.expect(1);
|
||||
this.server.get('/hosts/:id', () => new Response(403));
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
@@ -459,8 +451,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
};
|
||||
|
||||
this.server.get('/sessions/:id', () => new Response(403));
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
@@ -585,7 +576,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(1);
|
||||
this.ipcStub.withArgs('stop').throws();
|
||||
window.desktop.session.stopSession.throws();
|
||||
|
||||
await visit(urls.session);
|
||||
|
||||
@@ -606,12 +597,10 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
@@ -628,7 +617,7 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
await click(RDP_OPEN_BUTTON);
|
||||
|
||||
assert.ok(
|
||||
this.ipcStub.calledWith('launchRdpClient', instances.rdpSession.id),
|
||||
window.desktop.rdp.launchRdpClient.calledWith(instances.rdpSession.id),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -642,12 +631,10 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_NONE;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
@@ -672,19 +659,17 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
this.ipcStub.withArgs('launchRdpClient', instances.rdpSession.id).rejects();
|
||||
window.desktop.rdp.launchRdpClient.rejects();
|
||||
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
@@ -710,12 +695,11 @@ module('Acceptance | projects | sessions | session', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
instances.rdpSession.update({ authorized_actions: [] });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.rdpSession.id,
|
||||
host_id: 'h_123',
|
||||
address: 'a_123',
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
authenticateSession,
|
||||
currentSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
import WindowMockIPC from '../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../helpers/desktop-context-bridge-api-mock';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import {
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
|
||||
module('Acceptance | projects | settings | index', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
const instances = {
|
||||
@@ -95,25 +96,15 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
urls.projects = `${urls.scopes.org}/projects`;
|
||||
urls.settings = `${urls.projects}/settings`;
|
||||
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub
|
||||
.withArgs('getDesktopVersion')
|
||||
.returns({ desktopVersion: '0.1.0' });
|
||||
this.ipcStub
|
||||
.withArgs('getCliVersion')
|
||||
.returns({ versionNumber: 'Boundary CLI v0.1.0' });
|
||||
this.ipcStub
|
||||
.withArgs('cacheDaemonStatus')
|
||||
.returns({ version: 'Boundary CLI v0.1.0' });
|
||||
|
||||
// mock RDP client data
|
||||
this.ipcStub
|
||||
.withArgs('getRdpClients')
|
||||
.returns([RDP_CLIENT_MSTSC, RDP_CLIENT_NONE]);
|
||||
this.ipcStub.withArgs('getPreferredRdpClient').returns(RDP_CLIENT_MSTSC);
|
||||
this.ipcStub.withArgs('checkOS').returns({ isWindows: true, isMac: false });
|
||||
window.desktop.rdp.getRdpClients.resolves([
|
||||
RDP_CLIENT_MSTSC,
|
||||
RDP_CLIENT_NONE,
|
||||
]);
|
||||
window.desktop.rdp.getPreferredRdpClient.resolves(RDP_CLIENT_MSTSC);
|
||||
window.desktop.system.checkOS.resolves({ isWindows: true, isMac: false });
|
||||
});
|
||||
|
||||
test('can navigate to the settings page', async function (assert) {
|
||||
@@ -195,6 +186,8 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
window.desktop.session.hasRunningSessions.resolves(false);
|
||||
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
assert.expect(2);
|
||||
|
||||
@@ -223,9 +216,6 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
const stopAllSessions = this.ipcStub.withArgs('stopAll');
|
||||
this.ipcStub.withArgs('hasRunningSessions').returns(true);
|
||||
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
assert.ok(currentSession().isAuthenticated);
|
||||
|
||||
@@ -239,7 +229,7 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
await click(MODAL_CONFIRM_BTN);
|
||||
|
||||
assert.dom(MODAL_CLOSE_SESSIONS).isNotVisible();
|
||||
assert.ok(stopAllSessions.calledOnce);
|
||||
assert.ok(window.desktop.session.stopAllSessions.calledOnce);
|
||||
assert.notOk(currentSession().isAuthenticated);
|
||||
});
|
||||
|
||||
@@ -278,14 +268,13 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
// update IPC stub fo mac
|
||||
this.ipcStub.withArgs('checkOS').returns({ isWindows: false, isMac: true });
|
||||
this.ipcStub
|
||||
.withArgs('getRdpClients')
|
||||
.returns([RDP_CLIENT_WINDOWS_APP, RDP_CLIENT_NONE]);
|
||||
this.ipcStub
|
||||
.withArgs('getPreferredRdpClient')
|
||||
.returns(RDP_CLIENT_WINDOWS_APP);
|
||||
// update window bounday mock fo mac
|
||||
window.desktop.system.checkOS.resolves({ isWindows: false, isMac: true });
|
||||
window.desktop.rdp.getRdpClients.resolves([
|
||||
RDP_CLIENT_WINDOWS_APP,
|
||||
RDP_CLIENT_NONE,
|
||||
]);
|
||||
window.desktop.rdp.getPreferredRdpClient.resolves(RDP_CLIENT_WINDOWS_APP);
|
||||
await visit(urls.settings);
|
||||
|
||||
assert
|
||||
@@ -309,9 +298,9 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
// update IPC stub for no RDP clients
|
||||
this.ipcStub.withArgs('getRdpClients').returns([RDP_CLIENT_NONE]);
|
||||
this.ipcStub.withArgs('getPreferredRdpClient').returns(RDP_CLIENT_NONE);
|
||||
// update window boundary mock for no RDP clients
|
||||
window.desktop.rdp.getRdpClients.resolves([RDP_CLIENT_NONE]);
|
||||
window.desktop.rdp.getPreferredRdpClient.resolves(RDP_CLIENT_NONE);
|
||||
await visit(urls.settings);
|
||||
|
||||
assert.dom(RDP_RECOMMENDED_CLIENT).isVisible();
|
||||
@@ -337,13 +326,13 @@ module('Acceptance | projects | settings | index', function (hooks) {
|
||||
});
|
||||
|
||||
const rdpService = this.owner.lookup('service:rdp');
|
||||
this.ipcStub.withArgs('setPreferredRdpClient').resolves();
|
||||
await visit(urls.settings);
|
||||
await visit(urls.settings);
|
||||
|
||||
await select(RDP_PREFERRED_CLIENT, RDP_CLIENT_NONE);
|
||||
|
||||
assert.ok(
|
||||
this.ipcStub.calledWith('setPreferredRdpClient', RDP_CLIENT_NONE),
|
||||
window.desktop.rdp.setPreferredRdpClient.calledWith(RDP_CLIENT_NONE),
|
||||
);
|
||||
assert.strictEqual(rdpService.preferredRdpClient, RDP_CLIENT_NONE);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
click,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'desktop/tests/helpers';
|
||||
import WindowMockIPC from '../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../helpers/desktop-context-bridge-api-mock';
|
||||
import {
|
||||
invalidateSession,
|
||||
currentSession,
|
||||
@@ -28,6 +28,7 @@ import { RDP_CLIENT_NONE, RDP_CLIENT_WINDOWS_APP } from 'desktop/services/rdp';
|
||||
|
||||
module('Acceptance | projects | targets | index', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
let getTargetCount;
|
||||
@@ -164,11 +165,9 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
// Generate resource counter
|
||||
getTargetCount = () => this.server.schema.targets.all().models.length;
|
||||
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
|
||||
// mock RDP service calls
|
||||
@@ -321,8 +320,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').rejects();
|
||||
window.desktop.session.connectSession.rejects();
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
await visit(urls.projects);
|
||||
@@ -348,8 +346,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
instances.target.authorized_actions =
|
||||
instances.target.authorized_actions.filter((item) => item !== 'read');
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -380,8 +377,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
|
||||
instances.target.authorized_actions =
|
||||
instances.target.authorized_actions.filter((item) => item !== 'read');
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').rejects();
|
||||
window.desktop.session.connectSession.rejects();
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
@@ -625,7 +621,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(false);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(false);
|
||||
this.stubCacheDaemonSearch();
|
||||
const targetsCount = getTargetCount();
|
||||
|
||||
@@ -795,18 +791,15 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
this.ipcStub.withArgs('launchRdpClient').resolves();
|
||||
|
||||
// visit targets page
|
||||
await visit(urls.targets);
|
||||
@@ -815,7 +808,9 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
|
||||
await click(TARGET_OPEN_BUTTON(instances.target.id));
|
||||
|
||||
assert.ok(this.ipcStub.calledWith('launchRdpClient', instances.session.id));
|
||||
assert.ok(
|
||||
window.desktop.rdp.launchRdpClient.calledWith(instances.session.id),
|
||||
);
|
||||
});
|
||||
|
||||
test('clicking `Connect` button for RDP target without preferred client calls connect IPC', async function (assert) {
|
||||
@@ -828,12 +823,10 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
},
|
||||
});
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_NONE;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
const connectSessionStub = window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -847,7 +840,7 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
|
||||
await click(TARGET_CONNECT_BUTTON(instances.target.id));
|
||||
|
||||
assert.ok(this.ipcStub.calledWith('connect'));
|
||||
assert.ok(connectSessionStub.called);
|
||||
});
|
||||
|
||||
test('shows confirm modal when connection error occurs on launching rdp client', async function (assert) {
|
||||
@@ -863,15 +856,14 @@ module('Acceptance | projects | targets | index', function (hooks) {
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
// target quick connection is a success but launching RDP client fails
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
this.ipcStub.withArgs('launchRdpClient').rejects();
|
||||
window.desktop.rdp.launchRdpClient.rejects();
|
||||
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
waitFor,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'desktop/tests/helpers';
|
||||
import WindowMockIPC from '../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../helpers/desktop-context-bridge-api-mock';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import { TYPE_TARGET_RDP } from 'api/models/target';
|
||||
@@ -23,6 +23,7 @@ import { RDP_CLIENT_WINDOWS_APP } from 'desktop/services/rdp';
|
||||
|
||||
module('Acceptance | projects | targets | target', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
const TARGET_RESOURCE_LINK = (id) => `[data-test-visit-target="${id}"]`;
|
||||
@@ -149,11 +150,9 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
urls.targetWithTwoHosts = `${urls.targets}/${instances.targetWithTwoHosts.id}`;
|
||||
urls.targetWithManyHosts = `${urls.targets}/${instances.targetWithManyHosts.id}`;
|
||||
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
|
||||
// mock RDP service calls
|
||||
@@ -172,8 +171,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(3);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'localhost',
|
||||
port: 'p_123',
|
||||
@@ -202,7 +200,6 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(4);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
await visit(urls.targets);
|
||||
@@ -227,8 +224,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(4);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').rejects();
|
||||
window.desktop.session.connectSession.rejects();
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
await visit(urls.targets);
|
||||
@@ -244,7 +240,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
|
||||
test('user can retry on error', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.ipcStub.withArgs('cliExists').rejects();
|
||||
window.desktop.system.cliExists.rejects();
|
||||
this.stubCacheDaemonSearch();
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
@@ -273,8 +269,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
});
|
||||
|
||||
assert.expect(2);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -411,8 +406,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
instances.targetWithTwoHosts.hostSets.models[0].hosts.models[0].id;
|
||||
|
||||
assert.expect(2);
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -591,8 +585,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
|
||||
assert.expect(1);
|
||||
this.server.get('/host-sets/:id', () => new Response(403));
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -619,8 +612,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
|
||||
assert.expect(1);
|
||||
this.server.get('/hosts/:id', () => new Response(403));
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -674,15 +666,13 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
this.rdpService.preferredRdpClient = RDP_CLIENT_WINDOWS_APP;
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
protocol: 'rdp',
|
||||
});
|
||||
this.stubCacheDaemonSearch();
|
||||
this.ipcStub.withArgs('launchRdpClient').resolves();
|
||||
|
||||
const confirmService = this.owner.lookup('service:confirm');
|
||||
confirmService.enabled = true;
|
||||
@@ -691,7 +681,9 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
|
||||
await click(TARGET_OPEN_BUTTON);
|
||||
|
||||
assert.ok(this.ipcStub.calledWith('launchRdpClient', instances.session.id));
|
||||
assert.ok(
|
||||
window.desktop.rdp.launchRdpClient.calledWith(instances.session.id),
|
||||
);
|
||||
});
|
||||
|
||||
test('shows `Connect` button for rdp target without preferred client', async function (assert) {
|
||||
@@ -708,8 +700,7 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
instances.target.update({ type: TYPE_TARGET_RDP });
|
||||
|
||||
this.stubCacheDaemonSearch();
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns({
|
||||
const connectSessionStub = window.desktop.session.connectSession.resolves({
|
||||
session_id: instances.session.id,
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
@@ -723,8 +714,8 @@ module('Acceptance | projects | targets | target', function (hooks) {
|
||||
|
||||
await click(TARGET_CONNECT_BUTTON);
|
||||
|
||||
assert.ok(this.ipcStub.calledWith('connect'));
|
||||
assert.notOk(this.ipcStub.calledWith('launchRdpClient'));
|
||||
assert.ok(connectSessionStub.called);
|
||||
assert.notOk(window.desktop.rdp.launchRdpClient.called);
|
||||
});
|
||||
|
||||
test('shows confirm modal when quickConnectAndLaunchRdp fails', async function (assert) {
|
||||
|
||||
@@ -18,13 +18,14 @@ import {
|
||||
currentSession,
|
||||
invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
import WindowMockIPC from '../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../helpers/desktop-context-bridge-api-mock';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Acceptance | scopes', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
|
||||
const APP_STATE_TITLE = '.hds-application-state__title';
|
||||
@@ -134,10 +135,9 @@ module('Acceptance | scopes', function (hooks) {
|
||||
urls.globalTargets = `${urls.globalProjects}/targets`;
|
||||
urls.target = `${urls.targets}/${instances.target.id}`;
|
||||
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
|
||||
// mock RDP service calls
|
||||
@@ -319,7 +319,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
});
|
||||
|
||||
test.skip('pagination is not supported - windows build', async function (assert) {
|
||||
this.ipcStub.withArgs('checkOS').returns({
|
||||
window.desktop.system.checkOS.resolves({
|
||||
isWindows: true,
|
||||
isMac: false,
|
||||
isLinux: false,
|
||||
@@ -342,7 +342,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
});
|
||||
|
||||
test.skip('pagination is not supported - mac build', async function (assert) {
|
||||
this.ipcStub.withArgs('checkOS').returns({
|
||||
window.desktop.system.checkOS.resolves({
|
||||
isWindows: false,
|
||||
isMac: true,
|
||||
isLinux: false,
|
||||
@@ -365,7 +365,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
});
|
||||
|
||||
test.skip('pagination is not supported - linux build', async function (assert) {
|
||||
this.ipcStub.withArgs('checkOS').returns({
|
||||
window.desktop.system.checkOS.resolves({
|
||||
isWindows: false,
|
||||
isMac: false,
|
||||
isLinux: true,
|
||||
@@ -388,7 +388,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
});
|
||||
|
||||
test.skip('pagination is not supported - failed to fetch metaData', async function (assert) {
|
||||
this.ipcStub.withArgs('checkOS').returns({
|
||||
window.desktop.system.checkOS.resolves({
|
||||
isWindows: true,
|
||||
isMac: false,
|
||||
isLinux: false,
|
||||
@@ -413,7 +413,7 @@ module('Acceptance | scopes', function (hooks) {
|
||||
test('pagination is not supported - navigate to cluster url page', async function (assert) {
|
||||
await invalidateSession();
|
||||
this.stubCacheDaemonSearch();
|
||||
this.ipcStub.withArgs('checkOS').returns({
|
||||
window.desktop.system.checkOS.resolves({
|
||||
isWindows: true,
|
||||
isMac: false,
|
||||
isLinux: false,
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
import sinon from 'sinon';
|
||||
|
||||
/**
|
||||
* Helper to mock the `window.desktop` API in tests.
|
||||
* Use `setupDesktopContextBridgeApiMock(hooks)` in tests and access via `window.desktop`.
|
||||
*/
|
||||
export class DesktopContextBridgeAPIMock {
|
||||
constructor() {
|
||||
this.sandbox = sinon.createSandbox();
|
||||
this.clusterUrl = null;
|
||||
|
||||
this.cluster = {
|
||||
getClusterUrl: this.sandbox.stub().callsFake(() => {
|
||||
return Promise.resolve(this.clusterUrl);
|
||||
}),
|
||||
setClusterUrl: this.sandbox.stub().callsFake((clusterUrl) => {
|
||||
this.clusterUrl = clusterUrl;
|
||||
return this.clusterUrl;
|
||||
}),
|
||||
resetClusterUrl: this.sandbox.stub().callsFake(() => {
|
||||
this.clusterUrl = null;
|
||||
}),
|
||||
};
|
||||
|
||||
this.system = {
|
||||
openExternal: this.sandbox.stub().resolves(),
|
||||
cliExists: this.sandbox.stub().resolves(true),
|
||||
checkCommand: this.sandbox.stub().resolves(false),
|
||||
checkOS: this.sandbox.stub().resolves({ isMac: true }),
|
||||
getCliVersion: this.sandbox
|
||||
.stub()
|
||||
.resolves({ versionNumber: 'Boundary CLI v0.1.0' }),
|
||||
getDesktopVersion: this.sandbox
|
||||
.stub()
|
||||
.resolves({ desktopVersion: '1.0.0' }),
|
||||
};
|
||||
|
||||
this.session = {
|
||||
connectSession: this.sandbox.stub(),
|
||||
stopSession: this.sandbox.stub().resolves(true),
|
||||
stopAllSessions: this.sandbox.stub().resolves(),
|
||||
hasRunningSessions: this.sandbox.stub().resolves(true),
|
||||
};
|
||||
|
||||
this.windowAction = {
|
||||
hasMacOSChrome: this.sandbox.stub().resolves(false),
|
||||
showWindowActions: this.sandbox.stub().resolves(false),
|
||||
minimizeWindow: this.sandbox.stub().resolves(),
|
||||
toggleFullscreenWindow: this.sandbox.stub().resolves(),
|
||||
closeWindow: this.sandbox.stub().resolves(),
|
||||
focusWindow: this.sandbox.stub().resolves(),
|
||||
};
|
||||
|
||||
this.daemon = {
|
||||
addTokenToDaemons: this.sandbox.stub().resolves(),
|
||||
searchCacheDaemon: this.sandbox.stub().resolves({}),
|
||||
isCacheDaemonRunning: this.sandbox.stub().resolves(false),
|
||||
cacheDaemonStatus: this.sandbox
|
||||
.stub()
|
||||
.resolves({ version: 'Boundary CLI v1.0.0' }),
|
||||
};
|
||||
|
||||
this.clientAgent = {
|
||||
getClientAgentSessions: this.sandbox.stub().resolves([]),
|
||||
isClientAgentRunning: this.sandbox.stub().resolves(false),
|
||||
clientAgentStatus: this.sandbox
|
||||
.stub()
|
||||
.resolves({ version: '0.0.1-dev', status: 'running' }),
|
||||
pauseClientAgent: this.sandbox.stub().resolves(),
|
||||
resumeClientAgent: this.sandbox.stub().resolves(),
|
||||
};
|
||||
|
||||
this.logging = {
|
||||
getLogLevel: this.sandbox.stub().resolves('info'),
|
||||
setLogLevel: this.sandbox.stub().resolves(),
|
||||
getLogPath: this.sandbox
|
||||
.stub()
|
||||
.resolves('~/.config/Boundary/logs/desktop-client.log'),
|
||||
};
|
||||
|
||||
this.rdp = {
|
||||
getRdpClients: this.sandbox.stub().resolves([]),
|
||||
getPreferredRdpClient: this.sandbox.stub().resolves('none'),
|
||||
setPreferredRdpClient: this.sandbox.stub().callsFake((client) => {
|
||||
return Promise.resolve(client);
|
||||
}),
|
||||
launchRdpClient: this.sandbox.stub().resolves(true),
|
||||
};
|
||||
|
||||
this.app = {
|
||||
onAppQuit: this.sandbox.stub().callsFake((callback) => {
|
||||
const listenerCallback = () => callback();
|
||||
window.addEventListener('onAppQuit', listenerCallback);
|
||||
return () => {
|
||||
window.removeEventListener('onAppQuit', listenerCallback);
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function setupDesktopContextBridgeApiMock(hooks) {
|
||||
let mockDesktop;
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
mockDesktop = new DesktopContextBridgeAPIMock();
|
||||
window.desktop = mockDesktop;
|
||||
this.searchCacheDaemonStub = window.desktop.daemon.searchCacheDaemon;
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
mockDesktop.sandbox.restore();
|
||||
delete window.desktop;
|
||||
});
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Service from '@ember/service';
|
||||
|
||||
class MockIPC {
|
||||
clusterUrl = null;
|
||||
|
||||
invoke(method, payload) {
|
||||
return this[method](payload);
|
||||
}
|
||||
|
||||
getClusterUrl() {
|
||||
return this.clusterUrl;
|
||||
}
|
||||
|
||||
setClusterUrl(clusterUrl) {
|
||||
this.clusterUrl = clusterUrl;
|
||||
return this.clusterUrl;
|
||||
}
|
||||
|
||||
resetClusterUrl() {
|
||||
this.clusterUrl = null;
|
||||
}
|
||||
hasMacOSChrome() {}
|
||||
showWindowActions() {}
|
||||
addTokenToDaemons() {}
|
||||
searchCacheDaemon() {}
|
||||
isCacheDaemonRunning() {
|
||||
return false;
|
||||
}
|
||||
isClientAgentRunning() {
|
||||
return false;
|
||||
}
|
||||
cacheDaemonStatus() {
|
||||
return { version: 'Boundary v1.0.0' };
|
||||
}
|
||||
getCliVersion() {
|
||||
return { versionNumber: '1.0.0' };
|
||||
}
|
||||
getDesktopVersion() {
|
||||
return { desktopVersion: '1.0.0' };
|
||||
}
|
||||
getLogLevel() {
|
||||
return 'info';
|
||||
}
|
||||
getLogPath() {
|
||||
return '~/.config/Boundary/logs/desktop-client.log';
|
||||
}
|
||||
setLogLevel() {}
|
||||
clientAgentStatus() {
|
||||
return { version: '0.0.1-dev', status: 'running' };
|
||||
}
|
||||
pauseClientAgent() {}
|
||||
resumeClientAgent() {}
|
||||
hasRunningSessions() {}
|
||||
stopAll() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock window service with support for simplified postMessage and
|
||||
* MessageChannel APIs. This mock implements just enough functionality
|
||||
* to support the IPC mechanism.
|
||||
*
|
||||
* This mock is necessary to guarantee synchronous and deterministic
|
||||
* operation. The window's own API is evented and asynchronous, which
|
||||
* cannot be relied on for testing purposes.
|
||||
*
|
||||
* @todo refactor mock postMessage and MessageChannel for reusability
|
||||
* outside of tests
|
||||
*/
|
||||
export default class extends Service {
|
||||
// =attributes
|
||||
|
||||
mockIPC = new MockIPC();
|
||||
|
||||
MessageChannel = class MessageChannel {
|
||||
constructor() {
|
||||
this.port1.postMessage = this.port1.postMessage.bind(this);
|
||||
this.port2.postMessage = this.port2.postMessage.bind(this);
|
||||
}
|
||||
port1 = {
|
||||
postMessage() {},
|
||||
close() {},
|
||||
};
|
||||
port2 = {
|
||||
postMessage(data) {
|
||||
this.port1.onmessage({ data });
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
};
|
||||
|
||||
// =methods
|
||||
|
||||
postMessage(data, clusterUrl, ports) {
|
||||
if (clusterUrl !== window.location.origin) return;
|
||||
const { method, payload } = data;
|
||||
if (method) {
|
||||
const response = this.mockIPC.invoke(method, payload);
|
||||
ports[0].postMessage(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,24 +6,18 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import WindowMockIPC from '../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module('Unit | Controller | application', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
let controller;
|
||||
let clusterUrl;
|
||||
let mockIPC;
|
||||
let session;
|
||||
|
||||
const setupMockIpc = (test) => {
|
||||
test.owner.register('service:browser/window', WindowMockIPC);
|
||||
mockIPC = test.owner.lookup('service:browser/window').mockIPC;
|
||||
};
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
await authenticateSession({});
|
||||
setupMockIpc(this);
|
||||
controller = this.owner.lookup('controller:application');
|
||||
session = this.owner.lookup('service:session');
|
||||
clusterUrl = this.owner.lookup('service:cluster-url');
|
||||
@@ -52,12 +46,12 @@ module('Unit | Controller | application', function (hooks) {
|
||||
|
||||
assert.true(session.isAuthenticated);
|
||||
assert.strictEqual(clusterUrl.rendererClusterUrl, url);
|
||||
assert.strictEqual(mockIPC.clusterUrl, url);
|
||||
assert.strictEqual(await window.desktop.cluster.getClusterUrl(), url);
|
||||
|
||||
await controller.disconnect();
|
||||
|
||||
assert.false(session.isAuthenticated);
|
||||
assert.notOk(clusterUrl.rendererClusterUrl);
|
||||
assert.notOk(mockIPC.clusterUrl);
|
||||
assert.notOk(await window.desktop.cluster.getClusterUrl());
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import WindowMockIPC from '../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module('Unit | Controller | cluster-url', function (hooks) {
|
||||
setupTest(hooks);
|
||||
@@ -15,18 +15,13 @@ module('Unit | Controller | cluster-url', function (hooks) {
|
||||
|
||||
let controller;
|
||||
let clusterUrl;
|
||||
let mockIPC;
|
||||
|
||||
const setupMockIpc = (test) => {
|
||||
test.owner.register('service:browser/window', WindowMockIPC);
|
||||
mockIPC = test.owner.lookup('service:browser/window').mockIPC;
|
||||
};
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
await authenticateSession({});
|
||||
controller = this.owner.lookup('controller:cluster-url');
|
||||
clusterUrl = this.owner.lookup('service:cluster-url');
|
||||
setupMockIpc(this);
|
||||
});
|
||||
|
||||
test('it exists', function (assert) {
|
||||
@@ -36,11 +31,11 @@ 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(mockIPC.clusterUrl, newClusterUrl);
|
||||
assert.notEqual(window.desktop.clusterUrl, newClusterUrl);
|
||||
|
||||
await controller.setClusterUrl(newClusterUrl);
|
||||
|
||||
assert.strictEqual(clusterUrl.rendererClusterUrl, newClusterUrl);
|
||||
assert.strictEqual(mockIPC.clusterUrl, newClusterUrl);
|
||||
assert.strictEqual(window.desktop.clusterUrl, newClusterUrl);
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ import { setupTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'desktop/tests/helpers/mirage';
|
||||
import { visit, currentURL, settled } from '@ember/test-helpers';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import WindowMockIPC from '../../../../../../helpers/window-mock-ipc';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../../../../../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module(
|
||||
'Unit | Controller | scopes/scope/authenticate/method/index',
|
||||
@@ -16,6 +16,7 @@ module(
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
let controller;
|
||||
let store;
|
||||
@@ -57,7 +58,6 @@ module(
|
||||
|
||||
urls.targets = '/scopes/global/projects/targets';
|
||||
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
setDefaultClusterUrl(this);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import { setupMirage } from 'desktop/tests/helpers/mirage';
|
||||
import { setupDesktopContextBridgeApiMock } from 'desktop/tests/helpers/desktop-context-bridge-api-mock';
|
||||
import setupStubs from 'api/test-support/handlers/cache-daemon-search';
|
||||
import { visit } from '@ember/test-helpers';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
@@ -22,6 +23,7 @@ module(
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
|
||||
@@ -75,7 +77,7 @@ module(
|
||||
urls.projectScope = `/scopes/${instances.scopes.org.id}/projects`;
|
||||
urls.sessions = `${urls.projectScope}/sessions`;
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'sessions', 'targets');
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
import { setupMirage } from 'desktop/tests/helpers/mirage';
|
||||
import { setupDesktopContextBridgeApiMock } from 'desktop/tests/helpers/desktop-context-bridge-api-mock';
|
||||
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';
|
||||
@@ -26,6 +27,7 @@ module(
|
||||
function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupMirage(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
setupStubs(hooks);
|
||||
setupIntl(hooks, 'en-us');
|
||||
|
||||
@@ -79,7 +81,7 @@ module(
|
||||
urls.projectScope = `/scopes/${instances.scopes.org.id}/projects`;
|
||||
urls.targets = `${urls.projectScope}/targets`;
|
||||
|
||||
this.ipcStub.withArgs('isCacheDaemonRunning').returns(true);
|
||||
window.desktop.daemon.isCacheDaemonRunning.resolves(true);
|
||||
this.stubCacheDaemonSearch('sessions', 'targets', 'aliases', 'sessions');
|
||||
await authenticateSession({ account_id: instances.account.id });
|
||||
});
|
||||
@@ -237,8 +239,7 @@ module(
|
||||
address: 'a_123',
|
||||
port: 'p_123',
|
||||
};
|
||||
this.ipcStub.withArgs('cliExists').returns(true);
|
||||
this.ipcStub.withArgs('connect').returns(attrs);
|
||||
window.desktop.session.connectSession.resolves(attrs);
|
||||
await visit(urls.targets);
|
||||
const target = await store.findRecord('target', instances.target.id);
|
||||
const session = await store.findRecord('session', instances.session.id);
|
||||
|
||||
@@ -5,25 +5,26 @@
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import sinon from 'sinon';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module('Unit | Service | clusterUrl', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
let service, ipcService;
|
||||
let service;
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
service = this.owner.lookup('service:clusterUrl');
|
||||
ipcService = this.owner.lookup('service:ipc');
|
||||
});
|
||||
|
||||
test('resets clusterUrl on error', async function (assert) {
|
||||
assert.expect(4);
|
||||
const ipcServiceStubbed = sinon.stub(ipcService, 'invoke');
|
||||
|
||||
await service.setClusterUrl(window.location.origin);
|
||||
|
||||
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
|
||||
assert.strictEqual(service.adapter.host, window.location.origin);
|
||||
ipcServiceStubbed.withArgs('setClusterUrl').rejects();
|
||||
window.desktop.cluster.setClusterUrl.rejects();
|
||||
service.setClusterUrl('invalid-origin').catch(() => {
|
||||
assert.notOk(service.rendererClusterUrl);
|
||||
assert.strictEqual(service.adapter.host, window.location.origin);
|
||||
@@ -32,7 +33,6 @@ module('Unit | Service | clusterUrl', function (hooks) {
|
||||
|
||||
test('drops trailing slashes from clusterUrl on setClusterUrl', async function (assert) {
|
||||
assert.expect(4);
|
||||
sinon.stub(ipcService, 'invoke');
|
||||
await service.setClusterUrl(`${window.location.origin}/`);
|
||||
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
|
||||
assert.strictEqual(service.adapter.host, window.location.origin);
|
||||
@@ -43,7 +43,6 @@ module('Unit | Service | clusterUrl', function (hooks) {
|
||||
|
||||
test('trim spaces from clusterUrl on setClusterUrl', async function (assert) {
|
||||
assert.expect(4);
|
||||
sinon.stub(ipcService, 'invoke');
|
||||
await service.setClusterUrl(` ${window.location.origin}/ `);
|
||||
assert.strictEqual(service.rendererClusterUrl, window.location.origin);
|
||||
assert.strictEqual(service.adapter.host, window.location.origin);
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* Copyright IBM Corp. 2021, 2026
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { IPCRequest } from 'desktop/services/ipc';
|
||||
import sinon from 'sinon';
|
||||
|
||||
module('Unit | Service | ipc', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
class MessageChannelMock {
|
||||
constructor() {
|
||||
this.port1.postMessage = this.port1.postMessage.bind(this);
|
||||
this.port2.postMessage = this.port2.postMessage.bind(this);
|
||||
}
|
||||
port1 = {
|
||||
postMessage() {},
|
||||
close() {},
|
||||
};
|
||||
port2 = {
|
||||
postMessage(data) {
|
||||
this.port1.onmessage({ data });
|
||||
},
|
||||
close() {},
|
||||
};
|
||||
}
|
||||
|
||||
hooks.beforeEach(() => {
|
||||
// IPC requests talk to the main process indirectly via window.postMessage
|
||||
// proxy layer (mediated by preload.js).
|
||||
// But we don't want to send these for real in tests.
|
||||
sinon.stub(window, 'postMessage');
|
||||
});
|
||||
|
||||
hooks.afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
test('it creates new IPCRequest objects', function (assert) {
|
||||
assert.expect(1);
|
||||
const service = this.owner.lookup('service:ipc');
|
||||
const request = service.invoke('hello', 'world', 'http://localhost:4200');
|
||||
assert.strictEqual(request.constructor, IPCRequest);
|
||||
});
|
||||
|
||||
test('IPCRequest objects post messages on their window objects', function (assert) {
|
||||
assert.expect(2);
|
||||
const myWindow = {
|
||||
postMessage(request) {
|
||||
assert.strictEqual(request.method, 'hello');
|
||||
assert.strictEqual(request.payload, 'world');
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
new IPCRequest('hello', 'world', myWindow);
|
||||
});
|
||||
|
||||
test('IPCRequest objects resolve to the value posted via a response message port', async function (assert) {
|
||||
assert.expect(1);
|
||||
const myWindow = {
|
||||
postMessage(request, origin, [port]) {
|
||||
// Post back a message on the response port
|
||||
port.postMessage({
|
||||
foo: 'bar',
|
||||
test: 42,
|
||||
});
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
const request = new IPCRequest('hello', 'world', myWindow);
|
||||
const response = await request;
|
||||
assert.deepEqual(response, {
|
||||
foo: 'bar',
|
||||
test: 42,
|
||||
});
|
||||
});
|
||||
|
||||
test('IPCRequests reject if receiving an Error instance, where the rejection value is the parsed JSON Error message', async function (assert) {
|
||||
assert.expect(1);
|
||||
const stringifiedPayload = JSON.stringify({
|
||||
foo: 'bar',
|
||||
test: 42,
|
||||
});
|
||||
const myWindow = {
|
||||
postMessage(request, origin, [port]) {
|
||||
// Post back an error on the response port
|
||||
port.postMessage(new Error(stringifiedPayload));
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
try {
|
||||
await new IPCRequest('hello', 'world', myWindow);
|
||||
} catch (e) {
|
||||
assert.deepEqual(e, {
|
||||
foo: 'bar',
|
||||
test: 42,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -13,15 +13,16 @@ import {
|
||||
RDP_CLIENT_WINDOWS_APP_LINK,
|
||||
RDP_CLIENT_MSTSC_LINK,
|
||||
} from 'desktop/services/rdp';
|
||||
import { setupDesktopContextBridgeApiMock } from '../../helpers/desktop-context-bridge-api-mock';
|
||||
|
||||
module('Unit | Service | rdp', function (hooks) {
|
||||
setupTest(hooks);
|
||||
setupDesktopContextBridgeApiMock(hooks);
|
||||
|
||||
let service, ipcService;
|
||||
let service;
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
service = this.owner.lookup('service:rdp');
|
||||
ipcService = this.owner.lookup('service:ipc');
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
@@ -29,9 +30,7 @@ module('Unit | Service | rdp', function (hooks) {
|
||||
});
|
||||
|
||||
test('getRdpClients sets to fallback value on error', async function (assert) {
|
||||
const ipcStub = sinon.stub(ipcService, 'invoke');
|
||||
ipcStub.withArgs('getRdpClients').rejects();
|
||||
ipcStub.withArgs('checkOS').resolves({ isMac: true });
|
||||
window.desktop.rdp.getRdpClients.rejects();
|
||||
await service.getRdpClients();
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -42,10 +41,7 @@ module('Unit | Service | rdp', function (hooks) {
|
||||
});
|
||||
|
||||
test('getPreferredRdpClient sets to fallback value on error', async function (assert) {
|
||||
sinon
|
||||
.stub(ipcService, 'invoke')
|
||||
.withArgs('getPreferredRdpClient')
|
||||
.rejects();
|
||||
window.desktop.rdp.getPreferredRdpClient.rejects();
|
||||
await service.getPreferredRdpClient();
|
||||
|
||||
assert.strictEqual(
|
||||
@@ -56,10 +52,7 @@ module('Unit | Service | rdp', function (hooks) {
|
||||
});
|
||||
|
||||
test('setPreferredRdpClient sets to fallback value on error', async function (assert) {
|
||||
sinon
|
||||
.stub(ipcService, 'invoke')
|
||||
.withArgs('setPreferredRdpClient', RDP_CLIENT_MSTSC)
|
||||
.rejects();
|
||||
window.desktop.rdp.setPreferredRdpClient.rejects();
|
||||
await service.setPreferredRdpClient(RDP_CLIENT_MSTSC);
|
||||
|
||||
assert.strictEqual(
|
||||
@@ -70,10 +63,7 @@ module('Unit | Service | rdp', function (hooks) {
|
||||
});
|
||||
|
||||
test('sets recommendedRdpClient correctly based on OS', async function (assert) {
|
||||
sinon
|
||||
.stub(ipcService, 'invoke')
|
||||
.withArgs('checkOS')
|
||||
.resolves({ isWindows: true });
|
||||
window.desktop.system.checkOS.resolves({ isWindows: true });
|
||||
await service.getRecommendedRdpClient();
|
||||
|
||||
assert.deepEqual(
|
||||
@@ -85,7 +75,7 @@ module('Unit | Service | rdp', function (hooks) {
|
||||
'recommendedRdpClient is set correctly for windows',
|
||||
);
|
||||
|
||||
ipcService.invoke.withArgs('checkOS').resolves({ isMac: true });
|
||||
window.desktop.system.checkOS.resolves({ isMac: true });
|
||||
service.recommendedRdpClient = null;
|
||||
await service.getRecommendedRdpClient();
|
||||
|
||||
|
||||
Reference in new issue
Block a user