Skip to content

Commit

Permalink
test: updated unit tests for channel feature
Browse files Browse the repository at this point in the history
  • Loading branch information
kelvinator07 committed Apr 18, 2024
1 parent 9164722 commit 9e90081
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 133 deletions.
119 changes: 101 additions & 18 deletions src/lib/lightning/clightning/clightningService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,27 @@ describe('CLightningService', () => {
describe('removeListener', () => {
jest.spyOn(window, 'clearInterval');

it('should clear the interval if channelsInterval is not null', async () => {
clightningService.channelsInterval = setInterval(() => {}, 1000);
it('should remove channel if channel exists in cache', async () => {
const intervalId = setInterval(() => {}, 1000);
clightningService.channelCaches = {
[node.ports.rest!]: {
intervalId,
channels: [],
},
};
await clightningService.removeListener(node);
expect(clearInterval).toHaveBeenCalledTimes(1);
expect(clearInterval).toHaveBeenCalledWith(clightningService.channelsInterval);
expect(clearInterval).toHaveBeenCalledWith(intervalId);
});

it('should not clear the interval if channelsInterval is null', async () => {
clightningService.channelsInterval = null;
it('should do nothing if channel does not exists in cache', async () => {
clightningService.channelCaches = {
[1234]: {
// using a random node port
intervalId: setInterval(() => {}, 1000),
channels: [],
},
};
await clightningService.removeListener(node);
expect(clearInterval).not.toHaveBeenCalled();
});
Expand All @@ -246,24 +258,95 @@ describe('CLightningService', () => {
await clightningService.subscribeChannelEvents(node, callback);

expect(setInterval).toHaveBeenCalledTimes(1);
expect(setInterval).toHaveBeenLastCalledWith(expect.any(Function), 30000);
expect(setInterval).toHaveBeenLastCalledWith(expect.any(Function), 30 * 1000);
});

it('checkChannels calls callback with channel events', async () => {
const callback = jest.fn();
const mockChannels = [
{ pending: true, status: 'Pending' },
{ pending: false, status: 'Open' },
{ pending: false, status: 'Closed' },
it('checkChannels should call callback with channel events', async () => {
const mockCallback = jest.fn();
const mockChannels: Partial<CLN.GetChannelsResponse>[] = [
{
channelId: '01aa',
state: CLN.ChannelState.CHANNELD_AWAITING_LOCKIN,
},
{
channelId: '04bb',
state: CLN.ChannelState.CHANNELD_NORMAL,
},
{
channelId: '07cc',
state: CLN.ChannelState.CLOSED,
},
];
clightningApiMock.httpGet.mockResolvedValue(mockChannels);

await clightningService.checkChannels(node, mockCallback);

expect(mockCallback).toHaveBeenCalledTimes(3);
expect(mockCallback).toHaveBeenCalledWith({ type: 'Pending' });
expect(mockCallback).toHaveBeenCalledWith({ type: 'Open' });
expect(mockCallback).toHaveBeenCalledWith({ type: 'Closed' });
});

it('checkChannels should call callback for only new channel returned by api', async () => {
const mockCallback = jest.fn();
clightningService.channelCaches = {
[node.ports.rest!]: {
intervalId: setInterval(() => {}, 1000),
channels: [
{ channelID: '01ff', pending: true, status: 'Opening' },
{ channelID: '04bb', pending: false, status: 'Open' },
],
},
};
const mockChannels: Partial<CLN.GetChannelsResponse>[] = [
{
channelId: '01ff',
state: CLN.ChannelState.CHANNELD_AWAITING_LOCKIN,
},
{
channelId: '04bb',
state: CLN.ChannelState.CHANNELD_NORMAL,
},
{
channelId: '07cc',
state: CLN.ChannelState.CLOSED, // New channel returned by api
},
];
clightningApiMock.httpGet.mockResolvedValue(mockChannels);

await clightningService.checkChannels(node, mockCallback);

expect(mockCallback).toHaveBeenCalledTimes(1); // called once for new channel
expect(mockCallback).toHaveBeenCalledWith({ type: 'Closed' });
});

it('checkChannels should call callback if channel state has been updated', async () => {
const mockCallback = jest.fn();
clightningService.channelCaches = {
[node.ports.rest!]: {
intervalId: setInterval(() => {}, 1000),
channels: [
{ channelID: '01ff', pending: true, status: 'Opening' },
{ channelID: '04bb', pending: false, status: 'Open' },
],
},
};
const mockChannels: Partial<CLN.GetChannelsResponse>[] = [
{
channelId: '01ff',
state: CLN.ChannelState.CHANNELD_AWAITING_LOCKIN,
},
{
channelId: '04bb',
state: CLN.ChannelState.CLOSED, // updated channel state from open to closed
},
];
clightningService.getChannels = jest.fn().mockResolvedValue(mockChannels);
clightningApiMock.httpGet.mockResolvedValue(mockChannels);

await clightningService.checkChannels(node, callback);
await clightningService.checkChannels(node, mockCallback);

expect(callback).toHaveBeenCalledTimes(3);
expect(callback).toHaveBeenCalledWith({ type: 'Pending' });
expect(callback).toHaveBeenCalledWith({ type: 'Open' });
expect(callback).toHaveBeenCalledWith({ type: 'Closed' });
expect(mockCallback).toHaveBeenCalledTimes(1); // called once for updated channel state
expect(mockCallback).toHaveBeenCalledWith({ type: 'Closed' });
});
});
});
12 changes: 2 additions & 10 deletions src/lib/lightning/eclair/eclairApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import { EclairNode } from 'shared/types';
import * as ipc from 'lib/ipc/ipcService';
import { getNetwork } from 'utils/tests';
import { httpPost, setupListener } from './eclairApi';
import { EclairWebSocket } from 'eclair-ts/dist/types/network';
import EclairTs from 'eclair-ts';

jest.mock('lib/ipc/ipcService');
jest.mock('eclair-ts');

const ipcMock = ipc as jest.Mocked<typeof ipc>;

Expand Down Expand Up @@ -50,13 +47,8 @@ describe('EclairApi', () => {
});

it('should setup a listener for the provided EclairNode', () => {
const mockListener = jest.fn() as unknown as EclairWebSocket;
// Mock the EclairTs constructor to return a mock instance
const EclairTsMock = jest.fn().mockImplementation(() => ({
listen: jest.fn().mockReturnValue(mockListener),
}));
(EclairTs as unknown as jest.Mock).mockImplementation(EclairTsMock);
const listener = setupListener(node);
expect(listener).toBe(mockListener);
expect(listener).not.toBe(null);
expect(listener.on).not.toBe(null);
});
});
33 changes: 3 additions & 30 deletions src/lib/lightning/eclair/eclairService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { defaultStateBalances, defaultStateInfo, getNetwork } from 'utils/tests'
import { eclairService } from './';
import * as eclairApi from './eclairApi';
import * as ELN from './types';
import { EclairWebSocket } from 'eclair-ts/dist/types/network';

jest.mock('./eclairApi');
jest.mock('lib/bitcoin/bitcoindService');
Expand Down Expand Up @@ -444,40 +443,14 @@ describe('EclairService', () => {
});
});

it('should add listener to node', async () => {
const mockListener = jest.fn() as unknown as EclairWebSocket;
(eclairApi.setupListener as jest.Mock).mockReturnValue(mockListener);

// Expect this.listener to be null before call
expect(eclairService.listener).toBeNull();

await eclairService.addListenerToNode(node);

// Expect this.listener not to be null after
expect(eclairService.listener).not.toBeNull();
expect(eclairApiMock.setupListener).toHaveBeenCalledWith(node);
expect(eclairApiMock.setupListener).toHaveBeenCalledTimes(1);
});

it('should remove Listener', async () => {
const mockListener = {
close: jest.fn(),
} as unknown as EclairWebSocket;

eclairService.listener = mockListener;

await eclairService.removeListener(node); // remove listener
expect(eclairService.listener).toBeNull(); // expect to be null
});

it('should subscribe to channel events', async () => {
const mockListener = {
on: jest.fn(),
} as unknown as EclairWebSocket;
} as unknown as ELN.EclairWebSocket;

const callback = jest.fn();

(eclairApi.setupListener as jest.Mock).mockReturnValue(mockListener);
(eclairApi.getListener as jest.Mock).mockReturnValue(mockListener);

await eclairService.subscribeChannelEvents(node, callback);

Expand Down Expand Up @@ -510,6 +483,6 @@ describe('EclairService', () => {
expect(callback).toHaveBeenCalledWith({ type: 'Unknown' });
}
}
expect(eclairApi.setupListener).toHaveBeenCalledWith(node);
expect(eclairApi.getListener).toHaveBeenCalledWith(node);
});
});
16 changes: 3 additions & 13 deletions src/lib/lightning/lnd/lndProxyClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,13 @@ describe('LndService', () => {
});
});

