Files
Draupnir/test/integration/timelinePaginationTest.ts
Catalan Lover 9cc64074e1 Rename to Draupnir in Appropriate Places (#591)
* Rename to Draupnir in Appropriate Places

* Integrate Code review feedback on CHANGELOG.md
2024-10-04 20:32:10 +02:00

233 lines
7.0 KiB
TypeScript

// Copyright 2022 - 2024 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2021 - 2022 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from mjolnir
// https://github.com/matrix-org/mjolnir
// </text>
import { strict as assert } from "assert";
import { newTestUser } from "./clientHelper";
import { getMessagesByUserIn } from "../../src/utils";
import { TextMessageContent } from "matrix-protection-suite";
/**
* Ensure that Draupnir paginates only the necessary segment of the room timeline when backfilling.
*/
describe("Test: timeline pagination", function () {
it("does not paginate across the entire room history while backfilling.", async function () {
this.timeout(60000);
// Create a few users and a room.
const badUser = await newTestUser(this.config.homeserverUrl, {
name: { contains: "spammer" },
});
const badUserId = await badUser.getUserId();
const moderator = await newTestUser(this.config.homeserverUrl, {
name: { contains: "moderator" },
});
const targetRoom = await moderator.createRoom({
invite: [await badUser.getUserId()],
});
await badUser.joinRoom(targetRoom);
// send some irrelevant messages
await Promise.all(
[...Array(200).keys()].map((i) =>
moderator.sendMessage(targetRoom, {
msgtype: "m.text.",
body: `Irrelevant Message #${i}`,
})
)
);
// bad guy sends 5 messages
for (let i = 0; i < 5; i++) {
await badUser.sendMessage(targetRoom, {
msgtype: "m.text",
body: "Very Bad Stuff",
});
}
// send some irrelevant messages
await Promise.all(
[...Array(50).keys()].map((i) =>
moderator.sendMessage(targetRoom, {
msgtype: "m.text.",
body: `Irrelevant Message #${i}`,
})
)
);
// bad guy sends 1 extra message at the most recent edge of the timeline.
await badUser.sendMessage(targetRoom, {
msgtype: "m.text",
body: "Very Bad Stuff",
});
// then call this paignator and ensure that we don't go across the entire room history.
let cbCount = 0;
let eventCount = 0;
await getMessagesByUserIn(
moderator,
badUserId,
targetRoom,
1000,
function (events) {
cbCount += 1;
eventCount += events.length;
events.map((e) => {
assert.equal(
e.sender,
badUserId,
"All the events should be from the same sender"
);
});
}
);
assert.equal(
cbCount,
1,
"The callback only needs to be called once with all the messages because the events should be filtered."
);
assert.equal(
eventCount,
7,
"There shouldn't be any more events (1 member event and 6 messages), and they should all be from the same account."
);
});
it("does not call the callback with an empty array when there are no relevant events", async function () {
this.timeout(60000);
const badUser = await newTestUser(this.config.homeserverUrl, {
name: { contains: "spammer" },
});
const badUserId = await badUser.getUserId();
const moderator = await newTestUser(this.config.homeserverUrl, {
name: { contains: "moderator" },
});
const targetRoom = await moderator.createRoom();
// send some irrelevant messages
await Promise.all(
[...Array(200).keys()].map((i) =>
moderator.sendMessage(targetRoom, {
msgtype: "m.text.",
body: `Irrelevant Message #${i}`,
})
)
);
// The callback should not be called.
let cbCount = 0;
await getMessagesByUserIn(
moderator,
badUserId,
targetRoom,
1000,
(_events) => {
cbCount += 1;
}
);
assert.equal(cbCount, 0, "The callback should never get called");
});
it("The limit provided is respected", async function () {
this.timeout(60000);
const badUser = await newTestUser(this.config.homeserverUrl, {
name: { contains: "spammer" },
});
const badUserId = await badUser.getUserId();
const moderator = await newTestUser(this.config.homeserverUrl, {
name: { contains: "moderator" },
});
const targetRoom = await moderator.createRoom({
invite: [await badUser.getUserId()],
});
await badUser.joinRoom(targetRoom);
// send some bad person messages
// bad guy sends 5 messages at the start of the timeline
for (let i = 0; i < 5; i++) {
await badUser.sendMessage(targetRoom, {
msgtype: "m.text",
body: "Very Bad Stuff",
});
}
// send some irrelevant messages
await Promise.all(
[...Array(200).keys()].map((i) =>
moderator.sendMessage(targetRoom, {
msgtype: "m.text.",
body: `Irrelevant Message #${i}`,
})
)
);
let cbCount = 0;
await getMessagesByUserIn(
moderator,
"*spammer*",
targetRoom,
200,
(_events) => {
cbCount += 1;
}
);
// Remember that the limit is the number of events that getMessagesByUserIn has checked against the glob,
// not the number of events to provide to the callback.
// E.g. we don't want to paginate to the beginning of history just because less than 200 events match the glob,
// which is very likely if a user has only just started sending messages.
assert.equal(
cbCount,
0,
"The callback should never be called as the limit should be reached beforehand."
);
await getMessagesByUserIn(
moderator,
"*spammer*",
targetRoom,
205,
(events) => {
cbCount += 1;
events.map((e) => {
assert.equal(
e.sender,
badUserId,
"All the events should be from the same sender"
);
});
}
);
assert.equal(
cbCount,
1,
"The callback should be called once with events matching the glob."
);
});
it("Gives the events to the callback ordered by youngest first (even more important when the limit is reached halfway through a chunk).", async function () {
this.timeout(60000);
const moderator = await newTestUser(this.config.homeserverUrl, {
name: { contains: "moderator" },
});
const moderatorId = await moderator.getUserId();
const targetRoom = await moderator.createRoom();
for (let i = 0; i < 20; i++) {
await moderator.sendMessage(targetRoom, {
msgtype: "m.text.",
body: `${i}`,
});
}
await getMessagesByUserIn(
moderator,
moderatorId,
targetRoom,
5,
(events) => {
const messageNumbers = events.map((event) =>
parseInt((event.content as TextMessageContent).body, 10)
);
messageNumbers.map((n) => {
assert.equal(
n >= 15,
true,
"The youngest events should be given to the callback first."
);
});
}
);
});
});