Skip to content

Commit 8cdbd07

Browse files
committed
Add Snap export handler usage tracking
Use registry for metadata Replace controller with hook approach Fix broken unit tests Add unit tests Fix coverage after rebase Remove category Remove async from registry metadata and tracking Update throttling logic and tests Rearrange unit tests Update call to get registry metadata Remove unused getRegistryMetadata Refactor tracking call Add logic optimization for throttling function Skip preinstalled Snaps tracking Add error handling for MetaMetrics hook Add unit test for preinstalled snap
1 parent 37e79af commit 8cdbd07

File tree

9 files changed

+537
-112
lines changed

9 files changed

+537
-112
lines changed

packages/snaps-controllers/src/snaps/SnapController.test.tsx

Lines changed: 190 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4702,7 +4702,7 @@ describe('SnapController', () => {
47024702
},
47034703
});
47044704

4705-
expect(rootMessenger.call).toHaveBeenCalledTimes(4);
4705+
expect(rootMessenger.call).toHaveBeenCalledTimes(5);
47064706
expect(rootMessenger.call).toHaveBeenCalledWith(
47074707
'ExecutionService:handleRpcRequest',
47084708
MOCK_SNAP_ID,
@@ -9214,46 +9214,6 @@ describe('SnapController', () => {
92149214
});
92159215
});
92169216

9217-
describe('getRegistryMetadata', () => {
9218-
it('returns the metadata for a verified snap', async () => {
9219-
const registry = new MockSnapsRegistry();
9220-
const rootMessenger = getControllerMessenger(registry);
9221-
const messenger = getSnapControllerMessenger(rootMessenger);
9222-
registry.getMetadata.mockReturnValue({
9223-
name: 'Mock Snap',
9224-
});
9225-
9226-
const snapController = getSnapController(
9227-
getSnapControllerOptions({
9228-
messenger,
9229-
}),
9230-
);
9231-
9232-
expect(
9233-
await snapController.getRegistryMetadata(MOCK_SNAP_ID),
9234-
).toStrictEqual({
9235-
name: 'Mock Snap',
9236-
});
9237-
9238-
snapController.destroy();
9239-
});
9240-
9241-
it('returns null for a non-verified snap', async () => {
9242-
const registry = new MockSnapsRegistry();
9243-
const rootMessenger = getControllerMessenger(registry);
9244-
const messenger = getSnapControllerMessenger(rootMessenger);
9245-
const snapController = getSnapController(
9246-
getSnapControllerOptions({
9247-
messenger,
9248-
}),
9249-
);
9250-
9251-
expect(await snapController.getRegistryMetadata(MOCK_SNAP_ID)).toBeNull();
9252-
9253-
snapController.destroy();
9254-
});
9255-
});
9256-
92579217
describe('clearState', () => {
92589218
it('clears the state and terminates running snaps', async () => {
92599219
const rootMessenger = getControllerMessenger();
@@ -9416,6 +9376,195 @@ describe('SnapController', () => {
94169376

94179377
snapController.destroy();
94189378
});
9379+
9380+
it('should track event for allowed handler', async () => {
9381+
const mockTrackEvent = jest.fn();
9382+
const rootMessenger = getControllerMessenger();
9383+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9384+
getNodeEESMessenger(rootMessenger),
9385+
) as unknown as NodeThreadExecutionService;
9386+
9387+
const [snapController] = getSnapControllerWithEES(
9388+
getSnapControllerWithEESOptions({
9389+
rootMessenger,
9390+
trackEvent: mockTrackEvent,
9391+
state: {
9392+
snaps: getPersistedSnapsState(),
9393+
},
9394+
}),
9395+
executionEnvironmentStub,
9396+
);
9397+
9398+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9399+
await snapController.startSnap(snap.id);
9400+
9401+
await snapController.handleRequest({
9402+
snapId: snap.id,
9403+
origin: MOCK_ORIGIN,
9404+
handler: HandlerType.OnRpcRequest,
9405+
request: {
9406+
jsonrpc: '2.0',
9407+
method: 'test',
9408+
params: {},
9409+
id: 1,
9410+
},
9411+
});
9412+
9413+
expect(mockTrackEvent).toHaveBeenCalledTimes(1);
9414+
expect(mockTrackEvent).toHaveBeenCalledWith({
9415+
event: 'SnapExportUsed',
9416+
category: 'Snaps',
9417+
properties: {
9418+
export: 'onRpcRequest',
9419+
origin: 'https://example.com',
9420+
// eslint-disable-next-line @typescript-eslint/naming-convention
9421+
snap_category: null,
9422+
// eslint-disable-next-line @typescript-eslint/naming-convention
9423+
snap_id: 'npm:@metamask/example-snap',
9424+
success: true,
9425+
},
9426+
});
9427+
snapController.destroy();
9428+
});
9429+
9430+
it('should not track event for disallowed handler', async () => {
9431+
const mockTrackEvent = jest.fn();
9432+
const rootMessenger = getControllerMessenger();
9433+
9434+
rootMessenger.registerActionHandler(
9435+
'PermissionController:getPermissions',
9436+
() => ({
9437+
[SnapEndowments.Cronjob]: {
9438+
caveats: [
9439+
{ type: SnapCaveatType.SnapCronjob, value: '* * * * *' },
9440+
],
9441+
date: 1664187844588,
9442+
id: 'izn0WGUO8cvq_jqvLQuQP',
9443+
invoker: MOCK_SNAP_ID,
9444+
parentCapability: SnapEndowments.Cronjob,
9445+
},
9446+
}),
9447+
);
9448+
9449+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9450+
getNodeEESMessenger(rootMessenger),
9451+
) as unknown as NodeThreadExecutionService;
9452+
9453+
const [snapController] = getSnapControllerWithEES(
9454+
getSnapControllerWithEESOptions({
9455+
environmentEndowmentPermissions: ['endowment:cronjob'],
9456+
rootMessenger,
9457+
trackEvent: mockTrackEvent,
9458+
state: {
9459+
snaps: getPersistedSnapsState(),
9460+
},
9461+
}),
9462+
executionEnvironmentStub,
9463+
);
9464+
9465+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9466+
await snapController.startSnap(snap.id);
9467+
9468+
await snapController.handleRequest({
9469+
snapId: snap.id,
9470+
origin: MOCK_ORIGIN,
9471+
handler: HandlerType.OnCronjob,
9472+
request: {
9473+
jsonrpc: '2.0',
9474+
method: 'test',
9475+
params: {},
9476+
id: 1,
9477+
},
9478+
});
9479+
9480+
expect(mockTrackEvent).not.toHaveBeenCalled();
9481+
snapController.destroy();
9482+
});
9483+
9484+
it('should properly handle error when MetaMetrics hook throws an error', async () => {
9485+
const log = jest.spyOn(console, 'error').mockImplementation();
9486+
const error = new Error('MetaMetrics hook error');
9487+
const mockTrackEvent = jest.fn().mockImplementation(() => {
9488+
throw error;
9489+
});
9490+
const rootMessenger = getControllerMessenger();
9491+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9492+
getNodeEESMessenger(rootMessenger),
9493+
) as unknown as NodeThreadExecutionService;
9494+
9495+
const [snapController] = getSnapControllerWithEES(
9496+
getSnapControllerWithEESOptions({
9497+
rootMessenger,
9498+
trackEvent: mockTrackEvent,
9499+
state: {
9500+
snaps: getPersistedSnapsState(),
9501+
},
9502+
}),
9503+
executionEnvironmentStub,
9504+
);
9505+
9506+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9507+
await snapController.startSnap(snap.id);
9508+
9509+
await snapController.handleRequest({
9510+
snapId: snap.id,
9511+
origin: MOCK_ORIGIN,
9512+
handler: HandlerType.OnRpcRequest,
9513+
request: {
9514+
jsonrpc: '2.0',
9515+
method: 'test',
9516+
params: {},
9517+
id: 1,
9518+
},
9519+
});
9520+
9521+
expect(mockTrackEvent).toHaveBeenCalled();
9522+
expect(log).toHaveBeenCalledWith(
9523+
expect.stringContaining(
9524+
'Error when calling MetaMetrics hook for snap',
9525+
),
9526+
);
9527+
snapController.destroy();
9528+
});
9529+
9530+
it('should not track event for preinstalled snap', async () => {
9531+
const mockTrackEvent = jest.fn();
9532+
const rootMessenger = getControllerMessenger();
9533+
const executionEnvironmentStub = new ExecutionEnvironmentStub(
9534+
getNodeEESMessenger(rootMessenger),
9535+
) as unknown as NodeThreadExecutionService;
9536+
9537+
const [snapController] = getSnapControllerWithEES(
9538+
getSnapControllerWithEESOptions({
9539+
rootMessenger,
9540+
trackEvent: mockTrackEvent,
9541+
state: {
9542+
snaps: getPersistedSnapsState(
9543+
getPersistedSnapObject({ preinstalled: true }),
9544+
),
9545+
},
9546+
}),
9547+
executionEnvironmentStub,
9548+
);
9549+
9550+
const snap = snapController.getExpect(MOCK_SNAP_ID);
9551+
await snapController.startSnap(snap.id);
9552+
9553+
await snapController.handleRequest({
9554+
snapId: snap.id,
9555+
origin: MOCK_ORIGIN,
9556+
handler: HandlerType.OnRpcRequest,
9557+
request: {
9558+
jsonrpc: '2.0',
9559+
method: 'test',
9560+
params: {},
9561+
id: 1,
9562+
},
9563+
});
9564+
9565+
expect(mockTrackEvent).not.toHaveBeenCalled();
9566+
snapController.destroy();
9567+
});
94199568
});
94209569

