Skip to content

Commit

Permalink
Increase execution environments coverage (MetaMask#482)
Browse files Browse the repository at this point in the history
* added test for isConstructor, added rpcMethods test file

* added tests for rpcMethods, sortParams

* updated sortParams test description, added comments, created BaseSnapExecutor test file

* updated tests

* created mock for BaseSnapExecutor

* created executor mock w/ pass through stream

* added more tests

* updated tests

* finished BaseSnapExecutor tests

* added lockdown test files

* some updates

* finished coverage

* reverted some changes

* lint fix

* fixed tests

* update relevant test

* removed lockdown test

* update jest config

* Update snapshot

* updated test

Co-authored-by: Frederik Bolding <[email protected]>
  • Loading branch information
hmalik88 and FrederikBolding authored Jun 29, 2022
1 parent 0f39c01 commit 9163ed3
Show file tree
Hide file tree
Showing 11 changed files with 460 additions and 181 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ describe('NodeExecutionService', () => {
expect(await unhandledErrorPromise).toStrictEqual({
code: -32603,
data: { snapName: 'TestSnap' },
message: 'Execution Environment Error',
message: 'Unhandled promise rejection in snap.',
});

await service.terminateAllSnaps();
Expand Down
8 changes: 4 additions & 4 deletions packages/execution-environments/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ module.exports = {
coverageReporters: ['clover', 'json', 'lcov', 'text', 'json-summary'],
coverageThreshold: {
global: {
branches: 72.04,
functions: 80.39,
lines: 75.46,
statements: 75.8,
branches: 83.16,
functions: 86.41,
lines: 79.01,
statements: 79.29,
},
},
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
Expand Down
24 changes: 17 additions & 7 deletions packages/execution-environments/src/common/BaseSnapExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './commands';
import { removeEventListener, addEventListener } from './globalEvents';
import { sortParamKeys } from './sortParams';
import { constructError } from './utils';

type OnRpcRequestHandler = (args: {
origin: string;
Expand Down Expand Up @@ -77,17 +78,25 @@ export class BaseSnapExecutor {
);
}

private errorHandler(error: Error, data = {}) {
private errorHandler(
reason: string,
originalError: unknown,
data: Record<string, unknown>,
) {
const error = new Error(reason);

const _originalError: Error | undefined = constructError(originalError);

const serializedError = serializeError(error, {
fallbackError,
shouldIncludeStack: true,
shouldIncludeStack: false,
});

this.notify({
error: {
...serializedError,
data: {
...data,
stack: serializedError.stack,
originalError: _originalError,
},
},
});
Expand All @@ -97,7 +106,6 @@ export class BaseSnapExecutor {
if (!isJsonRpcRequest(message)) {
throw new Error('Command stream received a non Json Rpc Request');
}

const { id, method, params } = message;

if (id === undefined) {
Expand Down Expand Up @@ -181,11 +189,13 @@ export class BaseSnapExecutor {
}

this.snapErrorHandler = (error: ErrorEvent) => {
this.errorHandler(error.error, { snapName });
this.errorHandler('Uncaught error in snap.', error.error, { snapName });
};

this.snapPromiseErrorHandler = (error: PromiseRejectionEvent) => {
this.errorHandler(error.reason, { snapName });
this.errorHandler('Unhandled promise rejection in snap.', error.reason, {
snapName,
});
};

const wallet = this.createSnapProvider();
Expand Down
169 changes: 169 additions & 0 deletions packages/execution-environments/src/common/commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
CommandMethodsMapping,
getCommandMethodImplementations,
} from './commands';

describe('getCommandMethodImplementations', () => {
it('will return an object with ping, executeSnap, snapRpc and terminate methods', () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
['ping', 'executeSnap', 'snapRpc', 'terminate'].forEach((method) => {
expect(
typeof methodsObj[method as keyof CommandMethodsMapping],
).toStrictEqual('function');
});
expect(Object.keys(methodsObj)).toHaveLength(4);
});

it("the ping method will return 'OK'", async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
expect(await methodsObj.ping()).toStrictEqual('OK');
});

it("the terminate method will 'OK'", async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
expect(await methodsObj.terminate()).toStrictEqual('OK');
});

it("the executeSnap method will return 'OK'", async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
expect(
await methodsObj.executeSnap('foo', 'code', ['endowment1', 'endowment2']),
).toStrictEqual('OK');
});

it('the executeSnap method will throw an Error if the snapName is not a string', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
await expect(async () => {
await methodsObj.executeSnap(1 as any, 'code', [
'endowment1',
'endowment2',
]);
}).rejects.toThrow('snapName is not a string');
});

it('the executeSnap method will throw an Error if the sourceCode is not a string', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
await expect(async () => {
await methodsObj.executeSnap('foo', 2 as any, [
'endowment1',
'endowment2',
]);
}).rejects.toThrow('sourceCode is not a string');
});

it('the executeSnap method will throw an Error if it is not passed a proper Endowments object', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
await expect(async () => {
await methodsObj.executeSnap('foo', 'code', ['endowment1', 2 as any]);
}).rejects.toThrow('endowment is not a proper Endowments object');
});

it('the snapRpc method will invoke the invokeSnapRpc function', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
const rpcRequest = { jsonrpc: '2.0', method: 'hello' };
await methodsObj.snapRpc('foo', 'bar', rpcRequest as any);
expect(invokeSnapRpc).toHaveBeenCalledTimes(1);
expect(invokeSnapRpc).toHaveBeenCalledWith('foo', 'bar', rpcRequest);
});

it('the snapRpc method will throw an error if the target is not a string', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
const rpcRequest = { jsonrpc: '2.0', method: 'hello' };
await expect(async () => {
await methodsObj.snapRpc(2 as any, 'bar', rpcRequest as any);
}).rejects.toThrow('target is not a string');
});

it('the snapRpc method will throw an error if the origin is not a string', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
const rpcRequest = { jsonrpc: '2.0', method: 'hello' };
await expect(async () => {
await methodsObj.snapRpc('foo', 2 as any, rpcRequest as any);
}).rejects.toThrow('origin is not a string');
});

it('the snapRpc method will throw an error if the request is not a JSON RPC request', async () => {
const startSnap = jest.fn();
const invokeSnapRpc = jest.fn();
const onTerminate = jest.fn();
const methodsObj = getCommandMethodImplementations(
startSnap,
invokeSnapRpc,
onTerminate,
);
const rpcRequest = { method: 'hello' };
await expect(async () => {
await methodsObj.snapRpc('foo', 'bar', rpcRequest as any);
}).rejects.toThrow('request is not a proper JSON RPC Request');
});
});
Loading

0 comments on commit 9163ed3

Please sign in to comment.