Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests to check join/leaves are bridged correctly #1734

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
2 changes: 2 additions & 0 deletions changelog.d/1663.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- New PM rooms are configured to disable calls, reactions, redactions, and stickers;
as they could not be bridged anyway.
1 change: 1 addition & 0 deletions changelog.d/1709.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the bridge pooling so it supports TLS.
1 change: 1 addition & 0 deletions changelog.d/1711.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix setup widget failing to authenticate.
1 change: 1 addition & 0 deletions changelog.d/1715.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sort the list of channels in !listrooms output.
1 change: 1 addition & 0 deletions changelog.d/1717.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix cases where the IRC bridge may erronously believe a user is not joined to a channel in pooling mode.
1 change: 1 addition & 0 deletions changelog.d/1720.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Ensure that all passwords can be decrypted on startup, to detect any issues with the provided passkey.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"build:app": "tsc --project ./tsconfig.json",
"build:widget": "vite build --config widget/vite.config.ts",
"dev:widget": "vite dev --config widget/vite.config.ts",
"test": "BLUEBIRD_DEBUG=1 ts-node --project spec/tsconfig.json node_modules/jasmine/bin/jasmine --stop-on-failure=true",
"test": "ts-node --project spec/tsconfig.json node_modules/jasmine/bin/jasmine --stop-on-failure=true",
"test:e2e": "jest --config spec/e2e/jest.config.js --forceExit",
"lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
"check": "yarn test && yarn lint",
Expand Down Expand Up @@ -45,8 +45,8 @@
"logform": "^2.4.2",
"matrix-appservice-bridge": "^9.0.0",
"matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.6.6-element.1",
"matrix-org-irc": "^2.0.0",
"matrix-widget-api": "^1.1.1",
"matrix-org-irc": "^2.0.1",
"matrix-widget-api": "^1.4.0",
"nopt": "^6.0.0",
"p-queue": "^6.6.2",
"pg": "^8.8.0",
Expand Down
39 changes: 22 additions & 17 deletions spec/e2e/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { TestIrcServer } from "matrix-org-irc";
import { IrcBridgeE2ETest } from "../util/e2e-test";
import { describe, expect, it } from "@jest/globals";


describe('Basic bridge usage', () => {
Expand All @@ -11,33 +13,36 @@ describe('Basic bridge usage', () => {
await testEnv.setUp();
});
afterEach(() => {
return testEnv.tearDown();
return testEnv?.tearDown();
});
it('should be able to dynamically bridge a room via the !join command', async () => {
const { homeserver, ircBridge } = testEnv;
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const { bob } = testEnv.ircTest.clients;
await bob.join('#test');

const adminRoomId = await alice.createRoom({
is_direct: true,
invite: [ircBridge.appServiceUserId],
});
await alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircBridge.appServiceUserId, roomId: adminRoomId}
);
await alice.sendText(adminRoomId, `!join #test`);
const invite = await alice.waitForRoomInvite(
{sender: ircBridge.appServiceUserId}
);
const cRoomId = invite.roomId;
await alice.joinRoom(cRoomId);
// Create the channel
await bob.join(channel);

const adminRoomId = await testEnv.createAdminRoomHelper(alice);
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);
const roomName = await alice.getRoomStateEvent(cRoomId, 'm.room.name', '');
expect(roomName.name).toEqual('#test');
expect(roomName.name).toEqual(channel);

// And finally wait for bob to appear.
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
await alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
);

// Send some messages
const aliceMsg = bob.waitForEvent('message', 10000);
const bobMsg = alice.waitForRoomEvent(
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
);
alice.sendText(cRoomId, "Hello bob!");
await aliceMsg;
bob.say(channel, "Hi alice!");
await bobMsg;
});
});
130 changes: 130 additions & 0 deletions spec/e2e/membership.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { TestIrcServer } from "matrix-org-irc";
import { IrcBridgeE2ETest } from "../util/e2e-test";
import { describe, expect, it } from "@jest/globals";

const MEMBERSHIP_TIMEOUT = 3000;

