fix: various fixes to sentry-reported errors and more
This commit is contained in:
@@ -17,11 +17,11 @@
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createChannelID, createGuildID, createUserID} from '@fluxer/api/src/BrandedTypes';
|
||||
import {createChannelID, createGuildID, createUserID, type GuildID} from '@fluxer/api/src/BrandedTypes';
|
||||
import {GatewayRpcClient} from '@fluxer/api/src/infrastructure/GatewayRpcClient';
|
||||
import {GatewayRpcMethodErrorCodes} from '@fluxer/api/src/infrastructure/GatewayRpcError';
|
||||
import {GatewayService} from '@fluxer/api/src/infrastructure/GatewayService';
|
||||
import {createGatewayRpcMethodErrorHandler} from '@fluxer/api/src/test/msw/handlers/GatewayRpcHandlers';
|
||||
import {server} from '@fluxer/api/src/test/msw/server';
|
||||
import {MockGatewayRpcTransport} from '@fluxer/api/src/test/mocks/MockGatewayRpcTransport';
|
||||
import {CallAlreadyExistsError} from '@fluxer/errors/src/domains/channel/CallAlreadyExistsError';
|
||||
import {InvalidChannelTypeForCallError} from '@fluxer/errors/src/domains/channel/InvalidChannelTypeForCallError';
|
||||
import {NoActiveCallError} from '@fluxer/errors/src/domains/channel/NoActiveCallError';
|
||||
@@ -32,34 +32,30 @@ import {MissingPermissionsError} from '@fluxer/errors/src/domains/core/MissingPe
|
||||
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
||||
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';
|
||||
import {UserNotInVoiceError} from '@fluxer/errors/src/domains/user/UserNotInVoiceError';
|
||||
import {afterAll, afterEach, beforeAll, beforeEach, describe, expect, it} from 'vitest';
|
||||
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
|
||||
|
||||
describe('GatewayRpcService Error Handling', () => {
|
||||
const TEST_GUILD_ID = createGuildID(123456789n);
|
||||
const TEST_USER_ID = createUserID(987654321n);
|
||||
const TEST_CHANNEL_ID = createChannelID(111222333n);
|
||||
|
||||
let mockTransport: MockGatewayRpcTransport;
|
||||
let gatewayService: GatewayService;
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen({onUnhandledRequest: 'error'});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockTransport = new MockGatewayRpcTransport();
|
||||
GatewayRpcClient.createForTests(mockTransport);
|
||||
gatewayService = new GatewayService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
afterEach(async () => {
|
||||
gatewayService.destroy();
|
||||
await GatewayRpcClient.resetForTests();
|
||||
mockTransport.reset();
|
||||
});
|
||||
|
||||
it('transforms guild_not_found RPC error to UnknownGuildError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_data', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND));
|
||||
mockTransport.setMethodError('guild.get_data', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND);
|
||||
|
||||
await expect(
|
||||
gatewayService.getGuildData({
|
||||
@@ -70,7 +66,7 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('transforms forbidden RPC error to MissingPermissionsError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_data', GatewayRpcMethodErrorCodes.FORBIDDEN));
|
||||
mockTransport.setMethodError('guild.get_data', GatewayRpcMethodErrorCodes.FORBIDDEN);
|
||||
|
||||
await expect(
|
||||
gatewayService.getGuildData({
|
||||
@@ -81,13 +77,13 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('transforms guild_not_found RPC error to UnknownGuildError for non-batched calls', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_counts', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND));
|
||||
mockTransport.setMethodError('guild.get_counts', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND);
|
||||
|
||||
await expect(gatewayService.getGuildCounts(TEST_GUILD_ID)).rejects.toThrow(UnknownGuildError);
|
||||
});
|
||||
|
||||
it('transforms call_already_exists RPC error to CallAlreadyExistsError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('call.create', GatewayRpcMethodErrorCodes.CALL_ALREADY_EXISTS));
|
||||
mockTransport.setMethodError('call.create', GatewayRpcMethodErrorCodes.CALL_ALREADY_EXISTS);
|
||||
|
||||
await expect(gatewayService.createCall(TEST_CHANNEL_ID, '123', 'us-east', [], [])).rejects.toThrow(
|
||||
CallAlreadyExistsError,
|
||||
@@ -95,27 +91,25 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('transforms call_not_found RPC error to NoActiveCallError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('call.delete', GatewayRpcMethodErrorCodes.CALL_NOT_FOUND));
|
||||
mockTransport.setMethodError('call.delete', GatewayRpcMethodErrorCodes.CALL_NOT_FOUND);
|
||||
|
||||
await expect(gatewayService.deleteCall(TEST_CHANNEL_ID)).rejects.toThrow(NoActiveCallError);
|
||||
});
|
||||
|
||||
it('transforms channel_not_found RPC error to UnknownChannelError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('call.get', GatewayRpcMethodErrorCodes.CHANNEL_NOT_FOUND));
|
||||
mockTransport.setMethodError('call.get', GatewayRpcMethodErrorCodes.CHANNEL_NOT_FOUND);
|
||||
|
||||
await expect(gatewayService.getCall(TEST_CHANNEL_ID)).rejects.toThrow(UnknownChannelError);
|
||||
});
|
||||
|
||||
it('transforms channel_not_voice RPC error to InvalidChannelTypeForCallError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('call.get', GatewayRpcMethodErrorCodes.CHANNEL_NOT_VOICE));
|
||||
mockTransport.setMethodError('call.get', GatewayRpcMethodErrorCodes.CHANNEL_NOT_VOICE);
|
||||
|
||||
await expect(gatewayService.getCall(TEST_CHANNEL_ID)).rejects.toThrow(InvalidChannelTypeForCallError);
|
||||
});
|
||||
|
||||
it('transforms user_not_in_voice RPC error to UserNotInVoiceError', async () => {
|
||||
server.use(
|
||||
createGatewayRpcMethodErrorHandler('guild.update_member_voice', GatewayRpcMethodErrorCodes.USER_NOT_IN_VOICE),
|
||||
);
|
||||
mockTransport.setMethodError('guild.update_member_voice', GatewayRpcMethodErrorCodes.USER_NOT_IN_VOICE);
|
||||
|
||||
await expect(
|
||||
gatewayService.updateMemberVoice({
|
||||
@@ -128,7 +122,7 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('transforms timeout RPC error to GatewayTimeoutError', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_data', GatewayRpcMethodErrorCodes.TIMEOUT));
|
||||
mockTransport.setMethodError('guild.get_data', GatewayRpcMethodErrorCodes.TIMEOUT);
|
||||
|
||||
await expect(
|
||||
gatewayService.getGuildData({
|
||||
@@ -139,7 +133,7 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('does not open circuit breaker for mapped gateway business errors', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_data', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND));
|
||||
mockTransport.setMethodError('guild.get_data', GatewayRpcMethodErrorCodes.GUILD_NOT_FOUND);
|
||||
|
||||
for (let attempt = 0; attempt < 6; attempt += 1) {
|
||||
await expect(
|
||||
@@ -152,7 +146,7 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
});
|
||||
|
||||
it('opens circuit breaker for repeated gateway internal errors', async () => {
|
||||
server.use(createGatewayRpcMethodErrorHandler('guild.get_data', GatewayRpcMethodErrorCodes.INTERNAL_ERROR));
|
||||
mockTransport.setMethodError('guild.get_data', GatewayRpcMethodErrorCodes.INTERNAL_ERROR);
|
||||
|
||||
for (let attempt = 0; attempt < 5; attempt += 1) {
|
||||
await expect(
|
||||
@@ -170,4 +164,47 @@ describe('GatewayRpcService Error Handling', () => {
|
||||
}),
|
||||
).rejects.toThrow(ServiceUnavailableError);
|
||||
});
|
||||
|
||||
it('parses both member_count and online_count from getDiscoveryGuildCounts', async () => {
|
||||
const guildIdA = createGuildID(100n);
|
||||
const guildIdB = createGuildID(200n);
|
||||
|
||||
mockTransport.setMethodResult('guild.get_online_counts_batch', {
|
||||
online_counts: [
|
||||
{guild_id: '100', member_count: 500, online_count: 42},
|
||||
{guild_id: '200', member_count: 1200, online_count: 300},
|
||||
],
|
||||
});
|
||||
|
||||
const counts = await gatewayService.getDiscoveryGuildCounts([guildIdA, guildIdB]);
|
||||
|
||||
expect(counts.size).toBe(2);
|
||||
expect(counts.get(guildIdA)).toEqual({memberCount: 500, onlineCount: 42});
|
||||
expect(counts.get(guildIdB)).toEqual({memberCount: 1200, onlineCount: 300});
|
||||
|
||||
expect(mockTransport.call).toHaveBeenCalledWith('guild.get_online_counts_batch', {
|
||||
guild_ids: ['100', '200'],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty map from getDiscoveryGuildCounts when response has no entries', async () => {
|
||||
mockTransport.setMethodResult('guild.get_online_counts_batch', {
|
||||
online_counts: [],
|
||||
});
|
||||
|
||||
const counts = await gatewayService.getDiscoveryGuildCounts([createGuildID(999n)]);
|
||||
|
||||
expect(counts.size).toBe(0);
|
||||
});
|
||||
|
||||
it('getDiscoveryOnlineCounts still works with member_count present in response', async () => {
|
||||
mockTransport.setMethodResult('guild.get_online_counts_batch', {
|
||||
online_counts: [{guild_id: '100', member_count: 500, online_count: 42}],
|
||||
});
|
||||
|
||||
const counts = await gatewayService.getDiscoveryOnlineCounts([createGuildID(100n)]);
|
||||
|
||||
expect(counts.size).toBe(1);
|
||||
expect(counts.get(100n as GuildID)).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {decodeGatewayTcpFrames, encodeGatewayTcpFrame} from '@fluxer/api/src/infrastructure/GatewayTcpFrameCodec';
|
||||
import {describe, expect, test} from 'vitest';
|
||||
|
||||
describe('GatewayTcpFrameCodec', () => {
|
||||
test('encodes and decodes a single frame', () => {
|
||||
const frame = {type: 'ping'};
|
||||
const encoded = encodeGatewayTcpFrame(frame);
|
||||
const decoded = decodeGatewayTcpFrames(encoded);
|
||||
expect(decoded.frames).toEqual([frame]);
|
||||
expect(decoded.remainder.length).toBe(0);
|
||||
});
|
||||
|
||||
test('decodes multiple frames with trailing partial frame', () => {
|
||||
const frameA = {type: 'request', id: '1', method: 'process.node_stats', params: {}};
|
||||
const frameB = {type: 'pong'};
|
||||
const encodedA = encodeGatewayTcpFrame(frameA);
|
||||
const encodedB = encodeGatewayTcpFrame(frameB);
|
||||
const partial = Buffer.from('5\n{"ty', 'utf8');
|
||||
const combined = Buffer.concat([encodedA, encodedB, partial]);
|
||||
const decoded = decodeGatewayTcpFrames(combined);
|
||||
expect(decoded.frames).toEqual([frameA, frameB]);
|
||||
expect(decoded.remainder.equals(partial)).toBe(true);
|
||||
});
|
||||
|
||||
test('throws on invalid frame length', () => {
|
||||
const invalid = Buffer.from('x\n{}', 'utf8');
|
||||
expect(() => decodeGatewayTcpFrames(invalid)).toThrow('Invalid Gateway TCP frame length');
|
||||
});
|
||||
});
|
||||
@@ -1,309 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {createServer, type Server, type Socket} from 'node:net';
|
||||
import type {ILogger} from '@fluxer/api/src/ILogger';
|
||||
import {decodeGatewayTcpFrames, encodeGatewayTcpFrame} from '@fluxer/api/src/infrastructure/GatewayTcpFrameCodec';
|
||||
import {GatewayTcpRpcTransport} from '@fluxer/api/src/infrastructure/GatewayTcpRpcTransport';
|
||||
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest';
|
||||
|
||||
interface TestTcpServer {
|
||||
server: Server;
|
||||
port: number;
|
||||
}
|
||||
|
||||
interface ConnectionContext {
|
||||
socket: Socket;
|
||||
getBuffer: () => Buffer<ArrayBufferLike>;
|
||||
setBuffer: (buffer: Buffer<ArrayBufferLike>) => void;
|
||||
}
|
||||
|
||||
function createNoopLogger(): ILogger {
|
||||
return {
|
||||
trace: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
fatal: vi.fn(),
|
||||
child: vi.fn(() => createNoopLogger()),
|
||||
};
|
||||
}
|
||||
|
||||
async function startTestServer(
|
||||
handler: (context: ConnectionContext, frame: Record<string, unknown>) => void,
|
||||
): Promise<TestTcpServer> {
|
||||
const server = createServer((socket) => {
|
||||
let buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0);
|
||||
const context: ConnectionContext = {
|
||||
socket,
|
||||
getBuffer: () => buffer,
|
||||
setBuffer: (nextBuffer) => {
|
||||
buffer = nextBuffer;
|
||||
},
|
||||
};
|
||||
|
||||
socket.on('data', (chunk: Buffer<ArrayBufferLike> | string) => {
|
||||
const chunkBuffer = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
|
||||
const combined = Buffer.concat([context.getBuffer(), chunkBuffer]);
|
||||
const decoded = decodeGatewayTcpFrames(combined);
|
||||
context.setBuffer(decoded.remainder);
|
||||
for (const frame of decoded.frames) {
|
||||
handler(context, frame as Record<string, unknown>);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
server.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
throw new Error('Failed to get TCP server address');
|
||||
}
|
||||
|
||||
return {server, port: address.port};
|
||||
}
|
||||
|
||||
async function stopTestServer(server: Server): Promise<void> {
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
function sendFrame(socket: Socket, frame: Record<string, unknown>): void {
|
||||
socket.write(encodeGatewayTcpFrame(frame));
|
||||
}
|
||||
|
||||
describe('GatewayTcpRpcTransport', () => {
|
||||
let tcpServer: TestTcpServer | null = null;
|
||||
let transport: GatewayTcpRpcTransport | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (transport) {
|
||||
await transport.destroy();
|
||||
transport = null;
|
||||
}
|
||||
if (tcpServer) {
|
||||
await stopTestServer(tcpServer.server);
|
||||
tcpServer = null;
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('multiplexes concurrent requests and matches out-of-order responses', async () => {
|
||||
let requestOneId: string | null = null;
|
||||
let requestTwoId: string | null = null;
|
||||
|
||||
tcpServer = await startTestServer((context, frame) => {
|
||||
if (frame.type === 'hello') {
|
||||
sendFrame(context.socket, {
|
||||
type: 'hello_ack',
|
||||
protocol: 'fluxer.rpc.tcp.v1',
|
||||
ping_interval_ms: 60000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (frame.type === 'request') {
|
||||
const requestId = String(frame.id);
|
||||
const method = String(frame.method);
|
||||
if (method === 'first') {
|
||||
requestOneId = requestId;
|
||||
}
|
||||
if (method === 'second') {
|
||||
requestTwoId = requestId;
|
||||
}
|
||||
if (requestOneId && requestTwoId) {
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestTwoId,
|
||||
ok: true,
|
||||
result: 'second-result',
|
||||
});
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestOneId,
|
||||
ok: true,
|
||||
result: 'first-result',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
transport = new GatewayTcpRpcTransport({
|
||||
host: '127.0.0.1',
|
||||
port: tcpServer.port,
|
||||
authorization: 'Bearer test-secret',
|
||||
connectTimeoutMs: 300,
|
||||
requestTimeoutMs: 2000,
|
||||
defaultPingIntervalMs: 60000,
|
||||
logger: createNoopLogger(),
|
||||
});
|
||||
|
||||
const firstPromise = transport.call('first', {});
|
||||
const secondPromise = transport.call('second', {});
|
||||
const [firstResult, secondResult] = await Promise.all([firstPromise, secondPromise]);
|
||||
expect(firstResult).toBe('first-result');
|
||||
expect(secondResult).toBe('second-result');
|
||||
});
|
||||
|
||||
test('reconnects automatically after server-side disconnect', async () => {
|
||||
let connectionCount = 0;
|
||||
let firstRequestSeen = false;
|
||||
|
||||
tcpServer = await startTestServer((context, frame) => {
|
||||
if (frame.type === 'hello') {
|
||||
connectionCount += 1;
|
||||
sendFrame(context.socket, {
|
||||
type: 'hello_ack',
|
||||
protocol: 'fluxer.rpc.tcp.v1',
|
||||
ping_interval_ms: 60000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.type === 'request') {
|
||||
const requestId = String(frame.id);
|
||||
if (!firstRequestSeen) {
|
||||
firstRequestSeen = true;
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestId,
|
||||
ok: true,
|
||||
result: 'first-response',
|
||||
});
|
||||
setTimeout(() => {
|
||||
context.socket.destroy();
|
||||
}, 5);
|
||||
return;
|
||||
}
|
||||
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestId,
|
||||
ok: true,
|
||||
result: 'second-response',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
transport = new GatewayTcpRpcTransport({
|
||||
host: '127.0.0.1',
|
||||
port: tcpServer.port,
|
||||
authorization: 'Bearer test-secret',
|
||||
connectTimeoutMs: 300,
|
||||
requestTimeoutMs: 2000,
|
||||
defaultPingIntervalMs: 60000,
|
||||
logger: createNoopLogger(),
|
||||
});
|
||||
|
||||
const firstResult = await transport.call('first', {});
|
||||
expect(firstResult).toBe('first-response');
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
const secondResult = await transport.call('second', {});
|
||||
expect(secondResult).toBe('second-response');
|
||||
expect(connectionCount).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
test('rejects new requests when pending queue is full', async () => {
|
||||
tcpServer = await startTestServer((context, frame) => {
|
||||
if (frame.type === 'hello') {
|
||||
sendFrame(context.socket, {
|
||||
type: 'hello_ack',
|
||||
protocol: 'fluxer.rpc.tcp.v1',
|
||||
ping_interval_ms: 60000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (frame.type !== 'request') {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = String(frame.id);
|
||||
const method = String(frame.method);
|
||||
if (method === 'first') {
|
||||
setTimeout(() => {
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestId,
|
||||
ok: true,
|
||||
result: 'first-response',
|
||||
});
|
||||
}, 40);
|
||||
return;
|
||||
}
|
||||
|
||||
sendFrame(context.socket, {
|
||||
type: 'response',
|
||||
id: requestId,
|
||||
ok: true,
|
||||
result: 'unexpected-response',
|
||||
});
|
||||
});
|
||||
|
||||
transport = new GatewayTcpRpcTransport({
|
||||
host: '127.0.0.1',
|
||||
port: tcpServer.port,
|
||||
authorization: 'Bearer test-secret',
|
||||
connectTimeoutMs: 300,
|
||||
requestTimeoutMs: 2000,
|
||||
defaultPingIntervalMs: 60000,
|
||||
maxPendingRequests: 1,
|
||||
logger: createNoopLogger(),
|
||||
});
|
||||
|
||||
const firstPromise = transport.call('first', {});
|
||||
await expect(transport.call('second', {})).rejects.toThrow('Gateway TCP request queue is full');
|
||||
await expect(firstPromise).resolves.toBe('first-response');
|
||||
});
|
||||
|
||||
test('closes the connection when the input buffer exceeds limit', async () => {
|
||||
tcpServer = await startTestServer((context, frame) => {
|
||||
if (frame.type === 'hello') {
|
||||
sendFrame(context.socket, {
|
||||
type: 'hello_ack',
|
||||
protocol: 'fluxer.rpc.tcp.v1',
|
||||
ping_interval_ms: 60000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (frame.type === 'request') {
|
||||
context.socket.write(Buffer.from('9999999999999999999999999999999999999999999999999999999999999999999999'));
|
||||
}
|
||||
});
|
||||
|
||||
transport = new GatewayTcpRpcTransport({
|
||||
host: '127.0.0.1',
|
||||
port: tcpServer.port,
|
||||
authorization: 'Bearer test-secret',
|
||||
connectTimeoutMs: 300,
|
||||
requestTimeoutMs: 2000,
|
||||
defaultPingIntervalMs: 60000,
|
||||
maxBufferBytes: 64,
|
||||
logger: createNoopLogger(),
|
||||
});
|
||||
|
||||
await expect(transport.call('overflow', {})).rejects.toThrow('Gateway TCP input buffer exceeded maximum size');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user