it('should call the setupListener ipc', () => {
lndProxyClient.setupListener(node);
expect(lndProxyClient.ipc).toHaveBeenCalledWith(ipcChannels.setupListener, { node });
});

it('should call the removeListener ipc', () => {
lndProxyClient.removeListener(node);
expect(lndProxyClient.ipc).toHaveBeenCalledWith(ipcChannels.removeListener, { node });
});

it('should call the subscribeChannelEvents ipc', () => {
const callback = jest.fn();
lndProxyClient.subscribeChannelEvents(node, callback);
const mockCallback = jest.fn();
lndProxyClient.subscribeChannelEvents(node, mockCallback);
expect(lndProxyClient.ipc).toHaveBeenCalledWith(
ipcChannels.subscribeChannelEvents,
{ node },
callback,
mockCallback,
);
});
});
40 changes: 22 additions & 18 deletions src/lib/lightning/lnd/lndService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,25 +191,29 @@ describe('LndService', () => {
});
});

it('should add listener to node', async () => {
lndProxyClient.setupListener = jest.fn();
await lndService.addListenerToNode(node);
expect(lndProxyClient.setupListener).toHaveBeenCalledWith(node);
expect(lndProxyClient.setupListener).toHaveBeenCalledTimes(1);
});
it('should subscribe Channel Events', async () => {
const mockCallback = jest.fn();
const pendingChannelEvent = { pendingOpenChannel: { txid: 'txid' } };
const openChannelEvent = { activeChannel: { fundingTxidBytes: 'txid' } };
const inActiveChannelEvent = { inactiveChannel: { fundingTxidBytes: 'txid' } };
const closedChannelEvent = { closedChannel: { closingTxHash: 'txhash' } };

lndProxyClient.subscribeChannelEvents = jest.fn().mockImplementation((_, cb) => {
cb(pendingChannelEvent);
cb(openChannelEvent);
cb(inActiveChannelEvent);
cb(closedChannelEvent);
});

it('should remove Listener from node', async () => {
lndProxyClient.removeListener = jest.fn();
await lndService.removeListener(node);
expect(lndProxyClient.removeListener).toHaveBeenCalledWith(node);
expect(lndProxyClient.removeListener).toHaveBeenCalledTimes(1);
});
await lndService.subscribeChannelEvents(node, mockCallback);

it('should subscribe Channel Events', async () => {
const callback = jest.fn();
lndProxyClient.subscribeChannelEvents = jest.fn();
await lndService.subscribeChannelEvents(node, callback);
expect(lndProxyClient.subscribeChannelEvents).toHaveBeenCalledWith(node, callback);
expect(lndProxyClient.subscribeChannelEvents).toHaveBeenCalledTimes(1);
expect(lndProxyClient.subscribeChannelEvents).toHaveBeenCalledWith(
node,
expect.any(Function),
);

expect(mockCallback).toHaveBeenCalledWith({ type: 'Pending' });
expect(mockCallback).toHaveBeenCalledWith({ type: 'Open' });
expect(mockCallback).toHaveBeenCalledWith({ type: 'Closed' });
});
});
45 changes: 1 addition & 44 deletions src/utils/async.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { delay, waitFor, debounceFunction } from './async';
import { delay, waitFor } from './async';
import { mockProperty } from './tests';

