Skip to content

Commit 767e892

Browse files
authored
Add a few more sensible tests to the connection pools (#1717)
* Get restart tests going * Add tests to ensure we can cope with invalid legacy state * Add a note * Don't skip * Drop bluebird stuff * Oppertunistically discover channels that may be missing * Fix supported state being horribly bloaty * Forcibly delete bridge state when creating a new connection * Fix channel discovery * Update node-irc package * Drop unused * changelog * Add a check * Applying reccomendations
1 parent 536b0c6 commit 767e892

File tree

14 files changed

+522
-144
lines changed

14 files changed

+522
-144
lines changed

changelog.d/1717.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix cases where the IRC bridge may erronously believe a user is not joined to a channel in pooling mode.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"build:app": "tsc --project ./tsconfig.json",
1616
"build:widget": "vite build --config widget/vite.config.ts",
1717
"dev:widget": "vite dev --config widget/vite.config.ts",
18-
"test": "BLUEBIRD_DEBUG=1 ts-node --project spec/tsconfig.json node_modules/jasmine/bin/jasmine --stop-on-failure=true",
18+
"test": "ts-node --project spec/tsconfig.json node_modules/jasmine/bin/jasmine --stop-on-failure=true",
1919
"test:e2e": "jest --config spec/e2e/jest.config.js --forceExit",
2020
"lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
2121
"check": "yarn test && yarn lint",
@@ -45,7 +45,7 @@
4545
"logform": "^2.4.2",
4646
"matrix-appservice-bridge": "^9.0.0",
4747
"matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.6.6-element.1",
48-
"matrix-org-irc": "^2.0.0",
48+
"matrix-org-irc": "^2.0.1",
4949
"matrix-widget-api": "^1.4.0",
5050
"nopt": "^6.0.0",
5151
"p-queue": "^6.6.2",

spec/e2e/basic.spec.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { TestIrcServer } from "matrix-org-irc";
12
import { IrcBridgeE2ETest } from "../util/e2e-test";
3+
import { describe, expect, it } from "@jest/globals";
24

35

46
describe('Basic bridge usage', () => {
@@ -11,33 +13,36 @@ describe('Basic bridge usage', () => {
1113
await testEnv.setUp();
1214
});
1315
afterEach(() => {
14-
return testEnv.tearDown();
16+
return testEnv?.tearDown();
1517
});
1618
it('should be able to dynamically bridge a room via the !join command', async () => {
17-
const { homeserver, ircBridge } = testEnv;
19+
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
20+
const { homeserver } = testEnv;
1821
const alice = homeserver.users[0].client;
1922
const { bob } = testEnv.ircTest.clients;
20-
await bob.join('#test');
2123

22-
const adminRoomId = await alice.createRoom({
23-
is_direct: true,
24-
invite: [ircBridge.appServiceUserId],
25-
});
26-
await alice.waitForRoomEvent(
27-
{eventType: 'm.room.member', sender: ircBridge.appServiceUserId, roomId: adminRoomId}
28-
);
29-
await alice.sendText(adminRoomId, `!join #test`);
30-
const invite = await alice.waitForRoomInvite(
31-
{sender: ircBridge.appServiceUserId}
32-
);
33-
const cRoomId = invite.roomId;
34-
await alice.joinRoom(cRoomId);
24+
// Create the channel
25+
await bob.join(channel);
26+
27+
const adminRoomId = await testEnv.createAdminRoomHelper(alice);
28+
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);
3529
const roomName = await alice.getRoomStateEvent(cRoomId, 'm.room.name', '');
36-
expect(roomName.name).toEqual('#test');
30+
expect(roomName.name).toEqual(channel);
31+
3732
// And finally wait for bob to appear.
3833
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
3934
await alice.waitForRoomEvent(
4035
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
4136
);
37+
38+
// Send some messages
39+
const aliceMsg = bob.waitForEvent('message', 10000);
40+
const bobMsg = alice.waitForRoomEvent(
41+
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
42+
);
43+
alice.sendText(cRoomId, "Hello bob!");
44+
await aliceMsg;
45+
bob.say(channel, "Hi alice!");
46+
await bobMsg;
4247
});
4348
});

