Skip to content

Commit 30f37ab

Browse files
committed
Increase kernel snap test coverage
1 parent b29dbc6 commit 30f37ab

7 files changed

Lines changed: 497 additions & 5 deletions

File tree

packages/gator-permissions-snap/test/rpc/rpcHandler.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,4 +351,22 @@ describe('RpcHandler', () => {
351351
expect(result).toStrictEqual([VALID_PERMISSION_RESPONSE, secondResponse]);
352352
});
353353
});
354+
355+
describe('getPermissionOffers', () => {
356+
it('should return the default permission offers', async () => {
357+
const result = await handler.getPermissionOffers();
358+
expect(result).toStrictEqual([
359+
{
360+
id: '8c1697dd46c1e0be7a1627f8efb110612b9ee8510476d78da0f74687afbe0b10',
361+
proposedName: 'Native Token Stream',
362+
type: 'native-token-stream',
363+
},
364+
{
365+
id: '1241bf3c50b7d36d1921a9a5c6af5238a363debc86826f11bab553ceed7744ca',
366+
proposedName: 'Native Token Periodic Transfer',
367+
type: 'native-token-periodic',
368+
},
369+
]);
370+
});
371+
});
354372
});

packages/permissions-kernel-snap/snap.manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snap-7715-permissions.git"
88
},
99
"source": {
10-
"shasum": "HdcS9CdcQhLxR01pW6ReMNEspjGyKj15XtUzagTt+Us=",
10+
"shasum": "mb20j5FwfY+3rHHh3zIie2Zs0JZ2QGXYFgAESPYwS4A=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",

packages/permissions-kernel-snap/src/rpc/rpcHandler.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import type { Json, SnapsProvider } from '@metamask/snaps-sdk';
1010
import type { Registry } from '../registry';
1111
import type { StateManager } from '../stateManagement';
1212
import { EmptyRegistryPage, NoOffersFoundPage } from '../ui';
13-
import { parsePermissionRequestParam } from '../utils';
13+
import {
14+
parsePermissionRequestParam,
15+
parsePermissionsResponseParam,
16+
} from '../utils';
1417
import { ExternalMethod } from './rpcMethod';
1518

1619
/**
@@ -81,7 +84,7 @@ export function createRpcHandler(config: {
8184
`No relevant permissions to grant for origin ${siteOrigin}`,
8285
JSON.stringify(permissionsToGrant, null, 2),
8386
);
84-
await snap.request({
87+
await snapsProvider.request({
8588
method: 'snap_dialog',
8689
params: {
8790
type: 'alert',
@@ -114,7 +117,7 @@ export function createRpcHandler(config: {
114117
},
115118
});
116119

117-
return grantedPermissions;
120+
return parsePermissionsResponseParam(grantedPermissions) as Json;
118121
};
119122

120123
return {

packages/permissions-kernel-snap/src/utils/validate.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable @typescript-eslint/no-throw-literal */
2+
import type { PermissionsResponse } from '@metamask/7715-permissions-shared/types';
23
import {
34
type PermissionsRequest,
45
zPermissionsRequest,
6+
zPermissionsResponse,
57
} from '@metamask/7715-permissions-shared/types';
68
import { extractZodError } from '@metamask/7715-permissions-shared/utils';
79
import { InvalidParamsError } from '@metamask/snaps-sdk';
@@ -29,3 +31,27 @@ export const parsePermissionRequestParam = (
2931

3032
return validatePermissionsRequest.data;
3133
};
34+
35+
/**
36+
* Safely parses the grant permissions response parameters, validating them using Zod schema.
37+
*
38+
* @param params - The permissions to parse.
39+
* @returns The parsed and validated permissions as a PermissionsResponse object.
40+
* @throws Throws a InvalidParamsError if validation fails or if the permissions data is empty.
41+
*/
42+
export const parsePermissionsResponseParam = (
43+
params: any,
44+
): PermissionsResponse => {
45+
const validatePermissionsResponse = zPermissionsResponse.safeParse(params);
46+
if (!validatePermissionsResponse.success) {
47+
throw new InvalidParamsError(
48+
extractZodError(validatePermissionsResponse.error.errors),
49+
);
50+
}
51+
52+
if (validatePermissionsResponse.data.length === 0) {
53+
throw new InvalidParamsError('params are empty');
54+
}
55+
56+
return validatePermissionsResponse.data;
57+
};
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { GATOR_PERMISSIONS_PROVIDER_SNAP_ID } from '@metamask/7715-permissions-shared/constants';
2+
import { createMockSnapsProvider } from '@metamask/7715-permissions-shared/testing';
3+
import type { PermissionsRequest } from '@metamask/7715-permissions-shared/types';
4+
import { logger } from '@metamask/7715-permissions-shared/utils';
5+
6+
import { createRegistry, type Registry } from '../src/registry';
7+
import { ExternalMethod } from '../src/rpc/rpcMethod';
8+
9+
describe('Registry', () => {
10+
let registry: Registry;
11+
const mockSnapsProvider = createMockSnapsProvider();
12+
13+
beforeEach(() => {
14+
mockSnapsProvider.request.mockReset();
15+
registry = createRegistry(mockSnapsProvider);
16+
});
17+
18+
describe('buildPermissionProviderRegistry', () => {
19+
it('should build registry with valid permission offers', async () => {
20+
const mockOffers = [
21+
{
22+
type: 'native-token-transfer',
23+
id: '1',
24+
proposedName: 'Transfer native tokens',
25+
},
26+
];
27+
28+
mockSnapsProvider.request.mockResolvedValueOnce(mockOffers);
29+
30+
const result = await registry.buildPermissionProviderRegistry();
31+
32+
expect(mockSnapsProvider.request).toHaveBeenCalledWith({
33+
method: 'wallet_invokeSnap',
34+
params: {
35+
snapId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
36+
request: {
37+
method: ExternalMethod.PermissionProviderGetPermissionOffers,
38+
},
39+
},
40+
});
41+
42+
expect(result).toStrictEqual({
43+
[GATOR_PERMISSIONS_PROVIDER_SNAP_ID]: [
44+
{
45+
hostId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
46+
type: 'native-token-transfer',
47+
hostPermissionId: '1',
48+
proposedName: 'Transfer native tokens',
49+
},
50+
],
51+
});
52+
});
53+
54+
it('should handle empty permission offers and return empty registry', async () => {
55+
mockSnapsProvider.request.mockResolvedValueOnce([]);
56+
57+
const result = await registry.buildPermissionProviderRegistry();
58+
expect(result).toStrictEqual({});
59+
});
60+
61+
it('should handle invalid permission offers and return empty registry', async () => {
62+
mockSnapsProvider.request.mockResolvedValueOnce({ invalid: 'data' });
63+
64+
const result = await registry.buildPermissionProviderRegistry();
65+
expect(result).toStrictEqual({});
66+
});
67+
68+
it('should handle snap request error and return empty registry', async () => {
69+
jest.spyOn(logger, 'debug');
70+
mockSnapsProvider.request.mockRejectedValueOnce(new Error('Snap error'));
71+
72+
const result = await registry.buildPermissionProviderRegistry();
73+
expect(logger.debug).toHaveBeenCalledWith(
74+
{
75+
snapId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
76+
error: expect.any(Error),
77+
},
78+
expect.stringContaining('does not support'),
79+
);
80+
expect(result).toStrictEqual({});
81+
});
82+
});
83+
84+
describe('findRelevantPermissions', () => {
85+
const mockRegisteredOffers = [
86+
{
87+
hostId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
88+
type: 'native-token-transfer',
89+
hostPermissionId: '1',
90+
proposedName: 'Transfer native tokens',
91+
},
92+
{
93+
hostId: 'snap2',
94+
type: 'erc20-token-transfer',
95+
hostPermissionId: '2',
96+
proposedName: 'Transfer ERC20 tokens',
97+
},
98+
];
99+
100+
const mockPermissionsToGrant: PermissionsRequest = [
101+
{
102+
chainId: '0x1' as `0x${string}`,
103+
expiry: 1234567890,
104+
signer: {
105+
type: 'account' as const,
106+
data: {
107+
address:
108+
'0x1234567890123456789012345678901234567890' as `0x${string}`,
109+
},
110+
},
111+
permission: {
112+
type: 'native-token-transfer',
113+
data: {},
114+
},
115+
},
116+
{
117+
chainId: '0x1' as `0x${string}`,
118+
expiry: 1234567890,
119+
signer: {
120+
type: 'account' as const,
121+
data: {
122+
address:
123+
'0x1234567890123456789012345678901234567890' as `0x${string}`,
124+
},
125+
},
126+
permission: {
127+
type: 'erc20-token-transfer',
128+
data: {},
129+
},
130+
},
131+
];
132+
133+
it('should find matching permissions', () => {
134+
const result = registry.findRelevantPermissions(
135+
mockRegisteredOffers,
136+
mockPermissionsToGrant,
137+
);
138+
139+
const { length } = result;
140+
expect(length).toBe(1);
141+
expect(result[0]).toStrictEqual(mockPermissionsToGrant[0]);
142+
});
143+
144+
it('should return empty array when no matches found', () => {
145+
const result = registry.findRelevantPermissions(
146+
[],
147+
mockPermissionsToGrant,
148+
);
149+
150+
expect(result).toStrictEqual([]);
151+
});
152+
153+
it('should handle empty permissions to grant', () => {
154+
const result = registry.findRelevantPermissions(mockRegisteredOffers, []);
155+
156+
expect(result).toStrictEqual([]);
157+
});
158+
});
159+
160+
describe('reducePermissionOfferRegistry', () => {
161+
it('should reduce registry to array of offers', () => {
162+
const mockRegistry = {
163+
[GATOR_PERMISSIONS_PROVIDER_SNAP_ID]: [
164+
{
165+
hostId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
166+
type: 'native-token-transfer',
167+
hostPermissionId: '1',
168+
proposedName: 'Transfer native tokens',
169+
},
170+
],
171+
snap2: [
172+
{
173+
hostId: 'snap2',
174+
type: 'erc20-token-transfer',
175+
hostPermissionId: '2',
176+
proposedName: 'Transfer ERC20 tokens',
177+
},
178+
],
179+
};
180+
181+
const result = registry.reducePermissionOfferRegistry(mockRegistry);
182+
183+
expect(result).toStrictEqual([
184+
{
185+
hostId: GATOR_PERMISSIONS_PROVIDER_SNAP_ID,
186+
type: 'native-token-transfer',
187+
hostPermissionId: '1',
188+
proposedName: 'Transfer native tokens',
189+
},
190+
{
191+
hostId: 'snap2',
192+
type: 'erc20-token-transfer',
193+
hostPermissionId: '2',
194+
proposedName: 'Transfer ERC20 tokens',
195+
},
196+
]);
197+
});
198+
199+
it('should handle empty registry', () => {
200+
const result = registry.reducePermissionOfferRegistry({});
201+
202+
expect(result).toStrictEqual([]);
203+
});
204+
});
205+
});

0 commit comments

Comments
 (0)