describe('Async Util', () => {
Expand Down Expand Up @@ -51,47 +51,4 @@ describe('Async Util', () => {
expect(spy).toHaveBeenCalled();
});
});

describe('debounceFunction', () => {
let mockFunc: jest.Mock;

beforeEach(() => {
mockFunc = jest.fn();
});

it('should execute function immediately on first call', async () => {
jest.useFakeTimers();
jest.spyOn(window, 'setTimeout');
jest.spyOn(window, 'clearTimeout');

await debounceFunction(mockFunc);
// Ensure function is only called once immediately
expect(mockFunc).toHaveBeenCalledTimes(1); // First call

await debounceFunction(mockFunc); // Second call
await debounceFunction(mockFunc); // Third call

// Ensure setTimeout has been called twice
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(clearTimeout).toHaveBeenCalledTimes(1);

// Fast-forward time by 30 seconds
jest.advanceTimersByTime(30000);

// Ensure function is called again after debounce time
expect(mockFunc).toHaveBeenCalledTimes(2);
});

it('should execute function immediately after 30 seconds', async () => {
await debounceFunction(mockFunc); // First call
expect(mockFunc).toHaveBeenCalledTimes(1);

// Mock current time to be 30 seconds after first call
const mockedCurrentTime = new Date(Date.now() + 30000);
window.Date.now = jest.fn(() => mockedCurrentTime.getTime());

await debounceFunction(mockFunc); // Second call
expect(mockFunc).toHaveBeenCalledTimes(2);
});
});
});

0 comments on commit 9e90081

Please sign in to comment.