update tests to remove use of panataliamon

This commit is contained in:
H. Shay 2024-09-30 13:04:44 -07:00
parent c9a757f6e2
commit e640fc968b
8 changed files with 143 additions and 82 deletions

View File

@ -16,7 +16,7 @@ rawHomeserverUrl: "http://localhost:8081"
pantalaimon:
# If true, accessToken above is ignored and the username/password below will be
# used instead. The access token of the bot will be stored in the dataPath.
use: true
use: false
# The username to login with.
username: mjolnir
@ -25,6 +25,11 @@ pantalaimon:
# stored the access token.
password: mjolnir
encryption:
use: true
username: test
password: testPassword
# The directory the bot should store various bits of information in
dataPath: "./test/harness/mjolnir-data/"

View File

@ -44,6 +44,7 @@
"typescript-formatter": "^7.2"
},
"dependencies": {
"axios": "^1.7.6",
"@sentry/node": "^7.17.2",
"@sentry/tracing": "^7.17.2",
"@tensorflow/tfjs-node": "^4.21.0",

View File

@ -1,8 +1,7 @@
import { strict as assert } from "assert";
import { matrixClient } from "./mjolnirSetupUtils";
import { newTestUser } from "./clientHelper";
import { ReportManager, ABUSE_ACTION_CONFIRMATION_KEY, ABUSE_REPORT_KEY } from "../../src/report/ReportManager";
import { ABUSE_REPORT_KEY } from "../../src/report/ReportManager";
/**
* Test the ability to turn abuse reports into room messages.
@ -30,8 +29,9 @@ describe("Test: Reporting abuse", async () => {
this.timeout(90000);
// Listen for any notices that show up.
await new Promise(resolve => setTimeout(resolve, 1000));
let notices: any[] = [];
this.mjolnir.client.on("room.event", (roomId, event) => {
this.mjolnir.client.on("room.event", (roomId: string, event: any) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
@ -225,8 +225,9 @@ describe("Test: Reporting abuse", async () => {
this.timeout(60000);
// Listen for any notices that show up.
await new Promise(resolve => setTimeout(resolve, 1000));
let notices: any[] = [];
this.mjolnir.client.on("room.event", (roomId, event) => {
this.mjolnir.client.on("room.event", (roomId: string, event: any) => {
if (roomId = this.mjolnir.managementRoomId) {
notices.push(event);
}
@ -244,8 +245,8 @@ describe("Test: Reporting abuse", async () => {
let badUserId = await badUser.getUserId();
let roomId = await moderatorUser.createRoom({ invite: [await badUser.getUserId()] });
await moderatorUser.inviteUser(await goodUser.getUserId(), roomId);
await moderatorUser.inviteUser(await badUser.getUserId(), roomId);
await moderatorUser.inviteUser(goodUserId, roomId);
await moderatorUser.inviteUser(badUserId, roomId);
await badUser.joinRoom(roomId);
await goodUser.joinRoom(roomId);
@ -257,9 +258,9 @@ describe("Test: Reporting abuse", async () => {
// Exchange a few messages.
let goodText = `GOOD: ${Math.random()}`; // Will NOT be reported.
let badText = `BAD: ${Math.random()}`; // Will be reported as abuse.
let goodEventId = await goodUser.sendText(roomId, goodText);
await goodUser.sendText(roomId, goodText);
let badEventId = await badUser.sendText(roomId, badText);
let goodEventId2 = await goodUser.sendText(roomId, goodText);
await goodUser.sendText(roomId, goodText);
console.log("Test: Reporting abuse - send reports");

View File

@ -1,55 +1,80 @@
import { HmacSHA1 } from "crypto-js";
import { getRequestFn, LogService, MatrixClient, MemoryStorageProvider, PantalaimonClient } from "@vector-im/matrix-bot-sdk";
import {
MatrixClient,
MemoryStorageProvider,
RustSdkCryptoStorageProvider
} from "@vector-im/matrix-bot-sdk";
import { PathLike, promises as fs} from "fs";
import axios from "axios";
const REGISTRATION_ATTEMPTS = 10;
const REGISTRATION_RETRY_BASE_DELAY_MS = 100;
let CryptoStorePaths: any = [];
/**
* Register a user using the synapse admin api that requires the use of a registration secret rather than an admin user.
* This should only be used by test code and should not be included from any file in the source directory
* either by explicit imports or copy pasting.
*
* @param homeserver the homeserver url
* @param username The username to give the user.
* @param displayname The displayname to give the user.
* @param password The password to use.
* @param admin True to make the user an admin, false otherwise.
* @returns The response from synapse.
* @returns The access token from logging in.
*/
export async function registerUser(homeserver: string, username: string, displayname: string, password: string, admin: boolean): Promise<void> {
export async function registerUser(homeserver: string, username: string, displayname: string, password: string, admin: boolean): Promise<string> {
let registerUrl = `${homeserver}/_synapse/admin/v1/register`
const data: {nonce: string} = await new Promise((resolve, reject) => {
getRequestFn()({uri: registerUrl, method: "GET", timeout: 60000}, (error: any, response: any, resBody: any) => {
error ? reject(error) : resolve(JSON.parse(resBody))
});
});
const nonce = data.nonce!;
const response = await axios({method: 'get', url: registerUrl, timeout: 60000})
const nonce = response.data.nonce
let mac = HmacSHA1(`${nonce}\0${username}\0${password}\0${admin ? 'admin' : 'notadmin'}`, 'REGISTRATION_SHARED_SECRET');
for (let i = 1; i <= REGISTRATION_ATTEMPTS; ++i) {
const registerConfig = {
url: registerUrl,
method: "post",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({
nonce,
username,
displayname,
password,
admin,
mac: mac.toString()
}),
timeout: 60000
}
try {
const params = {
uri: registerUrl,
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
nonce,
username,
displayname,
password,
admin,
mac: mac.toString()
}),
timeout: 60000
}
return await new Promise((resolve, reject) => {
getRequestFn()(params, (error: any) => error ? reject(error) : resolve());
});
let resp = await axios(registerConfig)
return resp.data?.access_token
} catch (ex) {
const code = ex.response.data.errcode
// In case of timeout or throttling, backoff and retry.
if (ex?.code === 'ESOCKETTIMEDOUT' || ex?.code === 'ETIMEDOUT'
|| ex?.body?.errcode === 'M_LIMIT_EXCEEDED') {
if (code === 'ESOCKETTIMEDOUT' || code === 'ETIMEDOUT'
|| code === 'M_LIMIT_EXCEEDED') {
await new Promise(resolve => setTimeout(resolve, REGISTRATION_RETRY_BASE_DELAY_MS * i * i));
continue;
}
if (code === 'M_USER_IN_USE') {
const loginUrl = `${homeserver}/_matrix/client/r0/login`
const loginConfig = {
url: loginUrl,
method: "post",
headers: {"Content-Type": "application/json"},
data: JSON.stringify({
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": username
},
"password": password
}),
timeout: 60000
}
let resp2 = await axios(loginConfig)
return resp2.data?.access_token
}
throw ex;
}
}
@ -78,31 +103,24 @@ export type RegistrationOptions = {
/**
* Register a new test user.
*
* @returns A string that is both the username and password of a new user.
* @returns an access token for a new test account
*/
async function registerNewTestUser(homeserver: string, options: RegistrationOptions) {
async function registerNewTestUser(homeserver: string, options: RegistrationOptions): Promise<string> {
do {
let username;
let accessToken: string;
if ("exact" in options.name) {
username = options.name.exact;
} else {
username = `mjolnir-test-user-${options.name.contains}${Math.floor(Math.random() * 100000)}`
}
try {
await registerUser(homeserver, username, username, username, Boolean(options.isAdmin));
return username;
accessToken = await registerUser(homeserver, username, username, username, Boolean(options.isAdmin));
return accessToken;
} catch (e) {
if (e?.body?.errcode === 'M_USER_IN_USE') {
if ("exact" in options.name) {
LogService.debug("test/clientHelper", `${username} already registered, reusing`);
return username;
} else {
LogService.debug("test/clientHelper", `${username} already registered, trying another`);
}
} else {
console.error(`failed to register user ${e}`);
throw e;
}
console.error(`failed to register user ${e}`);
throw e;
}
} while (true);
}
@ -112,10 +130,17 @@ async function registerNewTestUser(homeserver: string, options: RegistrationOpti
*
* @returns A new `MatrixClient` session for a unique test user.
*/
export async function newTestUser(homeserver: string, options: RegistrationOptions): Promise<MatrixClient> {
const username = await registerNewTestUser(homeserver, options);
const pantalaimon = new PantalaimonClient(homeserver, new MemoryStorageProvider());
const client = await pantalaimon.createClientWithCredentials(username, username);
export async function newTestUser(homeserver: string, options: RegistrationOptions, encrypted: boolean = false): Promise<MatrixClient> {
const accessToken = await registerNewTestUser(homeserver, options);
let client;
if (encrypted) {
const cStore = await getTempCryptoStore();
client = new MatrixClient(homeserver, accessToken, new MemoryStorageProvider(), cStore);
await client.crypto.prepare()
} else {
client = new MatrixClient(homeserver, accessToken, new MemoryStorageProvider())
}
if (!options.isThrottled) {
let userId = await client.getUserId();
await overrideRatelimitForUser(homeserver, userId);
@ -132,17 +157,11 @@ let _globalAdminUser: MatrixClient;
async function getGlobalAdminUser(homeserver: string): Promise<MatrixClient> {
// Initialize global admin user if needed.
if (!_globalAdminUser) {
let accessToken: string;
const USERNAME = "mjolnir-test-internal-admin-user";
try {
await registerUser(homeserver, USERNAME, USERNAME, USERNAME, true);
} catch (e) {
if (e.isAxiosError && e?.response?.data?.errcode === 'M_USER_IN_USE') {
// Then we've already registered the user in a previous run and that is ok.
} else {
throw e;
}
}
_globalAdminUser = await new PantalaimonClient(homeserver, new MemoryStorageProvider()).createClientWithCredentials(USERNAME, USERNAME);
accessToken = await registerUser(homeserver, USERNAME, USERNAME, USERNAME, true);
_globalAdminUser = await new MatrixClient(homeserver, accessToken, new MemoryStorageProvider())
await _globalAdminUser
}
return _globalAdminUser;
}
@ -177,6 +196,23 @@ export function noticeListener(targetRoomdId: string, cb: (event: any) => void)
return (roomId: string, event: any) => {
if (roomId !== targetRoomdId) return;
if (event?.content?.msgtype !== "m.notice") return;
cb(event);
cb(event);
}
}
/**
* Tears down temporary crypto stores created for testing
*/
export async function teardownCryptoStores() {
await Promise.all(CryptoStorePaths.map((p: PathLike) => fs.rm(p, { force: true, recursive: true})));
CryptoStorePaths = [];
}
/**
* Helper function to create temp crypto store for testing
*/
export async function getTempCryptoStore() {
const cryptoDir = await fs.mkdtemp('mjolnir-integration-test');
CryptoStorePaths.push(cryptoDir);
return new RustSdkCryptoStorageProvider(cryptoDir, 0);
}

View File

@ -33,6 +33,8 @@ describe("Test: The make admin command", function () {
LogService.debug("makeadminTest", `moderator creating targetRoom: ${targetRoom}; and inviting ${mjolnirUserId}`);
await moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text.', body: `!mjolnir rooms add ${targetRoom}` });
LogService.debug("makeadminTest", `Adding targetRoom: ${targetRoom}`);
// allow bot time to join room
await new Promise(resolve => setTimeout(resolve, 1000));
try {
await moderator.start();
await userA.start();

View File

@ -1,6 +1,7 @@
import { read as configRead } from "../../src/config";
import { makeMjolnir, teardownManagementRoom } from "./mjolnirSetupUtils";
import dns from 'node:dns';
import {teardownCryptoStores} from "./clientHelper";
// Necessary for CI: Node 17+ defaults to using ipv6 first, but Github Actions does not support ipv6
dns.setDefaultResultOrder('ipv4first');
@ -39,7 +40,8 @@ export const mochaHooks = {
]);
// remove alias from management room and leave it.
await teardownManagementRoom(this.mjolnir.client, this.mjolnir.managementRoomId, this.managementRoomAlias);
await teardownCryptoStores()
console.error("---- completed test", JSON.stringify(this.currentTest.title), "\n\n"); // Makes MatrixClient error logs a bit easier to parse.
}
]
],
};

View File

@ -15,7 +15,6 @@ limitations under the License.
*/
import {
MatrixClient,
PantalaimonClient,
MemoryStorageProvider,
LogService,
LogLevel,
@ -23,7 +22,7 @@ import {
} from "@vector-im/matrix-bot-sdk";
import { Mjolnir} from '../../src/Mjolnir';
import { overrideRatelimitForUser, registerUser } from "./clientHelper";
import {getTempCryptoStore, overrideRatelimitForUser, registerUser} from "./clientHelper";
import { initializeGlobalPerformanceMetrics, initializeSentry, patchMatrixClient } from "../../src/utils";
import { IConfig } from "../../src/config";
@ -54,15 +53,9 @@ async function configureMjolnir(config: IConfig) {
initializeSentry(config);
initializeGlobalPerformanceMetrics(config);
try {
await registerUser(config.homeserverUrl, config.pantalaimon.username, config.pantalaimon.username, config.pantalaimon.password, true)
} catch (e) {
if (e?.body?.errcode === 'M_USER_IN_USE') {
console.log(`${config.pantalaimon.username} already registered, skipping`);
return;
}
throw e;
};
let accessToken = await registerUser(config.homeserverUrl, config.encryption.username, config.encryption.username, config.encryption.password, true)
return accessToken
}
export function mjolnir(): Mjolnir | null {
@ -78,12 +71,14 @@ let globalMjolnir: Mjolnir | null;
* Return a test instance of Mjolnir.
*/
export async function makeMjolnir(config: IConfig): Promise<Mjolnir> {
await configureMjolnir(config);
let accessToken = await configureMjolnir(config);
let cryptoStore = await getTempCryptoStore()
LogService.setLogger(new RichConsoleLogger());
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);
let client = new MatrixClient(config.homeserverUrl, accessToken, new MemoryStorageProvider(), cryptoStore);
await client.crypto.prepare()
await overrideRatelimitForUser(config.homeserverUrl, await client.getUserId());
patchMatrixClient();
await ensureAliasedRoomExists(client, config.managementRoom);

View File

@ -775,6 +775,15 @@ aws4@^1.8.0:
resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios@^1.7.6:
version "1.7.7"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f"
integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==
dependencies:
follow-redirects "^1.15.6"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@ -1741,6 +1750,11 @@ fn.name@1.x.x:
resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz"
integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==
follow-redirects@^1.15.6:
version "1.15.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
@ -3237,6 +3251,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz"