Files
Draupnir/apps/draupnir/test/integration/mjolnirSetupUtils.ts
T
Gnuxie 879e822332 Monoreponir (#1046)
* Move src to apps/draupnir/src

https://github.com/the-draupnir-project/planning/issues/100

* Move package.json

https://github.com/the-draupnir-project/planning/issues/100

* Add matrix-basic-types to monorepo.

Get everything working including linting and prettier :3

https://github.com/the-draupnir-project/planning/issues/100

* Add interface-manager to monorepo.

This was a bitch because apparently we forgot to delete node_modules
before creating the workspace package.json. So it had linked a bunch
of local stuff like was in node_modules for Draupnir...

Anyways i think we're still on track.

https://github.com/the-draupnir-project/planning/issues/100

* idk why there are prettier changes in apps but there are.

* Add matrix-protection-suite to monorepo.

https://github.com/the-draupnir-project/planning/issues/100

* Add matrix-protection-suite-for-matrix-bot-sdk

https://github.com/the-draupnir-project/planning/issues/100

We will need to add the real upstreams and versions and remove the
file links as we publish the packages.

* Move mps-interface-adaptor into monorepo

https://github.com/the-draupnir-project/planning/issues/100

Wohoo, i think only draupnir is left now?

* Move Draupnir test files to draupnir directory smh smh smh.

https://github.com/the-draupnir-project/planning/issues/100

* Fix typescript config for tests and eslint.

Now we get proper linting and type checking of tests.

https://github.com/the-draupnir-project/planning/issues/100

* WIP Integrating draupnir into monorepo tooling...

https://github.com/the-draupnir-project/planning/issues/100

We need to stop aliasing bot-sdk but we should first check that
upstream is using a consistent name too.

* Remove matrix-bot-sdk alias for vector fork.

https://github.com/the-draupnir-project/planning/issues/100

* Add top command description type and weave through API.

A more recent version of typescript meant that the exectutor's
contravariance got checked which destroyed the API so we had to make a
top type for command descriptions and parametrise some of the API.

https://github.com/the-draupnir-project/planning/issues/100

* Fix typescript errors related to class property initialisation changes.

https://www.typescriptlang.org/tsconfig/#useDefineForClassFields

Seems like they were using defineProperty before which meant
properites were initialised after the constructor ran.

Honestly i like that more but we're going to stick with what they
intend to be the default.

https://github.com/the-draupnir-project/planning/issues/100

* Fix tests lacking fixtures context.

https://github.com/the-draupnir-project/planning/issues/100

* Fix typescript errors related to error destructuring in tests.

https://github.com/the-draupnir-project/planning/issues/100

* Pin postgres package to workaround upstream issue

https://github.com/porsager/postgres/issues/1150
Documented in DEPENDENCIES.md

https://github.com/the-draupnir-project/planning/issues/100

* Fix contravariance issue in hash store helper.

Part of the TS 5.9 upgrade fallout.
https://github.com/the-draupnir-project/planning/issues/100

* Fix minor typescript 5.9 migration issuess

All typescript errors finished, yay.

* Fix REUSE missing headers.

* Fix assets script in draupnir app.

* Add Draupnir to eslint scope

* Remove the appservice web API.

There are too many eslint errors here to do with unsafe parsing of
properties from the body etc. And there's actually no consumers to
this API. It's also a widget API, and all it does is provision the bot
and nothing more.

* Fix eslint config for DeadDocumentJSX.

It wasn't working well with the jsx templates.
We should probably delete the tsconfig.eslint.json shite now.

* Update src/utils.ts for eslint.

This shit is legacy i hate it.

* Fix eslint errors in config.

Really this is paint over rot since the config doesn't have a schema,
and we can't really make one either.

* Fix eslint issues in ReportManager.

This code is diabolical. It hasn't really been fixed that will take
refactoring and making sure people don't write this sorts of bad code
ever again. Which thankfully we have process in place for.

* Fix clientHelper eslint issues.

* Fix eslint for ImportCommand.

* Grinding eslint fml.

* Fix miscellaneous eslint issues.

* allow no-deprecate for logMessage.

shit's being annoying.

* Fix remaining eslint issues...

We also deleted one of the scripts used to evaluate the performance of
various endpoints, which we were not using.

* Give bot toggle asyncDispose for code consistency.

* Fix package.json access issues.

* Adjust Docker and CI for new app location in monorepo.

* Fix broken integration tests.

* Remove prepare script from matrix-protection-suite package.

Isn't needed anymore


* Fix build:all script missing base files.

* Remove test script from matrix-protection-suite-for-matrix-bot-sdk

It doesn't have any tests :/

* Order of setup is wrong in integration test workflows.

* Fix mps interface adaptor doesn't have any tests.

* Fix appservice registration for test harness.

* Fix matrix-basic-types jest configuration

* Fix no build step in mjolnir.yaml

* Transfer common dev dependencies to the workspace root.


They were just wrong.
2026-03-19 16:13:14 +00:00

226 lines
6.6 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 {
MatrixClient,
PantalaimonClient,
MemoryStorageProvider,
LogService,
LogLevel,
} from "@vector-im/matrix-bot-sdk";
import { overrideRatelimitForUser, registerUser } from "./clientHelper";
import { initializeSentry, patchMatrixClient } from "../../src/utils";
import { IConfig } from "../../src/config";
import { Draupnir } from "../../src/Draupnir";
import { DraupnirBotModeToggle } from "../../src/DraupnirBotMode";
import {
MatrixSendClient,
SafeMatrixEmitter,
SafeMatrixEmitterWrapper,
} from "matrix-protection-suite-for-matrix-bot-sdk";
import {
DefaultEventDecoder,
MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE,
MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE,
OwnLifetime,
} from "matrix-protection-suite";
import { SafeModeDraupnir } from "../../src/safemode/DraupnirSafeMode";
import { TopLevelStores } from "../../src/backingstore/DraupnirStores";
patchMatrixClient();
// they are add [key: string]: any to their interface, amazing.
export type SafeMochaContext = Pick<
Mocha.Context,
| "test"
| "currentTest"
| "runnable"
| "timeout"
| "slow"
| "skip"
| "retries"
| "done"
>;
export interface DraupnirTestContext extends SafeMochaContext {
lifetime: OwnLifetime;
draupnir?: Draupnir;
managementRoomAlias?: string;
toggle?: DraupnirBotModeToggle;
config: IConfig;
stores?: TopLevelStores;
}
/**
* Ensures that a room exists with the alias, if it does not exist we create it.
* @param client The MatrixClient to use to resolve or create the aliased room.
* @param alias The alias of the room.
* @returns The room ID of the aliased room.
*/
export async function ensureAliasedRoomExists(
client: MatrixClient,
alias: string
): Promise<string> {
try {
return await client.resolveRoom(alias);
} catch (e) {
if (typeof e !== "object" || e === null) {
throw new TypeError("Something is throwing garbage");
}
if (!("body" in e) || typeof e.body !== "object" || e.body === null) {
throw e;
}
if (!("errcode" in e.body) || e.body.errcode !== "M_NOT_FOUND") {
throw e;
}
console.info(`${alias} hasn't been created yet, so we're making it now.`);
const roomId = await client.createRoom({
visibility: "public",
});
await client.createRoomAlias(alias, roomId);
return roomId;
}
}
async function configureMjolnir(config: IConfig) {
// Initialize error monitoring as early as possible.
initializeSentry(config);
try {
await registerUser(
config.homeserverUrl,
config.pantalaimon.username,
config.pantalaimon.username,
config.pantalaimon.password,
true
);
} catch (e) {
if (typeof e !== "object" || e === null) {
throw new TypeError("Something is throwing garbage");
}
if (!("body" in e) || typeof e.body !== "object" || e.body === null) {
throw e;
}
if (!("errcode" in e.body) || e.body.errcode !== "M_USER_IN_USE") {
throw e;
}
console.log(`${config.pantalaimon.username} already registered, skipping`);
return;
}
}
export function draupnir(): Draupnir {
if (globalMjolnir === null) {
throw new TypeError("Setup code didn't run before you called `draupnir()`");
}
return globalMjolnir;
}
export function draupnirClient(): MatrixClient | null {
return globalClient;
}
export function draupnirSafeEmitter(): SafeMatrixEmitter {
if (globalSafeEmitter !== undefined) {
return globalSafeEmitter;
}
throw new TypeError(`Setup code didn't run properly`);
}
let globalClient: MatrixClient | null;
let globalMjolnir: Draupnir | null;
let globalSafeEmitter: SafeMatrixEmitter | undefined;
/**
* Return a test instance of Draupnir.
*/
export async function makeBotModeToggle(
config: IConfig,
{
stores,
eraseAccountData,
allowSafeMode,
}: {
stores: TopLevelStores;
eraseAccountData?: boolean;
allowSafeMode?: boolean;
} = { stores: { dispose() {} } }
): Promise<DraupnirBotModeToggle> {
await configureMjolnir(config);
LogService.setLevel(LogLevel.fromString(config.logLevel, LogLevel.DEBUG));
LogService.info("test/mjolnirSetupUtils", "Starting bot...");
const pantalaimon = new PantalaimonClient(
config.homeserverUrl,
new MemoryStorageProvider()
);
const client = await pantalaimon.createClientWithCredentials(
config.pantalaimon.username,
config.pantalaimon.password
);
if (eraseAccountData) {
await Promise.all([
client.setAccountData(MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE, { rooms: [] }),
client.setAccountData(MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, {
references: [],
}),
leaveAllRooms(client),
]);
}
await overrideRatelimitForUser(
config.homeserverUrl,
await client.getUserId()
);
await client
.deleteRoomAlias(config.managementRoom)
.catch((_: unknown) => undefined);
await ensureAliasedRoomExists(client, config.managementRoom);
const toggle = await DraupnirBotModeToggle.create(
client,
new SafeMatrixEmitterWrapper(client, DefaultEventDecoder),
config,
stores
);
// we don't want to send status on startup incase we want to test e2ee from the manual launch script.
const mj = (
await toggle.startFromScratch({ sendStatusOnStart: false })
).expect("Could not create Draupnir");
if (mj instanceof SafeModeDraupnir && !allowSafeMode) {
throw new TypeError(
"Setup code is wrong, shouldn't be booting into safe mode"
);
}
globalClient = client;
if (mj instanceof Draupnir) {
globalMjolnir = mj;
}
console.info(`management room ${mj.managementRoom.toPermalink()}`);
globalSafeEmitter = new SafeMatrixEmitterWrapper(client, DefaultEventDecoder);
return toggle;
}
/**
* Remove the alias and leave the room, can't be implicitly provided from the config because Draupnir currently mutates it.
* @param client The client to use to leave the room.
* @param roomId The roomId of the room to leave.
* @param alias The alias to remove from the room.
*/
export async function teardownManagementRoom(
client: MatrixClient,
roomId: string,
alias: string
) {
await client.deleteRoomAlias(alias);
await client.leaveRoom(roomId);
}
export async function leaveAllRooms(client: MatrixSendClient): Promise<void> {
const joinedRooms = await client.getJoinedRooms();
await Promise.allSettled(
joinedRooms.map((roomID) => client.leaveRoom(roomID))
);
}