Skip to content

Commit 3b25a9d

Browse files
authored
Add more unit tests for SmartTransactionsController (#23)
* comments about where to add unit tests * add more test coverage for smart transactions controller * chore: add tests for getStxProcessingTime * chore: clean up tests * chore: more clean up * fix test leak
1 parent 8d9b79a commit 3b25a9d

File tree

4 files changed

+182
-11
lines changed

4 files changed

+182
-11
lines changed

jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
coverageReporters: ['text', 'html'],
77
coverageThreshold: {
88
global: {
9-
branches: 81,
9+
branches: 79,
1010
functions: 91,
1111
lines: 93,
1212
statements: 93,

src/SmartTransactionsController.test.ts

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import SmartTransactionsController, {
44
DEFAULT_INTERVAL,
55
} from './SmartTransactionsController';
66
import { API_BASE_URL, CHAIN_IDS, CHAIN_IDS_HEX_TO_DEC } from './constants';
7-
import { SmartTransaction } from './types';
7+
import { SmartTransaction, SmartTransactionStatuses } from './types';
88

99
const confirmExternalMock = jest.fn();
1010

@@ -31,9 +31,11 @@ jest.mock('ethers', () => ({
3131
},
3232
}));
3333

34+
const addressFrom = '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3';
35+
3436
const createUnsignedTransaction = () => {
3537
return {
36-
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
38+
from: addressFrom,
3739
to: '0x0000000000000000000000000000000000000000',
3840
value: 0,
3941
data: '0x',
@@ -195,6 +197,8 @@ const testHistory = [
195197

196198
const ethereumChainIdDec = CHAIN_IDS_HEX_TO_DEC[CHAIN_IDS.ETHEREUM];
197199

200+
const trackMetaMetricsEventSpy = jest.fn();
201+
198202
describe('SmartTransactionsController', () => {
199203
let smartTransactionsController: SmartTransactionsController;
200204
let networkListener: (networkState: NetworkState) => void;
@@ -217,12 +221,10 @@ describe('SmartTransactionsController', () => {
217221
txController: {
218222
confirmExternalTransaction: confirmExternalMock,
219223
},
220-
trackMetaMetricsEvent: jest.fn(),
224+
trackMetaMetricsEvent: trackMetaMetricsEventSpy,
221225
});
222-
223-
jest
224-
.spyOn(smartTransactionsController, 'checkPoll')
225-
.mockImplementation(() => ({}));
226+
// eslint-disable-next-line jest/prefer-spy-on
227+
smartTransactionsController.subscribe = jest.fn();
226228
});
227229

228230
afterEach(async () => {
@@ -264,6 +266,35 @@ describe('SmartTransactionsController', () => {
264266
});
265267
});
266268

269+
describe('checkPoll', () => {
270+
it('calls poll if there is no pending transaction and pending transactions', () => {
271+
const pollSpy = jest
272+
.spyOn(smartTransactionsController, 'poll')
273+
.mockImplementation(() => {
274+
return new Promise(() => ({}));
275+
});
276+
const { smartTransactionsState } = smartTransactionsController.state;
277+
const pendingStx = createStateAfterPending();
278+
smartTransactionsController.update({
279+
smartTransactionsState: {
280+
...smartTransactionsState,
281+
smartTransactions: {
282+
[CHAIN_IDS.ETHEREUM]: pendingStx as SmartTransaction[],
283+
},
284+
},
285+
});
286+
expect(pollSpy).toHaveBeenCalled();
287+
});
288+
289+
it('calls stop if there is a timeoutHandle and no pending transactions', () => {
290+
const stopSpy = jest.spyOn(smartTransactionsController, 'stop');
291+
smartTransactionsController.timeoutHandle = setInterval(() => ({}));
292+
smartTransactionsController.checkPoll(smartTransactionsController.state);
293+
expect(stopSpy).toHaveBeenCalled();
294+
clearInterval(smartTransactionsController.timeoutHandle);
295+
});
296+
});
297+
267298
describe('poll', () => {
268299
it('does not call updateSmartTransactions on unsupported networks', async () => {
269300
const updateSmartTransactionsSpy = jest.spyOn(
@@ -276,6 +307,59 @@ describe('SmartTransactionsController', () => {
276307
});
277308
});
278309

310+
describe('updateSmartTransactions', () => {
311+
it('calls fetchSmartTransactionsStatus if there are pending transactions', () => {
312+
const fetchSmartTransactionsStatusSpy = jest
313+
.spyOn(smartTransactionsController, 'fetchSmartTransactionsStatus')
314+
.mockImplementation(() => {
315+
return new Promise(() => ({}));
316+
});
317+
const { smartTransactionsState } = smartTransactionsController.state;
318+
const pendingStx = createStateAfterPending();
319+
smartTransactionsController.update({
320+
smartTransactionsState: {
321+
...smartTransactionsState,
322+
smartTransactions: {
323+
[CHAIN_IDS.ETHEREUM]: pendingStx as SmartTransaction[],
324+
},
325+
},
326+
});
327+
expect(fetchSmartTransactionsStatusSpy).toHaveBeenCalled();
328+
});
329+
});
330+
331+
describe('trackStxStatusChange', () => {
332+
it('does not track if no prevSmartTransactions', () => {
333+
const smartTransaction = createStateAfterPending()[0];
334+
smartTransactionsController.trackStxStatusChange(
335+
smartTransaction as SmartTransaction,
336+
);
337+
expect(trackMetaMetricsEventSpy).not.toHaveBeenCalled();
338+
});
339+
340+
it('does not track if smartTransaction and prevSmartTransaction have the same status', () => {
341+
const smartTransaction = createStateAfterPending()[0];
342+
smartTransactionsController.trackStxStatusChange(
343+
smartTransaction as SmartTransaction,
344+
smartTransaction as SmartTransaction,
345+
);
346+
expect(trackMetaMetricsEventSpy).not.toHaveBeenCalled();
347+
});
348+
349+
it('tracks status change if smartTransaction and prevSmartTransaction have different statuses', () => {
350+
const smartTransaction = {
351+
...createStateAfterPending()[0],
352+
swapMetaData: {},
353+
};
354+
const prevSmartTransaction = { ...smartTransaction, status: '' };
355+
smartTransactionsController.trackStxStatusChange(
356+
smartTransaction as SmartTransaction,
357+
prevSmartTransaction as SmartTransaction,
358+
);
359+
expect(trackMetaMetricsEventSpy).toHaveBeenCalled();
360+
});
361+
});
362+
279363
describe('setOptInState', () => {
280364
it('sets optIn state', () => {
281365
smartTransactionsController.setOptInState(true);
@@ -310,6 +394,11 @@ describe('SmartTransactionsController', () => {
310394
});
311395

312396
describe('submitSignedTransactions', () => {
397+
beforeEach(() => {
398+
// eslint-disable-next-line jest/prefer-spy-on
399+
smartTransactionsController.checkPoll = jest.fn(() => ({}));
400+
});
401+
313402
it('submits a smart transaction with signed transactions', async () => {
314403
const signedTransaction = createSignedTransaction();
315404
const signedCanceledTransaction = createSignedCanceledTransaction();
@@ -332,6 +421,11 @@ describe('SmartTransactionsController', () => {
332421
});
333422

334423
describe('fetchSmartTransactionsStatus', () => {
424+
beforeEach(() => {
425+
// eslint-disable-next-line jest/prefer-spy-on
426+
smartTransactionsController.checkPoll = jest.fn(() => ({}));
427+
});
428+
335429
it('fetches a pending status for a single smart transaction via batchStatus API', async () => {
336430
const uuids = ['uuid1'];
337431
const pendingBatchStatusApiResponse = createPendingBatchStatusApiResponse();
@@ -395,6 +489,11 @@ describe('SmartTransactionsController', () => {
395489
});
396490

397491
describe('updateSmartTransaction', () => {
492+
beforeEach(() => {
493+
// eslint-disable-next-line jest/prefer-spy-on
494+
smartTransactionsController.checkPoll = jest.fn(() => ({}));
495+
});
496+
398497
it('updates smart transaction based on uuid', () => {
399498
const pendingStx = {
400499
...createStateAfterPending()[0],
@@ -453,6 +552,11 @@ describe('SmartTransactionsController', () => {
453552
});
454553

455554
describe('confirmSmartTransaction', () => {
555+
beforeEach(() => {
556+
// eslint-disable-next-line jest/prefer-spy-on
557+
smartTransactionsController.checkPoll = jest.fn(() => ({}));
558+
});
559+
456560
it('calls confirm external transaction', async () => {
457561
const successfulStx = {
458562
...createStateAfterSuccess()[0],
@@ -463,6 +567,20 @@ describe('SmartTransactionsController', () => {
463567
);
464568
expect(confirmExternalMock).toHaveBeenCalled();
465569
});
570+
571+
it('throws an error if ethersProvider fails', async () => {
572+
smartTransactionsController.ethersProvider.getTransactionReceipt.mockRejectedValueOnce(
573+
'random error' as never,
574+
);
575+
const successfulStx = {
576+
...createStateAfterSuccess()[0],
577+
history: testHistory,
578+
};
579+
await smartTransactionsController.confirmSmartTransaction(
580+
successfulStx as SmartTransaction,
581+
);
582+
expect(trackMetaMetricsEventSpy).toHaveBeenCalled();
583+
});
466584
});
467585

468586
describe('cancelSmartTransaction', () => {
@@ -487,4 +605,43 @@ describe('SmartTransactionsController', () => {
487605
expect(configureSpy).toHaveBeenCalledTimes(0);
488606
});
489607
});
608+
609+
describe('getTransactions', () => {
610+
beforeEach(() => {
611+
// eslint-disable-next-line jest/prefer-spy-on
612+
smartTransactionsController.checkPoll = jest.fn(() => ({}));
613+
});
614+
615+
it('retrieves smart transactions by addressFrom and status', () => {
616+
const { smartTransactionsState } = smartTransactionsController.state;
617+
const pendingStx = {
618+
...createStateAfterPending()[0],
619+
history: testHistory,
620+
txParams: {
621+
from: addressFrom,
622+
},
623+
};
624+
smartTransactionsController.update({
625+
smartTransactionsState: {
626+
...smartTransactionsState,
627+
smartTransactions: {
628+
[CHAIN_IDS.ETHEREUM]: [pendingStx] as SmartTransaction[],
629+
},
630+
},
631+
});
632+
const pendingStxs = smartTransactionsController.getTransactions({
633+
addressFrom,
634+
status: SmartTransactionStatuses.PENDING,
635+
});
636+
expect(pendingStxs).toStrictEqual([pendingStx]);
637+
});
638+
639+
it('returns empty array if there are no smart transactions', () => {
640+
const transactions = smartTransactionsController.getTransactions({
641+
addressFrom,
642+
status: SmartTransactionStatuses.PENDING,
643+
});
644+
expect(transactions).toStrictEqual([]);
645+
});
646+
});
490647
});

src/SmartTransactionsController.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,13 @@ export default class SmartTransactionsController extends BaseController<
5656
SmartTransactionsControllerConfig,
5757
SmartTransactionsControllerState
5858
> {
59-
private timeoutHandle?: NodeJS.Timeout;
59+
public timeoutHandle?: NodeJS.Timeout;
6060

6161
private nonceTracker: any;
6262

6363
private getNetwork: any;
6464

65-
private ethersProvider: any;
65+
public ethersProvider: any;
6666

6767
public txController: any;
6868

@@ -172,7 +172,7 @@ export default class SmartTransactionsController extends BaseController<
172172
async poll(interval?: number): Promise<void> {
173173
const { chainId, supportedChainIds } = this.config;
174174
interval && this.configure({ interval }, false, false);
175-
this.timeoutHandle && clearTimeout(this.timeoutHandle);
175+
this.timeoutHandle && clearInterval(this.timeoutHandle);
176176
if (!supportedChainIds.includes(chainId)) {
177177
return;
178178
}

src/utils.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,18 @@ describe('src/utils.js', () => {
148148
);
149149
});
150150
});
151+
152+
describe('getStxProcessingTime', () => {
153+
it('returns undefined if no smartTransactionTimeSubmitted', () => {
154+
expect(utils.getStxProcessingTime(undefined)).toBeUndefined();
155+
});
156+
157+
it('returns 2m and 57s if processing time is 3s ago', () => {
158+
const THREE_SECONDS_AGO = 3 * 1000;
159+
const processingTime = utils.getStxProcessingTime(
160+
Date.now() - THREE_SECONDS_AGO,
161+
);
162+
expect(processingTime).toStrictEqual(3);
163+
});
164+
});
151165
});

0 commit comments

Comments
 (0)