94219570
it('handles a transaction insight request', async () => {
@@ -10388,35 +10537,6 @@ describe('SnapController', () => {
1038810537
});
1038910538
});
1039010539

10391-
describe('SnapController:getRegistryMetadata', () => {
10392-
it('calls SnapController.getRegistryMetadata()', async () => {
10393-
const registry = new MockSnapsRegistry();
10394-
const rootMessenger = getControllerMessenger(registry);
10395-
const messenger = getSnapControllerMessenger(rootMessenger);
10396-
10397-
registry.getMetadata.mockReturnValue({
10398-
name: 'Mock Snap',
10399-
});
10400-
10401-
const snapController = getSnapController(
10402-
getSnapControllerOptions({
10403-
messenger,
10404-
}),
10405-
);
10406-
10407-
expect(
10408-
await messenger.call(
10409-
'SnapController:getRegistryMetadata',
10410-
MOCK_SNAP_ID,
10411-
),
10412-
).toStrictEqual({
10413-
name: 'Mock Snap',
10414-
});
10415-
10416-
snapController.destroy();
10417-
});
10418-
});
10419-
1042010540
describe('SnapController:disconnectOrigin', () => {
1042110541
it('calls SnapController.removeSnapFromSubject()', () => {
1042210542
const messenger = getSnapControllerMessenger();

0 commit comments

Comments
 (0)