diff --git a/modules/restricted-guests/element-web/tests/restricted-guests.spec.ts b/modules/restricted-guests/element-web/tests/restricted-guests.spec.ts index f8f180a0cb..964cbdbd46 100644 --- a/modules/restricted-guests/element-web/tests/restricted-guests.spec.ts +++ b/modules/restricted-guests/element-web/tests/restricted-guests.spec.ts @@ -5,41 +5,115 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { StartedSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers"; -import { Credentials } from "@element-hq/element-web-playwright-common/lib/utils/api.ts"; +import { + MatrixAuthenticationServiceContainer, + type MasConfig, + type StartedMatrixAuthenticationServiceContainer, + type StartedSynapseContainer, + type SynapseConfig, +} from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; +import type { Credentials } from "@element-hq/element-web-playwright-common/lib/utils/api"; +import type { Fixtures } from "@playwright/test"; -import { test as base, expect } from "../../../../playwright/element-web-test.ts"; -import { RestrictedGuestsSynapseContainer } from "./services.ts"; +import { test as base, expect } from "../../../../playwright/element-web-test"; +import { RestrictedGuestsSynapseContainer, RestrictedGuestsSynapseWithMasContainer } from "./services"; -const test = base.extend< +const MAS_CLIENT_ID = "01ARZ3NDEKTSV4RRFFQ69G5FAV"; +const MAS_CLIENT_SECRET = "restricted-guests-secret"; +const MAS_SHARED_SECRET = "restricted-guests-shared-secret"; +const MAS_INTERNAL_URL = "http://mas:8080"; +const GUEST_HOMESERVER_NAME = "guest-homeserver"; +const GUEST_HOMESERVER_INTERNAL_URL = "http://guest-homeserver:8008"; + +const MAS_HTTP_LISTENERS: NonNullable["listeners"] = [ { - testRoomId: string; + name: "web", + resources: [ + { name: "discovery" }, + { name: "human" }, + { name: "oauth" }, + { name: "compat" }, + { name: "graphql" }, + { name: "assets" }, + { name: "adminapi" }, + ], + binds: [ + { + address: "[::]:8080", + }, + ], + proxy_protocol: false, }, { - guestHomeserver: StartedSynapseContainer; - bot: Credentials; - } ->({ + name: "internal", + resources: [ + { + name: "health", + }, + ], + binds: [ + { + address: "[::]:8081", + }, + ], + proxy_protocol: false, + }, +]; + +const MAS_CONFIG: Partial = { + http: { + listeners: MAS_HTTP_LISTENERS, + public_base: "", + }, + matrix: { + kind: "synapse", + homeserver: GUEST_HOMESERVER_NAME, + endpoint: GUEST_HOMESERVER_INTERNAL_URL, + secret: MAS_SHARED_SECRET, + }, + policy: { + data: { + admin_clients: [MAS_CLIENT_ID], + client_registration: { + allow_insecure_uris: true, + }, + }, + }, + clients: [ + { + client_id: MAS_CLIENT_ID, + client_auth_method: "client_secret_basic", + client_secret: MAS_CLIENT_SECRET, + }, + ], +}; + +const applySharedTestConfig = (testInstance: typeof base) => { + testInstance.use({ + displayName: "Tommy", + synapseConfig: { + allow_guest_access: true, + }, + labsFlags: ["feature_ask_to_join"], + }); +}; + +const sharedFixtures: Fixtures<{ testRoomId: string }, { bot: Credentials }, any, any> = { testRoomId: [ async ({ homeserver, bot }, use) => { - const { room_id: roomId } = await homeserver.csApi.request<{ room_id: string }>( - "POST", - "/v3/createRoom", - bot.accessToken, - { - name: "Test room", - preset: "public_chat", - topic: "All about happy hour", - initial_state: [ - { - // This is required to allow guests to join the room with this Synapse module - type: "m.room.join_rule", - state_key: "", - content: { join_rule: "knock" }, - }, - ], - }, - ); + const { room_id: roomId } = (await homeserver.csApi.request("POST", "/v3/createRoom", bot.accessToken, { + name: "Test room", + preset: "public_chat", + topic: "All about happy hour", + initial_state: [ + { + // This is required to allow guests to join the room with this Synapse module + type: "m.room.join_rule", + state_key: "", + content: { join_rule: "knock" }, + }, + ], + })) as { room_id: string }; await use(roomId); }, { scope: "test" }, @@ -51,13 +125,25 @@ const test = base.extend< }, { scope: "worker" }, ], +}; + +const test = base.extend< + { + testRoomId: string; + }, + { + guestHomeserver: StartedSynapseContainer; + bot: Credentials; + } +>({ + ...sharedFixtures, guestHomeserver: [ async ({ logger, synapseConfig, network }, use) => { const container = await new RestrictedGuestsSynapseContainer() .withConfig(synapseConfig) - .withConfig({ server_name: "guest-homeserver" }) + .withConfig({ server_name: GUEST_HOMESERVER_NAME }) .withNetwork(network) - .withNetworkAliases("guest-homeserver") + .withNetworkAliases(GUEST_HOMESERVER_NAME) .withLogConsumer(logger.getConsumer("guest_homeserver")) .start(); @@ -68,66 +154,123 @@ const test = base.extend< ], }); -test.use({ - displayName: "Tommy", - synapseConfig: { - allow_guest_access: true, +const masTest = base.extend< + { + testRoomId: string; }, - labsFlags: ["feature_ask_to_join"], + { + guestHomeserver: StartedSynapseContainer; + guestMas: StartedMatrixAuthenticationServiceContainer; + bot: Credentials; + } +>({ + ...sharedFixtures, + guestMas: [ + async ({ logger, network, postgres }, use) => { + const container = await new MatrixAuthenticationServiceContainer(postgres) + .withNetwork(network) + .withNetworkAliases("mas") + .withLogConsumer(logger.getConsumer("guest_mas")) + .withConfig(MAS_CONFIG) + .start(); + + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], + guestHomeserver: [ + async ({ logger, synapseConfig, network, guestMas }, use) => { + const container = await new RestrictedGuestsSynapseWithMasContainer({ + adminApiBaseUrl: MAS_INTERNAL_URL, + oauthBaseUrl: MAS_INTERNAL_URL, + clientId: MAS_CLIENT_ID, + clientSecret: MAS_CLIENT_SECRET, + }) + .withConfig(synapseConfig) + .withConfig({ + server_name: GUEST_HOMESERVER_NAME, + matrix_authentication_service: { + enabled: true, + endpoint: `${MAS_INTERNAL_URL}/`, + secret: MAS_SHARED_SECRET, + }, + } as Partial) + .withMatrixAuthenticationService(guestMas) + .withNetwork(network) + .withNetworkAliases(GUEST_HOMESERVER_NAME) + .withLogConsumer(logger.getConsumer("guest_homeserver")) + .start(); + + await use(container); + await container.stop(); + }, + { scope: "worker" }, + ], }); -test.describe("Restricted Guests", () => { - test.use({ - page: async ({ page, homeserver, guestHomeserver }, use) => { - await page.goto("/"); - await use(page); - }, - }); +type RestrictedGuestsTestInstance = typeof test; - test("should error if config is missing", async ({ page }) => { - await expect(page.getByText("Your Element is misconfigured")).toBeVisible(); - await expect(page.getByText("Errors in module configuration")).toBeVisible(); - }); +const defineRestrictedGuestsTests = (testInstance: RestrictedGuestsTestInstance, suiteName: string) => { + applySharedTestConfig(testInstance); - test.describe("with config", () => { - test.beforeEach(({ config, guestHomeserver }) => { - config["io.element.element-web-modules.restricted-guests"] = { - guest_user_homeserver_url: guestHomeserver.baseUrl, - }; + testInstance.describe(suiteName, () => { + testInstance.use({ + page: async ({ page }, use) => { + await page.goto("/"); + await use(page); + }, }); - test( - "should show the default room preview bar for logged in users", - { tag: ["@screenshot"] }, - async ({ page, user, testRoomId }) => { - // Go to a room we are not a member of - await page.goto(`/#/room/${testRoomId}`); + testInstance("should error if config is missing", async ({ page }) => { + await expect(page.getByText("Your Element is misconfigured")).toBeVisible(); + await expect(page.getByText("Errors in module configuration")).toBeVisible(); + }); - const button = page.getByRole("button", { name: "Join the discussion" }); - await expect(button).toBeVisible(); - }, - ); + testInstance.describe("with config", () => { + testInstance.beforeEach(({ config, guestHomeserver }) => { + config["io.element.element-web-modules.restricted-guests"] = { + guest_user_homeserver_url: guestHomeserver.baseUrl, + }; + }); - test( - "should show the module's room preview bar for guests", - { tag: ["@screenshot"] }, - async ({ page, testRoomId }) => { - // Go to a room we are not a member of - await page.goto(`/#/room/${testRoomId}`); + testInstance( + "should show the default room preview bar for logged in users", + { tag: ["@screenshot"] }, + async ({ page, user, testRoomId }) => { + // Go to a room we are not a member of + await page.goto(`/#/room/${testRoomId}`); - const button = page.getByRole("button", { name: "Join", exact: true }); - await expect(button).toBeVisible(); - await expect(page.locator(".mx_RoomPreviewBar")).toMatchScreenshot("preview-bar.png"); + const button = page.getByRole("button", { name: "Join the discussion" }); + await expect(button).toBeVisible(); + }, + ); - await button.click(); - const dialog = page.getByRole("dialog"); - await expect(dialog).toMatchScreenshot("dialog.png"); + testInstance( + "should show the module's room preview bar for guests", + { tag: ["@screenshot"] }, + async ({ page, testRoomId }) => { + // Go to a room we are not a member of + await page.goto(`/#/room/${testRoomId}`); - await dialog.getByPlaceholder("Name").fill("Jim"); - await dialog.getByRole("button", { name: "Continue as guest" }).click(); + const button = page.getByRole("button", { name: "Join", exact: true }); + await expect(button).toBeVisible(); + await expect(page.locator(".mx_RoomPreviewBar")).toMatchScreenshot(`preview-bar.png`); - await expect(page.getByText("Ask to join?")).toBeVisible(); - }, - ); + await button.click(); + const dialog = page.getByRole("dialog"); + await expect(dialog).toMatchScreenshot(`dialog.png`); + + await dialog.getByPlaceholder("Name").fill("Jim"); + await dialog.getByRole("button", { name: "Continue as guest" }).click(); + + await expect(page.getByText("Ask to join?")).toBeVisible(); + }, + ); + }); }); -}); +}; + +// The screenshots between the two tests should be identical. +defineRestrictedGuestsTests(test, "Restricted Guests"); +defineRestrictedGuestsTests(masTest as RestrictedGuestsTestInstance, "Restricted Guests (MAS)"); diff --git a/modules/restricted-guests/element-web/tests/services.ts b/modules/restricted-guests/element-web/tests/services.ts index 6b56108b06..c0c9f4c33d 100644 --- a/modules/restricted-guests/element-web/tests/services.ts +++ b/modules/restricted-guests/element-web/tests/services.ts @@ -16,6 +16,10 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); // We use the SynapseContainer as a base to have all of its utilities for config setting export class RestrictedGuestsSynapseContainer extends SynapseContainer { + protected getModuleConfig(): Record { + return {}; + } + public override async start(): Promise { this.withCopyDirectoriesToContainer([ { @@ -27,9 +31,33 @@ export class RestrictedGuestsSynapseContainer extends SynapseContainer { }); this.config.modules.push({ module: "synapse_guest_module.GuestModule", - config: {}, + config: this.getModuleConfig(), }); return super.start(); } } + +export interface RestrictedGuestsMasModuleConfig { + adminApiBaseUrl: string; + oauthBaseUrl?: string; + clientId: string; + clientSecret: string; +} + +export class RestrictedGuestsSynapseWithMasContainer extends RestrictedGuestsSynapseContainer { + public constructor(private readonly masConfig: RestrictedGuestsMasModuleConfig) { + super(); + } + + protected override getModuleConfig(): Record { + return { + mas: { + admin_api_base_url: this.masConfig.adminApiBaseUrl, + oauth_base_url: this.masConfig.oauthBaseUrl ?? this.masConfig.adminApiBaseUrl, + client_id: this.masConfig.clientId, + client_secret: this.masConfig.clientSecret, + }, + }; + } +}