Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EventType, TrackingEvents } from '@metamask/sdk-communication-layer';
import { MetaMaskSDK } from '../../../sdk';
import { MetaMaskSDKEvent } from '../../../types/MetaMaskSDKEvents';
import { PROVIDER_UPDATE_TYPE } from '../../../types/ProviderUpdateType';
import { STORAGE_PROVIDER_TYPE } from '../../../config';
import * as loggerModule from '../../../utils/logger';
Expand Down Expand Up @@ -68,16 +69,39 @@ describe('connectWithExtensionProvider', () => {
});

it('should handle error during account request', async () => {
mockRequest.mockRejectedValue(new Error('Some error'));
const error = new Error('User rejected the request');
mockRequest.mockRejectedValue(error);

const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
const originalProvider = instance.activeProvider;
instance.sdkProvider = originalProvider;

await connectWithExtensionProvider(instance);
await expect(connectWithExtensionProvider(instance)).rejects.toThrow(
'User rejected the request',
);

// Should log the error
expect(consoleWarnSpy).toHaveBeenCalledWith(
`[MetaMaskSDK: connectWithExtensionProvider()] can't request accounts error`,
new Error('Some error'),
error,
);

// Should emit ConnectWithResponse event with error
expect(instance.emit).toHaveBeenCalledWith(
MetaMaskSDKEvent.ConnectWithResponse,
{ error },
);

// Should send analytics event for rejection
expect(mockSendAnalyticsEvent).toHaveBeenCalledWith({
event: TrackingEvents.REJECTED,
});

// Should restore original provider
expect(instance.activeProvider).toBe(originalProvider);
expect((global.window as any).ethereum).toBe(originalProvider);

consoleWarnSpy.mockRestore();
});

it('should log debug information', async () => {
Expand Down Expand Up @@ -119,4 +143,56 @@ describe('connectWithExtensionProvider', () => {
await connectWithExtensionProvider(instance);
expect(instance.extensionActive).toBe(true);
});

it('should handle error when analytics is disabled', async () => {
const error = new Error('Connection cancelled');
mockRequest.mockRejectedValue(error);

// Disable analytics
instance.options.enableAnalytics = false;

const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
const originalProvider = instance.activeProvider;
instance.sdkProvider = originalProvider;

await expect(connectWithExtensionProvider(instance)).rejects.toThrow(
'Connection cancelled',
);

// Should still emit the error event
expect(instance.emit).toHaveBeenCalledWith(
MetaMaskSDKEvent.ConnectWithResponse,
{ error },
);

// Should NOT send analytics event when disabled
expect(mockSendAnalyticsEvent).not.toHaveBeenCalledWith({
event: TrackingEvents.REJECTED,
});

consoleWarnSpy.mockRestore();
});

it('should handle error when sdkProvider is not set', async () => {
const error = new Error('User denied access');
mockRequest.mockRejectedValue(error);

const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
instance.sdkProvider = undefined; // No previous provider to restore

await expect(connectWithExtensionProvider(instance)).rejects.toThrow(
'User denied access',
);

// Should emit error event
expect(instance.emit).toHaveBeenCalledWith(
MetaMaskSDKEvent.ConnectWithResponse,
{ error },
);

// activeProvider should be set to extension (not restored since no sdkProvider)
expect(instance.activeProvider).toStrictEqual((global.window as any).extension);

consoleWarnSpy.mockRestore();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: SDK Provider Overwrite Causes Test Failures

The connectWithExtensionProvider function unconditionally assigns instance.sdkProvider = instance.activeProvider before attempting connection. This overwrites the original SDK provider if the function is called multiple times. In error scenarios, this also causes instance.sdkProvider to always be truthy, which incorrectly triggers provider restoration in tests and prevents verifying behavior when no previous provider is available.

Additional Locations (1)

Fix in Cursor Fix in Web

});
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,27 @@ export async function connectWithExtensionProvider(instance: MetaMaskSDK) {
`[MetaMaskSDK: connectWithExtensionProvider()] accounts=${accounts}`,
);
} catch (err) {
// ignore error
console.warn(
`[MetaMaskSDK: connectWithExtensionProvider()] can't request accounts error`,
err,
);
return;

// Emit appropriate events for connection rejection/cancellation
instance.emit(MetaMaskSDKEvent.ConnectWithResponse, { error: err });

if (instance.options.enableAnalytics) {
instance.analytics?.send({ event: TrackingEvents.REJECTED });
}

// Restore original provider if extension connection failed
if (instance.sdkProvider) {
instance.activeProvider = instance.sdkProvider;
if (typeof window !== 'undefined') {
window.ethereum = instance.sdkProvider;
}
}

throw err; // Re-throw to allow calling code to handle the error
}

// remember setting for next time (until terminated)
Expand Down