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
5 changes: 0 additions & 5 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,4 @@ demos/**
dist/**
coverage/**
lib/wasm_exec.js
package-lock.json
package.json
README.md
tsconfig.json
tslint.json
webpack.config.js
7 changes: 4 additions & 3 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"singleQuote": true,
"tabWidth": 4,
"tabWidth": 2,
"semi": true,
"trailingComma": "none",
"bracketSpacing": true
}
"bracketSpacing": true,
"printWidth": 80
}
301 changes: 149 additions & 152 deletions lib/api/createRpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,173 +6,170 @@ import { createRpc } from './createRpc';

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

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

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(() => {
vi.clearAllMocks();
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('createRpc function', () => {
it('should create a proxy object', () => {
const packageName = 'lnrpc.Lightning';
const rpc = createRpc(packageName, mockLnc);
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();

expect(typeof rpc).toBe('object');
expect(rpc).toBeInstanceOf(Object);
});
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('Proxy behavior', () => {
const packageName = 'lnrpc.Lightning';
let rpc: Lightning;
describe('Method classification', () => {
it('should handle different package names correctly', () => {
const walletRpc = createRpc<WalletKit>('lnrpc.WalletKit', mockLnc);
const method = walletRpc.listUnspent;

beforeEach(() => {
rpc = createRpc(packageName, mockLnc);
});
const request = { minConfs: 1 };
method(request);

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

describe('Method name capitalization', () => {
it('should capitalize method names correctly', () => {
// Access a property to trigger the proxy get handler
const method = rpc.getInfo;
describe('Error handling', () => {
it('should handle LNC request errors', async () => {
const method = rpc.getInfo;
const error = new Error('RPC Error');
mockLnc.request.mockRejectedValueOnce(error);

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');
});
});
const request = {};
await expect(method(request)).rejects.toThrow('RPC Error');
});
});
});
});
46 changes: 23 additions & 23 deletions lib/api/createRpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,30 @@ const capitalize = (s: string) => s && s[0].toUpperCase() + s.slice(1);
* subscribe methods depending on which function is called on the object
*/
export function createRpc<T extends object>(packageName: string, lnc: LNC): T {
const rpc = {};
return new Proxy(rpc, {
get(target, key, c) {
const methodName = capitalize(key.toString());
// the full name of the method (ex: lnrpc.Lightning.OpenChannel)
const method = `${packageName}.${methodName}`;
const rpc = {};
return new Proxy(rpc, {
get(target, key, c) {
const methodName = capitalize(key.toString());
// the full name of the method (ex: lnrpc.Lightning.OpenChannel)
const method = `${packageName}.${methodName}`;

if (subscriptionMethods.includes(method)) {
// call subscribe for streaming methods
return (
request: object,
callback: (msg: object) => void,
errCallback?: (err: Error) => void
): void => {
lnc.subscribe(method, request, callback, errCallback);
};
} else {
// call request for unary methods
return async (request: object): Promise<any> => {
return await lnc.request(method, request);
};
}
}
}) as T;
if (subscriptionMethods.includes(method)) {
// call subscribe for streaming methods
return (
request: object,
callback: (msg: object) => void,
errCallback?: (err: Error) => void
): void => {
lnc.subscribe(method, request, callback, errCallback);
};
} else {
// call request for unary methods
return async (request: object): Promise<any> => {
return await lnc.request(method, request);
};
}
}
}) as T;
}

export default createRpc;
Loading