mirror of
https://github.com/nestjs/nest.git
synced 2026-10-28 09:45:43 +08:00
759 lines
23 KiB
TypeScript
759 lines
23 KiB
TypeScript
import {
|
|
throwError as _throw,
|
|
lastValueFrom,
|
|
Observable,
|
|
of,
|
|
Subject,
|
|
} from 'rxjs';
|
|
import { BaseRpcContext } from '../../ctx-host/base-rpc.context.js';
|
|
import { InvalidPacketIdException } from '../../errors/invalid-packet-id.exception.js';
|
|
import { Server } from '../../server/server.js';
|
|
|
|
class TestServer extends Server {
|
|
public on<
|
|
EventKey extends string = string,
|
|
EventCallback extends Function = Function,
|
|
>(event: EventKey, callback: EventCallback) {}
|
|
public unwrap<T>(): T {
|
|
return null!;
|
|
}
|
|
public listen(callback: () => void) {}
|
|
public close() {}
|
|
}
|
|
|
|
describe('Server', () => {
|
|
const server = new TestServer();
|
|
const untypedServer = server as any;
|
|
const callback = () => {},
|
|
pattern = { test: 'test pattern' };
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('addHandler', () => {
|
|
it(`should add handler`, () => {
|
|
const handlerRoute = 'hello';
|
|
untypedServer.messageHandlers = new Map();
|
|
|
|
const messageHandlersSetSpy = vi.spyOn(
|
|
untypedServer.messageHandlers,
|
|
'set',
|
|
);
|
|
const normalizePatternStub = vi
|
|
.spyOn(server as any, 'normalizePattern')
|
|
.mockReturnValue(handlerRoute);
|
|
|
|
server.addHandler(pattern, callback as any);
|
|
|
|
expect(messageHandlersSetSpy).toHaveBeenCalled();
|
|
expect(messageHandlersSetSpy.mock.calls[0][0]).toBe(handlerRoute);
|
|
expect(messageHandlersSetSpy.mock.calls[0][1]).toBe(callback);
|
|
|
|
normalizePatternStub.mockRestore();
|
|
});
|
|
describe('when handler is an event handler', () => {
|
|
describe('and there are other handlers registered for the pattern already', () => {
|
|
it('should find tail and assign a handler ref to it', () => {
|
|
const handlerRoute = 'hello';
|
|
const headHandler: any = () => null;
|
|
const nextHandler: any = () => null;
|
|
|
|
headHandler.next = nextHandler;
|
|
untypedServer['messageHandlers'] = new Map([
|
|
[handlerRoute, headHandler],
|
|
]);
|
|
const normalizePatternStub = vi
|
|
.spyOn(server as any, 'normalizePattern')
|
|
.mockReturnValue(handlerRoute);
|
|
|
|
server.addHandler(pattern, callback as any, true);
|
|
|
|
expect(nextHandler.next).toBe(callback);
|
|
normalizePatternStub.mockRestore();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getRouteFromPattern', () => {
|
|
let normalizePatternStub: ReturnType<typeof vi.fn>;
|
|
|
|
beforeEach(() => {
|
|
normalizePatternStub = vi
|
|
.spyOn(server as any, 'normalizePattern')
|
|
.mockImplementation(() => ({}) as any);
|
|
});
|
|
|
|
afterEach(() => {
|
|
normalizePatternStub.mockRestore();
|
|
});
|
|
|
|
describe(`when gets 'string' pattern`, () => {
|
|
it(`should call 'transformPatternToRoute' with 'string' argument`, () => {
|
|
const inputServerPattern = 'hello';
|
|
const transformedServerPattern = inputServerPattern;
|
|
untypedServer.getRouteFromPattern(inputServerPattern);
|
|
|
|
expect(normalizePatternStub.mock.calls[0][0]).toBe(
|
|
transformedServerPattern,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(`when gets 'json' pattern as 'string'`, () => {
|
|
it(`should call 'transformPatternToRoute' with 'json' argument`, () => {
|
|
const inputServerPattern = '{"controller":"app","use":"getHello"}';
|
|
const transformedServerPattern = {
|
|
controller: 'app',
|
|
use: 'getHello',
|
|
};
|
|
untypedServer.getRouteFromPattern(inputServerPattern);
|
|
|
|
expect(normalizePatternStub.mock.calls[0][0]).toEqual(
|
|
transformedServerPattern,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe(`when gets a 'string' pattern that is not 'json'`, () => {
|
|
it(`should not parse it`, () => {
|
|
const parseSpy = vi.spyOn(JSON, 'parse');
|
|
untypedServer.getRouteFromPattern('user.created');
|
|
untypedServer.getRouteFromPattern('notify');
|
|
untypedServer.getRouteFromPattern(' trueish');
|
|
|
|
expect(parseSpy).not.toHaveBeenCalled();
|
|
expect(normalizePatternStub.mock.calls.map(([arg]) => arg)).toEqual([
|
|
'user.created',
|
|
'notify',
|
|
' trueish',
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe(`when gets a 'string' pattern that may be 'json'`, () => {
|
|
it(`should call 'transformPatternToRoute' with what 'JSON.parse' returns`, () => {
|
|
const patterns = [
|
|
'42',
|
|
'-1',
|
|
' true ',
|
|
'false',
|
|
'null',
|
|
'"hello"',
|
|
'[1]',
|
|
];
|
|
patterns.forEach(item => untypedServer.getRouteFromPattern(item));
|
|
|
|
expect(normalizePatternStub.mock.calls.map(([arg]) => arg)).toEqual([
|
|
42,
|
|
-1,
|
|
true,
|
|
false,
|
|
null,
|
|
'hello',
|
|
[1],
|
|
]);
|
|
});
|
|
|
|
it(`should call 'transformPatternToRoute' with the 'string' argument when it is not 'json'`, () => {
|
|
const patterns = ['2fa.verify', '{"cmd":', '"hello', 'null.created'];
|
|
patterns.forEach(item => untypedServer.getRouteFromPattern(item));
|
|
|
|
expect(normalizePatternStub.mock.calls.map(([arg]) => arg)).toEqual(
|
|
patterns,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('send', () => {
|
|
let stream$: Observable<string>;
|
|
let sendSpy: ReturnType<typeof vi.fn>;
|
|
beforeEach(() => {
|
|
stream$ = of('test');
|
|
});
|
|
describe('when stream', () => {
|
|
beforeEach(() => {
|
|
sendSpy = vi.fn();
|
|
});
|
|
describe('throws exception', () => {
|
|
beforeEach(() => {
|
|
server.send(_throw(() => 'test') as any, sendSpy);
|
|
});
|
|
it('should send error and complete', async () => {
|
|
await new Promise<void>(resolve => process.nextTick(resolve));
|
|
expect(sendSpy).toHaveBeenCalledWith({
|
|
err: 'test',
|
|
isDisposed: true,
|
|
});
|
|
});
|
|
});
|
|
describe('emits response', () => {
|
|
beforeEach(() => {
|
|
server.send(stream$, sendSpy);
|
|
});
|
|
it('should send response and "complete" event', async () => {
|
|
await new Promise<void>(resolve => process.nextTick(resolve));
|
|
expect(sendSpy).toHaveBeenCalledWith({
|
|
response: 'test',
|
|
isDisposed: true,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
describe('when respond rejects', () => {
|
|
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
|
|
it('should log the error and still publish the replies queued behind it', async () => {
|
|
const error = new Error('publish failed');
|
|
const loggerErrorSpy = vi
|
|
.spyOn(untypedServer.logger, 'error')
|
|
.mockImplementation(() => {});
|
|
sendSpy = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(error)
|
|
.mockResolvedValue(undefined);
|
|
|
|
server.send(of('first', 'second'), sendSpy);
|
|
await flush();
|
|
|
|
expect(loggerErrorSpy).toHaveBeenCalledExactlyOnceWith(error);
|
|
expect(sendSpy).toHaveBeenCalledTimes(2);
|
|
expect(sendSpy).toHaveBeenLastCalledWith({
|
|
response: 'second',
|
|
isDisposed: true,
|
|
});
|
|
});
|
|
|
|
it('should keep draining replies that arrive after a failure', async () => {
|
|
vi.spyOn(untypedServer.logger, 'error').mockImplementation(() => {});
|
|
sendSpy = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('publish failed'))
|
|
.mockResolvedValue(undefined);
|
|
const subject = new Subject<string>();
|
|
|
|
server.send(subject, sendSpy);
|
|
subject.next('first');
|
|
await flush();
|
|
subject.next('second');
|
|
await flush();
|
|
|
|
expect(sendSpy).toHaveBeenCalledTimes(2);
|
|
expect(sendSpy).toHaveBeenLastCalledWith({ response: 'second' });
|
|
});
|
|
});
|
|
describe('when the final reply cannot be published', () => {
|
|
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
let loggerErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
|
|
beforeEach(() => {
|
|
loggerErrorSpy = vi
|
|
.spyOn(untypedServer.logger, 'error')
|
|
.mockImplementation(() => {});
|
|
});
|
|
|
|
it('should answer the request with an internal server error', async () => {
|
|
const error = new TypeError('Do not know how to serialize a BigInt');
|
|
sendSpy = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(error)
|
|
.mockResolvedValue(undefined);
|
|
|
|
server.send(of({ id: 1n }), sendSpy);
|
|
await flush();
|
|
|
|
expect(loggerErrorSpy).toHaveBeenCalledExactlyOnceWith(error);
|
|
expect(sendSpy.mock.calls).toEqual([
|
|
[{ response: { id: 1n }, isDisposed: true }],
|
|
[
|
|
{
|
|
err: { status: 'error', message: 'Internal server error' },
|
|
isDisposed: true,
|
|
},
|
|
],
|
|
]);
|
|
});
|
|
|
|
it('should answer with an internal server error when an error reply cannot be published', async () => {
|
|
sendSpy = vi
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('publish failed'))
|
|
.mockResolvedValue(undefined);
|
|
|
|
server.send(
|
|
_throw(() => ({ id: 1n })),
|
|
sendSpy,
|
|
);
|
|
await flush();
|
|
|
|
expect(loggerErrorSpy).toHaveBeenCalledOnce();
|
|
expect(sendSpy.mock.calls).toEqual([
|
|
[{ err: { id: 1n }, isDisposed: true }],
|
|
[
|
|
{
|
|
err: { status: 'error', message: 'Internal server error' },
|
|
isDisposed: true,
|
|
},
|
|
],
|
|
]);
|
|
});
|
|
|
|
it('should answer only once when the internal server error cannot be published either', async () => {
|
|
sendSpy = vi.fn().mockRejectedValue(new Error('publish failed'));
|
|
|
|
server.send(of('test'), sendSpy);
|
|
await flush();
|
|
|
|
expect(loggerErrorSpy).toHaveBeenCalledTimes(2);
|
|
expect(sendSpy).toHaveBeenCalledTimes(2);
|
|
expect(sendSpy).toHaveBeenLastCalledWith({
|
|
err: { status: 'error', message: 'Internal server error' },
|
|
isDisposed: true,
|
|
});
|
|
});
|
|
});
|
|
describe('when the stream emits many values', () => {
|
|
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
|
|
it('should not shift each reply off the queue', async () => {
|
|
// Each shift() moves the replies queued behind the one it removes, so
|
|
// draining a stream of n replies that way takes time quadratic in n
|
|
// Patched by hand, as vi.spyOn() calls shift() itself
|
|
const shift = Array.prototype.shift;
|
|
const shifted: unknown[] = [];
|
|
Array.prototype.shift = function (this: unknown[]) {
|
|
const item = shift.call(this);
|
|
shifted.push(item);
|
|
return item;
|
|
};
|
|
sendSpy = vi.fn();
|
|
try {
|
|
server.send(of('first', 'second', 'third'), sendSpy);
|
|
await flush();
|
|
} finally {
|
|
Array.prototype.shift = shift;
|
|
}
|
|
|
|
expect(sendSpy.mock.calls).toEqual([
|
|
[{ response: 'first' }],
|
|
[{ response: 'second' }],
|
|
[{ response: 'third', isDisposed: true }],
|
|
]);
|
|
expect(shifted).not.toContainEqual({ response: 'first' });
|
|
});
|
|
|
|
it('should send the completion on its own once the last reply is out', async () => {
|
|
let sent: () => void = () => {};
|
|
sendSpy = vi
|
|
.fn()
|
|
.mockImplementationOnce(
|
|
() => new Promise<void>(resolve => (sent = resolve)),
|
|
)
|
|
.mockResolvedValue(undefined);
|
|
const subject = new Subject<string>();
|
|
|
|
server.send(subject, sendSpy);
|
|
subject.next('first');
|
|
await flush();
|
|
subject.complete();
|
|
sent();
|
|
await flush();
|
|
|
|
expect(sendSpy.mock.calls).toEqual([
|
|
[{ response: 'first' }],
|
|
[{ isDisposed: true }],
|
|
]);
|
|
});
|
|
});
|
|
});
|
|
describe('transformToObservable', () => {
|
|
describe('when resultOrDeferred', () => {
|
|
describe('is Promise', () => {
|
|
it('should return Observable that emits the resolved value of the supplied promise', async () => {
|
|
const value = 100;
|
|
expect(
|
|
await lastValueFrom(
|
|
server.transformToObservable(Promise.resolve(value)),
|
|
),
|
|
).toBe(100);
|
|
});
|
|
});
|
|
describe('is Observable', () => {
|
|
it('should return the observable itself', async () => {
|
|
const value = 100;
|
|
expect(
|
|
await lastValueFrom(server.transformToObservable(of(value))),
|
|
).toBe(100);
|
|
});
|
|
});
|
|
describe('is any number', () => {
|
|
it('should return Observable that emits the supplied number', async () => {
|
|
const value = 100;
|
|
expect(await lastValueFrom(server.transformToObservable(value))).toBe(
|
|
100,
|
|
);
|
|
});
|
|
});
|
|
describe('is an array', () => {
|
|
it('should return Observable that emits the supplied array', async () => {
|
|
const value = [1, 2, 3];
|
|
expect(await lastValueFrom(server.transformToObservable(value))).toBe(
|
|
value,
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getHandlers', () => {
|
|
it('should return registered handlers', () => {
|
|
const messageHandlers = [() => null, () => true];
|
|
const original = untypedServer.messageHandlers;
|
|
untypedServer.messageHandlers = messageHandlers;
|
|
expect(server.getHandlers()).toBe(messageHandlers);
|
|
untypedServer.messageHandlers = original;
|
|
});
|
|
});
|
|
|
|
describe('assertValidPacketId', () => {
|
|
it('should accept a number or a string of up to 256 characters', () => {
|
|
for (const id of [1, 'a'.repeat(256)]) {
|
|
expect(() => untypedServer.assertValidPacketId(id)).not.toThrow();
|
|
}
|
|
});
|
|
it('should reject any other id', () => {
|
|
for (const id of ['a'.repeat(257), [['a']], { id: 'a' }, null, true]) {
|
|
expect(() => untypedServer.assertValidPacketId(id)).toThrow(
|
|
InvalidPacketIdException,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('getHandlerByPattern', () => {
|
|
let messageHandlersGetSpy: ReturnType<typeof vi.spyOn>;
|
|
let messageHandlersHasSpy: ReturnType<typeof vi.spyOn>;
|
|
let originalMessageHandlers: any;
|
|
const handlerRoute = 'hello';
|
|
|
|
beforeEach(() => {
|
|
originalMessageHandlers = untypedServer.messageHandlers;
|
|
untypedServer.messageHandlers = new Map();
|
|
messageHandlersGetSpy = vi
|
|
.spyOn(untypedServer.messageHandlers, 'get')
|
|
.mockReturnValue(callback);
|
|
messageHandlersHasSpy = vi
|
|
.spyOn(untypedServer.messageHandlers, 'has')
|
|
.mockReturnValue(false);
|
|
|
|
vi.spyOn(server as any, 'getRouteFromPattern').mockReturnValue(
|
|
handlerRoute,
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
messageHandlersGetSpy.mockRestore();
|
|
messageHandlersHasSpy.mockRestore();
|
|
untypedServer.messageHandlers = originalMessageHandlers;
|
|
});
|
|
|
|
describe('when handler exists', () => {
|
|
it('should return expected handler', () => {
|
|
messageHandlersHasSpy.mockReturnValue(true);
|
|
|
|
const value = server.getHandlerByPattern(handlerRoute);
|
|
|
|
expect(messageHandlersHasSpy.mock.calls[0][0]).toBe(handlerRoute);
|
|
expect(messageHandlersGetSpy).toHaveBeenCalled();
|
|
expect(messageHandlersGetSpy.mock.calls[0][0]).toBe(handlerRoute);
|
|
expect(value).toBe(callback);
|
|
});
|
|
});
|
|
|
|
describe('when handler does not exists', () => {
|
|
it('should return null', () => {
|
|
messageHandlersHasSpy.mockReturnValue(false);
|
|
|
|
const value = server.getHandlerByPattern(handlerRoute);
|
|
|
|
expect(messageHandlersHasSpy.mock.calls[0][0]).toBe(handlerRoute);
|
|
expect(messageHandlersGetSpy).not.toHaveBeenCalled();
|
|
expect(value).toBeNull();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('handleEvent', () => {
|
|
const context = new BaseRpcContext([]);
|
|
const eventPattern = 'test_event';
|
|
|
|
function bindHandler(result: unknown) {
|
|
const endHook = vi.fn();
|
|
untypedServer.onProcessingStartHook = (
|
|
_transportId: unknown,
|
|
_ctx: unknown,
|
|
fn: () => Promise<void>,
|
|
) => fn();
|
|
untypedServer.onProcessingEndHook = endHook;
|
|
untypedServer.messageHandlers = new Map([
|
|
[
|
|
eventPattern,
|
|
Object.assign(async () => result, { isEventHandler: true }),
|
|
],
|
|
]);
|
|
return endHook;
|
|
}
|
|
|
|
it('should log an error if no event handler exists', async () => {
|
|
const loggerErrorSpy = vi
|
|
.spyOn(untypedServer.logger, 'error')
|
|
.mockImplementation(() => {});
|
|
untypedServer.messageHandlers = new Map();
|
|
|
|
await server.handleEvent(
|
|
'unknown_event',
|
|
{ pattern: 'unknown_event', data: null },
|
|
context,
|
|
);
|
|
|
|
expect(loggerErrorSpy).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook when the handler returns a plain value', async () => {
|
|
const endHook = bindHandler('plain');
|
|
|
|
await server.handleEvent(
|
|
eventPattern,
|
|
{ pattern: eventPattern, data: null },
|
|
context,
|
|
);
|
|
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook when the returned stream completes', async () => {
|
|
const endHook = bindHandler(of('streamed'));
|
|
|
|
await server.handleEvent(
|
|
eventPattern,
|
|
{ pattern: eventPattern, data: null },
|
|
context,
|
|
);
|
|
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook when the returned stream fails', async () => {
|
|
const endHook = bindHandler(_throw(() => new Error('failed')));
|
|
|
|
await server.handleEvent(
|
|
eventPattern,
|
|
{ pattern: eventPattern, data: null },
|
|
context,
|
|
);
|
|
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook when the handler throws an error', async () => {
|
|
const endHook = vi.fn();
|
|
untypedServer.onProcessingStartHook = (
|
|
_transportId: unknown,
|
|
_ctx: unknown,
|
|
fn: () => Promise<void>,
|
|
) => fn();
|
|
untypedServer.onProcessingEndHook = endHook;
|
|
untypedServer.messageHandlers = new Map([
|
|
[
|
|
eventPattern,
|
|
Object.assign(
|
|
async () => {
|
|
throw new Error('handler failed');
|
|
},
|
|
{ isEventHandler: true },
|
|
),
|
|
],
|
|
]);
|
|
|
|
await expect(
|
|
server.handleEvent(
|
|
eventPattern,
|
|
{ pattern: eventPattern, data: null },
|
|
context,
|
|
),
|
|
).rejects.toThrow('handler failed');
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook once per event', async () => {
|
|
const endHook = bindHandler(
|
|
new Observable(subscriber => {
|
|
subscriber.next('first');
|
|
subscriber.next('second');
|
|
subscriber.complete();
|
|
}),
|
|
);
|
|
|
|
await server.handleEvent(
|
|
eventPattern,
|
|
{ pattern: eventPattern, data: null },
|
|
context,
|
|
);
|
|
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe('handleRequest', () => {
|
|
const context = new BaseRpcContext([]);
|
|
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
|
|
function bindHooks() {
|
|
const endHook = vi.fn();
|
|
untypedServer.onProcessingStartHook = (
|
|
_transportId: unknown,
|
|
_ctx: unknown,
|
|
fn: () => Promise<void>,
|
|
) => fn();
|
|
untypedServer.onProcessingEndHook = endHook;
|
|
return endHook;
|
|
}
|
|
|
|
it('should reply with every value and run the end hook once', async () => {
|
|
const endHook = bindHooks();
|
|
const respond = vi.fn();
|
|
|
|
await untypedServer.handleRequest(
|
|
context,
|
|
async () => of('first', 'second'),
|
|
respond,
|
|
);
|
|
await flush();
|
|
|
|
expect(respond).toHaveBeenCalledTimes(2);
|
|
expect(respond).toHaveBeenLastCalledWith({
|
|
response: 'second',
|
|
isDisposed: true,
|
|
});
|
|
expect(endHook).toHaveBeenCalledExactlyOnceWith(
|
|
server.transportId,
|
|
context,
|
|
);
|
|
});
|
|
|
|
it('should run the end hook once when the response stream fails', async () => {
|
|
const endHook = bindHooks();
|
|
const respond = vi.fn();
|
|
const error = new Error('failed');
|
|
|
|
await untypedServer.handleRequest(
|
|
context,
|
|
async () => _throw(() => error),
|
|
respond,
|
|
);
|
|
await flush();
|
|
|
|
expect(respond).toHaveBeenCalledExactlyOnceWith({
|
|
err: error,
|
|
isDisposed: true,
|
|
});
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should run the end hook once and pass the rejection on when "produce" rejects', async () => {
|
|
const endHook = bindHooks();
|
|
const respond = vi.fn();
|
|
|
|
await expect(
|
|
untypedServer.handleRequest(
|
|
context,
|
|
async () => {
|
|
throw new Error('handler failed');
|
|
},
|
|
respond,
|
|
),
|
|
).rejects.toThrow('handler failed');
|
|
await flush();
|
|
|
|
expect(respond).not.toHaveBeenCalled();
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe('warnIfEventHandlerReplies', () => {
|
|
let warnSpy: ReturnType<typeof vi.spyOn>;
|
|
|
|
beforeEach(() => {
|
|
warnSpy = vi
|
|
.spyOn(untypedServer.logger, 'warn')
|
|
.mockImplementation(() => {});
|
|
});
|
|
|
|
it('should warn once per event handler that replying to requests is deprecated', () => {
|
|
const created = Object.assign(async () => null, { isEventHandler: true });
|
|
const deleted = Object.assign(async () => null, { isEventHandler: true });
|
|
|
|
untypedServer.warnIfEventHandlerReplies(created, 'user.created');
|
|
untypedServer.warnIfEventHandlerReplies(created, 'user.created');
|
|
untypedServer.warnIfEventHandlerReplies(deleted, 'user.deleted');
|
|
|
|
expect(warnSpy).toHaveBeenCalledTimes(2);
|
|
expect(warnSpy.mock.calls[0][0]).toMatch(
|
|
/^DEPRECATED! .*@EventPattern handlers will not reply to "send\(\)".* Pattern: user\.created$/,
|
|
);
|
|
expect(warnSpy.mock.calls[1][0]).toMatch(/Pattern: user\.deleted$/);
|
|
});
|
|
|
|
it('should not warn for a message handler', () => {
|
|
untypedServer.warnIfEventHandlerReplies(async () => null, 'user.get');
|
|
|
|
expect(warnSpy).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('runWithProcessingHooks', () => {
|
|
const context = new BaseRpcContext([]);
|
|
|
|
it('should run the end hook once when "fn" both runs it and rejects', async () => {
|
|
const endHook = vi.fn();
|
|
untypedServer.onProcessingEndHook = endHook;
|
|
|
|
await expect(
|
|
untypedServer.runWithProcessingHooks(
|
|
context,
|
|
async (runEndHook: () => void) => {
|
|
runEndHook();
|
|
throw new Error('late failure');
|
|
},
|
|
),
|
|
).rejects.toThrow('late failure');
|
|
|
|
expect(endHook).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('should pass the start and end hook contexts separately', async () => {
|
|
const startHook = vi.fn(
|
|
(_transportId: unknown, _ctx: unknown, fn: () => Promise<void>) => fn(),
|
|
);
|
|
const endHook = vi.fn();
|
|
const endHookContext = new BaseRpcContext(['end']);
|
|
untypedServer.onProcessingStartHook = startHook;
|
|
untypedServer.onProcessingEndHook = endHook;
|
|
|
|
await untypedServer.runWithProcessingHooks(
|
|
context,
|
|
async (runEndHook: () => void) => runEndHook(),
|
|
endHookContext,
|
|
);
|
|
|
|
expect(startHook.mock.calls[0][1]).toBe(context);
|
|
expect(endHook).toHaveBeenCalledExactlyOnceWith(
|
|
server.transportId,
|
|
endHookContext,
|
|
);
|
|
});
|
|
});
|
|
});
|