mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-05-10 17:55:18 +00:00
Merge pull request #567 from the-draupnir-project/gnuxie/safe-mode-appservice-integration-test
appservice provisioned draupnir safe mode test https://github.com/the-draupnir-project/planning/issues/26
This commit is contained in:
+36
-2
@@ -64,10 +64,18 @@ import {
|
||||
MatrixAdaptorContext,
|
||||
sendMatrixEventsFromDeadDocument,
|
||||
} from "./commands/interface-manager/MPSMatrixInterfaceAdaptor";
|
||||
import { makeDraupnirCommandDispatcher } from "./commands/DraupnirCommandDispatcher";
|
||||
import {
|
||||
makeDraupnirCommandDispatcher,
|
||||
makeDraupnirJSCommandDispatcher,
|
||||
} from "./commands/DraupnirCommandDispatcher";
|
||||
import { SafeModeToggle } from "./safemode/SafeModeToggle";
|
||||
import { makeCommandDispatcherTimelineListener } from "./safemode/ManagementRoom";
|
||||
|
||||
import {
|
||||
BasicInvocationInformation,
|
||||
JSInterfaceCommandDispatcher,
|
||||
Presentation,
|
||||
StandardPresentationArgumentStream,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
const log = new Logger("Draupnir");
|
||||
|
||||
// webAPIS should not be included on the Draupnir class.
|
||||
@@ -113,6 +121,8 @@ export class Draupnir implements Client, MatrixAdaptorContext {
|
||||
this.commandDispatcher
|
||||
);
|
||||
|
||||
private readonly JSInterfaceDispatcher: JSInterfaceCommandDispatcher<BasicInvocationInformation> =
|
||||
makeDraupnirJSCommandDispatcher(this);
|
||||
private constructor(
|
||||
public readonly client: MatrixSendClient,
|
||||
public readonly clientUserID: StringUserID,
|
||||
@@ -354,4 +364,28 @@ export class Draupnir implements Client, MatrixAdaptorContext {
|
||||
public get commandRoomID() {
|
||||
return this.managementRoomID;
|
||||
}
|
||||
|
||||
/**
|
||||
* API for integration tests to be able to test commands, mostly to ensure
|
||||
* functionality of the appservice bots.
|
||||
*/
|
||||
public async sendPresentationCommand<CommandReturn>(
|
||||
sender: StringUserID,
|
||||
...items: Presentation[]
|
||||
): Promise<ActionResult<CommandReturn>> {
|
||||
return await this.JSInterfaceDispatcher.invokeCommandFromPresentationStream(
|
||||
{ commandSender: sender },
|
||||
new StandardPresentationArgumentStream(items)
|
||||
);
|
||||
}
|
||||
|
||||
public async sendTextCommand<CommandReturn>(
|
||||
sender: StringUserID,
|
||||
command: string
|
||||
): Promise<ActionResult<CommandReturn>> {
|
||||
return await this.JSInterfaceDispatcher.invokeCommandFromBody(
|
||||
{ commandSender: sender },
|
||||
command
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
MatrixInterfaceCommandDispatcher,
|
||||
StandardMatrixInterfaceCommandDispatcher,
|
||||
CommandPrefixExtractor,
|
||||
JSInterfaceCommandDispatcher,
|
||||
BasicInvocationInformation,
|
||||
StandardJSInterfaceCommandDispatcher,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
import { Draupnir } from "../Draupnir";
|
||||
import {
|
||||
@@ -21,7 +24,10 @@ import {
|
||||
import { DraupnirHelpCommand } from "./Help";
|
||||
import { userLocalpart } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { DraupnirTopLevelCommands } from "./DraupnirCommandTable";
|
||||
import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites";
|
||||
import {
|
||||
DraupnirContextToCommandContextTranslator,
|
||||
DraupnirInterfaceAdaptor,
|
||||
} from "./DraupnirCommandPrerequisites";
|
||||
import "./DraupnirCommands";
|
||||
|
||||
function makePrefixExtractor(draupnir: Draupnir): CommandPrefixExtractor {
|
||||
@@ -60,3 +66,18 @@ export function makeDraupnirCommandDispatcher(
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function makeDraupnirJSCommandDispatcher(
|
||||
draupnir: Draupnir
|
||||
): JSInterfaceCommandDispatcher<BasicInvocationInformation> {
|
||||
return new StandardJSInterfaceCommandDispatcher(
|
||||
DraupnirTopLevelCommands,
|
||||
DraupnirHelpCommand,
|
||||
draupnir,
|
||||
{
|
||||
...MPSCommandDispatcherCallbacks,
|
||||
prefixExtractor: makePrefixExtractor(draupnir),
|
||||
},
|
||||
DraupnirContextToCommandContextTranslator
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ export class StandardDraupnirManager {
|
||||
config,
|
||||
this.makeSafeModeToggle(clientUserID, managementRoom, config)
|
||||
);
|
||||
if (this.isDraupnirAvailable(clientUserID)) {
|
||||
if (this.isNormalDraupnir(clientUserID)) {
|
||||
return ActionError.Result(
|
||||
`There is a draupnir for ${clientUserID} already running`
|
||||
);
|
||||
@@ -83,6 +83,7 @@ export class StandardDraupnirManager {
|
||||
}
|
||||
this.draupnir.set(clientUserID, draupnir.ok);
|
||||
this.failedDraupnir.delete(clientUserID);
|
||||
this.safeModeDraupnir.delete(clientUserID);
|
||||
draupnir.ok.start();
|
||||
return draupnir;
|
||||
}
|
||||
@@ -93,7 +94,7 @@ export class StandardDraupnirManager {
|
||||
config: IConfig,
|
||||
cause: SafeModeCause
|
||||
): Promise<ActionResult<SafeModeDraupnir>> {
|
||||
if (this.isDraupnirAvailable(clientUserID)) {
|
||||
if (this.isSafeModeDraupnir(clientUserID)) {
|
||||
return ActionError.Result(
|
||||
`There is a draupnir for ${clientUserID} already running`
|
||||
);
|
||||
@@ -115,16 +116,26 @@ export class StandardDraupnirManager {
|
||||
}
|
||||
safeModeDraupnir.ok.start();
|
||||
this.safeModeDraupnir.set(clientUserID, safeModeDraupnir.ok);
|
||||
this.draupnir.delete(clientUserID);
|
||||
this.failedDraupnir.delete(clientUserID);
|
||||
return safeModeDraupnir;
|
||||
}
|
||||
|
||||
private isNormalDraupnir(drapunirClientID: StringUserID): boolean {
|
||||
return this.draupnir.has(drapunirClientID);
|
||||
}
|
||||
|
||||
private isSafeModeDraupnir(drapunirClientID: StringUserID): boolean {
|
||||
return this.safeModeDraupnir.has(drapunirClientID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the draupnir is available to the user, either normally or via safe mode.
|
||||
*/
|
||||
public isDraupnirAvailable(draupnirClientID: StringUserID): boolean {
|
||||
return (
|
||||
this.draupnir.has(draupnirClientID) ||
|
||||
this.safeModeDraupnir.has(draupnirClientID)
|
||||
this.isNormalDraupnir(draupnirClientID) ||
|
||||
this.isSafeModeDraupnir(draupnirClientID)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk";
|
||||
import { MatrixReactionHandler } from "../commands/interface-manager/MatrixReactionHandler";
|
||||
import { IConfig } from "../config";
|
||||
import { SafeModeCause } from "./SafeModeCause";
|
||||
import { makeSafeModeCommandDispatcher } from "./SafeModeCommandDispatcher";
|
||||
import {
|
||||
makeSafeModeCommandDispatcher,
|
||||
makeSafeModeJSDispatcher,
|
||||
} from "./SafeModeCommandDispatcher";
|
||||
import {
|
||||
ARGUMENT_PROMPT_LISTENER,
|
||||
DEFAUILT_ARGUMENT_PROMPT_LISTENER,
|
||||
@@ -48,6 +51,7 @@ export class SafeModeDraupnir implements MatrixAdaptorContext {
|
||||
this.client,
|
||||
this.commandDispatcher
|
||||
);
|
||||
private readonly JSInterfaceDispatcher = makeSafeModeJSDispatcher(this);
|
||||
public constructor(
|
||||
public readonly cause: SafeModeCause,
|
||||
public readonly client: MatrixSendClient,
|
||||
@@ -111,4 +115,14 @@ export class SafeModeDraupnir implements MatrixAdaptorContext {
|
||||
) as Promise<Result<void>>
|
||||
);
|
||||
}
|
||||
|
||||
public async sendTextCommand<CommandReturn>(
|
||||
sender: StringUserID,
|
||||
command: string
|
||||
): Promise<Result<CommandReturn>> {
|
||||
return await this.JSInterfaceDispatcher.invokeCommandFromBody(
|
||||
{ commandSender: sender },
|
||||
command
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
// SPDX-License-Identifier: AFL-3.0
|
||||
|
||||
import {
|
||||
BasicInvocationInformation,
|
||||
CommandPrefixExtractor,
|
||||
JSInterfaceCommandDispatcher,
|
||||
MatrixInterfaceCommandDispatcher,
|
||||
StandardJSInterfaceCommandDispatcher,
|
||||
StandardMatrixInterfaceCommandDispatcher,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
import { SafeModeDraupnir } from "./DraupnirSafeMode";
|
||||
@@ -16,7 +19,10 @@ import {
|
||||
import { userLocalpart } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { SafeModeCommands } from "./commands/SafeModeCommands";
|
||||
import { SafeModeHelpCommand } from "./commands/HelpCommand";
|
||||
import { SafeModeInterfaceAdaptor } from "./commands/SafeModeAdaptor";
|
||||
import {
|
||||
SafeModeContextToCommandContextTranslator,
|
||||
SafeModeInterfaceAdaptor,
|
||||
} from "./commands/SafeModeAdaptor";
|
||||
|
||||
function makePrefixExtractor(
|
||||
safeModeDraupnir: SafeModeDraupnir
|
||||
@@ -55,3 +61,18 @@ export function makeSafeModeCommandDispatcher(
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function makeSafeModeJSDispatcher(
|
||||
safeModeDraupnir: SafeModeDraupnir
|
||||
): JSInterfaceCommandDispatcher<BasicInvocationInformation> {
|
||||
return new StandardJSInterfaceCommandDispatcher(
|
||||
SafeModeCommands,
|
||||
SafeModeHelpCommand,
|
||||
safeModeDraupnir,
|
||||
{
|
||||
...MPSCommandDispatcherCallbacks,
|
||||
prefixExtractor: makePrefixExtractor(safeModeDraupnir),
|
||||
},
|
||||
SafeModeContextToCommandContextTranslator
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AFL-3.0
|
||||
|
||||
import { StringUserID } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { MjolnirAppService } from "../../../src/appservice/AppService";
|
||||
import { newTestUser } from "../../integration/clientHelper";
|
||||
import { StandardProvisionHelper } from "../utils/ProvisionHelper";
|
||||
import { setupHarness } from "../utils/harness";
|
||||
import { SafeModeDraupnir } from "../../../src/safemode/DraupnirSafeMode";
|
||||
|
||||
interface Context extends Mocha.Context {
|
||||
appservice?: MjolnirAppService;
|
||||
}
|
||||
|
||||
describe("Test safe mode commands on a provisioned Draupnir", function () {
|
||||
beforeEach(async function (this: Context) {
|
||||
this.appservice = await setupHarness();
|
||||
});
|
||||
afterEach(function (this: Context) {
|
||||
if (this.appservice) {
|
||||
return this.appservice.close();
|
||||
} else {
|
||||
console.warn("Missing Appservice in this context, so cannot stop it.");
|
||||
return Promise.resolve(); // TS7030: Not all code paths return a value.
|
||||
}
|
||||
});
|
||||
it("Provisioned draupnir can switch to safe mode and back.", async function (this: Context) {
|
||||
const appservice = this.appservice;
|
||||
if (appservice === undefined) {
|
||||
throw new TypeError(`Test setup failed`);
|
||||
}
|
||||
const provisionHelper = new StandardProvisionHelper(appservice);
|
||||
const moderator = await newTestUser(appservice.config.homeserver.url, {
|
||||
name: { contains: "moderator" },
|
||||
});
|
||||
const moderatorUserID = (await moderator.getUserId()) as StringUserID;
|
||||
const initialDraupnir = (
|
||||
await provisionHelper.provisionDraupnir(moderatorUserID)
|
||||
).expect("Failed to provision a draupnir for the test");
|
||||
const safeModeDraupnir = (
|
||||
await initialDraupnir.sendTextCommand<SafeModeDraupnir>(
|
||||
moderatorUserID,
|
||||
"!draupnir safe mode"
|
||||
)
|
||||
).expect("Failed to switch to safe mode");
|
||||
(
|
||||
await safeModeDraupnir.sendTextCommand(
|
||||
moderatorUserID,
|
||||
"!draupnir restart"
|
||||
)
|
||||
).expect("Failed to restart back to draupnir from safe mode");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: AFL-3.0
|
||||
|
||||
import { Ok, Result, ResultError, isError } from "@gnuxie/typescript-result";
|
||||
import { Draupnir } from "../../../src/Draupnir";
|
||||
import { MjolnirAppService } from "../../../src/appservice/AppService";
|
||||
import { StringUserID } from "@the-draupnir-project/matrix-basic-types";
|
||||
|
||||
export interface ProvisionHelper {
|
||||
/**
|
||||
* Automatically make a draupnir and a management room.
|
||||
*/
|
||||
provisionDraupnir(requestingUserID: StringUserID): Promise<Result<Draupnir>>;
|
||||
}
|
||||
|
||||
export class StandardProvisionHelper implements ProvisionHelper {
|
||||
public constructor(private readonly appservice: MjolnirAppService) {
|
||||
// nothing to do.
|
||||
}
|
||||
async provisionDraupnir(
|
||||
requestingUserID: StringUserID
|
||||
): Promise<Result<Draupnir>> {
|
||||
const provisionResult =
|
||||
await this.appservice.draupnirManager.provisionNewDraupnir(
|
||||
requestingUserID
|
||||
);
|
||||
if (isError(provisionResult)) {
|
||||
return provisionResult;
|
||||
}
|
||||
const draupnir = await this.appservice.draupnirManager.getRunningDraupnir(
|
||||
this.appservice.draupnirManager.draupnirMXID(provisionResult.ok),
|
||||
requestingUserID
|
||||
);
|
||||
if (draupnir === undefined) {
|
||||
return ResultError.Result(`Failed to find draupnir after provisioning`);
|
||||
}
|
||||
return Ok(draupnir);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user