Merge pull request #1134 from vector-im/invite/implement-invite-command

Invite - PR [1/2] - Add command /invite to message composer
This commit is contained in:
R Midhun Suresh 2023-09-04 12:52:38 +05:30 committed by GitHub
commit 0cad1e5048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 165 additions and 25 deletions

View File

@ -18,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [14.x] node-version: [18.1.0]
steps: steps:
- name: Checkout source - name: Checkout source

View File

@ -45,6 +45,7 @@
"escodegen": "^2.0.0", "escodegen": "^2.0.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"fake-indexeddb": "^3.1.2", "fake-indexeddb": "^3.1.2",
"fs-extra": "^11.1.1",
"impunity": "^1.0.9", "impunity": "^1.0.9",
"mdn-polyfills": "^5.20.0", "mdn-polyfills": "^5.20.0",
"merge-options": "^3.0.4", "merge-options": "^3.0.4",

View File

@ -14,15 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/// <reference types="cypress" />
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as fse from "fs-extra"; import * as fse from "fs-extra";
import {dockerRun, dockerStop } from "../docker"; import {dockerRun, dockerStop } from "../docker";
// A cypress plugins to add command to start & stop dex instances // A plugin that adds command to start & stop dex instances
interface DexConfig { interface DexConfig {
configDir: string; configDir: string;

View File

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/// <reference types="cypress" />
import * as os from "os"; import * as os from "os";
import * as childProcess from "child_process"; import * as childProcess from "child_process";
import * as fse from "fs-extra"; import * as fse from "fs-extra";
@ -36,7 +34,8 @@ export function dockerRun(args: {
} }
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
childProcess.execFile('docker', [ childProcess.execFile('sudo', [
"docker",
"run", "run",
"--name", args.containerName, "--name", args.containerName,
"-d", "-d",
@ -57,7 +56,8 @@ export function dockerExec(args: {
params: string[]; params: string[];
}): Promise<void> { }): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
childProcess.execFile("docker", [ childProcess.execFile("sudo", [
"docker",
"exec", args.containerId, "exec", args.containerId,
...args.params, ...args.params,
], { encoding: 'utf8' }, (err, stdout, stderr) => { ], { encoding: 'utf8' }, (err, stdout, stderr) => {
@ -79,10 +79,12 @@ export function dockerCreateNetwork(args: {
networkName: string; networkName: string;
}): Promise<void> { }): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
childProcess.execFile("docker", [ childProcess.execFile("sudo", [
"docker",
"network", "network",
"create", "create",
args.networkName args.networkName,
"--subnet", "172.18.0.0/16"
], { encoding: 'utf8' }, (err, stdout, stderr) => { ], { encoding: 'utf8' }, (err, stdout, stderr) => {
if(err) { if(err) {
if (stderr.includes(`network with name ${args.networkName} already exists`)) { if (stderr.includes(`network with name ${args.networkName} already exists`)) {
@ -106,7 +108,8 @@ export async function dockerLogs(args: {
const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore"; const stderrFile = args.stderrFile ? await fse.open(args.stderrFile, "w") : "ignore";
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
childProcess.spawn("docker", [ childProcess.spawn("sudo", [
"docker",
"logs", "logs",
args.containerId, args.containerId,
], { ], {
@ -122,7 +125,8 @@ export function dockerStop(args: {
containerId: string; containerId: string;
}): Promise<void> { }): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [ childProcess.execFile('sudo', [
"docker",
"stop", "stop",
args.containerId, args.containerId,
], err => { ], err => {
@ -138,7 +142,8 @@ export function dockerRm(args: {
containerId: string; containerId: string;
}): Promise<void> { }): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
childProcess.execFile('docker', [ childProcess.execFile('sudo', [
"docker",
"rm", "rm",
args.containerId, args.containerId,
], err => { ], err => {

View File

@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/// <reference types="cypress" />
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as crypto from "crypto"; import * as crypto from "crypto";
@ -25,7 +23,7 @@ import {dockerCreateNetwork, dockerExec, dockerLogs, dockerRun, dockerStop} from
import {request} from "@playwright/test"; import {request} from "@playwright/test";
// A cypress plugins to add command to start & stop synapses in // A plugin to add command to start & stop synapses in
// docker with preset templates. // docker with preset templates.
interface SynapseConfig { interface SynapseConfig {

View File

@ -0,0 +1,86 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {test} from '@playwright/test';
import {synapseStart, synapseStop, registerUser} from "../plugins/synapsedocker";
import type {SynapseInstance} from "../plugins/synapsedocker";
test.describe("Login", () => {
let synapse: SynapseInstance;
test.beforeEach(async () => {
synapse = await synapseStart("default");
});
test.afterEach(async () => {
await synapseStop(synapse.synapseId);
});
test("Invite user using /invite command from composer", async ({ page }) => {
const user1 = ["foobaraccount", "password123"] as const;
const user2 = ["foobaraccount2", "password123"] as const;
await registerUser(synapse, ...user1);
const { userId } = await registerUser(synapse, ...user2);
await page.goto("/");
await page.locator("#homeserver").fill("");
await page.locator("#homeserver").type(synapse.baseUrl);
const [username, password] = user1;
await page.locator("#username").type(username);
await page.locator("#password").type(password);
await page.getByText("Log In", { exact: true }).click();
await page.locator(".SessionView").waitFor();
// Create the room
await page.getByLabel("Create room").click();
await page.getByText("Create Room").click();
await page.locator("#name").type("My Room");
await page.locator(".CreateRoomView_detailsForm")
.getByRole("button", { name: "Create room" })
.click();
await page.locator(".RoomList")
.locator("li")
.first()
.click();
await page.locator(".MessageComposer_input textarea").type(`/invite ${userId}`);
await page.keyboard.press("Enter");
await page.locator(".AnnouncementView").last().getByText("was invited to the room").waitFor();
});
test("Error is shown when using /invite command from composer", async ({ page }) => {
const user1 = ["foobaraccount", "password123"] as const;
await registerUser(synapse, ...user1);
await page.goto("/");
await page.locator("#homeserver").fill("");
await page.locator("#homeserver").type(synapse.baseUrl);
const [username, password] = user1;
await page.locator("#username").type(username);
await page.locator("#password").type(password);
await page.getByText("Log In", { exact: true }).click();
await page.locator(".SessionView").waitFor();
// Create the room
await page.getByLabel("Create room").click();
await page.getByText("Create Room").click();
await page.locator("#name").type("My Room");
await page.locator(".CreateRoomView_detailsForm")
.getByRole("button", { name: "Create room" })
.click();
await page.locator(".RoomList")
.locator("li")
.first()
.click();
await page.locator(".MessageComposer_input textarea").type("/invite foobar");
await page.keyboard.press("Enter");
await page.locator(".RoomView").locator(".ErrorView").waitFor();
});
});

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Make sure docker is available # Make sure docker is available
if ! docker info > /dev/null 2>&1; then if ! docker --version > /dev/null 2>&1; then
echo "You need to intall docker before you can run the tests!" echo "You need to intall docker before you can run the tests!"
exit 1 exit 1
fi fi

View File

@ -273,6 +273,14 @@ export class RoomViewModel extends ErrorReportViewModel {
this.reportError(new Error("join syntax: /join <room-id>")); this.reportError(new Error("join syntax: /join <room-id>"));
} }
break; break;
case "invite":
if (args.length === 1) {
const userId = args[0];
await this._room.inviteUser(userId);
} else {
this.reportError(new Error("invite syntax: /invite <user-id>"));
}
break;
case "shrug": case "shrug":
message = "¯\\_(ツ)_/¯ " + args.join(" "); message = "¯\\_(ツ)_/¯ " + args.join(" ");
msgtype = "m.text"; msgtype = "m.text";

View File

@ -267,6 +267,15 @@ export class HomeServerApi {
return this._get("/pushers", undefined, undefined, options); return this._get("/pushers", undefined, undefined, options);
} }
invite(roomId: string, userId: string, reason?: string, options?: BaseRequestOptions): IHomeServerRequest {
return this._post(
`/rooms/${encodeURIComponent(roomId)}/invite`,
{},
{ user_id: userId, reason },
options
);
}
join(roomId: string, options?: BaseRequestOptions): IHomeServerRequest { join(roomId: string, options?: BaseRequestOptions): IHomeServerRequest {
return this._post(`/rooms/${encodeURIComponent(roomId)}/join`, {}, {}, options); return this._post(`/rooms/${encodeURIComponent(roomId)}/join`, {}, {}, options);
} }

View File

@ -470,6 +470,13 @@ export class Room extends BaseRoom {
}); });
} }
async inviteUser(userId, reason) {
if (!userId) {
throw new Error("userId is null/undefined");
}
await this._hsApi.invite(this.id, userId, reason).response();
}
/* called by BaseRoom to pass pendingEvents when opening the timeline */ /* called by BaseRoom to pass pendingEvents when opening the timeline */
_getPendingEvents() { _getPendingEvents() {
return this._sendQueue.pendingEvents; return this._sendQueue.pendingEvents;

View File

@ -83,12 +83,12 @@
fastq "^1.6.0" fastq "^1.6.0"
"@playwright/test@^1.27.1": "@playwright/test@^1.27.1":
version "1.32.1" version "1.37.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.32.1.tgz#749c9791adb048c266277a39ba0f7e33fe593ffe" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.37.1.tgz#e7f44ae0faf1be52d6360c6bbf689fd0057d9b6f"
integrity sha512-FTwjCuhlm1qHUGf4hWjfr64UMJD/z0hXYbk+O387Ioe6WdyZQ+0TBDAc6P+pHjx2xCv1VYNgrKbYrNixFWy4Dg== integrity sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
playwright-core "1.32.1" playwright-core "1.37.1"
optionalDependencies: optionalDependencies:
fsevents "2.3.2" fsevents "2.3.2"
@ -1000,6 +1000,15 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
fs-extra@^11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs.realpath@^1.0.0: fs.realpath@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -1065,6 +1074,11 @@ globby@^11.0.3:
merge2 "^1.3.0" merge2 "^1.3.0"
slash "^3.0.0" slash "^3.0.0"
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-flag@^3.0.0: has-flag@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@ -1199,6 +1213,15 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@ -1384,10 +1407,10 @@ picomatch@^2.2.3:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
playwright-core@1.32.1: playwright-core@1.37.1:
version "1.32.1" version "1.37.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.32.1.tgz#5a10c32403323b07d75ea428ebeed866a80b76a1" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.37.1.tgz#cb517d52e2e8cb4fa71957639f1cd105d1683126"
integrity sha512-KZYUQC10mXD2Am1rGlidaalNGYk3LU1vZqqNk0gT4XPty1jOqgup8KDP8l2CUlqoNKhXM5IfGjWgW37xvGllBA== integrity sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==
postcss-css-variables@^0.18.0: postcss-css-variables@^0.18.0:
version "0.18.0" version "0.18.0"
@ -1714,6 +1737,11 @@ typeson@^6.0.0:
resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b"
integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA==
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
uri-js@^4.2.2: uri-js@^4.2.2:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"