Merge pull request #548 from hashicorp/fix-flaky-origin-test
fix: 🐛 migrate IPC to service-based window for reliable testing
This commit is contained in:
4 files changed
+96
-45
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import Service from '@ember/service';
|
||||
import { getOwner } from '@ember/application';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { Promise } from 'rsvp';
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ export class IPCRequest {
|
||||
this.method = method;
|
||||
this.payload = payload;
|
||||
this.origin = window?.location?.origin;
|
||||
this.#channel = new MessageChannel();
|
||||
this.#channel = new window.MessageChannel();
|
||||
this.#promise = new Promise((resolve, reject) => {
|
||||
this.#channel.port1.onmessage = (event) => {
|
||||
this.close();
|
||||
@@ -124,18 +124,9 @@ export class IPCRequest {
|
||||
* }
|
||||
*/
|
||||
export default class IpcService extends Service {
|
||||
// =attributes
|
||||
// =services
|
||||
|
||||
/**
|
||||
* Looks up the window object indirectly.
|
||||
* @type {Window}
|
||||
*/
|
||||
get window() {
|
||||
// The Ember way of accessing globals...
|
||||
const document = getOwner(this).lookup('service:-document').documentElement;
|
||||
// defaultView === window, but without using globals directly
|
||||
return document.parentNode.defaultView;
|
||||
}
|
||||
@service('browser/window') window;
|
||||
|
||||
// =methods
|
||||
|
||||
|
||||
@@ -19,15 +19,17 @@ import {
|
||||
// authenticateSession,
|
||||
invalidateSession,
|
||||
} from 'ember-simple-auth/test-support';
|
||||
import { setupBrowserFakes } from 'ember-browser-services/test-support';
|
||||
import WindowMockIPC from '../helpers/window-mock-ipc';
|
||||
import config from '../../config/environment';
|
||||
|
||||
module('Acceptance | origin', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupBrowserFakes(hooks, { window: true });
|
||||
setupMirage(hooks);
|
||||
|
||||
const currentOrigin = window.location.origin;
|
||||
let mockIPC;
|
||||
let messageHandler;
|
||||
|
||||
const instances = {
|
||||
scopes: {
|
||||
@@ -104,40 +106,12 @@ module('Acceptance | origin', function (hooks) {
|
||||
urls.projects = `${urls.scopes.global}/projects`;
|
||||
urls.targets = `${urls.projects}/targets`;
|
||||
|
||||
class MockIPC {
|
||||
origin = null;
|
||||
|
||||
invoke(method, payload) {
|
||||
return this[method](payload);
|
||||
}
|
||||
|
||||
getOrigin() {
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
setOrigin(origin) {
|
||||
this.origin = origin;
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
resetOrigin() {}
|
||||
}
|
||||
|
||||
mockIPC = new MockIPC();
|
||||
messageHandler = async function (event) {
|
||||
if (event.origin !== currentOrigin) return;
|
||||
const { method, payload } = event.data;
|
||||
if (method) {
|
||||
const response = await mockIPC.invoke(method, payload);
|
||||
event.ports[0].postMessage(response);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageHandler);
|
||||
// Mock the postMessage interface used by IPC.
|
||||
this.owner.register('service:browser/window', WindowMockIPC);
|
||||
mockIPC = this.owner.lookup('service:browser/window').mockIPC;
|
||||
});
|
||||
|
||||
hooks.afterEach(function () {
|
||||
window.removeEventListener('message', messageHandler);
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import Service from '@ember/service';
|
||||
|
||||
class MockIPC {
|
||||
origin = null;
|
||||
|
||||
invoke(method, payload) {
|
||||
return this[method](payload);
|
||||
}
|
||||
|
||||
getOrigin() {
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
setOrigin(origin) {
|
||||
this.origin = origin;
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
resetOrigin() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 asychronous, 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, origin, ports) {
|
||||
if (origin !== window.location.origin) return;
|
||||
const { method, payload } = data;
|
||||
if (method) {
|
||||
const response = this.mockIPC.invoke(method, payload);
|
||||
ports[0].postMessage(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,23 @@ 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).
|
||||
@@ -31,6 +48,7 @@ module('Unit | Service | ipc', function (hooks) {
|
||||
assert.equal(request.method, 'hello');
|
||||
assert.equal(request.payload, 'world');
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
new IPCRequest('hello', 'world', myWindow);
|
||||
});
|
||||
@@ -45,6 +63,7 @@ module('Unit | Service | ipc', function (hooks) {
|
||||
test: 42,
|
||||
});
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
const request = new IPCRequest('hello', 'world', myWindow);
|
||||
const response = await request;
|
||||
@@ -65,6 +84,7 @@ module('Unit | Service | ipc', function (hooks) {
|
||||
// Post back an error on the response port
|
||||
port.postMessage(new Error(stringifiedPayload));
|
||||
},
|
||||
MessageChannel: MessageChannelMock,
|
||||
};
|
||||
try {
|
||||
await new IPCRequest('hello', 'world', myWindow);
|
||||
|
||||
Reference in new issue
Block a user