Add unit tests for ServerAddOn
Reviewed By: mweststrate Differential Revision: D34303780 fbshipit-source-id: 03a4570a6e891d979b87caca14f51068d74df877
This commit is contained in:
committed by
Facebook GitHub Bot
parent
81d0057a8d
commit
d96cf8127f
@@ -12,42 +12,11 @@ import {assertNotNull} from '../comms/Utilities';
|
||||
import {
|
||||
FlipperServerForServerAddOn,
|
||||
ServerAddOnCleanup,
|
||||
ServerAddOn as ServerAddOnFn,
|
||||
ServerAddOnStartDetails,
|
||||
} from 'flipper-common';
|
||||
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection';
|
||||
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
|
||||
// @ts-ignore
|
||||
import defaultPlugins from '../defaultPlugins';
|
||||
|
||||
interface ServerAddOnModule {
|
||||
default: ServerAddOnFn;
|
||||
}
|
||||
|
||||
const loadPlugin = (
|
||||
pluginName: string,
|
||||
details: ServerAddOnStartDetails,
|
||||
): ServerAddOnModule => {
|
||||
console.debug('loadPlugin', pluginName, details);
|
||||
|
||||
if (details.isBundled) {
|
||||
const bundledPlugin = defaultPlugins[pluginName];
|
||||
assertNotNull(
|
||||
bundledPlugin,
|
||||
`loadPlugin (isBundled = true) -> plugin ${pluginName} not found.`,
|
||||
);
|
||||
return bundledPlugin;
|
||||
}
|
||||
|
||||
assertNotNull(
|
||||
details.path,
|
||||
`loadPlugin (isBundled = false) -> server add-on path is empty plugin ${pluginName}.`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
const serverAddOnModule = eval(`require("${details.path}")`);
|
||||
return serverAddOnModule;
|
||||
};
|
||||
import {loadServerAddOn} from './loadServerAddOn';
|
||||
|
||||
export class ServerAddOn {
|
||||
private owners: Set<string>;
|
||||
@@ -69,7 +38,7 @@ export class ServerAddOn {
|
||||
): Promise<ServerAddOn> {
|
||||
console.info('ServerAddOn.start', pluginName, details);
|
||||
|
||||
const {default: serverAddOn} = loadPlugin(pluginName, details);
|
||||
const {default: serverAddOn} = loadServerAddOn(pluginName, details);
|
||||
assertNotNull(serverAddOn);
|
||||
assert(
|
||||
typeof serverAddOn === 'function',
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {ServerAddOnStartDetails} from 'flipper-common';
|
||||
import {loadServerAddOn} from '../loadServerAddOn';
|
||||
import {ServerAddOn} from '../ServerAddOn';
|
||||
import {ServerAddOnModuleToDesktopConnection} from '../ServerAddOnModuleToDesktopConnection';
|
||||
|
||||
jest.mock('../loadServerAddOn');
|
||||
const loadServerAddOnMock = loadServerAddOn as jest.Mock;
|
||||
|
||||
const createControlledPromise = () => {
|
||||
let resolve!: () => void;
|
||||
let reject!: (reason: unknown) => void;
|
||||
const promise = new Promise<void>((resolveP, rejectP) => {
|
||||
resolve = resolveP;
|
||||
reject = rejectP;
|
||||
});
|
||||
return {
|
||||
promise,
|
||||
resolve,
|
||||
reject,
|
||||
};
|
||||
};
|
||||
|
||||
describe('ServerAddOn', () => {
|
||||
const pluginName = 'lightSaber';
|
||||
const initialOwner = 'yoda';
|
||||
const detailsBundled: ServerAddOnStartDetails = {
|
||||
isBundled: true,
|
||||
};
|
||||
const detailsInstalled: ServerAddOnStartDetails = {
|
||||
path: '/dagobar/',
|
||||
};
|
||||
|
||||
const startServerAddOn = async (details: ServerAddOnStartDetails) => {
|
||||
const addOnCleanupMock = jest.fn();
|
||||
const addOnMock = jest.fn().mockImplementation(() => addOnCleanupMock);
|
||||
loadServerAddOnMock.mockImplementation(() => ({
|
||||
default: addOnMock,
|
||||
}));
|
||||
|
||||
const flipperServer = {
|
||||
connect: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
exec: jest.fn(),
|
||||
close: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(0);
|
||||
expect(addOnMock).toBeCalledTimes(0);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
|
||||
const serverAddOn = await ServerAddOn.start(
|
||||
pluginName,
|
||||
details,
|
||||
initialOwner,
|
||||
flipperServer,
|
||||
);
|
||||
|
||||
expect(loadServerAddOnMock).toBeCalledTimes(1);
|
||||
expect(loadServerAddOnMock).toBeCalledWith(pluginName, details);
|
||||
expect(addOnMock).toBeCalledTimes(1);
|
||||
expect(addOnMock).toBeCalledWith(
|
||||
expect.any(ServerAddOnModuleToDesktopConnection),
|
||||
{
|
||||
flipperServer,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
addOnCleanupMock,
|
||||
addOnMock,
|
||||
flipperServer,
|
||||
serverAddOn,
|
||||
};
|
||||
};
|
||||
|
||||
describe.each([
|
||||
['bundled', detailsBundled],
|
||||
['installed', detailsInstalled],
|
||||
])('%s', (_name, details) => {
|
||||
test('stops the add-on when the initial owner is removed', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes = serverAddOn.removeOwner(initialOwner);
|
||||
|
||||
expect(removeOwnerRes).toBeInstanceOf(Promise);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
controlledP.resolve();
|
||||
await removeOwnerRes;
|
||||
});
|
||||
|
||||
test('adds a new owner, stops the add-on when all owners are removed', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const newOwner = 'luke';
|
||||
serverAddOn.addOwner(newOwner);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes1 = serverAddOn.removeOwner(initialOwner);
|
||||
expect(removeOwnerRes1).toBeUndefined();
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
|
||||
const removeOwnerRes2 = serverAddOn.removeOwner(newOwner);
|
||||
|
||||
expect(removeOwnerRes2).toBeInstanceOf(Promise);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
controlledP.resolve();
|
||||
await removeOwnerRes2;
|
||||
});
|
||||
|
||||
test('does nothing when removeOwner is called with a missing owner', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const missingOwner = 'luke';
|
||||
|
||||
const removeOwnerRes = serverAddOn.removeOwner(missingOwner);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(0);
|
||||
expect(removeOwnerRes).toBeUndefined();
|
||||
});
|
||||
|
||||
test('calls stop only once when removeOwner is called twice with the same owner', async () => {
|
||||
const {serverAddOn, addOnCleanupMock} = await startServerAddOn(details);
|
||||
|
||||
const controlledP = createControlledPromise();
|
||||
addOnCleanupMock.mockImplementation(() => controlledP.promise);
|
||||
|
||||
const removeOwnerRes1 = serverAddOn.removeOwner(initialOwner);
|
||||
|
||||
expect(removeOwnerRes1).toBeInstanceOf(Promise);
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
const removeOwnerRes2 = serverAddOn.removeOwner(initialOwner);
|
||||
expect(removeOwnerRes2).toBeUndefined();
|
||||
expect(addOnCleanupMock).toBeCalledTimes(1);
|
||||
|
||||
controlledP.resolve();
|
||||
await removeOwnerRes1;
|
||||
});
|
||||
});
|
||||
});
|
||||
46
desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx
Normal file
46
desktop/flipper-server-core/src/plugins/loadServerAddOn.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {
|
||||
ServerAddOn as ServerAddOnFn,
|
||||
ServerAddOnStartDetails,
|
||||
} from 'flipper-common';
|
||||
import {assertNotNull} from '../comms/Utilities';
|
||||
// The file is generated automatically by "prepareDefaultPlugins" in "scripts"
|
||||
// @ts-ignore
|
||||
import defaultPlugins from '../defaultPlugins';
|
||||
|
||||
interface ServerAddOnModule {
|
||||
default: ServerAddOnFn;
|
||||
}
|
||||
|
||||
export const loadServerAddOn = (
|
||||
pluginName: string,
|
||||
details: ServerAddOnStartDetails,
|
||||
): ServerAddOnModule => {
|
||||
console.debug('loadPlugin', pluginName, details);
|
||||
|
||||
if (details.isBundled) {
|
||||
const bundledPlugin = defaultPlugins[pluginName];
|
||||
assertNotNull(
|
||||
bundledPlugin,
|
||||
`loadPlugin (isBundled = true) -> plugin ${pluginName} not found.`,
|
||||
);
|
||||
return bundledPlugin;
|
||||
}
|
||||
|
||||
assertNotNull(
|
||||
details.path,
|
||||
`loadPlugin (isBundled = false) -> server add-on path is empty plugin ${pluginName}.`,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
const serverAddOnModule = eval(`require("${details.path}")`);
|
||||
return serverAddOnModule;
|
||||
};
|
||||
Reference in New Issue
Block a user