Skip to content

Commit

Permalink
fix: handle attempt to send response when port is disconnected
Browse files Browse the repository at this point in the history
  • Loading branch information
fellowseb committed Feb 26, 2024
1 parent 4de9585 commit a302c2c
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 51 deletions.
40 changes: 25 additions & 15 deletions src/Transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,23 +177,33 @@ export class Transport {
// Call local handlers with the request data
callHandlers(data, handlers)
// If the resulting promise is fulfilled, send a response to the far end
.then(response => this._channel.send({
type: 'response',
slotName,
id,
data: response,
param
}))
.then(async (response) => {
await this.autoReconnect()
if (!this.isDisconnected()) {
this._channel.send({
type: 'response',
slotName,
id,
data: response,
param
})
}
})

// If the resulting promise is rejected, send an error to the far end
.catch((error: Error) => this._channel.send({
id,
message: `${error}`,
param,
slotName,
stack: error.stack || '',
type: 'error'
}))
.catch(async (error: Error) => {
await this.autoReconnect()
if (!this.isDisconnected()) {
this._channel.send({
id,
message: `${error}`,
param,
slotName,
stack: error.stack || '',
type: 'error'
})
}
})
}

/**
Expand Down
74 changes: 39 additions & 35 deletions test/TestChannel.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,83 @@
import { ChunkedChannel } from './../src/Channels/ChunkedChannel'
import { GenericChannel } from './../src/Channels/GenericChannel'
import { TransportMessage } from './../src/Message'
import { DEFAULT_PARAM } from './../src/Constants'
import { ChunkedChannel } from "./../src/Channels/ChunkedChannel";
import { GenericChannel } from "./../src/Channels/GenericChannel";
import { TransportMessage } from "./../src/Message";
import { DEFAULT_PARAM } from "./../src/Constants";

export class TestChannel extends GenericChannel {
public sendSpy = jest.fn();

public sendSpy = jest.fn()
public autoReconnectSpy = jest.fn();

public autoReconnectSpy = jest.fn()
constructor(
options: { withAutoReconnect: boolean } = { withAutoReconnect: true }
) {
super();

constructor() {
super()
if (options.withAutoReconnect) {
this.autoReconnect = () => {
this.callConnected();

this.autoReconnectSpy();
};
}
}

/**
* Allows to inspect calls to Channel.send() by a transport
*/

public callConnected() {
this._connected()
this._connected();
}

public callDisconnected() {
this._disconnected()
this._disconnected();
}

public callMessageReceived() {
this._messageReceived({
type: 'error',
slotName: 'test',
type: "error",
slotName: "test",
param: DEFAULT_PARAM,
id: '1',
message: 'error'
})
id: "1",
message: "error",
});
}

public callError() {
this._error(new Error('LOLOL'))
this._error(new Error("LOLOL"));
}

public autoReconnect() {
this.callConnected()

this.autoReconnectSpy()
}
public autoReconnect?: () => void;

public send(message: TransportMessage) {
this.sendSpy(message)
this.sendSpy(message);
}

/**
* Allows to fake reception of messages from the far end
*/
public fakeReceive(message: TransportMessage) {
this._messageReceived(message)
this._messageReceived(message);
}

}

export class TestChunkedChannel extends ChunkedChannel {
public sendSpy = jest.fn()
public dataSpy = jest.fn()
public sendSpy = jest.fn();
public dataSpy = jest.fn();

constructor(chunkSize: number, stringAlloc = -1) {
super({
chunkSize,
sender: null as any,
maxStringAlloc: stringAlloc
})
this._sender = m => {
this.sendSpy(m)
this._messageReceived(m)
}

this.onData(this.dataSpy)
this._connected()
maxStringAlloc: stringAlloc,
});
this._sender = (m) => {
this.sendSpy(m);
this._messageReceived(m);
};

this.onData(this.dataSpy);
this._connected();
}
}
81 changes: 80 additions & 1 deletion test/Transport.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ describe('Transport', () => {
expect(channel.sendSpy).not.toHaveBeenCalledWith({ type: 'handler_unregistered', slotName, param })
})


it('should call the appropriate handler when a request is received', async () => {

const slotName = 'buildCelery'
Expand Down Expand Up @@ -103,6 +102,86 @@ describe('Transport', () => {
})
})

describe('when the channel has autoReconnect capability', () => {
it('should call the handler when a request is received if the channel went disconnected', async () => {

const slotName = 'buildCelery'
const handler = slots[slotName][0]

// Register handler on slot
transport.registerHandler(slotName, param, handler)

const request: TransportMessage = {
type: 'request',
slotName,
id: '5',
param,
data: {
height: 5,
constitution: 'strong'
}
}

channel.callDisconnected()
channel.fakeReceive(request)

await flushPromises();
expect(handler).toHaveBeenCalledWith(request.data)
expect(channel.sendSpy).toHaveBeenCalledWith({
slotName,
type: 'response',
id: '5',
param,
data: {
color: 'blue'
}
})
})
});

describe('when the channel does not have autoReconnect capability', () => {
beforeEach(() => {
channel = new TestChannel({ withAutoReconnect: false })
transport = new Transport(channel)
channel.callConnected()
})

it('should not call the handler when a request is received if the channel went disconnected', async () => {

const slotName = 'buildCelery'
const handler = slots[slotName][0]

// Register handler on slot
transport.registerHandler(slotName, param, handler)

const request: TransportMessage = {
type: 'request',
slotName,
id: '5',
param,
data: {
height: 5,
constitution: 'strong'
}
}

channel.fakeReceive(request)
channel.callDisconnected()

await flushPromises();
expect(handler).toHaveBeenCalledWith(request.data)
expect(channel.sendSpy).not.toHaveBeenCalledWith({
slotName,
type: 'response',
id: '5',
param,
data: {
color: 'blue'
}
})
})
});

it('should send a handler_unregistered message when the last local handler is unregistered', () => {

const slotName = 'buildCelery'
Expand Down

0 comments on commit a302c2c

Please sign in to comment.