mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
223 lines
5.4 KiB
JavaScript
223 lines
5.4 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const Module = require('node:module');
|
|
const { EventEmitter } = require('node:events');
|
|
|
|
require('ts-node/register/transpile-only');
|
|
|
|
const originalModuleLoad = Module._load;
|
|
const pluginNodeExecutorModulePath = path.resolve(__dirname, 'plugin-node-executor.ts');
|
|
|
|
let ipcHandlers;
|
|
let dialogCalls;
|
|
let nextDialogResult;
|
|
let nextDialogPromise;
|
|
|
|
class FakeWebContents extends EventEmitter {
|
|
constructor(id, url = 'app://index.html') {
|
|
super();
|
|
this.id = id;
|
|
this._url = url;
|
|
this._isDestroyed = false;
|
|
this.window = { id: `window-${id}` };
|
|
}
|
|
|
|
getURL() {
|
|
return this._url;
|
|
}
|
|
|
|
isDestroyed() {
|
|
return this._isDestroyed;
|
|
}
|
|
|
|
navigate(url) {
|
|
this._url = url;
|
|
this.emit('will-navigate', {}, url);
|
|
this.emit('did-navigate', {}, url);
|
|
}
|
|
|
|
startNavigation(url, isInPlace = false, isMainFrame = true) {
|
|
this._url = url;
|
|
this.emit('did-start-navigation', {}, url, isInPlace, isMainFrame);
|
|
}
|
|
|
|
destroy() {
|
|
this._isDestroyed = true;
|
|
this.emit('destroyed');
|
|
}
|
|
}
|
|
|
|
const resetModule = () => {
|
|
for (const key of Object.keys(require.cache)) {
|
|
if (
|
|
key === pluginNodeExecutorModulePath ||
|
|
key.endsWith('/electron/plugin-node-executor.ts') ||
|
|
key.endsWith('/electron/plugin-node-executor.js')
|
|
) {
|
|
delete require.cache[key];
|
|
}
|
|
}
|
|
};
|
|
|
|
const installMocks = () => {
|
|
Module._load = function patchedLoad(request, parent, isMain) {
|
|
if (request === 'electron') {
|
|
return {
|
|
app: {
|
|
isPackaged: false,
|
|
},
|
|
BrowserWindow: {
|
|
fromWebContents: (webContents) => webContents.window,
|
|
},
|
|
dialog: {
|
|
showMessageBox: async (...args) => {
|
|
dialogCalls.push(args);
|
|
if (nextDialogPromise) {
|
|
return nextDialogPromise;
|
|
}
|
|
return nextDialogResult;
|
|
},
|
|
},
|
|
ipcMain: {
|
|
handle: (eventName, handler) => {
|
|
ipcHandlers.set(eventName, handler);
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return originalModuleLoad.call(this, request, parent, isMain);
|
|
};
|
|
};
|
|
|
|
const loadModule = () => {
|
|
resetModule();
|
|
return require(pluginNodeExecutorModulePath);
|
|
};
|
|
|
|
const callIpc = (channel, sender, ...args) => {
|
|
const handler = ipcHandlers.get(channel);
|
|
assert.equal(typeof handler, 'function');
|
|
return handler({ sender }, ...args);
|
|
};
|
|
|
|
test.beforeEach(() => {
|
|
ipcHandlers = new Map();
|
|
dialogCalls = [];
|
|
nextDialogResult = { response: 0 };
|
|
nextDialogPromise = undefined;
|
|
installMocks();
|
|
});
|
|
|
|
test.afterEach(() => {
|
|
Module._load = originalModuleLoad;
|
|
resetModule();
|
|
});
|
|
|
|
test('issues a main-owned token and reuses it for the same webContents', async () => {
|
|
loadModule();
|
|
const webContents = new FakeWebContents(1);
|
|
|
|
const firstGrant = await callIpc(
|
|
'PLUGIN_REQUEST_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
);
|
|
const secondGrant = await callIpc(
|
|
'PLUGIN_REQUEST_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
);
|
|
|
|
assert.equal(typeof firstGrant.token, 'string');
|
|
assert.equal(secondGrant.token, firstGrant.token);
|
|
assert.equal(dialogCalls.length, 1);
|
|
});
|
|
|
|
test('requires the issuing webContents and token for script execution', async () => {
|
|
loadModule();
|
|
const webContents = new FakeWebContents(2);
|
|
const otherWebContents = new FakeWebContents(3);
|
|
|
|
const grant = await callIpc(
|
|
'PLUGIN_REQUEST_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
);
|
|
const result = await callIpc(
|
|
'PLUGIN_EXEC_NODE_SCRIPT',
|
|
webContents,
|
|
'sync-md',
|
|
grant.token,
|
|
{ script: 'return args[0] + 1;', args: [1] },
|
|
);
|
|
|
|
assert.equal(result.success, true);
|
|
assert.equal(result.result, 2);
|
|
assert.equal(typeof result.executionTime, 'number');
|
|
await assert.rejects(
|
|
() =>
|
|
callIpc('PLUGIN_EXEC_NODE_SCRIPT', otherWebContents, 'sync-md', grant.token, {
|
|
script: 'return true;',
|
|
}),
|
|
/not authorized/,
|
|
);
|
|
});
|
|
|
|
test('does not mint a token when the sender navigates while consent is pending', async () => {
|
|
loadModule();
|
|
const webContents = new FakeWebContents(5);
|
|
let resolveDialog;
|
|
nextDialogPromise = new Promise((resolve) => {
|
|
resolveDialog = resolve;
|
|
});
|
|
|
|
const grantPromise = callIpc(
|
|
'PLUGIN_REQUEST_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
);
|
|
assert.equal(dialogCalls.length, 1);
|
|
|
|
webContents.navigate('http://localhost:4200/after-navigation');
|
|
resolveDialog({ response: 0 });
|
|
|
|
await assert.doesNotReject(grantPromise);
|
|
assert.equal(await grantPromise, null);
|
|
});
|
|
|
|
test('revoke requires the issuing webContents and token', async () => {
|
|
loadModule();
|
|
const webContents = new FakeWebContents(6);
|
|
const otherWebContents = new FakeWebContents(7);
|
|
|
|
const grant = await callIpc(
|
|
'PLUGIN_REQUEST_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
);
|
|
await callIpc(
|
|
'PLUGIN_REVOKE_NODE_EXECUTION_GRANT',
|
|
otherWebContents,
|
|
'sync-md',
|
|
grant.token,
|
|
);
|
|
await callIpc('PLUGIN_EXEC_NODE_SCRIPT', webContents, 'sync-md', grant.token, {
|
|
script: 'return true;',
|
|
});
|
|
|
|
await callIpc(
|
|
'PLUGIN_REVOKE_NODE_EXECUTION_GRANT',
|
|
webContents,
|
|
'sync-md',
|
|
grant.token,
|
|
);
|
|
await assert.rejects(
|
|
() =>
|
|
callIpc('PLUGIN_EXEC_NODE_SCRIPT', webContents, 'sync-md', grant.token, {
|
|
script: 'return true;',
|
|
}),
|
|
/not authorized/,
|
|
);
|
|
});
|