Add unit tests for ServerAddOn

Reviewed By: mweststrate

Differential Revision: D34303780

fbshipit-source-id: 03a4570a6e891d979b87caca14f51068d74df877
This commit is contained in:
Andrey Goncharov
2022-02-28 03:50:34 -08:00
committed by Facebook GitHub Bot
parent 81d0057a8d
commit d96cf8127f
3 changed files with 205 additions and 33 deletions

View File

@@ -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',

View File

@@ -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;
});
});
});

View 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;
};