Skip to content

Commit da2316c

Browse files
committed
test: add unit tests for WasmManager
1 parent 61d4c84 commit da2316c

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

lib/wasmManager.test.ts

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2+
import { WasmManager, lncGlobal } from './wasmManager';
3+
import { wasmLog } from './util/log';
4+
5+
vi.mock('./util/log', () => ({
6+
wasmLog: {
7+
info: vi.fn(),
8+
debug: vi.fn()
9+
}
10+
}));
11+
12+
vi.mock('@lightninglabs/lnc-core', () => ({
13+
snakeKeysToCamel: (value: unknown) => value
14+
}));
15+
16+
class FakeGo implements GoInstance {
17+
importObject: WebAssembly.Imports = {};
18+
argv: string[] = [];
19+
run = vi.fn().mockResolvedValue(undefined);
20+
}
21+
22+
type WasmNamespace = {
23+
wasmClientIsReady: ReturnType<typeof vi.fn>;
24+
wasmClientIsConnected: ReturnType<typeof vi.fn>;
25+
wasmClientConnectServer: ReturnType<typeof vi.fn>;
26+
wasmClientDisconnect: ReturnType<typeof vi.fn>;
27+
};
28+
29+
const createWasmNamespace = (overrides: Partial<WasmNamespace> = {}) => ({
30+
wasmClientIsReady: vi.fn().mockReturnValue(true),
31+
wasmClientIsConnected: vi.fn().mockReturnValue(true),
32+
wasmClientConnectServer: vi.fn(),
33+
wasmClientDisconnect: vi.fn(),
34+
wasmClientInvokeRPC: vi.fn(),
35+
wasmClientStatus: vi.fn(),
36+
wasmClientGetExpiry: vi.fn(),
37+
wasmClientIsReadOnly: vi.fn(),
38+
wasmClientHasPerms: vi.fn(),
39+
...overrides
40+
});
41+
42+
const restoreWindow = (originalWindow: typeof window | undefined) => {
43+
if (originalWindow === undefined) {
44+
delete (globalThis as any).window;
45+
} else {
46+
(globalThis as any).window = originalWindow;
47+
}
48+
};
49+
50+
describe('WasmManager', () => {
51+
const namespaces: string[] = [];
52+
let originalWindow: typeof window | undefined;
53+
54+
beforeEach(() => {
55+
vi.clearAllMocks();
56+
(lncGlobal as any).Go = FakeGo as unknown as typeof lncGlobal.Go;
57+
originalWindow = (globalThis as any).window;
58+
});
59+
60+
afterEach(() => {
61+
namespaces.forEach((ns) => {
62+
delete (lncGlobal as any)[ns];
63+
});
64+
namespaces.length = 0;
65+
restoreWindow(originalWindow);
66+
vi.useRealTimers();
67+
vi.clearAllMocks();
68+
});
69+
70+
const registerNamespace = (namespace: string, wasmNamespace: object) => {
71+
(lncGlobal as any)[namespace] = wasmNamespace;
72+
namespaces.push(namespace);
73+
};
74+
75+
describe('waitTilReady', () => {
76+
it('resolves once the WASM client reports ready', async () => {
77+
vi.useFakeTimers();
78+
const namespace = 'ready-namespace';
79+
let ready = false;
80+
const wasm = createWasmNamespace({
81+
wasmClientIsReady: vi.fn().mockImplementation(() => {
82+
if (!ready) {
83+
ready = true;
84+
return false;
85+
}
86+
return true;
87+
})
88+
});
89+
registerNamespace(namespace, wasm);
90+
91+
const manager = new WasmManager(namespace, 'code');
92+
const promise = manager.waitTilReady();
93+
94+
vi.advanceTimersByTime(500); // first check - not ready
95+
await Promise.resolve();
96+
vi.advanceTimersByTime(500); // second check - ready
97+
98+
await expect(promise).resolves.toBeUndefined();
99+
expect(wasm.wasmClientIsReady).toHaveBeenCalledTimes(2);
100+
expect(wasmLog.info).toHaveBeenCalledWith('The WASM client is ready');
101+
});
102+
103+
it('rejects when readiness times out', async () => {
104+
vi.useFakeTimers();
105+
const namespace = 'timeout-namespace';
106+
const wasm = createWasmNamespace({
107+
wasmClientIsReady: vi.fn().mockReturnValue(false)
108+
});
109+
registerNamespace(namespace, wasm);
110+
111+
const manager = new WasmManager(namespace, 'code');
112+
const promise = manager.waitTilReady();
113+
114+
vi.advanceTimersByTime(21 * 500);
115+
116+
await expect(promise).rejects.toThrow('Failed to load the WASM client');
117+
});
118+
});
119+
120+
describe('connect', () => {
121+
it('throws when no credential provider is available', async () => {
122+
const namespace = 'no-credentials';
123+
const wasm = createWasmNamespace();
124+
registerNamespace(namespace, wasm);
125+
126+
const manager = new WasmManager(namespace, 'code');
127+
128+
await expect(manager.connect()).rejects.toThrow(
129+
'No credential provider available'
130+
);
131+
});
132+
133+
it('runs setup when WASM is not ready and window is unavailable', async () => {
134+
vi.useFakeTimers();
135+
const namespace = 'connect-flow';
136+
let connected = false;
137+
const wasm = createWasmNamespace({
138+
wasmClientIsReady: vi.fn().mockReturnValue(false),
139+
wasmClientIsConnected: vi.fn().mockImplementation(() => connected)
140+
});
141+
registerNamespace(namespace, wasm);
142+
delete (globalThis as any).window;
143+
144+
const manager = new WasmManager(namespace, 'code');
145+
const runSpy = vi.spyOn(manager, 'run').mockResolvedValue(undefined);
146+
const waitSpy = vi
147+
.spyOn(manager, 'waitTilReady')
148+
.mockResolvedValue(undefined);
149+
150+
const credentials = {
151+
pairingPhrase: 'pair',
152+
localKey: 'local',
153+
remoteKey: 'remote',
154+
serverHost: 'server',
155+
password: 'secret',
156+
clear: vi.fn()
157+
};
158+
159+
const connectPromise = manager.connect(credentials);
160+
161+
await vi.advanceTimersByTimeAsync(500);
162+
connected = true;
163+
await vi.advanceTimersByTimeAsync(500);
164+
165+
await expect(connectPromise).resolves.toBeUndefined();
166+
167+
expect(runSpy).toHaveBeenCalled();
168+
expect(waitSpy).toHaveBeenCalled();
169+
expect(wasm.wasmClientConnectServer).toHaveBeenCalledWith(
170+
'server',
171+
false,
172+
'pair',
173+
'local',
174+
'remote'
175+
);
176+
expect(credentials.clear).toHaveBeenCalledWith(true);
177+
expect(wasmLog.info).toHaveBeenCalledWith(
178+
'No unload event listener added. window is not available'
179+
);
180+
});
181+
182+
it('adds unload listener when window is available', async () => {
183+
vi.useFakeTimers();
184+
const namespace = 'window-connect';
185+
let connected = false;
186+
const wasm = createWasmNamespace({
187+
wasmClientIsConnected: vi.fn().mockImplementation(() => connected)
188+
});
189+
registerNamespace(namespace, wasm);
190+
191+
const addEventListener = vi.fn();
192+
(globalThis as any).window = { addEventListener } as any;
193+
194+
const manager = new WasmManager(namespace, 'code');
195+
const credentials = {
196+
pairingPhrase: 'phrase',
197+
localKey: 'local',
198+
remoteKey: 'remote',
199+
serverHost: 'server',
200+
clear: vi.fn()
201+
};
202+
203+
const promise = manager.connect(credentials);
204+
205+
vi.advanceTimersByTime(500);
206+
connected = true;
207+
vi.advanceTimersByTime(500);
208+
209+
await expect(promise).resolves.toBeUndefined();
210+
expect(addEventListener).toHaveBeenCalledWith(
211+
'unload',
212+
wasm.wasmClientDisconnect
213+
);
214+
});
215+
216+
it('rejects when connection cannot be established in time', async () => {
217+
vi.useFakeTimers();
218+
const namespace = 'connect-timeout';
219+
const wasm = createWasmNamespace({
220+
wasmClientIsConnected: vi.fn().mockReturnValue(false)
221+
});
222+
registerNamespace(namespace, wasm);
223+
224+
const manager = new WasmManager(namespace, 'code');
225+
const credentials = {
226+
pairingPhrase: 'pair',
227+
localKey: 'local',
228+
remoteKey: 'remote',
229+
serverHost: 'server',
230+
clear: vi.fn()
231+
};
232+
233+
const promise = manager.connect(credentials);
234+
vi.advanceTimersByTime(21 * 500);
235+
236+
await expect(promise).rejects.toThrow(
237+
'Failed to connect the WASM client to the proxy server'
238+
);
239+
});
240+
});
241+
242+
describe('pair', () => {
243+
it('throws when no credential provider is configured', async () => {
244+
const namespace = 'pair-error';
245+
const wasm = createWasmNamespace();
246+
registerNamespace(namespace, wasm);
247+
248+
const manager = new WasmManager(namespace, 'code');
249+
250+
await expect(manager.pair('test')).rejects.toThrow(
251+
'No credential provider available'
252+
);
253+
});
254+
255+
it('delegates to connect after setting the pairing phrase', async () => {
256+
const namespace = 'pair-success';
257+
const wasm = createWasmNamespace();
258+
registerNamespace(namespace, wasm);
259+
260+
const manager = new WasmManager(namespace, 'code');
261+
const credentials = {
262+
pairingPhrase: '',
263+
localKey: 'local',
264+
remoteKey: 'remote',
265+
serverHost: 'server',
266+
clear: vi.fn()
267+
};
268+
manager.setCredentialProvider(credentials);
269+
270+
const connectSpy = vi
271+
.spyOn(manager, 'connect')
272+
.mockResolvedValue(undefined);
273+
274+
await manager.pair('new-phrase');
275+
276+
expect(credentials.pairingPhrase).toBe('new-phrase');
277+
expect(connectSpy).toHaveBeenCalledWith(credentials);
278+
});
279+
});
280+
});

0 commit comments

Comments
 (0)