describe('Ensure membership is synced to IRC rooms', () => {
let testEnv: IrcBridgeE2ETest;
beforeEach(async () => {
testEnv = await IrcBridgeE2ETest.createTestEnv({
matrixLocalparts: ['alice'],
ircNicks: ['bob', 'charlie', 'basil'].flatMap(nick => Array.from({length: 1}, (_, i) => `${nick}${i}`)),
});
await testEnv.setUp();
});
afterEach(() => {
return testEnv?.tearDown();
});
it('ensure IRC puppets join', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const clients = Object.values(testEnv.ircTest.clients)
.map(client => ({userId: `@irc_${client.nick}:${homeserver.domain}`, client}));
const creatorClient = clients.pop()!;

// Create the channel
await creatorClient.client.join(channel);

const cRoomId = await testEnv.joinChannelHelper(alice, await testEnv.createAdminRoomHelper(alice), channel);

const joinPromises: Promise<unknown>[] = [];

// Join all the users, and check all the membership events appear.
for (const ircUser of clients) {
joinPromises.push(
alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircUser.userId, stateKey: ircUser.userId, roomId: cRoomId},
MEMBERSHIP_TIMEOUT,
).then(({data}) => expect(data.content.membership).toEqual("join"))
)
await ircUser.client.join(channel);
}

await Promise.all(joinPromises);
});

it('ensure IRC puppets leave on part', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const clients = Object.values(testEnv.ircTest.clients)
.map(client => ({userId: `@irc_${client.nick}:${homeserver.domain}`, client}));
const creatorClient = clients.pop()!;

// Create the channel
await creatorClient.client.join(channel);

const cRoomId = await testEnv.joinChannelHelper(alice, await testEnv.createAdminRoomHelper(alice), channel);

const joinPromises: Promise<unknown>[] = [];

// Join all the users, and check all the membership events appear.
for (const ircUser of clients) {
joinPromises.push(
alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircUser.userId, stateKey: ircUser.userId, roomId: cRoomId},
MEMBERSHIP_TIMEOUT,
).then(({data}) => expect(data.content.membership).toEqual("join"))
)
await ircUser.client.join(channel);
}

await Promise.all(joinPromises);
const partPromises: Promise<unknown>[] = [];

for (const ircUser of clients) {
partPromises.push(
alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircUser.userId, stateKey: ircUser.userId, roomId: cRoomId},
MEMBERSHIP_TIMEOUT,
).then(({data}) => expect(data.content.membership).toEqual("leave"))
)
await ircUser.client.part(channel, 'getting out of here!');
}
await Promise.all(partPromises);
});

it('ensure IRC puppets leave on quit', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const clients = Object.values(testEnv.ircTest.clients)
.map(client => ({userId: `@irc_${client.nick}:${homeserver.domain}`, client}));
const creatorClient = clients.pop()!;

// Create the channel
await creatorClient.client.join(channel);

const cRoomId = await testEnv.joinChannelHelper(alice, await testEnv.createAdminRoomHelper(alice), channel);

const joinPromises: Promise<unknown>[] = [];

// Join all the users, and check all the membership events appear.
for (const ircUser of clients) {
joinPromises.push(
alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircUser.userId, stateKey: ircUser.userId, roomId: cRoomId},
MEMBERSHIP_TIMEOUT,
).then(({data}) => expect(data.content.membership).toEqual("join"))
)
await ircUser.client.join(channel);
}

await Promise.all(joinPromises);
const partPromises: Promise<unknown>[] = [];

for (const ircUser of clients) {
partPromises.push(
alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: ircUser.userId, stateKey: ircUser.userId, roomId: cRoomId},
MEMBERSHIP_TIMEOUT,
).then(({data}) => expect(data.content.membership).toEqual("leave"))
)
await ircUser.client.disconnect();
}
await Promise.all(partPromises);
});
});
119 changes: 119 additions & 0 deletions spec/e2e/pooling.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { TestIrcServer } from "matrix-org-irc";
import { IrcBridgeE2ETest } from "../util/e2e-test";
import { describe, it } from "@jest/globals";

const describeif = IrcBridgeE2ETest.usingRedis ? describe : describe.skip;

