@lukasrosario/account
Version:
Base Account SDK
441 lines (370 loc) • 13.4 kB
text/typescript
import * as telemetryModule from ':core/telemetry/initCCA.js';
import { store } from ':store/store.js';
import * as checkCrossOriginModule from ':util/checkCrossOriginOpenerPolicy.js';
import * as validatePreferencesModule from ':util/validatePreferences.js';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { BaseAccountProvider } from './BaseAccountProvider.js';
import { CreateProviderOptions, createBaseAccountSDK } from './createBaseAccountSDK.js';
import * as getInjectedProviderModule from './getInjectedProvider.js';
// Mock all dependencies
vi.mock(':store/store.js', () => ({
store: {
subAccountsConfig: {
set: vi.fn(),
},
config: {
set: vi.fn(),
},
persist: {
rehydrate: vi.fn(),
},
},
}));
vi.mock(':core/telemetry/initCCA.js', () => ({
loadTelemetryScript: vi.fn(),
}));
vi.mock(':util/checkCrossOriginOpenerPolicy.js', () => ({
checkCrossOriginOpenerPolicy: vi.fn(),
}));
vi.mock(':util/validatePreferences.js', () => ({
validatePreferences: vi.fn(),
validateSubAccount: vi.fn(),
}));
vi.mock('./BaseAccountProvider.js', () => ({
BaseAccountProvider: vi.fn(),
}));
vi.mock('./getInjectedProvider.js', () => ({
getInjectedProvider: vi.fn(),
}));
const mockStore = store as any;
const mockLoadTelemetryScript = telemetryModule.loadTelemetryScript as any;
const mockCheckCrossOriginOpenerPolicy = checkCrossOriginModule.checkCrossOriginOpenerPolicy as any;
const mockValidatePreferences = validatePreferencesModule.validatePreferences as any;
const mockValidateSubAccount = validatePreferencesModule.validateSubAccount as any;
const mockBaseAccountProvider = BaseAccountProvider as any;
const mockGetInjectedProvider = getInjectedProviderModule.getInjectedProvider as any;
describe('createProvider', () => {
beforeEach(() => {
vi.clearAllMocks();
mockBaseAccountProvider.mockReturnValue({
mockProvider: true,
});
// Default: getInjectedProvider returns null to test BaseAccountProvider fallback
mockGetInjectedProvider.mockReturnValue(null);
});
describe('Basic functionality', () => {
it('should create a provider with minimal parameters', () => {
const result = createBaseAccountSDK({}).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith({
metadata: {
appName: 'App',
appLogoUrl: '',
appChainIds: [],
},
preference: {},
paymasterUrls: undefined,
});
expect(result).toEqual({ mockProvider: true });
});
it('should create a provider with custom app metadata', () => {
const params: CreateProviderOptions = {
appName: 'Test App',
appLogoUrl: 'https://example.com/logo.png',
appChainIds: [1, 137],
};
createBaseAccountSDK(params).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith({
metadata: {
appName: 'Test App',
appLogoUrl: 'https://example.com/logo.png',
appChainIds: [1, 137],
},
preference: {},
paymasterUrls: undefined,
});
});
it('should create a provider with custom preference', () => {
const params: CreateProviderOptions = {
preference: {
attribution: { auto: true },
},
};
createBaseAccountSDK(params).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith({
metadata: {
appName: 'App',
appLogoUrl: '',
appChainIds: [],
},
preference: {
attribution: { auto: true },
},
paymasterUrls: undefined,
});
});
it('should create a provider with paymaster URLs', () => {
const params: CreateProviderOptions = {
paymasterUrls: {
1: 'https://paymaster.example.com',
137: 'https://paymaster-polygon.example.com',
},
};
createBaseAccountSDK(params).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith(
expect.objectContaining({
paymasterUrls: {
1: 'https://paymaster.example.com',
137: 'https://paymaster-polygon.example.com',
},
})
);
});
});
describe('Sub-account configuration', () => {
it('should set sub-account configuration when provided', () => {
const mockToOwnerAccount = vi.fn();
const params: CreateProviderOptions = {
subAccounts: {
toOwnerAccount: mockToOwnerAccount,
// @ts-expect-error - enableAutoSubAccounts is not officially supported yet
enableAutoSubAccounts: true,
},
};
createBaseAccountSDK(params).getProvider();
expect(mockValidateSubAccount).toHaveBeenCalledWith(mockToOwnerAccount);
expect(mockStore.subAccountsConfig.set).toHaveBeenCalledWith({
toOwnerAccount: mockToOwnerAccount,
enableAutoSubAccounts: true,
});
});
it('should handle partial sub-account configuration', () => {
const params: CreateProviderOptions = {
subAccounts: {
// @ts-expect-error - enableAutoSubAccounts is not officially supported yet
enableAutoSubAccounts: true,
},
};
createBaseAccountSDK(params).getProvider();
expect(mockValidateSubAccount).not.toHaveBeenCalled();
expect(mockStore.subAccountsConfig.set).toHaveBeenCalledWith({
toOwnerAccount: undefined,
enableAutoSubAccounts: true,
});
});
it('should handle empty sub-account configuration', () => {
const params: CreateProviderOptions = {
subAccounts: undefined,
};
createBaseAccountSDK(params).getProvider();
expect(mockStore.subAccountsConfig.set).toHaveBeenCalledWith({
toOwnerAccount: undefined,
enableAutoSubAccounts: undefined,
defaultSpendPermissions: undefined,
});
});
});
describe('Store configuration', () => {
it('should set store configuration', () => {
const params: CreateProviderOptions = {
appName: 'Test App',
preference: {},
paymasterUrls: { 1: 'https://paymaster.example.com' },
};
createBaseAccountSDK(params).getProvider();
expect(mockStore.config.set).toHaveBeenCalledWith({
metadata: {
appName: 'Test App',
appLogoUrl: '',
appChainIds: [],
},
preference: {},
paymasterUrls: { 1: 'https://paymaster.example.com' },
});
});
it('should rehydrate store from storage', () => {
createBaseAccountSDK({}).getProvider();
expect(mockStore.persist.rehydrate).toHaveBeenCalled();
});
});
describe('Validation', () => {
it('should validate preferences', () => {
const preference = { telemetry: true };
createBaseAccountSDK({ preference }).getProvider();
expect(mockValidatePreferences).toHaveBeenCalledWith(preference);
});
it('should validate sub-account when toOwnerAccount is provided', () => {
const mockToOwnerAccount = vi.fn();
createBaseAccountSDK({
subAccounts: { toOwnerAccount: mockToOwnerAccount },
}).getProvider();
expect(mockValidateSubAccount).toHaveBeenCalledWith(mockToOwnerAccount);
});
it('should check cross-origin opener policy', () => {
createBaseAccountSDK({}).getProvider();
expect(mockCheckCrossOriginOpenerPolicy).toHaveBeenCalled();
});
});
describe('Telemetry', () => {
it('should load telemetry script when telemetry is not disabled', () => {
createBaseAccountSDK({
preference: { telemetry: true },
}).getProvider();
expect(mockLoadTelemetryScript).toHaveBeenCalled();
});
it('should load telemetry script when telemetry is undefined (default)', () => {
createBaseAccountSDK({
preference: {},
}).getProvider();
expect(mockLoadTelemetryScript).toHaveBeenCalled();
});
it('should not load telemetry script when telemetry is disabled', () => {
createBaseAccountSDK({
preference: { telemetry: false },
}).getProvider();
expect(mockLoadTelemetryScript).not.toHaveBeenCalled();
});
});
describe('Error handling', () => {
it('should handle sub-account validation errors', () => {
mockValidateSubAccount.mockImplementationOnce(() => {
throw new Error('Invalid sub-account function');
});
expect(() => {
createBaseAccountSDK({
subAccounts: { toOwnerAccount: 'not-a-function' as any },
}).getProvider();
}).toThrow('Invalid sub-account function');
});
});
describe('Provider fallback behavior', () => {
it('should use injected provider when getInjectedProvider returns a provider', () => {
const mockInjectedProvider = {
request: vi.fn(),
isCoinbaseBrowser: true,
type: 'injected',
};
mockGetInjectedProvider.mockReturnValue(mockInjectedProvider);
const result = createBaseAccountSDK({}).getProvider();
expect(mockGetInjectedProvider).toHaveBeenCalled();
expect(mockBaseAccountProvider).not.toHaveBeenCalled();
expect(result).toBe(mockInjectedProvider);
});
it('should fallback to BaseAccountProvider when getInjectedProvider returns null', () => {
mockGetInjectedProvider.mockReturnValue(null);
const result = createBaseAccountSDK({}).getProvider();
expect(mockGetInjectedProvider).toHaveBeenCalled();
expect(mockBaseAccountProvider).toHaveBeenCalledWith({
metadata: {
appName: 'App',
appLogoUrl: '',
appChainIds: [],
},
preference: {},
paymasterUrls: undefined,
});
expect(result).toEqual({ mockProvider: true });
});
});
describe('Integration', () => {
it('should perform all setup steps in correct order', () => {
const mockToOwnerAccount = vi.fn();
const params: CreateProviderOptions = {
appName: 'Integration Test',
appLogoUrl: 'https://example.com/logo.png',
appChainIds: [1, 137],
preference: {
telemetry: true,
},
subAccounts: {
toOwnerAccount: mockToOwnerAccount,
// @ts-expect-error - enableAutoSubAccounts is not officially supported yet
enableAutoSubAccounts: true,
},
paymasterUrls: {
1: 'https://paymaster.example.com',
},
};
const result = createBaseAccountSDK(params).getProvider();
// Check sub-account validation and configuration
expect(mockValidateSubAccount).toHaveBeenCalledWith(mockToOwnerAccount);
expect(mockStore.subAccountsConfig.set).toHaveBeenCalledWith({
toOwnerAccount: mockToOwnerAccount,
enableAutoSubAccounts: true,
});
// Check store configuration
expect(mockStore.config.set).toHaveBeenCalledWith({
metadata: {
appName: 'Integration Test',
appLogoUrl: 'https://example.com/logo.png',
appChainIds: [1, 137],
},
preference: {
telemetry: true,
},
paymasterUrls: {
1: 'https://paymaster.example.com',
},
});
// Check store rehydration
expect(mockStore.persist.rehydrate).toHaveBeenCalled();
// Check validation
expect(mockCheckCrossOriginOpenerPolicy).toHaveBeenCalled();
expect(mockValidatePreferences).toHaveBeenCalledWith({
telemetry: true,
});
// Check telemetry
expect(mockLoadTelemetryScript).toHaveBeenCalled();
// Check provider creation
expect(mockBaseAccountProvider).toHaveBeenCalledWith({
metadata: {
appName: 'Integration Test',
appLogoUrl: 'https://example.com/logo.png',
appChainIds: [1, 137],
},
preference: {
telemetry: true,
},
paymasterUrls: {
1: 'https://paymaster.example.com',
},
});
expect(result).toEqual({ mockProvider: true });
});
});
describe('Edge cases', () => {
it('should handle null app logo URL', () => {
createBaseAccountSDK({ appLogoUrl: null }).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith(
expect.objectContaining({
metadata: expect.objectContaining({
appLogoUrl: '',
}),
})
);
});
it('should handle empty app chain IDs array', () => {
createBaseAccountSDK({ appChainIds: [] }).getProvider();
expect(mockBaseAccountProvider).toHaveBeenCalledWith(
expect.objectContaining({
metadata: expect.objectContaining({
appChainIds: [],
}),
})
);
});
it('should handle complex nested preference objects', () => {
const complexPreference = {
attribution: { dataSuffix: '0x1234567890123456' as `0x${string}` },
telemetry: false,
customProperty: 'custom value',
};
createBaseAccountSDK({ preference: complexPreference }).getProvider();
expect(mockValidatePreferences).toHaveBeenCalledWith(complexPreference);
expect(mockBaseAccountProvider).toHaveBeenCalledWith(
expect.objectContaining({
preference: complexPreference,
})
);
});
});
});