Skip to content
Open
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
19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Tests
on:
push:
branches:
- "main"
pull_request:
branches:
- "*"
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit -- since users might be using this package with different ndoe versions, would it make sense to test several stable node versions?

- run: yarn install --frozen-lockfile
- run: yarn run test:coverage
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.DS_Store
node_modules/
dist/
coverage/
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
.github/**
demos/**
dist/**
coverage/**
lib/wasm_exec.js
package-lock.json
package.json
README.md
test/**
tsconfig.json
tslint.json
webpack.config.js
178 changes: 178 additions & 0 deletions lib/api/createRpc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { Lightning } from '@lightninglabs/lnc-core/dist/types/proto/lnrpc';
import { WalletKit } from '@lightninglabs/lnc-core/dist/types/proto/walletrpc';
import { beforeEach, describe, expect, it, Mocked, vi } from 'vitest';
import LNC from '../lnc';
import { createRpc } from './createRpc';

// Mock the external dependency
vi.mock('@lightninglabs/lnc-core', () => ({
subscriptionMethods: [
'lnrpc.Lightning.SubscribeInvoices',
'lnrpc.Lightning.SubscribeChannelEvents',
'lnrpc.Lightning.ChannelAcceptor'
]
}));

// Create the mocked LNC instance
const mockLnc = {
request: vi.fn(),
subscribe: vi.fn()
} as unknown as Mocked<LNC>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the as unknown as Mocked<LNC>? Could we instead have it be a partial?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove the unknown there will be a TS error.

image


describe('RPC Creation', () => {
beforeEach(() => {
vi.clearAllMocks();
});

describe('createRpc function', () => {
it('should create a proxy object', () => {
const packageName = 'lnrpc.Lightning';
const rpc = createRpc(packageName, mockLnc);

expect(typeof rpc).toBe('object');
expect(rpc).toBeInstanceOf(Object);
});
});

describe('Proxy behavior', () => {
const packageName = 'lnrpc.Lightning';
let rpc: Lightning;

beforeEach(() => {
rpc = createRpc(packageName, mockLnc);
});

describe('Method name capitalization', () => {
it('should capitalize method names correctly', () => {
// Access a property to trigger the proxy get handler
const method = rpc.getInfo;

expect(typeof method).toBe('function');

// Call the method to verify capitalization
const request = { includeChannels: true };
method(request);

expect(mockLnc.request).toHaveBeenCalledWith(
'lnrpc.Lightning.GetInfo',
request
);
});

it('should handle method names with numbers', () => {
const method = (rpc as any).method123;

const request = {};
method(request);

expect(mockLnc.request).toHaveBeenCalledWith(
'lnrpc.Lightning.Method123',
request
);
});
});

describe('Unary RPC methods', () => {
it('should create async functions for non-subscription methods', async () => {
const method = rpc.getInfo;
expect(typeof method).toBe('function');

const mockResponse = { identityPubkey: 'test' };
mockLnc.request.mockResolvedValue(mockResponse);

const request = {};
const result = await method(request);

expect(result).toBe(mockResponse);
expect(mockLnc.request).toHaveBeenCalledWith(
'lnrpc.Lightning.GetInfo',
request
);
});

it('should handle empty request objects', async () => {
const method = rpc.getInfo;
const request = {};

mockLnc.request.mockResolvedValue({});

await method(request);

expect(mockLnc.request).toHaveBeenCalledWith(
'lnrpc.Lightning.GetInfo',
request
);
});
});

describe('Streaming RPC methods (subscriptions)', () => {
it('should create subscription functions for streaming methods', () => {
// Test with SubscribeInvoices which is in subscriptionMethods
const method = rpc.subscribeInvoices;

expect(typeof method).toBe('function');

const request = { addIndex: '1' };
const callback = vi.fn();
const errCallback = vi.fn();

method(request, callback, errCallback);

expect(mockLnc.subscribe).toHaveBeenCalledWith(
'lnrpc.Lightning.SubscribeInvoices',
request,
callback,
errCallback
);
});

it('should create subscription functions for ChannelAcceptor', () => {
const method = rpc.channelAcceptor;

expect(typeof method).toBe('function');

const request = {};
const callback = vi.fn();
const errCallback = vi.fn();

method(request, callback, errCallback);

expect(mockLnc.subscribe).toHaveBeenCalledWith(
'lnrpc.Lightning.ChannelAcceptor',
request,
callback,
errCallback
);
});
});

describe('Method classification', () => {
it('should handle different package names correctly', () => {
const walletRpc = createRpc<WalletKit>(
'lnrpc.WalletKit',
mockLnc
);
const method = walletRpc.listUnspent;

const request = { minConfs: 1 };
method(request);

expect(mockLnc.request).toHaveBeenCalledWith(
'lnrpc.WalletKit.ListUnspent',
request
);
});
});

describe('Error handling', () => {
it('should handle LNC request errors', async () => {
const method = rpc.getInfo;
const error = new Error('RPC Error');
mockLnc.request.mockRejectedValueOnce(error);

const request = {};
await expect(method(request)).rejects.toThrow('RPC Error');
});
});
});
});
69 changes: 69 additions & 0 deletions lib/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

// Mock the wasm_exec module
vi.mock('../../lib/wasm_exec', () => ({}));

// Mock @lightninglabs/lnc-core to avoid actual imports
vi.mock('@lightninglabs/lnc-core');

describe('Index Module', () => {
let originalInstantiateStreaming: any;

beforeEach(() => {
// Store original values
originalInstantiateStreaming =
globalThis.WebAssembly?.instantiateStreaming;

// Mock WebAssembly for testing
globalThis.WebAssembly = {
instantiateStreaming: vi.fn(),
instantiate: vi.fn(),
compile: vi.fn()
} as any;
});

afterEach(() => {
// Restore original values
if (originalInstantiateStreaming) {
globalThis.WebAssembly.instantiateStreaming =
originalInstantiateStreaming;
}
vi.restoreAllMocks();
});

describe('WebAssembly Polyfill', () => {
it('should polyfill WebAssembly.instantiateStreaming when not available', async () => {
// Remove instantiateStreaming to test polyfill
delete (globalThis.WebAssembly as any).instantiateStreaming;

// Import the index module to trigger the polyfill
await import('./index');

// Now WebAssembly.instantiateStreaming should exist
expect(typeof globalThis.WebAssembly?.instantiateStreaming).toBe(
'function'
);
});

it('should use existing WebAssembly.instantiateStreaming when available', async () => {
// Set up existing WebAssembly.instantiateStreaming
const existingInstantiateStreaming = vi.fn().mockResolvedValue({
module: {},
instance: {}
});

globalThis.WebAssembly = {
...globalThis.WebAssembly,
instantiateStreaming: existingInstantiateStreaming
};

// Import the index module
await import('./index');

// The existing function should still be there
expect(globalThis.WebAssembly.instantiateStreaming).toBe(
existingInstantiateStreaming
);
});
});
});
Loading