spec/e2e/pooling.spec.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { TestIrcServer } from "matrix-org-irc";
2+
import { IrcBridgeE2ETest } from "../util/e2e-test";
3+
import { describe, it } from "@jest/globals";
4+
5+
const describeif = IrcBridgeE2ETest.usingRedis ? describe : describe.skip;
6+
7+
describeif('Connection pooling', () => {
8+
let testEnv: IrcBridgeE2ETest;
9+
10+
beforeEach(async () => {
11+
// Initial run of the bridge to setup a testing environment
12+
testEnv = await IrcBridgeE2ETest.createTestEnv({
13+
matrixLocalparts: ['alice'],
14+
ircNicks: ['bob'],
15+
config: {
16+
connectionPool: {
17+
redisUrl: 'unused',
18+
persistConnectionsOnShutdown: true,
19+
}
20+
}
21+
});
22+
await testEnv.setUp();
23+
})
24+
25+
// Ensure we always tear down
26+
afterEach(() => {
27+
return testEnv.tearDown();
28+
});
29+
30+
it('should be able to shut down the bridge and start back up again', async () => {
31+
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
32+
33+
const { homeserver } = testEnv;
34+
const alice = homeserver.users[0].client;
35+
const { bob } = testEnv.ircTest.clients;
36+
37+
// Create the channel
38+
await bob.join(channel);
39+
40+
const adminRoomId = await testEnv.createAdminRoomHelper(alice);
41+
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);
42+
43+
// And finally wait for bob to appear.
44+
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
45+
await alice.waitForRoomEvent(
46+
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
47+
);
48+
49+
50+
// Send some messages
51+
let aliceMsg = bob.waitForEvent('message', 10000);
52+
let bobMsg = alice.waitForRoomEvent(
53+
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
54+
);
55+
alice.sendText(cRoomId, "Hello bob!");
56+
await aliceMsg;
57+
bob.say(channel, "Hi alice!");
58+
await bobMsg;
59+
60+
console.log('Recreating bridge');
61+
62+
// Now kill the bridge, do NOT kill the dependencies.
63+
await testEnv.recreateBridge();
64+
await testEnv.setUp();
65+
66+
aliceMsg = bob.waitForEvent('message', 10000);
67+
bobMsg = alice.waitForRoomEvent(
68+
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
69+
);
70+
alice.sendText(cRoomId, "Hello bob!");
71+
await aliceMsg;
72+
bob.say(channel, "Hi alice!");
73+
await bobMsg;
74+
});
75+
76+
it('should be able to recover from legacy client state', async () => {
77+
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
78+
79+
const { homeserver } = testEnv;
80+
const alice = homeserver.users[0].client;
81+
const { bob } = testEnv.ircTest.clients;
82+
83+
// Create the channel
84+
await bob.join(channel);
85+
86+
const adminRoomId = await testEnv.createAdminRoomHelper(alice);
87+
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);
88+
89+
// And finally wait for bob to appear.
90+
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
91+
await alice.waitForRoomEvent(
92+
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
93+
);
94+
95+
96+
// Send some messages
97+
let aliceMsg = bob.waitForEvent('message', 10000);
98+
let bobMsg = alice.waitForRoomEvent(
99+
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
100+
);
101+
alice.sendText(cRoomId, "Hello bob!");
102+
await aliceMsg;
103+
bob.say(channel, "Hi alice!");
104+
await bobMsg;
105+
106+
// Now kill the bridge, do NOT kill the dependencies.
107+
await testEnv.recreateBridge();
108+
await testEnv.setUp();
109+
110+
aliceMsg = bob.waitForEvent('message', 10000);
111+
bobMsg = alice.waitForRoomEvent(
112+
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
113+
);
114+
alice.sendText(cRoomId, "Hello bob!");
115+
await aliceMsg;
116+
bob.say(channel, "Hi alice!");
117+
await bobMsg;
118+
});
119+
});
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { DefaultIrcSupported } from "matrix-org-irc";
2+
import { IrcClientRedisState, IrcClientStateDehydrated } from "../../../src/pool-service/IrcClientRedisState";
3+
4+
const userId = "@foo:bar";
5+
6+
function fakeRedis(existingData: string|null = null): any {
7+
return {
8+
async hget(key, clientId) {
9+
if (clientId !== userId) {
10+
throw Error('Wrong user!');
11+
}
12+
return existingData;
13+
}
14+
}
15+
}
16+
17+
const EXISTING_STATE: IrcClientStateDehydrated = {
18+
loggedIn: true,
19+
registered: true,
20+
currentNick: "alice",
21+
whoisData: [],
22+
nickMod: 0,
23+
modeForPrefix: {
24+
50: 'o',
25+
},
26+
capabilities: {
27+
serverCapabilites: ['some'],
28+
serverCapabilitesSasl: ['caps'],
29+
userCapabilites: ['for'],
30+
userCapabilitesSasl: []
31+
},
32+
supportedState: DefaultIrcSupported,
33+
hostMask: "",
34+
chans: [
35+
['fibble', {
36+
key: '',
37+
serverName: 'egg',
38+
users: [
39+
['bob', 'o']
40+
],
41+
mode: 'a',
42+
modeParams: [
43+
['o', ['bob']]
44+
]
45+
}]
46+
],
47+
prefixForMode: {
48+
'+': 'o',
49+
},
50+
maxLineLength: 100,
51+
lastSendTime: 12345,
52+
}
53+
54+
describe("IrcClientRedisState", () => {
55+
it("should be able to create a fresh state", async () => {
56+
const state = await IrcClientRedisState.create(
57+
fakeRedis(),
58+
userId
59+
);
60+
expect(state.loggedIn).toBeFalse();
61+
expect(state.registered).toBeFalse();
62+
expect(state.chans.size).toBe(0);
63+
});
64+
it("should be able to load existing state", async () => {
65+
const state = await IrcClientRedisState.create(
66+
fakeRedis(JSON.stringify(EXISTING_STATE)),
67+
userId
68+
);
69+
expect(state.loggedIn).toBeTrue();
70+
expect(state.registered).toBeTrue();
71+
expect(state.chans.size).toBe(1);
72+
console.log(state);
73+
});
74+
it('should be able to repair previously buggy state', async () => {
75+
const existingState = {
76+
...EXISTING_STATE,
77+
chans: [
78+
[
79+
"#matrix-bridge-test",
80+
{
81+
"key": "#matrix-bridge-test",
82+
"serverName": "#matrix-bridge-test",
83+
"users": {},
84+
"mode": "+Cnst",
85+
"modeParams": {},
86+
"created": "1683732619"
87+
}
88+
],
89+
[
90+
"#halfy-plumbs",
91+
{
92+
"key": "#halfy-plumbs",
93+
"serverName": "#halfy-plumbs",
94+
"users": {},
95+
"mode": "+Cnst",
96+
"modeParams": {},
97+
"created": "1683732619"
98+
}
99+
],
100+
]
101+
}
102+
const state = await IrcClientRedisState.create(
103+
fakeRedis(JSON.stringify(existingState)),
104+
userId
105+
);
106+
})
107+
});

0 commit comments

Comments
 (0)