Skip to content

Commit 6e5d520

Browse files
author
David Teller
authored
Fix: roomMemberTest off-by-one error (#324)
1 parent cb34af0 commit 6e5d520

File tree

1 file changed

+68
-29
lines changed

1 file changed

+68
-29
lines changed

test/integration/roomMembersTest.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -401,25 +401,37 @@ describe("Test: Testing RoomMemberManager", function() {
401401
}
402402

403403
// Create and protect rooms.
404-
// - room 0 remains unprotected, as witness;
405-
// - room 1 is protected but won't be targeted directly, also as witness.
404+
//
405+
// We reserve two control rooms:
406+
// - room 0, also known as the "control unprotected room" is unprotected
407+
// (we're not calling `!mjolnir rooms add` for this room), so none
408+
// of the operations of `!mjolnir since` shoud affect it. We are
409+
// using it to control, at the end of each experiment, that none of
410+
// the `!mjolnir since` operations affect it.
411+
// - room 1, also known as the "control protected room" is protected
412+
// (we are calling `!mjolnir rooms add` for this room), but we are
413+
// never directly requesting any `!mjolnir since` action against
414+
// this room. We are using it to control, at the end of each experiment,
415+
// that none of the `!mjolnir since` operations that should target
416+
// one single other room also affect that room. It is, however, affected
417+
// by general operations that are designed to affect all protected rooms.
406418
const NUMBER_OF_ROOMS = 18;
407-
const roomIds: string[] = [];
408-
const roomAliases: string[] = [];
419+
const allRoomIds: string[] = [];
420+
const allRoomAliases: string[] = [];
409421
const mjolnirUserId = await this.mjolnir.client.getUserId();
410422
for (let i = 0; i < NUMBER_OF_ROOMS; ++i) {
411423
const roomId = await this.moderator.createRoom({
412424
invite: [mjolnirUserId, ...goodUserIds, ...badUserIds],
413425
});
414-
roomIds.push(roomId);
426+
allRoomIds.push(roomId);
415427

416428
const alias = `#since-test-${randomUUID()}:localhost:9999`;
417429
await this.moderator.createRoomAlias(alias, roomId);
418-
roomAliases.push(alias);
430+
allRoomAliases.push(alias);
419431
}
420-
for (let i = 1; i < roomIds.length; ++i) {
421-
// Protect all rooms except roomIds[0], as witness.
422-
const roomId = roomIds[i];
432+
for (let i = 1; i < allRoomIds.length; ++i) {
433+
// Protect all rooms except allRoomIds[0], as control.
434+
const roomId = allRoomIds[i];
423435
await this.mjolnir.client.joinRoom(roomId);
424436
await this.moderator.setUserPowerLevel(mjolnirUserId, roomId, 100);
425437
await this.moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir rooms add ${roomId}` });
@@ -429,8 +441,8 @@ describe("Test: Testing RoomMemberManager", function() {
429441
do {
430442
let protectedRooms = this.mjolnir.protectedRooms;
431443
protectedRoomsUpdated = true;
432-
for (let i = 1; i < roomIds.length; ++i) {
433-
const roomId = roomIds[i];
444+
for (let i = 1; i < allRoomIds.length; ++i) {
445+
const roomId = allRoomIds[i];
434446
if (!(roomId in protectedRooms)) {
435447
protectedRoomsUpdated = false;
436448
await new Promise(resolve => setTimeout(resolve, 1_000));
@@ -440,7 +452,7 @@ describe("Test: Testing RoomMemberManager", function() {
440452

441453
// Good users join before cut date.
442454
for (let user of this.goodUsers) {
443-
for (let roomId of roomIds) {
455+
for (let roomId of allRoomIds) {
444456
await user.joinRoom(roomId);
445457
}
446458
}
@@ -453,25 +465,30 @@ describe("Test: Testing RoomMemberManager", function() {
453465

454466
// Bad users join after cut date.
455467
for (let user of this.badUsers) {
456-
for (let roomId of roomIds) {
468+
for (let roomId of allRoomIds) {
457469
await user.joinRoom(roomId);
458470
}
459471
}
460472

473+
// Finally, prepare our control rooms and separate them
474+
// from the regular rooms.
475+
const CONTROL_UNPROTECTED_ROOM_ID = allRoomIds[0];
476+
const CONTROL_PROTECTED_ID = allRoomIds[1];
477+
const roomIds = allRoomIds.slice(2);
478+
const roomAliases = allRoomAliases.slice(2);
479+
461480
enum Method {
462481
kick,
463482
ban,
464483
mute,
465484
unmute,
466485
}
467-
const WITNESS_UNPROTECTED_ROOM_ID = roomIds[0];
468-
const WITNESS_ROOM_ID = roomIds[1];
469486
class Experiment {
470487
// A human-readable name for the command.
471488
readonly name: string;
472-
// If `true`, this command should affect room `WITNESS_ROOM_ID`.
489+
// If `true`, this command should affect room `CONTROL_PROTECTED_ID`.
473490
// Defaults to `false`.
474-
readonly shouldAffectWitnessRoom: boolean;
491+
readonly shouldAffectControlProtected: boolean;
475492
// The actual command-line.
476493
readonly command: (roomId: string, roomAlias: string) => string;
477494
// The number of responses we expect to this command.
@@ -484,17 +501,23 @@ describe("Test: Testing RoomMemberManager", function() {
484501
// Defaults to `false`.
485502
readonly isSameRoomAsPrevious: boolean;
486503

504+
// The index of the room on which we're acting.
505+
//
506+
// Initialized by `addTo`.
487507
roomIndex: number | undefined;
488508

489-
constructor({name, shouldAffectWitnessRoom, command, n, method, sameRoom}: {name: string, command: (roomId: string, roomAlias: string) => string, shouldAffectWitnessRoom?: boolean, n?: number, method: Method, sameRoom?: boolean}) {
509+
constructor({name, shouldAffectControlProtected, command, n, method, sameRoom}: {name: string, command: (roomId: string, roomAlias: string) => string, shouldAffectControlProtected?: boolean, n?: number, method: Method, sameRoom?: boolean}) {
490510
this.name = name;
491-
this.shouldAffectWitnessRoom = typeof shouldAffectWitnessRoom === "undefined" ? false : shouldAffectWitnessRoom;
511+
this.shouldAffectControlProtected = typeof shouldAffectControlProtected === "undefined" ? false : shouldAffectControlProtected;
492512
this.command = command;
493513
this.n = typeof n === "undefined" ? 1 : n;
494514
this.method = method;
495515
this.isSameRoomAsPrevious = typeof sameRoom === "undefined" ? false : sameRoom;
496516
}
497517

518+
// Add an experiment to the list of experiments.
519+
//
520+
// This is how `roomIndex` gets initialized.
498521
addTo(experiments: Experiment[]) {
499522
if (this.isSameRoomAsPrevious) {
500523
this.roomIndex = experiments[experiments.length - 1].roomIndex;
@@ -586,7 +609,7 @@ describe("Test: Testing RoomMemberManager", function() {
586609
new Experiment({
587610
name: "kick with date and reason",
588611
command: (roomId: string) => `!mjolnir since "${cutDate}" kick 100 ${roomId} bad, bad user`,
589-
shouldAffectWitnessRoom: false,
612+
shouldAffectControlProtected: false,
590613
n: 1,
591614
method: Method.kick,
592615
}),
@@ -626,19 +649,35 @@ describe("Test: Testing RoomMemberManager", function() {
626649
new Experiment({
627650
name: "kick with date everywhere",
628651
command: () => `!mjolnir since "${cutDate}" kick 100 * bad, bad user`,
629-
shouldAffectWitnessRoom: true,
652+
shouldAffectControlProtected: true,
630653
n: NUMBER_OF_ROOMS - 1,
631654
method: Method.kick,
632655
}),
633656
]) {
634657
experiment.addTo(EXPERIMENTS);
635658
}
659+
660+
// Just-in-case health check, before starting.
661+
{
662+
const usersInUnprotectedControlProtected = await this.mjolnir.client.getJoinedRoomMembers(CONTROL_UNPROTECTED_ROOM_ID);
663+
const usersInControlProtected = await this.mjolnir.client.getJoinedRoomMembers(CONTROL_PROTECTED_ID);
664+
for (let userId of goodUserIds) {
665+
assert.ok(usersInUnprotectedControlProtected.includes(userId), `Initially, good user ${userId} should be in the unprotected control room`);
666+
assert.ok(usersInControlProtected.includes(userId), `Initially, good user ${userId} should be in the control room`);
667+
}
668+
for (let userId of badUserIds) {
669+
assert.ok(usersInUnprotectedControlProtected.includes(userId), `Initially, bad user ${userId} should be in the unprotected control room`);
670+
assert.ok(usersInControlProtected.includes(userId), `Initially, bad user ${userId} should be in the control room`);
671+
}
672+
}
673+
636674
for (let i = 0; i < EXPERIMENTS.length; ++i) {
637675
const experiment = EXPERIMENTS[i];
638-
const index = experiment.roomIndex! + 1;
639-
const roomId = roomIds[index];
676+
const index = experiment.roomIndex!;
677+
const roomId = roomIds[index];
640678
const roomAlias = roomAliases[index];
641679
const joined = this.mjolnir.roomJoins.getUsersInRoom(roomId, start, 100);
680+
console.debug(`Running experiment ${i} "${experiment.name}" in room index ${index} (${roomId} / ${roomAlias}): \`${experiment.command(roomId, roomAlias)}\``);
642681
assert.ok(joined.length >= 2 * SAMPLE_SIZE, `In experiment ${experiment.name}, we should have seen ${2 * SAMPLE_SIZE} users, saw ${joined.length}`);
643682

644683
// Run experiment.
@@ -650,12 +689,12 @@ describe("Test: Testing RoomMemberManager", function() {
650689

651690
// Check post-conditions.
652691
const usersInRoom = await this.mjolnir.client.getJoinedRoomMembers(roomId);
653-
const usersInUnprotectedWitnessRoom = await this.mjolnir.client.getJoinedRoomMembers(WITNESS_UNPROTECTED_ROOM_ID);
654-
const usersInWitnessRoom = await this.mjolnir.client.getJoinedRoomMembers(WITNESS_ROOM_ID);
692+
const usersInUnprotectedControlProtected = await this.mjolnir.client.getJoinedRoomMembers(CONTROL_UNPROTECTED_ROOM_ID);
693+
const usersInControlProtected = await this.mjolnir.client.getJoinedRoomMembers(CONTROL_PROTECTED_ID);
655694
for (let userId of goodUserIds) {
656695
assert.ok(usersInRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in affected room`);
657-
assert.ok(usersInWitnessRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in witness room`);
658-
assert.ok(usersInUnprotectedWitnessRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in unprotected witness room`);
696+
assert.ok(usersInControlProtected.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in control room (${CONTROL_PROTECTED_ID})`);
697+
assert.ok(usersInUnprotectedControlProtected.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in unprotected control room (${CONTROL_UNPROTECTED_ROOM_ID})`);
659698
}
660699
if (experiment.method === Method.mute) {
661700
for (let userId of goodUserIds) {
@@ -678,8 +717,8 @@ describe("Test: Testing RoomMemberManager", function() {
678717
} else {
679718
for (let userId of badUserIds) {
680719
assert.ok(!usersInRoom.includes(userId), `After a ${experiment.name}, bad user ${userId} should NOT be in affected room`);
681-
assert.equal(usersInWitnessRoom.includes(userId), !experiment.shouldAffectWitnessRoom, `After a ${experiment.name}, bad user ${userId} should ${experiment.shouldAffectWitnessRoom ? "NOT" : "still"} be in witness room`);
682-
assert.ok(usersInUnprotectedWitnessRoom.includes(userId), `After a ${experiment.name}, bad user ${userId} should still be in unprotected witness room`);
720+
assert.equal(usersInControlProtected.includes(userId), !experiment.shouldAffectControlProtected, `After a ${experiment.name}, bad user ${userId} should ${experiment.shouldAffectControlProtected ? "NOT" : "still"} be in control room`);
721+
assert.ok(usersInUnprotectedControlProtected.includes(userId), `After a ${experiment.name}, bad user ${userId} should still be in unprotected control room`);
683722
const leaveEvent = await this.mjolnir.client.getRoomStateEvent(roomId, "m.room.member", userId);
684723
switch (experiment.method) {
685724
case Method.kick:

0 commit comments

Comments
 (0)