Unit tests for the ban and unban commands. (#542)

* Unit test Ban and UnbanCommands.

Fixes https://github.com/the-draupnir-project/Draupnir/issues/441
will follow up with https://github.com/the-draupnir-project/interface-manager/issues/7.

Part of https://github.com/the-draupnir-project/planning/issues/22.

* Update to @the-draupnir-project/interface-manager@2.1.0.

* Rest parameters can only give one argument from prompts.

* Update to @the-draupnir-project/interface-manager@2.2.0.
This commit is contained in:
Gnuxie
2024-09-11 10:24:01 +01:00
committed by GitHub
parent 986afb7e2b
commit a5afdbe9ad
10 changed files with 496 additions and 69 deletions
+1 -1
View File
@@ -56,7 +56,7 @@
"@sentry/node": "^7.17.2",
"@sentry/tracing": "^7.17.2",
"@sinclair/typebox": "0.32.34",
"@the-draupnir-project/interface-manager": "2.0.0",
"@the-draupnir-project/interface-manager": "2.2.0",
"@the-draupnir-project/matrix-basic-types": "^0.1.1",
"await-lock": "^2.2.2",
"better-sqlite3": "^9.4.3",
+57 -25
View File
@@ -8,19 +8,21 @@
// https://github.com/matrix-org/mjolnir
// </text>
import { Draupnir } from "../Draupnir";
import {
ActionResult,
PolicyRoomEditor,
PolicyRuleType,
isError,
Ok,
PolicyRoomManager,
RoomResolver,
PolicyListConfig,
} from "matrix-protection-suite";
import { resolveRoomReferenceSafe } from "matrix-protection-suite-for-matrix-bot-sdk";
import { findPolicyRoomIDFromShortcode } from "./CreateBanListCommand";
import {
MatrixRoomReference,
MatrixUserID,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import {
BasicInvocationInformation,
@@ -32,22 +34,31 @@ import {
tuple,
union,
} from "@the-draupnir-project/interface-manager";
import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites";
import {
DraupnirContextToCommandContextTranslator,
DraupnirInterfaceAdaptor,
} from "./DraupnirCommandPrerequisites";
export async function findPolicyRoomEditorFromRoomReference(
draupnir: Draupnir,
roomResolver: RoomResolver,
policyRoomManager: PolicyRoomManager,
policyRoomReference: MatrixRoomReference
): Promise<ActionResult<PolicyRoomEditor>> {
const policyRoomID = await resolveRoomReferenceSafe(
draupnir.client,
policyRoomReference
);
const policyRoomID = await roomResolver.resolveRoom(policyRoomReference);
if (isError(policyRoomID)) {
return policyRoomID;
}
return await draupnir.policyRoomManager.getPolicyRoomEditor(policyRoomID.ok);
return await policyRoomManager.getPolicyRoomEditor(policyRoomID.ok);
}
export type DraupnirBanCommandContext = {
policyRoomManager: PolicyRoomManager;
issuerManager: PolicyListConfig;
defaultReasons: string[];
roomResolver: RoomResolver;
clientUserID: StringUserID;
};
export const DraupnirBanCommand = describeCommand({
summary: "Bans an entity from the policy list.",
parameters: tuple(
@@ -67,13 +78,13 @@ export const DraupnirBanCommand = describeCommand({
MatrixRoomReferencePresentationSchema,
StringPresentationType
),
prompt: async function (draupnir: Draupnir) {
prompt: async function ({
policyRoomManager,
clientUserID,
}: DraupnirBanCommandContext) {
return Ok({
suggestions: draupnir.policyRoomManager
.getEditablePolicyRoomIDs(
draupnir.clientUserID,
PolicyRuleType.User
)
suggestions: policyRoomManager
.getEditablePolicyRoomIDs(clientUserID, PolicyRuleType.User)
.map((room) => MatrixRoomIDPresentationType.wrap(room)),
});
},
@@ -83,16 +94,21 @@ export const DraupnirBanCommand = describeCommand({
name: "reason",
description: "The reason for the ban.",
acceptor: StringPresentationType,
prompt: async function (draupnir: Draupnir) {
prompt: async function ({ defaultReasons }: DraupnirBanCommandContext) {
return Ok({
suggestions: draupnir.config.commands.ban.defaultReasons.map(
(reason) => [StringPresentationType.wrap(reason)]
suggestions: defaultReasons.map((reason) =>
StringPresentationType.wrap(reason)
),
});
},
},
async executor(
draupnir: Draupnir,
{
issuerManager,
policyRoomManager,
roomResolver,
clientUserID,
}: DraupnirBanCommandContext,
_info: BasicInvocationInformation,
_keywords,
reasonParts,
@@ -101,13 +117,19 @@ export const DraupnirBanCommand = describeCommand({
): Promise<ActionResult<string>> {
const policyRoomReference =
typeof policyRoomDesignator === "string"
? await findPolicyRoomIDFromShortcode(draupnir, policyRoomDesignator)
? await findPolicyRoomIDFromShortcode(
issuerManager,
policyRoomManager,
clientUserID,
policyRoomDesignator
)
: Ok(policyRoomDesignator);
if (isError(policyRoomReference)) {
return policyRoomReference;
}
const policyListEditorResult = await findPolicyRoomEditorFromRoomReference(
draupnir,
roomResolver,
policyRoomManager,
policyRoomReference.ok
);
if (isError(policyListEditorResult)) {
@@ -128,10 +150,7 @@ export const DraupnirBanCommand = describeCommand({
reason
);
} else {
const resolvedRoomReference = await resolveRoomReferenceSafe(
draupnir.client,
entity
);
const resolvedRoomReference = await roomResolver.resolveRoom(entity);
if (isError(resolvedRoomReference)) {
return resolvedRoomReference;
}
@@ -144,6 +163,19 @@ export const DraupnirBanCommand = describeCommand({
},
});
DraupnirContextToCommandContextTranslator.registerTranslation(
DraupnirBanCommand,
function (draupnir) {
return {
policyRoomManager: draupnir.policyRoomManager,
issuerManager: draupnir.protectedRoomsSet.issuerManager,
defaultReasons: draupnir.config.commands.ban.defaultReasons,
roomResolver: draupnir.clientPlatform.toRoomResolver(),
clientUserID: draupnir.clientUserID,
};
}
);
DraupnirInterfaceAdaptor.describeRenderer(DraupnirBanCommand, {
isAlwaysSupposedToUseDefaultRenderer: true,
});
+11 -4
View File
@@ -12,13 +12,18 @@ import {
ActionError,
ActionResult,
Ok,
PolicyListConfig,
PolicyRoomManager,
PolicyRuleType,
PropagationType,
isError,
} from "matrix-protection-suite";
import { listInfo } from "./StatusCommand";
import { Draupnir } from "../Draupnir";
import { MatrixRoomID } from "@the-draupnir-project/matrix-basic-types";
import {
MatrixRoomID,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import {
BasicInvocationInformation,
ParsedKeywords,
@@ -84,10 +89,12 @@ DraupnirInterfaceAdaptor.describeRenderer(DraupnirListCreateCommand, {
});
export async function findPolicyRoomIDFromShortcode(
draupnir: Draupnir,
issuerManager: PolicyListConfig,
policyRoomManager: PolicyRoomManager,
editingClientUserID: StringUserID,
shortcode: string
): Promise<ActionResult<MatrixRoomID>> {
const info = await listInfo(draupnir);
const info = await listInfo(issuerManager, policyRoomManager);
const matchingRevisions = info.filter(
(list) => list.revision.shortcode === shortcode
);
@@ -99,7 +106,7 @@ export async function findPolicyRoomIDFromShortcode(
return Ok(matchingRevisions[0].revision.room);
} else {
const remainingRevisions = matchingRevisions.filter((revision) =>
revision.revision.isAbleToEdit(draupnir.clientUserID, PolicyRuleType.User)
revision.revision.isAbleToEdit(editingClientUserID, PolicyRuleType.User)
);
if (
remainingRevisions.length !== 1 ||
+8 -2
View File
@@ -90,7 +90,10 @@ export const DraupnirListRulesCommand = describeCommand({
summary: "Lists the rules currently in use by Draupnir.",
parameters: [],
async executor(draupnir: Draupnir): Promise<Result<ListMatches[]>> {
const infoResult = await listInfo(draupnir);
const infoResult = await listInfo(
draupnir.protectedRoomsSet.issuerManager,
draupnir.policyRoomManager
);
return Ok(
infoResult.map((policyRoom) => ({
room: policyRoom.revision.room,
@@ -124,7 +127,10 @@ export const DraupnirRulesMatchingCommand = describeCommand({
_rest,
entity
): Promise<Result<ListMatches[]>> {
const policyRooms = await listInfo(draupnir);
const policyRooms = await listInfo(
draupnir.protectedRoomsSet.issuerManager,
draupnir.policyRoomManager
);
return Ok(
policyRooms.map((policyRoom) => {
return {
+12 -5
View File
@@ -12,6 +12,8 @@ import { DOCUMENTATION_URL, PACKAGE_JSON, SOFTWARE_VERSION } from "../config";
import {
ActionResult,
Ok,
PolicyListConfig,
PolicyRoomManager,
PolicyRoomRevision,
PolicyRoomWatchProfile,
PolicyRuleType,
@@ -47,12 +49,14 @@ export interface StatusInfo {
documentationURL: string;
}
export async function listInfo(draupnir: Draupnir): Promise<ListInfo[]> {
const watchedListProfiles =
draupnir.protectedRoomsSet.issuerManager.allWatchedLists;
export async function listInfo(
issuerManager: PolicyListConfig,
policyRoomManager: PolicyRoomManager
): Promise<ListInfo[]> {
const watchedListProfiles = issuerManager.allWatchedLists;
const issuerResults = await Promise.all(
watchedListProfiles.map((profile) =>
draupnir.policyRoomManager.getPolicyRoomRevisionIssuer(profile.room)
policyRoomManager.getPolicyRoomRevisionIssuer(profile.room)
)
);
return issuerResults.map((result) => {
@@ -89,7 +93,10 @@ DraupnirInterfaceAdaptor.describeRenderer(DraupnirStatusCommand, {
export async function draupnirStatusInfo(
draupnir: Draupnir
): Promise<StatusInfo> {
const watchedListInfo = await listInfo(draupnir);
const watchedListInfo = await listInfo(
draupnir.protectedRoomsSet.issuerManager,
draupnir.policyRoomManager
);
const protectedWatchedLists = watchedListInfo.filter((info) =>
draupnir.protectedRoomsSet.isProtectedRoom(
info.revision.room.toRoomIDOrAlias()
+91 -26
View File
@@ -8,14 +8,23 @@
// https://github.com/matrix-org/mjolnir
// </text>
import { Draupnir } from "../Draupnir";
import { isError, Ok, PolicyRuleType } from "matrix-protection-suite";
import {
isError,
Ok,
PolicyListConfig,
PolicyRoomManager,
PolicyRuleType,
RoomResolver,
RoomUnbanner,
SetMembership,
} from "matrix-protection-suite";
import { LogLevel } from "matrix-bot-sdk";
import { findPolicyRoomIDFromShortcode } from "./CreateBanListCommand";
import {
isStringUserID,
MatrixGlob,
MatrixUserID,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import {
describeCommand,
@@ -27,33 +36,47 @@ import {
union,
} from "@the-draupnir-project/interface-manager";
import { Result } from "@gnuxie/typescript-result";
import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites";
import {
DraupnirContextToCommandContextTranslator,
DraupnirInterfaceAdaptor,
} from "./DraupnirCommandPrerequisites";
import ManagementRoomOutput from "../ManagementRoomOutput";
import { DraupnirBanCommandContext } from "./Ban";
import { UnlistedUserRedactionQueue } from "../queues/UnlistedUserRedactionQueue";
async function unbanUserFromRooms(draupnir: Draupnir, rule: MatrixGlob) {
await draupnir.managementRoomOutput.logMessage(
async function unbanUserFromRooms(
{
managementRoomOutput,
setMembership,
roomUnbanner,
noop,
}: DraupnirUnbanCommandContext,
rule: MatrixGlob
) {
await managementRoomOutput.logMessage(
LogLevel.INFO,
"Unban",
`Unbanning users that match glob: ${rule.regex}`
);
for (const revision of draupnir.protectedRoomsSet.setMembership.allRooms) {
for (const revision of setMembership.allRooms) {
for (const member of revision.members()) {
if (member.membership !== "ban") {
continue;
}
if (rule.test(member.userID)) {
await draupnir.managementRoomOutput.logMessage(
await managementRoomOutput.logMessage(
LogLevel.DEBUG,
"Unban",
`Unbanning ${member.userID} in ${revision.room.toRoomIDOrAlias()}`,
revision.room.toRoomIDOrAlias()
);
if (!draupnir.config.noop) {
await draupnir.client.unbanUser(
member.userID,
revision.room.toRoomIDOrAlias()
if (!noop) {
await roomUnbanner.unbanUser(
revision.room.toRoomIDOrAlias(),
member.userID
);
} else {
await draupnir.managementRoomOutput.logMessage(
await managementRoomOutput.logMessage(
LogLevel.WARN,
"Unban",
`Attempted to unban ${member.userID} in ${revision.room.toRoomIDOrAlias()} but Mjolnir is running in no-op mode`,
@@ -65,6 +88,18 @@ async function unbanUserFromRooms(draupnir: Draupnir, rule: MatrixGlob) {
}
}
export type DraupnirUnbanCommandContext = {
policyRoomManager: PolicyRoomManager;
issuerManager: PolicyListConfig;
roomResolver: RoomResolver;
clientUserID: StringUserID;
setMembership: SetMembership;
managementRoomOutput: ManagementRoomOutput;
noop: boolean;
roomUnbanner: RoomUnbanner;
unlistedUserRedactionQueue: UnlistedUserRedactionQueue;
};
export const DraupnirUnbanCommand = describeCommand({
summary:
"Removes an entity from a policy list. If the entity is a glob, then the flag --true must be provided to unban users matching the glob from all protected rooms.",
@@ -85,13 +120,13 @@ export const DraupnirUnbanCommand = describeCommand({
MatrixRoomReferencePresentationSchema,
StringPresentationType
),
prompt: async function (draupnir: Draupnir) {
prompt: async function ({
policyRoomManager,
clientUserID,
}: DraupnirBanCommandContext) {
return Ok({
suggestions: draupnir.policyRoomManager
.getEditablePolicyRoomIDs(
draupnir.clientUserID,
PolicyRuleType.User
)
suggestions: policyRoomManager
.getEditablePolicyRoomIDs(clientUserID, PolicyRuleType.User)
.map((room) => MatrixRoomIDPresentationType.wrap(room)),
});
},
@@ -105,17 +140,29 @@ export const DraupnirUnbanCommand = describeCommand({
},
},
async executor(
draupnir: Draupnir,
context: DraupnirUnbanCommandContext,
_info,
keywords,
_rest,
entity,
policyRoomDesignator
): Promise<Result<void>> {
const roomResolver = draupnir.clientPlatform.toRoomResolver();
const {
roomResolver,
policyRoomManager,
issuerManager,
clientUserID,
unlistedUserRedactionQueue,
managementRoomOutput,
} = context;
const policyRoomReference =
typeof policyRoomDesignator === "string"
? await findPolicyRoomIDFromShortcode(draupnir, policyRoomDesignator)
? await findPolicyRoomIDFromShortcode(
issuerManager,
policyRoomManager,
clientUserID,
policyRoomDesignator
)
: Ok(policyRoomDesignator);
if (isError(policyRoomReference)) {
return policyRoomReference;
@@ -124,8 +171,9 @@ export const DraupnirUnbanCommand = describeCommand({
if (isError(policyRoom)) {
return policyRoom;
}
const policyRoomEditor =
await draupnir.policyRoomManager.getPolicyRoomEditor(policyRoom.ok);
const policyRoomEditor = await policyRoomManager.getPolicyRoomEditor(
policyRoom.ok
);
if (isError(policyRoomEditor)) {
return policyRoomEditor;
}
@@ -156,15 +204,15 @@ export const DraupnirUnbanCommand = describeCommand({
string.includes("*") ? true : string.includes("?");
const rule = new MatrixGlob(entity.toString());
if (isStringUserID(rawEnttiy)) {
draupnir.unlistedUserRedactionQueue.removeUser(rawEnttiy);
unlistedUserRedactionQueue.removeUser(rawEnttiy);
}
if (
!isGlob(rawEnttiy) ||
keywords.getKeywordValue<boolean>("true", false)
) {
await unbanUserFromRooms(draupnir, rule);
await unbanUserFromRooms(context, rule);
} else {
await draupnir.managementRoomOutput.logMessage(
await managementRoomOutput.logMessage(
LogLevel.WARN,
"Unban",
"Running unban without `unban <list> <user> true` will not override existing room level bans"
@@ -175,6 +223,23 @@ export const DraupnirUnbanCommand = describeCommand({
},
});
DraupnirContextToCommandContextTranslator.registerTranslation(
DraupnirUnbanCommand,
function (draupnir) {
return {
policyRoomManager: draupnir.policyRoomManager,
issuerManager: draupnir.protectedRoomsSet.issuerManager,
roomResolver: draupnir.clientPlatform.toRoomResolver(),
clientUserID: draupnir.clientUserID,
setMembership: draupnir.protectedRoomsSet.setMembership,
managementRoomOutput: draupnir.managementRoomOutput,
noop: draupnir.config.noop,
roomUnbanner: draupnir.clientPlatform.toRoomUnbanner(),
unlistedUserRedactionQueue: draupnir.unlistedUserRedactionQueue,
};
}
);
DraupnirInterfaceAdaptor.describeRenderer(DraupnirUnbanCommand, {
isAlwaysSupposedToUseDefaultRenderer: true,
});
+4 -1
View File
@@ -298,7 +298,10 @@ export class BanPropagationProtection
PolicyRuleType.User,
Recommendation.Ban
);
const policyRoomInfo = await listInfo(draupnir);
const policyRoomInfo = await listInfo(
draupnir.protectedRoomsSet.issuerManager,
draupnir.policyRoomManager
);
if (rulesMatchingUser.length === 0) {
return; // user is already unbanned.
}
+176
View File
@@ -0,0 +1,176 @@
// SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0
import {
CommandExecutorHelper,
MatrixRoomIDPresentationType,
MatrixUserIDPresentationType,
PromptRequiredError,
} from "@the-draupnir-project/interface-manager";
import {
MatrixRoomID,
MatrixUserID,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import {
Ok,
PolicyRoomEditor,
PolicyRoomManager,
PolicyRuleType,
PowerLevelsEventContent,
Recommendation,
RoomResolver,
describeProtectedRoomsSet,
isOk,
randomEventID,
} from "matrix-protection-suite";
import { createMock } from "ts-auto-mock";
import expect from "expect";
import { DraupnirBanCommand } from "../../../src/commands/Ban";
const DraupnirUserID = `@draupnir:ourserver.example.com` as StringUserID;
async function createProtectedRooms() {
return await describeProtectedRoomsSet({
rooms: [
{
policyDescriptions: [
{
entity: "@existing-spam:spam.example.com",
recommendation: Recommendation.Ban,
type: PolicyRuleType.User,
},
],
stateDescriptions: [
{
sender: DraupnirUserID,
type: "m.room.power_levels",
state_key: "",
content: {
users: {
[DraupnirUserID]: 100,
},
} satisfies PowerLevelsEventContent,
},
],
},
],
});
}
describe("Test the DraupnirBanCommand", function () {
const roomResolver = createMock<RoomResolver>({
async resolveRoom(roomReference) {
if (roomReference instanceof MatrixRoomID) {
return Ok(roomReference);
}
throw new TypeError(`We don't really expect to resolve anything`);
},
});
it("Will prompt for the policy list when it is missing", async function () {
const { protectedRoomsSet, policyRoomManager } =
await createProtectedRooms();
const banResult = await CommandExecutorHelper.parseAndInvoke(
DraupnirBanCommand,
{
policyRoomManager,
roomResolver,
issuerManager: protectedRoomsSet.issuerManager,
defaultReasons: ["spam"],
clientUserID: `@draupnir:ourserver.example.com` as StringUserID,
},
{},
MatrixUserIDPresentationType.wrap(
MatrixUserID.fromUserID("@spam:spam.example.com" as StringUserID)
),
undefined // no policy room provided, we expect a prompt.
);
if (isOk(banResult)) {
throw new TypeError(`We expect a prompt error to be returned`);
}
if (!(banResult.error instanceof PromptRequiredError)) {
throw new TypeError(`Expected to have a prompt required error`);
}
expect(banResult.error.parameterRequiringPrompt.name).toBe("list");
});
it("Will prompt for the reason when it is missing", async function () {
const { protectedRoomsSet, policyRoomManager } =
await createProtectedRooms();
const policyRoom = protectedRoomsSet.allProtectedRooms[0];
if (policyRoom === undefined) {
throw new TypeError(
`There should be a policy room available from the setup`
);
}
const banResult = await CommandExecutorHelper.parseAndInvoke(
DraupnirBanCommand,
{
policyRoomManager,
roomResolver,
issuerManager: protectedRoomsSet.issuerManager,
defaultReasons: ["spam"],
clientUserID: `@draupnir:ourserver.example.com` as StringUserID,
},
{},
MatrixUserIDPresentationType.wrap(
MatrixUserID.fromUserID("@spam:spam.example.com" as StringUserID)
),
MatrixRoomIDPresentationType.wrap(policyRoom)
);
if (isOk(banResult)) {
throw new TypeError(`We expect a prompt error to be returned`);
}
if (!(banResult.error instanceof PromptRequiredError)) {
throw new TypeError(`Expected to have a prompt required error`);
}
expect(banResult.error.parameterRequiringPrompt.name).toBe("reason");
});
it("Will add a user to the policy list when they are banned", async function () {
const { protectedRoomsSet } = await createProtectedRooms();
const policyRoom = protectedRoomsSet.allProtectedRooms[0];
if (policyRoom === undefined) {
throw new TypeError(
`There should be a policy room available from the setup`
);
}
const policyRoomManager = createMock<PolicyRoomManager>({
async getPolicyRoomEditor(room) {
expect(room).toBe(policyRoom);
return Ok(
createMock<PolicyRoomEditor>({
async createPolicy(
entityType,
recommendation,
entity,
reason,
_additionalProperties
) {
expect(entityType).toBe(PolicyRuleType.User);
expect(recommendation).toBe(Recommendation.Ban);
expect(entity).toBe("@spam:spam.example.com");
expect(reason).toBe("spam");
return Ok(randomEventID());
},
})
);
},
});
const banResult = await CommandExecutorHelper.execute(
DraupnirBanCommand,
{
policyRoomManager,
roomResolver,
issuerManager: protectedRoomsSet.issuerManager,
defaultReasons: ["spam"],
clientUserID: `@draupnir:ourserver.example.com` as StringUserID,
},
{
rest: ["spam"],
},
MatrixUserID.fromUserID("@spam:spam.example.com" as StringUserID),
policyRoom
);
expect(banResult.isOkay).toBe(true);
});
});
+132
View File
@@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0
import { CommandExecutorHelper } from "@the-draupnir-project/interface-manager";
import {
MatrixRoomID,
MatrixUserID,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import {
Membership,
Ok,
PolicyRoomEditor,
PolicyRoomManager,
PolicyRuleType,
PowerLevelsEventContent,
Recommendation,
RoomResolver,
RoomUnbanner,
describeProtectedRoomsSet,
isError,
} from "matrix-protection-suite";
import { createMock } from "ts-auto-mock";
import expect from "expect";
import { DraupnirUnbanCommand } from "../../../src/commands/Unban";
import ManagementRoomOutput from "../../../src/ManagementRoomOutput";
import { UnlistedUserRedactionQueue } from "../../../src/queues/UnlistedUserRedactionQueue";
const DraupnirUserID = `@draupnir:ourserver.example.com` as StringUserID;
const ExistingBanUserID = "@existing-spam:spam.example.com" as StringUserID;
async function createProtectedRooms() {
return await describeProtectedRoomsSet({
rooms: [
{
membershipDescriptions: [
{
sender: DraupnirUserID,
target: ExistingBanUserID,
membership: Membership.Ban,
},
],
policyDescriptions: [
{
entity: ExistingBanUserID,
recommendation: Recommendation.Ban,
type: PolicyRuleType.User,
},
],
stateDescriptions: [
{
sender: DraupnirUserID,
type: "m.room.power_levels",
state_key: "",
content: {
users: {
[DraupnirUserID]: 100,
},
} satisfies PowerLevelsEventContent,
},
],
},
],
});
}
describe("Test the DraupnirUnbanCommand", function () {
const roomResolver = createMock<RoomResolver>({
async resolveRoom(roomReference) {
if (roomReference instanceof MatrixRoomID) {
return Ok(roomReference);
}
throw new TypeError(`We don't really expect to resolve anything`);
},
});
it("Will add a user to the policy list when they are banned", async function () {
const roomUnbanner = createMock<RoomUnbanner>({
async unbanUser(_room, userID, _reason) {
expect(userID).toBe(ExistingBanUserID);
return Ok(undefined);
},
});
const { protectedRoomsSet, policyRoomManager } =
await createProtectedRooms();
const policyRoom = protectedRoomsSet.allProtectedRooms[0];
if (policyRoom === undefined) {
throw new TypeError(
`There should be a policy room available from the setup`
);
}
const revisionIssuer =
await policyRoomManager.getPolicyRoomRevisionIssuer(policyRoom);
if (isError(revisionIssuer)) {
throw new TypeError(`Test is wrong`);
}
const mockPolicyRoomManager = createMock<PolicyRoomManager>({
async getPolicyRoomEditor(room) {
expect(room).toBe(policyRoom);
return Ok(
createMock<PolicyRoomEditor>({
async unbanEntity(ruleType, entity) {
expect(ruleType).toBe(PolicyRuleType.User);
expect(entity).toBe(ExistingBanUserID);
return Ok(revisionIssuer.ok.currentRevision.allRules());
},
})
);
},
});
const banResult = await CommandExecutorHelper.execute(
DraupnirUnbanCommand,
{
policyRoomManager: mockPolicyRoomManager,
setMembership: protectedRoomsSet.setMembership,
managementRoomOutput: createMock<ManagementRoomOutput>(),
roomResolver,
issuerManager: protectedRoomsSet.issuerManager,
clientUserID: `@draupnir:ourserver.example.com` as StringUserID,
noop: false,
roomUnbanner,
unlistedUserRedactionQueue: createMock<UnlistedUserRedactionQueue>(),
},
{
rest: ["spam"],
},
MatrixUserID.fromUserID(ExistingBanUserID),
policyRoom
);
expect(banResult.isOkay).toBe(true);
});
});
+4 -5
View File
@@ -256,14 +256,13 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
"@the-draupnir-project/interface-manager@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@the-draupnir-project/interface-manager/-/interface-manager-2.0.0.tgz#1386bb7d59257d867f963adf600cc506f3ec591d"
integrity sha512-DD3D3cOx0LqPJ4oET1+ttpUlVZf+4u8RTqFwxL0Qh8KOWLS9oxBwiw+U0IPDbTa+07hbhzY/hRMRBiqheXS/LQ==
"@the-draupnir-project/interface-manager@2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@the-draupnir-project/interface-manager/-/interface-manager-2.2.0.tgz#2326aca4ccbf0b0ed743efb3a20222a5d78ab23e"
integrity sha512-ycNBKT78RsRhU+HfHe3gjgtQY2pCn7Umw8uAJBHGzLFpXwk2E9kmv6dGbtGubINjsFdkacr7lVxZwm69K4bSUg==
dependencies:
"@gnuxie/super-cool-stream" "^0.2.1"
"@gnuxie/typescript-result" "^0.2.0"
"@the-draupnir-project/matrix-basic-types" "^0.1.1"
"@the-draupnir-project/matrix-basic-types@^0.1.1":
version "0.1.1"