diff --git a/changelog.d/1752.bugfix b/changelog.d/1752.bugfix new file mode 100644 index 000000000..cfc3f9abf --- /dev/null +++ b/changelog.d/1752.bugfix @@ -0,0 +1 @@ +Ensure QUIT messages are always sent. diff --git a/spec/e2e/basic.spec.ts b/spec/e2e/basic.spec.ts index c62dadf19..9344b6eef 100644 --- a/spec/e2e/basic.spec.ts +++ b/spec/e2e/basic.spec.ts @@ -7,7 +7,7 @@ describe('Basic bridge usage', () => { let testEnv: IrcBridgeE2ETest; beforeEach(async () => { testEnv = await IrcBridgeE2ETest.createTestEnv({ - matrixLocalparts: ['alice'], + matrixLocalparts: [TestIrcServer.generateUniqueNick("alice")], ircNicks: ['bob'], traceToFile: true, }); diff --git a/spec/e2e/pooling.spec.ts b/spec/e2e/pooling.spec.ts index f398ca5d4..c08ffd2d1 100644 --- a/spec/e2e/pooling.spec.ts +++ b/spec/e2e/pooling.spec.ts @@ -10,7 +10,7 @@ describeif('Connection pooling', () => { beforeEach(async () => { // Initial run of the bridge to setup a testing environment testEnv = await IrcBridgeE2ETest.createTestEnv({ - matrixLocalparts: ['alice'], + matrixLocalparts: [TestIrcServer.generateUniqueNick('alice')], ircNicks: ['bob'], config: { connectionPool: { diff --git a/spec/e2e/quit.spec.ts b/spec/e2e/quit.spec.ts new file mode 100644 index 000000000..6f43b2324 --- /dev/null +++ b/spec/e2e/quit.spec.ts @@ -0,0 +1,43 @@ +import { TestIrcServer } from "matrix-org-irc"; +import { IrcBridgeE2ETest } from "../util/e2e-test"; +import { describe, it } from "@jest/globals"; + + +describe('Ensure quit messsage is sent', () => { + let testEnv: IrcBridgeE2ETest; + beforeEach(async () => { + testEnv = await IrcBridgeE2ETest.createTestEnv({ + matrixLocalparts: [TestIrcServer.generateUniqueNick('alice')], + ircNicks: ['bob'], + traceToFile: true, + }); + await testEnv.setUp(); + }); + afterEach(() => { + return testEnv?.tearDown(); + }); + it('should correctly send a quit message on !reconnect', async () => { + const channel = `#${TestIrcServer.generateUniqueNick("test")}`; + const { homeserver, opts } = testEnv; + const [alice] = homeserver.users.map(c => c.client); + const { bob } = testEnv.ircTest.clients; + + // Create the channel + await bob.join(channel); + const adminRoom = await testEnv.createAdminRoomHelper(alice) + const cRoomId = await testEnv.joinChannelHelper(alice, adminRoom, channel); + + // Ensure we join the IRC side + await alice.sendText(cRoomId, `Hello world!`); + await bob.waitForEvent('message', 10000); + + const quitEvent = bob.waitForEvent('quit', 10000); + + // Now, have alice quit. + await alice.sendText(adminRoom, `!reconnect`); + const [nick, message, channels] = await quitEvent; + expect(nick).toEqual(`M-${opts.matrixLocalparts?.[0]}`); + expect(message).toEqual('Quit: Reconnecting'); + expect(channels).toContain(channel); + }); +}); diff --git a/spec/util/e2e-test.ts b/spec/util/e2e-test.ts index 071d5d8fc..a94fcbff5 100644 --- a/spec/util/e2e-test.ts +++ b/spec/util/e2e-test.ts @@ -300,7 +300,9 @@ export class IrcBridgeE2ETest { } }), }, registration); - return new IrcBridgeE2ETest(homeserver, ircBridge, registration, postgresDb, ircTest, redisPool, traceStream) + return new IrcBridgeE2ETest( + homeserver, ircBridge, registration, postgresDb, ircTest, opts, redisPool, traceStream + ); } private constructor( @@ -309,6 +311,7 @@ export class IrcBridgeE2ETest { public readonly registration: AppServiceRegistration, readonly postgresDb: string, public readonly ircTest: TestIrcServer, + public readonly opts: Opts, public readonly pool?: IrcConnectionPool, private traceLog?: WriteStream, ) { @@ -356,9 +359,6 @@ export class IrcBridgeE2ETest { } public async tearDown(): Promise { - if (this.traceLog) { - this.traceLog.close(); - } await Promise.allSettled([ this.ircBridge?.kill(), this.ircTest.tearDown(), @@ -367,6 +367,9 @@ export class IrcBridgeE2ETest { this.dropDatabase(), ]); await this.pool?.close(); + if (this.traceLog) { + this.traceLog.close(); + } } public async createAdminRoomHelper(client: E2ETestMatrixClient): Promise { diff --git a/src/pool-service/RedisIrcConnection.ts b/src/pool-service/RedisIrcConnection.ts index 7080d0f75..5f1c0a10b 100644 --- a/src/pool-service/RedisIrcConnection.ts +++ b/src/pool-service/RedisIrcConnection.ts @@ -11,12 +11,18 @@ export type RedisIrcConnectionEvents = { export class RedisIrcConnection extends (EventEmitter as unknown as new () => TypedEmitter) implements IrcConnection { - private readonly log = new Logger(`RedisIrcConnection:${this.clientId}`); + private readonly log: Logger; public get connecting() { return this.isConnecting; } + public get readyState(): string { + // TODO: Should this be just pulled directly from the socket. + // No support for readonly / writeonly. + return this.isConnecting ? 'opening' : 'open'; + } + private isConnecting = true; public localPort?: number; public localIp?: string; @@ -25,6 +31,7 @@ export class RedisIrcConnection extends (EventEmitter as unknown as public readonly clientId: ClientId, public state: IrcClientState) { super(); + this.log = new Logger(`RedisIrcConnection:${this.clientId}`); this.once('connect', () => { this.isConnecting = false; });