Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more unit tests for SmartTransactionsController #23

Merged
merged 6 commits into from
Nov 17, 2021
Merged
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
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
coverageReporters: ['text', 'html'],
coverageThreshold: {
global: {
branches: 81,
branches: 79,
functions: 91,
lines: 93,
statements: 93,
Expand Down
171 changes: 164 additions & 7 deletions src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SmartTransactionsController, {
DEFAULT_INTERVAL,
} from './SmartTransactionsController';
import { API_BASE_URL, CHAIN_IDS, CHAIN_IDS_HEX_TO_DEC } from './constants';
import { SmartTransaction } from './types';
import { SmartTransaction, SmartTransactionStatuses } from './types';

const confirmExternalMock = jest.fn();

Expand All @@ -31,9 +31,11 @@ jest.mock('ethers', () => ({
},
}));

const addressFrom = '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3';

const createUnsignedTransaction = () => {
return {
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
from: addressFrom,
meppsilon marked this conversation as resolved.
Show resolved Hide resolved
to: '0x0000000000000000000000000000000000000000',
value: 0,
data: '0x',
Expand Down Expand Up @@ -195,6 +197,8 @@ const testHistory = [

const ethereumChainIdDec = CHAIN_IDS_HEX_TO_DEC[CHAIN_IDS.ETHEREUM];

const trackMetaMetricsEventSpy = jest.fn();

describe('SmartTransactionsController', () => {
let smartTransactionsController: SmartTransactionsController;
let networkListener: (networkState: NetworkState) => void;
Expand All @@ -217,12 +221,10 @@ describe('SmartTransactionsController', () => {
txController: {
confirmExternalTransaction: confirmExternalMock,
},
trackMetaMetricsEvent: jest.fn(),
trackMetaMetricsEvent: trackMetaMetricsEventSpy,
});

jest
.spyOn(smartTransactionsController, 'checkPoll')
.mockImplementation(() => ({}));
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.subscribe = jest.fn();
});

afterEach(async () => {
Expand Down Expand Up @@ -264,6 +266,35 @@ describe('SmartTransactionsController', () => {
});
});

describe('checkPoll', () => {
meppsilon marked this conversation as resolved.
Show resolved Hide resolved
it('calls poll if there is no pending transaction and pending transactions', () => {
const pollSpy = jest
.spyOn(smartTransactionsController, 'poll')
.mockImplementation(() => {
return new Promise(() => ({}));
});
const { smartTransactionsState } = smartTransactionsController.state;
const pendingStx = createStateAfterPending();
smartTransactionsController.update({
smartTransactionsState: {
...smartTransactionsState,
smartTransactions: {
[CHAIN_IDS.ETHEREUM]: pendingStx as SmartTransaction[],
},
},
});
expect(pollSpy).toHaveBeenCalled();
});

it('calls stop if there is a timeoutHandle and no pending transactions', () => {
const stopSpy = jest.spyOn(smartTransactionsController, 'stop');
smartTransactionsController.timeoutHandle = setInterval(() => ({}));
smartTransactionsController.checkPoll(smartTransactionsController.state);
expect(stopSpy).toHaveBeenCalled();
clearInterval(smartTransactionsController.timeoutHandle);
});
});

describe('poll', () => {
it('does not call updateSmartTransactions on unsupported networks', async () => {
const updateSmartTransactionsSpy = jest.spyOn(
Expand All @@ -276,6 +307,59 @@ describe('SmartTransactionsController', () => {
});
});

describe('updateSmartTransactions', () => {
it('calls fetchSmartTransactionsStatus if there are pending transactions', () => {
const fetchSmartTransactionsStatusSpy = jest
.spyOn(smartTransactionsController, 'fetchSmartTransactionsStatus')
.mockImplementation(() => {
return new Promise(() => ({}));
});
const { smartTransactionsState } = smartTransactionsController.state;
const pendingStx = createStateAfterPending();
smartTransactionsController.update({
smartTransactionsState: {
...smartTransactionsState,
smartTransactions: {
[CHAIN_IDS.ETHEREUM]: pendingStx as SmartTransaction[],
},
},
});
expect(fetchSmartTransactionsStatusSpy).toHaveBeenCalled();
});
});

describe('trackStxStatusChange', () => {
meppsilon marked this conversation as resolved.
Show resolved Hide resolved
it('does not track if no prevSmartTransactions', () => {
const smartTransaction = createStateAfterPending()[0];
smartTransactionsController.trackStxStatusChange(
smartTransaction as SmartTransaction,
);
expect(trackMetaMetricsEventSpy).not.toHaveBeenCalled();
});

it('does not track if smartTransaction and prevSmartTransaction have the same status', () => {
const smartTransaction = createStateAfterPending()[0];
smartTransactionsController.trackStxStatusChange(
smartTransaction as SmartTransaction,
smartTransaction as SmartTransaction,
);
expect(trackMetaMetricsEventSpy).not.toHaveBeenCalled();
});

it('tracks status change if smartTransaction and prevSmartTransaction have different statuses', () => {
const smartTransaction = {
...createStateAfterPending()[0],
swapMetaData: {},
};
const prevSmartTransaction = { ...smartTransaction, status: '' };
smartTransactionsController.trackStxStatusChange(
smartTransaction as SmartTransaction,
prevSmartTransaction as SmartTransaction,
);
expect(trackMetaMetricsEventSpy).toHaveBeenCalled();
});
});

describe('setOptInState', () => {
it('sets optIn state', () => {
smartTransactionsController.setOptInState(true);
Expand Down Expand Up @@ -310,6 +394,11 @@ describe('SmartTransactionsController', () => {
});

describe('submitSignedTransactions', () => {
beforeEach(() => {
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.checkPoll = jest.fn(() => ({}));
});

it('submits a smart transaction with signed transactions', async () => {
const signedTransaction = createSignedTransaction();
const signedCanceledTransaction = createSignedCanceledTransaction();
Expand All @@ -332,6 +421,11 @@ describe('SmartTransactionsController', () => {
});

describe('fetchSmartTransactionsStatus', () => {
beforeEach(() => {
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.checkPoll = jest.fn(() => ({}));
});

it('fetches a pending status for a single smart transaction via batchStatus API', async () => {
const uuids = ['uuid1'];
const pendingBatchStatusApiResponse = createPendingBatchStatusApiResponse();
Expand Down Expand Up @@ -395,6 +489,11 @@ describe('SmartTransactionsController', () => {
});

describe('updateSmartTransaction', () => {
beforeEach(() => {
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.checkPoll = jest.fn(() => ({}));
});

it('updates smart transaction based on uuid', () => {
const pendingStx = {
...createStateAfterPending()[0],
Expand Down Expand Up @@ -453,6 +552,11 @@ describe('SmartTransactionsController', () => {
});

describe('confirmSmartTransaction', () => {
beforeEach(() => {
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.checkPoll = jest.fn(() => ({}));
});

it('calls confirm external transaction', async () => {
const successfulStx = {
...createStateAfterSuccess()[0],
Expand All @@ -463,6 +567,20 @@ describe('SmartTransactionsController', () => {
);
expect(confirmExternalMock).toHaveBeenCalled();
});

it('throws an error if ethersProvider fails', async () => {
smartTransactionsController.ethersProvider.getTransactionReceipt.mockRejectedValueOnce(
'random error' as never,
);
const successfulStx = {
...createStateAfterSuccess()[0],
history: testHistory,
};
await smartTransactionsController.confirmSmartTransaction(
successfulStx as SmartTransaction,
);
expect(trackMetaMetricsEventSpy).toHaveBeenCalled();
});
});

describe('cancelSmartTransaction', () => {
Expand All @@ -487,4 +605,43 @@ describe('SmartTransactionsController', () => {
expect(configureSpy).toHaveBeenCalledTimes(0);
});
});

describe('getTransactions', () => {
beforeEach(() => {
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.checkPoll = jest.fn(() => ({}));
});

it('retrieves smart transactions by addressFrom and status', () => {
const { smartTransactionsState } = smartTransactionsController.state;
const pendingStx = {
...createStateAfterPending()[0],
history: testHistory,
txParams: {
from: addressFrom,
},
};
smartTransactionsController.update({
smartTransactionsState: {
...smartTransactionsState,
smartTransactions: {
[CHAIN_IDS.ETHEREUM]: [pendingStx] as SmartTransaction[],
},
},
});
const pendingStxs = smartTransactionsController.getTransactions({
addressFrom,
status: SmartTransactionStatuses.PENDING,
});
expect(pendingStxs).toStrictEqual([pendingStx]);
});

it('returns empty array if there are no smart transactions', () => {
const transactions = smartTransactionsController.getTransactions({
addressFrom,
status: SmartTransactionStatuses.PENDING,
});
expect(transactions).toStrictEqual([]);
});
});
});
6 changes: 3 additions & 3 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ export default class SmartTransactionsController extends BaseController<
SmartTransactionsControllerConfig,
SmartTransactionsControllerState
> {
private timeoutHandle?: NodeJS.Timeout;
public timeoutHandle?: NodeJS.Timeout;

private nonceTracker: any;

private getNetwork: any;

private ethersProvider: any;
public ethersProvider: any;

public txController: any;

Expand Down Expand Up @@ -172,7 +172,7 @@ export default class SmartTransactionsController extends BaseController<
async poll(interval?: number): Promise<void> {
const { chainId, supportedChainIds } = this.config;
interval && this.configure({ interval }, false, false);
this.timeoutHandle && clearTimeout(this.timeoutHandle);
this.timeoutHandle && clearInterval(this.timeoutHandle);
if (!supportedChainIds.includes(chainId)) {
return;
}
Expand Down
14 changes: 14 additions & 0 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,18 @@ describe('src/utils.js', () => {
);
});
});

describe('getStxProcessingTime', () => {
it('returns undefined if no smartTransactionTimeSubmitted', () => {
expect(utils.getStxProcessingTime(undefined)).toBeUndefined();
});

it('returns 2m and 57s if processing time is 3s ago', () => {
const THREE_SECONDS_AGO = 3 * 1000;
const processingTime = utils.getStxProcessingTime(
Date.now() - THREE_SECONDS_AGO,
);
expect(processingTime).toStrictEqual(3);
});
});
});