Skip to content

Commit

Permalink
Add onKeyringRequest to snaps-jest (#2777)
Browse files Browse the repository at this point in the history
Add possibility to test `onKeyringRequest` with `snaps-jest`.

Fixes: #2775

Keyring test example:
```typescript
describe("onKeyringRequest", () => {
  it("sends keyring request", async () => {
    const { onKeyringRequest } = await installSnap();

    const response = await onKeyringRequest(
      {
        origin: "https://metamask.github.io",
        params: {
          options: {
            privateKey: "foo bar"
          }
        },
        method: "keyring_createAccount"
      });

    expect(response).toBe({ /* Add expected result here */ });
  });
});
```
  • Loading branch information
david0xd authored Oct 9, 2024
1 parent fae7855 commit 02c70c7
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 0 deletions.
33 changes: 33 additions & 0 deletions packages/snaps-jest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,39 @@ describe('MySnap', () => {
});
```

### `snap.onKeyringRequest`

The `onKeyringRequest` function can be used to process keyring request. It takes
few arguments, which are similar to a JSON-RPC request object. It returns
a promise that resolves to the response from the keyring request handler.

```js
import { installSnap } from '@metamask/snaps-jest';

describe('onKeyringRequest', () => {
it('sends keyring request', async () => {
const { onKeyringRequest } = await installSnap();

const response = await onKeyringRequest({
origin: 'https://metamask.github.io',
params: {
options: {
privateKey: 'foo-bar',
},
},
method: 'keyring_createAccount',
});

expect(response).toBe({
/* Add expected result here */
});
});
});
```

It returns an object with a response, and some additional metadata, which can be
checked using the [Jest matchers](#jest-matchers):

### Jest matchers

`@metamask/snaps-jest` includes a set of Jest matchers that can be used to
Expand Down
40 changes: 40 additions & 0 deletions packages/snaps-jest/src/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,46 @@ describe('installSnap', () => {
});
});

describe('onKeyringRequest', () => {
it('sends a keyring request and returns the result', async () => {
jest.spyOn(console, 'log').mockImplementation();

const { snapId, close: closeServer } = await getMockServer({
sourceCode: `
module.exports.onKeyringRequest = async ({ origin, request }) => {
return request;
}
`,
});

const { onKeyringRequest, close } = await installSnap(snapId);
const response = await onKeyringRequest({
origin: 'metamask.io',
params: {},
method: 'keyring_listAccounts',
});

expect(response).toStrictEqual(
expect.objectContaining({
response: {
result: {
params: {},
id: 1,
method: 'keyring_listAccounts',
jsonrpc: '2.0',
},
},
}),
);

// `close` is deprecated because the Jest environment will automatically
// close the Snap when the test finishes. However, we still need to close
// the Snap in this test because it's run outside the Jest environment.
await close();
await closeServer();
});
});

describe('mockJsonRpc', () => {
it('mocks a JSON-RPC method', async () => {
jest.spyOn(console, 'log').mockImplementation();
Expand Down
2 changes: 2 additions & 0 deletions packages/snaps-jest/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ export async function installSnap<
onCronjob,
runCronjob,
onHomePage,
onKeyringRequest,
mockJsonRpc,
close,
} = await getEnvironment().installSnap(...resolvedOptions);
Expand All @@ -190,6 +191,7 @@ export async function installSnap<
onCronjob,
runCronjob,
onHomePage,
onKeyringRequest,
mockJsonRpc,
close: async () => {
log('Closing execution service.');
Expand Down
39 changes: 39 additions & 0 deletions packages/snaps-simulation/src/helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,45 @@ describe('helpers', () => {
});
});

describe('onKeyringRequest', () => {
it('sends a keyring request and returns the result', async () => {
jest.spyOn(console, 'log').mockImplementation();

const { snapId, close: closeServer } = await getMockServer({
sourceCode: `
module.exports.onKeyringRequest = async ({ origin, request }) => {
return { success: true };
}
`,
});

const { onKeyringRequest, close } = await installSnap(snapId);
const response = await onKeyringRequest({
origin: 'metamask.io',
params: {
foo: 'bar',
},
method: 'keyring_createAccount',
});

expect(response).toStrictEqual(
expect.objectContaining({
response: {
result: {
success: true,
},
},
}),
);

// `close` is deprecated because the Jest environment will automatically
// close the Snap when the test finishes. However, we still need to close
// the Snap in this test because it's run outside the Jest environment.
await close();
await closeServer();
});
});

describe('mockJsonRpc', () => {
it('mocks a JSON-RPC method', async () => {
jest.spyOn(console, 'log').mockImplementation();
Expand Down
32 changes: 32 additions & 0 deletions packages/snaps-simulation/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import {
import type {
CronjobOptions,
JsonRpcMockOptions,
KeyringOptions,
RequestOptions,
SignatureOptions,
SnapRequest,
SnapResponseWithInterface,
SnapResponseWithoutInterface,
TransactionOptions,
} from './types';

Expand Down Expand Up @@ -114,6 +116,16 @@ export type SnapHelpers = {
*/
onHomePage(): Promise<SnapResponseWithInterface>;

/**
* Send a keyring request to the Snap.
*
* @param keyringRequest - Keyring request.
* @returns The response.
*/
onKeyringRequest(
keyringRequest: KeyringOptions,
): Promise<SnapResponseWithoutInterface>;

/**
* Mock a JSON-RPC request. This will cause the snap to respond with the
* specified response when a request with the specified method is sent.
Expand Down Expand Up @@ -216,6 +228,24 @@ export function getHelpers({
});
};

const onKeyringRequest = async (
request: KeyringOptions,
): Promise<SnapResponseWithoutInterface> => {
log('Sending keyring request %o.', request);

const response = await handleRequest({
snapId,
store,
executionService,
runSaga,
controllerMessenger,
handler: HandlerType.OnKeyringRequest,
request,
});

return response;
};

return {
request: (request) => {
log('Sending request %o.', request);
Expand All @@ -234,6 +264,8 @@ export function getHelpers({
onTransaction,
sendTransaction: onTransaction,

onKeyringRequest,

onSignature: async (
request: unknown,
): Promise<SnapResponseWithInterface> => {
Expand Down
15 changes: 15 additions & 0 deletions packages/snaps-simulation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export type CronjobOptions = Omit<RequestOptions, 'origin'>;
*/
export type TransactionOptions = Infer<typeof TransactionOptionsStruct>;

/**
* The options to use for keyring requests.
*/
export type KeyringOptions = RequestOptions;

/**
* The options to use for signature requests.
*
Expand Down Expand Up @@ -405,6 +410,16 @@ export type Snap = {
*/
onHomePage(): Promise<SnapResponseWithInterface>;

/**
* Send a keyring to the Snap.
*
* @param keyringRequest - Keyring request options.
* @returns The response.
*/
onKeyringRequest(
keyringRequest: KeyringOptions,
): Promise<SnapResponseWithoutInterface>;

/**
* Mock a JSON-RPC request. This will cause the snap to respond with the
* specified response when a request with the specified method is sent.
Expand Down

0 comments on commit 02c70c7

Please sign in to comment.