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:
Susmitha Girumala authored and GitHub committed 2021-05-10 11:07:32 -04:00
commit 3e2ed2dc27
4 files changed
+96 -45

No files matched your search

+4 -13
View File
@@ -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
+6 -32
View File
@@ -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);