import { describe, it, expect, vi, beforeEach } from 'vitest'; import { // 简化 API createRpcPair, mockRemote, createServer, createClient, // 底层 API createDeepProxy, LocalTransport, serialize, deserialize, SerializedValueType, isRpcProxy, getProxyMeta, RpcServer, MessageTransport, createMessageServerHandler, } from '@/napcat-rpc/src'; // 测试用目标对象 interface TestObject { name: string; count: number; nested: { value: string; deep: { level: number; getData (): { info: string; }; }; }; items: string[]; greet (name: string): string; asyncGreet (name: string): Promise; add (a: number, b: number): number; multiply (a: number, b: number): number; withCallback (fn: (x: number) => number): number; asyncWithCallback (fn: (x: number) => Promise): Promise; multiCallback ( onSuccess: (result: string) => void, onError: (error: Error) => void ): void; getObject (): { id: number; name: string; }; createInstance: new (name: string) => { name: string; greet (): string; getName (): string; }; getData (): Map; getSet (): Set; getDate (): Date; getBuffer (): Uint8Array; throwError (): never; asyncThrowError (): Promise; } function createTestObject (): TestObject { return { name: 'test', count: 42, nested: { value: 'nested-value', deep: { level: 3, getData () { return { info: 'deep-info' }; }, }, }, items: ['a', 'b', 'c'], greet (name: string) { return `Hello, ${name}!`; }, async asyncGreet (name: string) { await Promise.resolve(); return `Async Hello, ${name}!`; }, add (a: number, b: number) { return a + b; }, multiply (a: number, b: number) { return a * b; }, withCallback (fn: (x: number) => number) { return fn(10) * 2; }, async asyncWithCallback (fn: (x: number) => Promise) { const result = await fn(5); return result + 100; }, multiCallback ( onSuccess: (result: string) => void, onError: (error: Error) => void ) { try { onSuccess('Operation completed'); } catch (e) { onError(e as Error); } }, getObject () { return { id: 1, name: 'object-name' }; }, createInstance: class TestClass { constructor (public name: string) { } greet () { return `Instance: ${this.name}`; } getName () { return this.name; } } as unknown as new (name: string) => { name: string; greet (): string; getName (): string; }, getData () { return new Map([['a', 1], ['b', 2]]); }, getSet () { return new Set(['x', 'y', 'z']); }, getDate () { return new Date('2024-01-15T10:30:00.000Z'); }, getBuffer () { return new Uint8Array([1, 2, 3, 4, 5]); }, throwError () { throw new Error('Test error'); }, async asyncThrowError () { await Promise.resolve(); throw new Error('Async test error'); }, }; } describe('napcat-rpc RPC', () => { describe('serialize / deserialize', () => { it('should serialize and deserialize primitive types', () => { // undefined expect(deserialize(serialize(undefined))).toBeUndefined(); // null expect(deserialize(serialize(null))).toBeNull(); // boolean expect(deserialize(serialize(true))).toBe(true); expect(deserialize(serialize(false))).toBe(false); // number expect(deserialize(serialize(42))).toBe(42); expect(deserialize(serialize(3.14))).toBe(3.14); expect(deserialize(serialize(NaN))).toBeNaN(); expect(deserialize(serialize(Infinity))).toBe(Infinity); expect(deserialize(serialize(-Infinity))).toBe(-Infinity); // bigint expect(deserialize(serialize(BigInt('9007199254740993')))).toBe(BigInt('9007199254740993')); // string expect(deserialize(serialize('hello'))).toBe('hello'); expect(deserialize(serialize(''))).toBe(''); }); it('should serialize and deserialize Date', () => { const date = new Date('2024-01-15T10:30:00.000Z'); const result = deserialize(serialize(date)) as Date; expect(result).toBeInstanceOf(Date); expect(result.toISOString()).toBe(date.toISOString()); }); it('should serialize and deserialize RegExp', () => { const regex = /test\d+/gi; const result = deserialize(serialize(regex)) as RegExp; expect(result).toBeInstanceOf(RegExp); expect(result.source).toBe(regex.source); expect(result.flags).toBe(regex.flags); }); it('should serialize and deserialize Error', () => { const error = new Error('test error'); const result = deserialize(serialize(error)) as Error; expect(result).toBeInstanceOf(Error); expect(result.message).toBe('test error'); }); it('should serialize and deserialize arrays', () => { const arr = [1, 'two', { three: 3 }, [4, 5]]; const result = deserialize(serialize(arr)); expect(result).toEqual(arr); }); it('should serialize and deserialize objects', () => { const obj = { name: 'test', count: 42, nested: { value: 'nested', }, }; const result = deserialize(serialize(obj)); expect(result).toEqual(obj); }); it('should serialize and deserialize Map', () => { const map = new Map([['a', 1], ['b', 2]]); const result = deserialize(serialize(map)) as Map; expect(result).toBeInstanceOf(Map); expect(result.get('a')).toBe(1); expect(result.get('b')).toBe(2); }); it('should serialize and deserialize Set', () => { const set = new Set(['x', 'y', 'z']); const result = deserialize(serialize(set)) as Set; expect(result).toBeInstanceOf(Set); expect(result.has('x')).toBe(true); expect(result.has('y')).toBe(true); expect(result.has('z')).toBe(true); }); it('should serialize and deserialize Uint8Array', () => { const buffer = new Uint8Array([1, 2, 3, 4, 5]); const result = deserialize(serialize(buffer)) as Uint8Array; expect(result).toBeInstanceOf(Uint8Array); expect(Array.from(result)).toEqual([1, 2, 3, 4, 5]); }); it('should handle circular references gracefully', () => { const obj: { self?: unknown; } = {}; obj.self = obj; // 应该不抛出错误 const serialized = serialize(obj); expect(serialized.type).toBe(SerializedValueType.OBJECT); }); }); describe('LocalTransport + createDeepProxy', () => { let target: TestObject; let transport: LocalTransport; let proxy: TestObject; beforeEach(() => { target = createTestObject(); transport = new LocalTransport(target); proxy = createDeepProxy({ transport }); }); it('should identify RPC proxy', () => { expect(isRpcProxy(proxy)).toBe(true); expect(isRpcProxy({})).toBe(false); expect(isRpcProxy(null)).toBe(false); }); it('should get proxy metadata', () => { const meta = getProxyMeta(proxy); expect(meta).toBeDefined(); expect(meta?.isProxy).toBe(true); expect(meta?.path).toEqual([]); const nestedMeta = getProxyMeta(proxy.nested); expect(nestedMeta?.path).toEqual(['nested']); }); describe('property access', () => { it('should access nested properties through method call', async () => { // 通过方法调用获取嵌套属性 const result = await proxy.nested.deep.getData(); expect(result).toEqual({ info: 'deep-info' }); }); }); describe('method calls', () => { it('should call synchronous methods', async () => { const result = await proxy.greet('World'); expect(result).toBe('Hello, World!'); }); it('should call methods with multiple arguments', async () => { const sum = await proxy.add(5, 3); expect(sum).toBe(8); const product = await proxy.multiply(4, 7); expect(product).toBe(28); }); it('should call async methods', async () => { const result = await proxy.asyncGreet('Async'); expect(result).toBe('Async Hello, Async!'); }); it('should call deeply nested methods', async () => { const result = await proxy.nested.deep.getData(); expect(result).toEqual({ info: 'deep-info' }); }); }); describe('callback proxying', () => { it('should proxy callbacks to remote (async pattern)', async () => { // RPC 回调本质上是异步的,因此服务端必须 await 回调结果 // 测试 asyncWithCallback 场景(服务端已经 await 回调) const callback = vi.fn(async (x: number) => x * 3); const result = await proxy.asyncWithCallback(callback); expect(callback).toHaveBeenCalledWith(5); expect(result).toBe(115); // (5 * 3) + 100 }); it('should proxy async callbacks', async () => { const callback = vi.fn(async (x: number) => { await Promise.resolve(); return x * 4; }); const result = await proxy.asyncWithCallback(callback); expect(callback).toHaveBeenCalledWith(5); expect(result).toBe(120); // (5 * 4) + 100 }); it('should handle multiple callbacks', async () => { const onSuccess = vi.fn(); const onError = vi.fn(); await proxy.multiCallback(onSuccess, onError); expect(onSuccess).toHaveBeenCalledWith('Operation completed'); expect(onError).not.toHaveBeenCalled(); }); }); describe('return value types', () => { it('should handle object return values', async () => { const result = await proxy.getObject(); expect(result).toEqual({ id: 1, name: 'object-name' }); }); it('should handle Map return values', async () => { const result = await proxy.getData(); expect(result).toBeInstanceOf(Map); expect((result as Map).get('a')).toBe(1); }); it('should handle Set return values', async () => { const result = await proxy.getSet(); expect(result).toBeInstanceOf(Set); expect((result as Set).has('x')).toBe(true); }); it('should handle Date return values', async () => { const result = await proxy.getDate(); expect(result).toBeInstanceOf(Date); expect((result as Date).toISOString()).toBe('2024-01-15T10:30:00.000Z'); }); it('should handle Uint8Array return values', async () => { const result = await proxy.getBuffer(); expect(result).toBeInstanceOf(Uint8Array); expect(Array.from(result as Uint8Array)).toEqual([1, 2, 3, 4, 5]); }); }); describe('error handling', () => { it('should propagate sync errors', async () => { await expect(proxy.throwError()).rejects.toThrow('Test error'); }); it('should propagate async errors', async () => { await expect(proxy.asyncThrowError()).rejects.toThrow('Async test error'); }); }); describe('constructor proxying', () => { it('should proxy constructor calls', async () => { const Constructor = proxy.createInstance; const instance = await new Constructor('TestInstance'); // 返回的是代理对象,需要 await 获取属性值 expect(await instance.getName()).toBe('TestInstance'); }); }); describe('property modification', () => { it('should set properties through proxy', async () => { proxy.count = 100; // 等待一下让异步操作完成 await new Promise(resolve => setTimeout(resolve, 10)); expect(target.count).toBe(100); }); it('should set nested properties', async () => { proxy.nested.value = 'modified'; await new Promise(resolve => setTimeout(resolve, 10)); expect(target.nested.value).toBe('modified'); }); }); describe('delete property', () => { it('should delete properties through proxy', async () => { (target as { deletable?: string; }).deletable = 'will be deleted'; delete (proxy as { deletable?: string; }).deletable; await new Promise(resolve => setTimeout(resolve, 10)); expect((target as { deletable?: string; }).deletable).toBeUndefined(); }); }); }); describe('RpcServer', () => { it('should handle GET requests', async () => { const target = { name: 'test', nested: { value: 123 } }; const server = new RpcServer({ target }); const response = await server.handleRequest({ id: 'req1', type: 'get' as never, path: ['name'], }); expect(response.success).toBe(true); expect(deserialize(response.result!)).toBe('test'); }); it('should handle SET requests', async () => { const target = { name: 'test' }; const server = new RpcServer({ target }); const response = await server.handleRequest({ id: 'req1', type: 'set' as never, path: ['name'], args: [serialize('modified')], }); expect(response.success).toBe(true); expect(target.name).toBe('modified'); }); it('should handle APPLY requests', async () => { const target = { add: (a: number, b: number) => a + b, }; const server = new RpcServer({ target }); const response = await server.handleRequest({ id: 'req1', type: 'apply' as never, path: ['add'], args: [serialize(3), serialize(4)], }); expect(response.success).toBe(true); expect(deserialize(response.result!)).toBe(7); }); it('should handle errors gracefully', async () => { const target = { fail: () => { throw new Error('Intentional failure'); }, }; const server = new RpcServer({ target }); const response = await server.handleRequest({ id: 'req1', type: 'apply' as never, path: ['fail'], args: [], }); expect(response.success).toBe(false); expect(response.error).toBe('Intentional failure'); }); }); describe('MessageTransport', () => { it('should communicate via message channel', async () => { const target = { echo: (msg: string) => `Echo: ${msg}`, add: (a: number, b: number) => a + b, }; // 模拟消息通道 type MessageHandler = (message: string) => void; let clientHandler: MessageHandler = () => { }; let serverHandler: MessageHandler = () => { }; // 创建服务端处理器 createMessageServerHandler(target, { sendMessage: (msg) => { setTimeout(() => clientHandler(msg), 0); }, onMessage: (handler) => { serverHandler = handler; }, }); // 创建客户端传输层 const transport = new MessageTransport({ sendMessage: (msg) => { setTimeout(() => serverHandler(msg), 0); }, onMessage: (handler) => { clientHandler = handler; }, }); const proxy = createDeepProxy({ transport }); // 测试调用 const echoResult = await proxy.echo('Hello'); expect(echoResult).toBe('Echo: Hello'); const addResult = await proxy.add(10, 20); expect(addResult).toBe(30); transport.close(); }); }); describe('complex scenarios', () => { it('should handle chained method calls with await', async () => { const target = { calculator: { add: (a: number, b: number) => a + b, multiply: (a: number, b: number) => a * b, }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); // RPC 场景中,可以通过嵌套路径直接调用方法 const sum = await proxy.calculator.add(5, 3); expect(sum).toBe(8); const product = await proxy.calculator.multiply(4, 5); expect(product).toBe(20); }); it('should handle arrays with objects', async () => { const target = { getUsers: () => [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, ], }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); const users = await proxy.getUsers(); expect(users).toHaveLength(2); expect(users[0]).toEqual({ id: 1, name: 'Alice' }); expect(users[1]).toEqual({ id: 2, name: 'Bob' }); }); it('should handle async callbacks that return objects', async () => { const target = { transformAsync: async (items: number[], fn: (item: number) => Promise<{ doubled: number; }>) => { const results: { doubled: number; }[] = []; for (const item of items) { results.push(await fn(item)); } return results; }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); const callback = vi.fn(async (x: number) => ({ doubled: x * 2 })); const result = await proxy.transformAsync([1, 2, 3], callback); expect(callback).toHaveBeenCalledTimes(3); expect(result).toEqual([ { doubled: 2 }, { doubled: 4 }, { doubled: 6 }, ]); }); it('should handle nested callback with Promise', async () => { const target = { processWithCallback: async ( value: number, processor: (x: number) => Promise ): Promise => { const result = await processor(value); return result * 2; }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); const callback = vi.fn(async (x: number) => x + 10); const result = await proxy.processWithCallback(5, callback); expect(callback).toHaveBeenCalledWith(5); expect(result).toBe(30); // (5 + 10) * 2 }); it('should handle Promise-returning callbacks', async () => { const target = { processAsync: async ( data: number[], processor: (item: number) => Promise ): Promise => { const results: number[] = []; for (const item of data) { results.push(await processor(item)); } return results; }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); const processor = vi.fn(async (x: number) => { await new Promise(r => setTimeout(r, 1)); return x * 10; }); const result = await proxy.processAsync([1, 2, 3], processor); // 数组返回代理,需 await 获取元素 expect(await result[0]).toBe(10); expect(await result[1]).toBe(20); expect(await result[2]).toBe(30); expect(processor).toHaveBeenCalledTimes(3); }); it('should maintain this context in methods', async () => { const target = { value: 100, getValue () { return this.value; }, double () { return this.value * 2; }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); expect(await proxy.getValue()).toBe(100); expect(await proxy.double()).toBe(200); }); it('should handle Symbol properties', async () => { const sym = Symbol('test'); const target = { [sym]: 'symbol-value', getSymbolValue () { return this[sym]; }, }; const transport = new LocalTransport(target); const proxy = createDeepProxy({ transport }); const result = await proxy.getSymbolValue(); expect(result).toBe('symbol-value'); }); }); // ========== 简化 API 测试 ========== describe('Easy API - createRpcPair', () => { it('should create isolated client/server pair', async () => { const { client, server } = createRpcPair({ counter: 0, increment () { return ++this.counter; }, getCounter () { return this.counter; }, }); // client 端操作会影响 server 端 expect(await client.increment()).toBe(1); expect(await client.increment()).toBe(2); expect(server.counter).toBe(2); expect(await client.getCounter()).toBe(2); }); it('should support nested objects', async () => { const { client } = createRpcPair({ user: { profile: { name: 'Alice', age: 25, getInfo () { return `${this.name}, ${this.age}`; }, }, }, }); const info = await client.user.profile.getInfo(); expect(info).toBe('Alice, 25'); }); it('should support .register({ cb1, cb2 }) pattern', async () => { const results: string[] = []; const { client } = createRpcPair({ async register (handlers: { onConnect: () => Promise; onMessage: (msg: string) => Promise; onDisconnect: (reason: string) => Promise; }) { await handlers.onConnect(); await handlers.onMessage('Hello'); await handlers.onMessage('World'); await handlers.onDisconnect('bye'); return 'registered'; }, }); const result = await client.register({ onConnect: async () => { results.push('connected'); }, onMessage: async (msg) => { results.push(`msg:${msg}`); }, onDisconnect: async (reason) => { results.push(`disconnected:${reason}`); }, }); expect(result).toBe('registered'); expect(results).toEqual([ 'connected', 'msg:Hello', 'msg:World', 'disconnected:bye', ]); }); it('should support complex callback objects with return values', async () => { const { client } = createRpcPair({ async process (handlers: { transform: (x: number) => Promise; validate: (x: number) => Promise; format: (x: number) => Promise; }) { const values: string[] = []; for (const num of [1, 2, 3, 4, 5]) { const transformed = await handlers.transform(num); if (await handlers.validate(transformed)) { values.push(await handlers.format(transformed)); } } return values; }, }); const result = await client.process({ transform: async (x) => x * 2, validate: async (x) => x > 5, format: async (x) => `value:${x}`, }); // 数组返回代理 expect(await result[0]).toBe('value:6'); expect(await result[1]).toBe('value:8'); expect(await result[2]).toBe('value:10'); }); }); describe('Easy API - mockRemote', () => { it('should create remote-like proxy easily', async () => { const api = mockRemote({ add: (a: number, b: number) => a + b, multiply: (a: number, b: number) => a * b, }); expect(await api.add(2, 3)).toBe(5); expect(await api.multiply(4, 5)).toBe(20); }); it('should handle async methods', async () => { const api = mockRemote({ async fetchUser (id: number) { return { id, name: `User${id}` }; }, async delay (ms: number) { await new Promise(r => setTimeout(r, ms)); return 'done'; }, }); const user = await api.fetchUser(123); // 对象返回代理,属性已缓存,可直接访问 expect(user.id).toBe(123); expect(user.name).toBe('User123'); }); }); describe('Easy API - createServer/createClient', () => { it('should create server and connect clients', async () => { const server = createServer({ data: [] as string[], add (item: string) { this.data.push(item); return this.data.length; }, getAll () { return [...this.data]; }, }); const client1 = createClient(server.getTransport()); const client2 = createClient(server.getTransport()); await client1.add('from-client1'); await client2.add('from-client2'); // 数组返回代理 const list1 = await client1.getAll(); const list2 = await client2.getAll(); expect(await list1[0]).toBe('from-client1'); expect(await list1[1]).toBe('from-client2'); expect(await list2[0]).toBe('from-client1'); expect(await list2[1]).toBe('from-client2'); }); }); // ========== 多层调用场景 ========== describe('Multi-level deep proxy operations', () => { it('should handle 5+ levels of nesting', async () => { const { client } = createRpcPair({ level1: { level2: { level3: { level4: { level5: { getValue () { return 'deep-value'; }, async getAsyncValue () { return 'async-deep-value'; }, }, }, }, }, }, }); expect(await client.level1.level2.level3.level4.level5.getValue()).toBe('deep-value'); expect(await client.level1.level2.level3.level4.level5.getAsyncValue()).toBe('async-deep-value'); }); it('should handle array of objects with data', async () => { const { client } = createRpcPair({ getItems () { return [ { id: 1, name: 'Item1', active: true }, { id: 2, name: 'Item2', active: false }, { id: 3, name: 'Item3', active: true }, ]; }, getItemById (id: number) { const items = [ { id: 1, name: 'Item1' }, { id: 2, name: 'Item2' }, ]; return items.find(item => item.id === id); }, }); const items = await client.getItems(); // 数组直接序列化,元素可直接访问 expect(items[0]!.id).toBe(1); expect(items[0]!.name).toBe('Item1'); expect(items[1]!.id).toBe(2); const item = await client.getItemById(2); // 普通对象(无方法)直接序列化 expect(item!.id).toBe(2); expect(item!.name).toBe('Item2'); }); it('should handle Map and Set in nested structures', async () => { const { client } = createRpcPair({ storage: { getMap () { return new Map([ ['key1', { value: 1 }], ['key2', { value: 2 }], ]); }, getSet () { return new Set([1, 2, 3, 4, 5]); }, }, }); const map = await client.storage.getMap(); expect(map).toBeInstanceOf(Map); expect(map.get('key1')).toEqual({ value: 1 }); const set = await client.storage.getSet(); expect(set).toBeInstanceOf(Set); expect(set.has(3)).toBe(true); }); it('should handle event emitter pattern', async () => { const events: string[] = []; const { client } = createRpcPair({ eventEmitter: { async on (event: string, handler: (data: string) => Promise) { // 模拟事件触发 await handler(`${event}:data1`); await handler(`${event}:data2`); return () => { /* unsubscribe */ }; }, }, }); await client.eventEmitter.on('message', async (data) => { events.push(data); }); expect(events).toEqual(['message:data1', 'message:data2']); }); it('should handle Promise chain in callbacks', async () => { const { client } = createRpcPair({ async pipeline ( input: number, stages: { step1: (x: number) => Promise; step2: (x: number) => Promise; step3: (x: number) => Promise; } ) { const r1 = await stages.step1(input); const r2 = await stages.step2(r1); const r3 = await stages.step3(r2); return r3; }, }); const result = await client.pipeline(5, { step1: async (x) => x * 2, // 10 step2: async (x) => x + 3, // 13 step3: async (x) => `result:${x}`, // 'result:13' }); expect(result).toBe('result:13'); }); it('should handle mixed sync/async handlers in object', async () => { const logs: string[] = []; const { client } = createRpcPair({ async execute (handlers: { beforeStart: () => Promise; onProgress: (percent: number) => Promise; afterComplete: () => Promise; }) { await handlers.beforeStart(); await handlers.onProgress(25); await handlers.onProgress(50); await handlers.onProgress(75); await handlers.onProgress(100); return await handlers.afterComplete(); }, }); const result = await client.execute({ beforeStart: async () => { logs.push('started'); }, onProgress: async (percent) => { logs.push(`progress:${percent}%`); }, afterComplete: async () => { logs.push('completed'); return 'success'; }, }); expect(result).toBe('success'); expect(logs).toEqual([ 'started', 'progress:25%', 'progress:50%', 'progress:75%', 'progress:100%', 'completed', ]); }); }); // ========== 边界情况 ========== describe('Edge cases', () => { it('should handle undefined and null in callbacks', async () => { const { client } = createRpcPair({ async process (handler: (val: unknown) => Promise) { const results: unknown[] = []; results.push(await handler(undefined)); results.push(await handler(null)); results.push(await handler(0)); results.push(await handler('')); results.push(await handler(false)); return results; }, }); const result = await client.process(async (val) => { if (val === undefined) return 'undefined'; if (val === null) return 'null'; if (val === 0) return 'zero'; if (val === '') return 'empty'; if (val === false) return 'false'; return 'other'; }); // 数组返回代理 expect(await result[0]).toBe('undefined'); expect(await result[1]).toBe('null'); expect(await result[2]).toBe('zero'); expect(await result[3]).toBe('empty'); expect(await result[4]).toBe('false'); }); it('should handle errors in callbacks', async () => { const { client } = createRpcPair({ async executeWithErrorHandler ( action: () => Promise, onError: (err: Error) => Promise ) { try { return await action(); } catch (e) { return await onError(e as Error); } }, }); const result = await client.executeWithErrorHandler( async () => { throw new Error('action failed'); }, async (err) => { return `caught: ${err.message}`; } ); expect(result).toBe('caught: action failed'); }); it('should handle large data transfer', async () => { const { client } = createRpcPair({ processLargeArray (arr: number[]) { return arr.reduce((a, b) => a + b, 0); }, }); const largeArray = Array.from({ length: 10000 }, (_, i) => i); const sum = await client.processLargeArray(largeArray); expect(sum).toBe(49995000); // sum of 0 to 9999 }); }); describe('Deep return value proxying (class instances)', () => { it('should keep class instance as proxy and call methods remotely', async () => { class User { constructor (public name: string, public age: number) { } greet () { return `Hi, I am ${this.name}`; } getInfo () { return { name: this.name, age: this.age }; } updateAge (newAge: number) { this.age = newAge; return this.age; } } const { client } = createRpcPair({ createUser (name: string, age: number) { return new User(name, age); }, }); const user = await client.createUser('Alice', 25); // user 应该是代理,调用方法时发送 RPC 请求 const greeting = await user.greet(); expect(greeting).toBe('Hi, I am Alice'); const info = await user.getInfo(); // getInfo 返回普通对象,直接序列化,属性可直接访问 expect(info.name).toBe('Alice'); expect(info.age).toBe(25); // 更新远程对象状态 const newAge = await user.updateAge(30); expect(newAge).toBe(30); // 验证状态被更新 const updatedInfo = await user.getInfo(); expect(updatedInfo.age).toBe(30); }); it('should support chained method calls on returned class instance', async () => { class Counter { value = 0; increment () { this.value++; return this; } decrement () { this.value--; return this; } getValue () { return this.value; } } const { client } = createRpcPair({ createCounter () { return new Counter(); }, }); const counter = await client.createCounter(); // 链式调用 await counter.increment(); await counter.increment(); await counter.increment(); await counter.decrement(); const value = await counter.getValue(); expect(value).toBe(2); }); it('should handle nested class instances', async () => { class Address { constructor (public city: string, public country: string) { } getFullAddress () { return `${this.city}, ${this.country}`; } } class Person { constructor (public name: string, public address: Address) { } getAddress () { return this.address; } getFormattedInfo () { return `${this.name} lives in ${this.address.city}`; } } const { client } = createRpcPair({ createPerson (name: string, city: string, country: string) { return new Person(name, new Address(city, country)); }, }); const person = await client.createPerson('Bob', 'Tokyo', 'Japan'); const info = await person.getFormattedInfo(); expect(info).toBe('Bob lives in Tokyo'); // 获取嵌套对象(如果 Address 也是类实例,也应该是代理) const address = await person.getAddress(); const fullAddress = await address.getFullAddress(); expect(fullAddress).toBe('Tokyo, Japan'); }); it('should handle objects with methods (not class instances)', async () => { // 这种对象有方法,应该也返回代理 const { client } = createRpcPair({ createApi () { let data = 'initial'; return { getData () { return data; }, setData (newData: string) { data = newData; }, processData (transformer: (d: string) => string) { data = transformer(data); return data; }, }; }, }); const api = await client.createApi(); const initial = await api.getData(); expect(initial).toBe('initial'); await api.setData('updated'); const updated = await api.getData(); expect(updated).toBe('updated'); // 测试回调 const result = await api.processData((d: string) => d.toUpperCase()); expect(result).toBe('UPDATED'); }); it('should proxy simple objects and allow property access', async () => { // 简单对象(无方法)直接序列化,属性可直接访问 const { client } = createRpcPair({ getSimpleData () { return { id: 1, name: 'test', active: true }; }, }); const data = await client.getSimpleData(); // 普通对象直接序列化,属性可直接访问 expect(data.id).toBe(1); expect(data.name).toBe('test'); expect(data.active).toBe(true); }); it('should handle async methods in returned class', async () => { class AsyncService { private value = 0; async fetchAndAdd (amount: number) { await new Promise(resolve => setTimeout(resolve, 10)); this.value += amount; return this.value; } async getValue () { await new Promise(resolve => setTimeout(resolve, 5)); return this.value; } } const { client } = createRpcPair({ createService () { return new AsyncService(); }, }); const service = await client.createService(); const first = await service.fetchAndAdd(10); expect(first).toBe(10); const second = await service.fetchAndAdd(5); expect(second).toBe(15); const current = await service.getValue(); expect(current).toBe(15); }); it('should handle constructor on returned proxy', async () => { class Factory { create (name: string): { name: string; } { return { name }; } Widget = class Widget { constructor (public id: number) { } getId () { return this.id; } }; } const { client } = createRpcPair({ getFactory () { return new Factory(); }, }); const factory = await client.getFactory(); // 调用返回代理上的方法,返回带方法的对象被代理 const obj = await factory.create('widget1'); // 属性已缓存,可直接访问 expect(obj.name).toBe('widget1'); // 在返回代理上调用构造函数 const Widget = await factory.Widget; const widget = await new Widget(123); const id = await widget.getId(); expect(id).toBe(123); }); it('should support getService().method({fn: callback}) pattern', async () => { // 核心场景:链式调用 + 回调 const { client } = createRpcPair({ getService () { return { execute (options: { onProgress: (p: number) => void; onComplete: (r: string) => void; }) { options.onProgress(25); options.onProgress(50); options.onProgress(100); options.onComplete('done'); return 'success'; }, process (data: { transformer: (x: number) => number; }) { return data.transformer(10); }, }; }, }); const service = await client.getService(); const progressValues: number[] = []; let completedWith = ''; const result = await service.execute({ onProgress: (p: number) => { progressValues.push(p); }, onComplete: (r: string) => { completedWith = r; }, }); expect(result).toBe('success'); expect(progressValues).toEqual([25, 50, 100]); expect(completedWith).toBe('done'); // 测试返回值的回调 const transformed = await service.process({ transformer: (x: number) => x * 3, }); expect(transformed).toBe(30); }); it('should support deep chain call with callbacks at any level', async () => { const { client } = createRpcPair({ getApi () { return { getModule () { return { getHandler () { return { handle (input: number, callbacks: { onSuccess: (r: number) => void; onError: (e: string) => void; }) { if (input > 0) { callbacks.onSuccess(input * 2); } else { callbacks.onError('negative input'); } return input > 0; }, }; }, }; }, }; }, }); const api = await client.getApi(); const module = await api.getModule(); const handler = await module.getHandler(); let successResult = 0; const isSuccess = await handler.handle(5, { onSuccess: (r: number) => { successResult = r; }, onError: () => { }, }); expect(isSuccess).toBe(true); expect(successResult).toBe(10); let errorMsg = ''; const isFailed = await handler.handle(-1, { onSuccess: () => { }, onError: (e: string) => { errorMsg = e; }, }); expect(isFailed).toBe(false); expect(errorMsg).toBe('negative input'); }); it('should support register pattern with multiple callbacks', async () => { const { client } = createRpcPair({ createEventEmitter () { const listeners: Map void>> = new Map(); return { register (events: Record void>) { for (const [name, handler] of Object.entries(events)) { if (!listeners.has(name)) listeners.set(name, []); listeners.get(name)!.push(handler); } }, emit (name: string, ...args: unknown[]) { const handlers = listeners.get(name) ?? []; handlers.forEach(h => h(...args)); }, }; }, }); const emitter = await client.createEventEmitter(); const events: Array<{ type: string; data: unknown; }> = []; await emitter.register({ data: (value: unknown) => events.push({ type: 'data', data: value }), error: (err: unknown) => events.push({ type: 'error', data: err }), complete: () => events.push({ type: 'complete', data: null }), }); await emitter.emit('data', 'hello'); await emitter.emit('data', 'world'); await emitter.emit('error', 'oops'); await emitter.emit('complete'); expect(events).toEqual([ { type: 'data', data: 'hello' }, { type: 'data', data: 'world' }, { type: 'error', data: 'oops' }, { type: 'complete', data: null }, ]); }); it('should handle array return values with proxy', async () => { // 通过 getService 返回的类实例来测试数组代理 class ItemService { getItems () { return [ { id: 1, name: 'item1' }, { id: 2, name: 'item2' }, ]; } getItem (index: number) { return { id: index, getValue: () => `item${index}` }; } } const { client } = createRpcPair({ getItemService () { return new ItemService(); }, }); const service = await client.getItemService(); // 通过代理的 service 调用方法获取数组 const items = await service.getItems(); expect(items).toEqual([ { id: 1, name: 'item1' }, { id: 2, name: 'item2' }, ]); // 获取单个 item 作为代理(因为它有方法) const item = await service.getItem(3); expect(await item.getValue()).toBe('item3'); }); }); });