describeif('Connection pooling', () => {
let testEnv: IrcBridgeE2ETest;

beforeEach(async () => {
// Initial run of the bridge to setup a testing environment
testEnv = await IrcBridgeE2ETest.createTestEnv({
matrixLocalparts: ['alice'],
ircNicks: ['bob'],
config: {
connectionPool: {
redisUrl: 'unused',
persistConnectionsOnShutdown: true,
}
}
});
await testEnv.setUp();
})

// Ensure we always tear down
afterEach(() => {
return testEnv.tearDown();
});

it('should be able to shut down the bridge and start back up again', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;

const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const { bob } = testEnv.ircTest.clients;

// Create the channel
await bob.join(channel);

const adminRoomId = await testEnv.createAdminRoomHelper(alice);
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);

// And finally wait for bob to appear.
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
await alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
);


// Send some messages
let aliceMsg = bob.waitForEvent('message', 10000);
let bobMsg = alice.waitForRoomEvent(
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
);
alice.sendText(cRoomId, "Hello bob!");
await aliceMsg;
bob.say(channel, "Hi alice!");
await bobMsg;

console.log('Recreating bridge');

// Now kill the bridge, do NOT kill the dependencies.
await testEnv.recreateBridge();
await testEnv.setUp();

aliceMsg = bob.waitForEvent('message', 10000);
bobMsg = alice.waitForRoomEvent(
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
);
alice.sendText(cRoomId, "Hello bob!");
await aliceMsg;
bob.say(channel, "Hi alice!");
await bobMsg;
});

it('should be able to recover from legacy client state', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;

const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const { bob } = testEnv.ircTest.clients;

// Create the channel
await bob.join(channel);

const adminRoomId = await testEnv.createAdminRoomHelper(alice);
const cRoomId = await testEnv.joinChannelHelper(alice, adminRoomId, channel);

// And finally wait for bob to appear.
const bobUserId = `@irc_${bob.nick}:${homeserver.domain}`;
await alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: bobUserId, stateKey: bobUserId, roomId: cRoomId}
);


// Send some messages
let aliceMsg = bob.waitForEvent('message', 10000);
let bobMsg = alice.waitForRoomEvent(
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
);
alice.sendText(cRoomId, "Hello bob!");
await aliceMsg;
bob.say(channel, "Hi alice!");
await bobMsg;

// Now kill the bridge, do NOT kill the dependencies.
await testEnv.recreateBridge();
await testEnv.setUp();

aliceMsg = bob.waitForEvent('message', 10000);
bobMsg = alice.waitForRoomEvent(
{eventType: 'm.room.message', sender: bobUserId, roomId: cRoomId}
);
alice.sendText(cRoomId, "Hello bob!");
await aliceMsg;
bob.say(channel, "Hi alice!");
await bobMsg;
});
});
48 changes: 48 additions & 0 deletions spec/e2e/powerlevels.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { TestIrcServer } from "matrix-org-irc";
import { IrcBridgeE2ETest } from "../util/e2e-test";
import { describe, expect, it } from "@jest/globals";
import { PowerLevelContent } from "matrix-appservice-bridge";


describe('Ensure powerlevels are appropriately applied', () => {
let testEnv: IrcBridgeE2ETest;
beforeEach(async () => {
testEnv = await IrcBridgeE2ETest.createTestEnv({
matrixLocalparts: ['alice'],
ircNicks: ['bob', 'charlie'],
});
await testEnv.setUp();
});
afterEach(() => {
return testEnv?.tearDown();
});
it('should update powerlevel of IRC user when OPed by an IRC user', async () => {
const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
const { homeserver } = testEnv;
const alice = homeserver.users[0].client;
const { bob, charlie } = testEnv.ircTest.clients;
const charlieUserId = `@irc_${charlie.nick}:${homeserver.domain}`;

// Create the channel
await bob.join(channel);

const cRoomId = await testEnv.joinChannelHelper(alice, await testEnv.createAdminRoomHelper(alice), channel);

// Now have charlie join and be opped.
await charlie.join(channel);
await alice.waitForRoomEvent(
{eventType: 'm.room.member', sender: charlieUserId, stateKey: charlieUserId, roomId: cRoomId}
);
await bob.send('MODE', channel, '+o', charlie.nick);
const powerLevel = alice.waitForRoomEvent<PowerLevelContent>(
{eventType: 'm.room.power_levels', roomId: cRoomId, sender: testEnv.ircBridge.appServiceUserId}
);

const powerlevelContent = (await powerLevel).data.content;
console.log(powerlevelContent.users);
expect(powerlevelContent.users![charlieUserId]).toEqual(
testEnv.ircBridge.config.ircService.servers.localhost.modePowerMap!.o
);
});
});
Loading