Skip to content

Commit 7d3c0c6

Browse files
authored
Merge pull request #123 from lightninglabs/add-unit-tests
Add vitest and unit tests for existing functionality
2 parents 0be66bb + e5aa7ea commit 7d3c0c6

23 files changed

+3548
-398
lines changed

.github/workflows/test.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: Tests
2+
on:
3+
push:
4+
branches:
5+
- "main"
6+
pull_request:
7+
branches:
8+
- "*"
9+
jobs:
10+
unit:
11+
strategy:
12+
matrix:
13+
node_version: [20, 22, 24]
14+
runs-on: ubuntu-latest
15+
name: Unit Tests on Node v${{ matrix.node_version }}
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Use Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: ${{ matrix.node_version }}
22+
- run: yarn install --frozen-lockfile
23+
- run: yarn run test:coverage

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.DS_Store
22
node_modules/
33
dist/
4+
coverage/

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
.github/**
22
demos/**
33
dist/**
4+
coverage/**
45
lib/wasm_exec.js
56
package-lock.json
67
package.json
78
README.md
8-
test/**
99
tsconfig.json
1010
tslint.json
1111
webpack.config.js

lib/api/createRpc.test.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { Lightning } from '@lightninglabs/lnc-core/dist/types/proto/lnrpc';
2+
import { WalletKit } from '@lightninglabs/lnc-core/dist/types/proto/walletrpc';
3+
import { beforeEach, describe, expect, it, Mocked, vi } from 'vitest';
4+
import LNC from '../lnc';
5+
import { createRpc } from './createRpc';
6+
7+
// Mock the external dependency
8+
vi.mock('@lightninglabs/lnc-core', () => ({
9+
subscriptionMethods: [
10+
'lnrpc.Lightning.SubscribeInvoices',
11+
'lnrpc.Lightning.SubscribeChannelEvents',
12+
'lnrpc.Lightning.ChannelAcceptor'
13+
]
14+
}));
15+
16+
// Create the mocked LNC instance
17+
const mockLnc = {
18+
request: vi.fn(),
19+
subscribe: vi.fn()
20+
} as unknown as Mocked<LNC>;
21+
22+
describe('RPC Creation', () => {
23+
beforeEach(() => {
24+
vi.clearAllMocks();
25+
});
26+
27+
describe('createRpc function', () => {
28+
it('should create a proxy object', () => {
29+
const packageName = 'lnrpc.Lightning';
30+
const rpc = createRpc(packageName, mockLnc);
31+
32+
expect(typeof rpc).toBe('object');
33+
expect(rpc).toBeInstanceOf(Object);
34+
});
35+
});
36+
37+
describe('Proxy behavior', () => {
38+
const packageName = 'lnrpc.Lightning';
39+
let rpc: Lightning;
40+
41+
beforeEach(() => {
42+
rpc = createRpc(packageName, mockLnc);
43+
});
44+
45+
describe('Method name capitalization', () => {
46+
it('should capitalize method names correctly', () => {
47+
// Access a property to trigger the proxy get handler
48+
const method = rpc.getInfo;
49+
50+
expect(typeof method).toBe('function');
51+
52+
// Call the method to verify capitalization
53+
const request = { includeChannels: true };
54+
method(request);
55+
56+
expect(mockLnc.request).toHaveBeenCalledWith(
57+
'lnrpc.Lightning.GetInfo',
58+
request
59+
);
60+
});
61+
62+
it('should handle method names with numbers', () => {
63+
const method = (rpc as any).method123;
64+
65+
const request = {};
66+
method(request);
67+
68+
expect(mockLnc.request).toHaveBeenCalledWith(
69+
'lnrpc.Lightning.Method123',
70+
request
71+
);
72+
});
73+
});
74+
75+
describe('Unary RPC methods', () => {
76+
it('should create async functions for non-subscription methods', async () => {
77+
const method = rpc.getInfo;
78+
expect(typeof method).toBe('function');
79+
80+
const mockResponse = { identityPubkey: 'test' };
81+
mockLnc.request.mockResolvedValue(mockResponse);
82+
83+
const request = {};
84+
const result = await method(request);
85+
86+
expect(result).toBe(mockResponse);
87+
expect(mockLnc.request).toHaveBeenCalledWith(
88+
'lnrpc.Lightning.GetInfo',
89+
request
90+
);
91+
});
92+
93+
it('should handle empty request objects', async () => {
94+
const method = rpc.getInfo;
95+
const request = {};
96+
97+
mockLnc.request.mockResolvedValue({});
98+
99+
await method(request);
100+
101+
expect(mockLnc.request).toHaveBeenCalledWith(
102+
'lnrpc.Lightning.GetInfo',
103+
request
104+
);
105+
});
106+
});
107+
108+
describe('Streaming RPC methods (subscriptions)', () => {
109+
it('should create subscription functions for streaming methods', () => {
110+
// Test with SubscribeInvoices which is in subscriptionMethods
111+
const method = rpc.subscribeInvoices;
112+
113+
expect(typeof method).toBe('function');
114+
115+
const request = { addIndex: '1' };
116+
const callback = vi.fn();
117+
const errCallback = vi.fn();
118+
119+
method(request, callback, errCallback);
120+
121+
expect(mockLnc.subscribe).toHaveBeenCalledWith(
122+
'lnrpc.Lightning.SubscribeInvoices',
123+
request,
124+
callback,
125+
errCallback
126+
);
127+
});
128+
129+
it('should create subscription functions for ChannelAcceptor', () => {
130+
const method = rpc.channelAcceptor;
131+
132+
expect(typeof method).toBe('function');
133+
134+
const request = {};
135+
const callback = vi.fn();
136+
const errCallback = vi.fn();
137+
138+
method(request, callback, errCallback);
139+
140+
expect(mockLnc.subscribe).toHaveBeenCalledWith(
141+
'lnrpc.Lightning.ChannelAcceptor',
142+
request,
143+
callback,
144+
errCallback
145+
);
146+
});
147+
});
148+
149+
describe('Method classification', () => {
150+
it('should handle different package names correctly', () => {
151+
const walletRpc = createRpc<WalletKit>(
152+
'lnrpc.WalletKit',
153+
mockLnc
154+
);
155+
const method = walletRpc.listUnspent;
156+
157+
const request = { minConfs: 1 };
158+
method(request);
159+
160+
expect(mockLnc.request).toHaveBeenCalledWith(
161+
'lnrpc.WalletKit.ListUnspent',
162+
request
163+
);
164+
});
165+
});
166+
167+
describe('Error handling', () => {
168+
it('should handle LNC request errors', async () => {
169+
const method = rpc.getInfo;
170+
const error = new Error('RPC Error');
171+
mockLnc.request.mockRejectedValueOnce(error);
172+
173+
const request = {};
174+
await expect(method(request)).rejects.toThrow('RPC Error');
175+
});
176+
});
177+
});
178+
});

lib/index.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
3+
// Mock the wasm_exec module
4+
vi.mock('../../lib/wasm_exec', () => ({}));
5+
6+
// Mock @lightninglabs/lnc-core to avoid actual imports
7+
vi.mock('@lightninglabs/lnc-core');
8+
9+
describe('Index Module', () => {
10+
let originalInstantiateStreaming: any;
11+
12+
beforeEach(() => {
13+
// Store original values
14+
originalInstantiateStreaming =
15+
globalThis.WebAssembly?.instantiateStreaming;
16+
17+
// Mock WebAssembly for testing
18+
globalThis.WebAssembly = {
19+
instantiateStreaming: vi.fn(),
20+
instantiate: vi.fn(),
21+
compile: vi.fn()
22+
} as any;
23+
});
24+
25+
afterEach(() => {
26+
// Restore original values
27+
if (originalInstantiateStreaming) {
28+
globalThis.WebAssembly.instantiateStreaming =
29+
originalInstantiateStreaming;
30+
}
31+
vi.restoreAllMocks();
32+
});
33+
34+
describe('WebAssembly Polyfill', () => {
35+
it('should polyfill WebAssembly.instantiateStreaming when not available', async () => {
36+
// Remove instantiateStreaming to test polyfill
37+
delete (globalThis.WebAssembly as any).instantiateStreaming;
38+
39+
// Import the index module to trigger the polyfill
40+
await import('./index');
41+
42+
// Now WebAssembly.instantiateStreaming should exist
43+
expect(typeof globalThis.WebAssembly?.instantiateStreaming).toBe(
44+
'function'
45+
);
46+
});
47+
48+
it('should use existing WebAssembly.instantiateStreaming when available', async () => {
49+
// Set up existing WebAssembly.instantiateStreaming
50+
const existingInstantiateStreaming = vi.fn().mockResolvedValue({
51+
module: {},
52+
instance: {}
53+
});
54+
55+
globalThis.WebAssembly = {
56+
...globalThis.WebAssembly,
57+
instantiateStreaming: existingInstantiateStreaming
58+
};
59+
60+
// Import the index module
61+
await import('./index');
62+
63+
// The existing function should still be there
64+
expect(globalThis.WebAssembly.instantiateStreaming).toBe(
65+
existingInstantiateStreaming
66+
);
67+
});
68+
});
69+
});

0 commit comments

Comments
 (0)