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 {
|
import {
|
||||||
FlipperServerForServerAddOn,
|
FlipperServerForServerAddOn,
|
||||||
ServerAddOnCleanup,
|
ServerAddOnCleanup,
|
||||||
ServerAddOn as ServerAddOnFn,
|
|
||||||
ServerAddOnStartDetails,
|
ServerAddOnStartDetails,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection';
|
import {ServerAddOnDesktopToModuleConnection} from './ServerAddOnDesktopToModuleConnection';
|
||||||
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
|
import {ServerAddOnModuleToDesktopConnection} from './ServerAddOnModuleToDesktopConnection';
|
||||||
// @ts-ignore
|
import {loadServerAddOn} from './loadServerAddOn';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ServerAddOn {
|
export class ServerAddOn {
|
||||||
private owners: Set<string>;
|
private owners: Set<string>;
|
||||||
@@ -69,7 +38,7 @@ export class ServerAddOn {
|
|||||||
): Promise<ServerAddOn> {
|
): Promise<ServerAddOn> {
|
||||||
console.info('ServerAddOn.start', pluginName, details);
|
console.info('ServerAddOn.start', pluginName, details);
|
||||||
|
|
||||||
const {default: serverAddOn} = loadPlugin(pluginName, details);
|
const {default: serverAddOn} = loadServerAddOn(pluginName, details);
|
||||||
assertNotNull(serverAddOn);
|
assertNotNull(serverAddOn);
|
||||||
assert(
|
assert(
|
||||||
typeof serverAddOn === 'function',
|
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