Skip to content

Commit

Permalink
Fix more flakes
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
  • Loading branch information
t3chguy committed Jan 10, 2025
1 parent db47e68 commit 8f68dbb
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 91 deletions.
10 changes: 5 additions & 5 deletions playwright/e2e/spotlight/spotlight.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,28 +366,28 @@ test.describe("Spotlight", () => {
await spotlight.search("b");

let resultLocator = spotlight.results;
await expect(resultLocator).toHaveCount(2);
await expect(resultLocator.count()).resolves.toBeGreaterThan(2);
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");

await spotlight.searchBox.press("ArrowDown");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.nth(2)).toHaveAttribute("aria-selected", "true");

await spotlight.searchBox.press("ArrowDown");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.nth(2)).toHaveAttribute("aria-selected", "false");

await spotlight.searchBox.press("ArrowUp");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.nth(2)).toHaveAttribute("aria-selected", "true");

await spotlight.searchBox.press("ArrowUp");
resultLocator = spotlight.results;
await expect(resultLocator.first()).toHaveAttribute("aria-selected", "true");
await expect(resultLocator.last()).toHaveAttribute("aria-selected", "false");
await expect(resultLocator.nth(2)).toHaveAttribute("aria-selected", "false");
});
});
2 changes: 2 additions & 0 deletions playwright/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export const test = base.extend<{}, Services>({
.withConfig(synapseConfigOptions)
.start();

container.setMatrixAuthenticationService(mas);

await use(container);
await container.stop();
},
Expand Down
2 changes: 2 additions & 0 deletions playwright/testcontainers/HomeserverContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AbstractStartedContainer, GenericContainer } from "testcontainers";
import { APIRequestContext, TestInfo } from "@playwright/test";

import { HomeserverInstance } from "../plugins/homeserver";
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";

export interface HomeserverContainer<Config> extends GenericContainer {
withConfigField(key: string, value: any): this;
Expand All @@ -18,5 +19,6 @@ export interface HomeserverContainer<Config> extends GenericContainer {

export interface StartedHomeserverContainer extends AbstractStartedContainer, HomeserverInstance {
setRequest(request: APIRequestContext): void;
setMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): void;
onTestFinished(testInfo: TestInfo): Promise<void>;
}
2 changes: 1 addition & 1 deletion playwright/testcontainers/dendrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,5 @@ export class PineconeContainer extends DendriteContainer {
}
}

// Surprisingly, Dendrite implements the same register user Admin API Synapse, so we can just extend it
// Surprisingly, Dendrite implements the same register user Synapse Admin API, so we can just extend it
export class StartedDendriteContainer extends StartedSynapseContainer {}
91 changes: 76 additions & 15 deletions playwright/testcontainers/mas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,20 @@ import * as YAML from "yaml";

import { getFreePort } from "../plugins/utils/port.ts";
import { deepCopy } from "../plugins/utils/object.ts";
import { Credentials } from "../plugins/homeserver";
import { ClientServerApi } from "./utils.ts";

const DEFAULT_CONFIG = {
http: {
listeners: [
{
name: "web",
resources: [
{
name: "discovery",
},
{
name: "human",
},
{
name: "oauth",
},
{
name: "compat",
},
{ name: "discovery" },
{ name: "human" },
{ name: "oauth" },
{ name: "compat" },
{ name: "adminapi" },
{
name: "graphql",
playground: true,
Expand Down Expand Up @@ -172,17 +167,20 @@ const DEFAULT_CONFIG = {

export class MatrixAuthenticationServiceContainer extends GenericContainer {
private config: typeof DEFAULT_CONFIG;
private readonly args = ["-c", "/config/config.yaml"];

constructor(db: StartedPostgreSqlContainer) {
super("ghcr.io/element-hq/matrix-authentication-service:0.12.0");
// We rely on `mas-cli manage add-email` which isn't in a release yet
// https://github.com/element-hq/matrix-authentication-service/pull/3235
super("ghcr.io/element-hq/matrix-authentication-service:sha-0b90c33");

this.config = deepCopy(DEFAULT_CONFIG);
this.config.database.username = db.getUsername();
this.config.database.password = db.getPassword();

this.withExposedPorts(8080, 8081)
.withWaitStrategy(Wait.forHttp("/health", 8081))
.withCommand(["server", "--config", "/config/config.yaml"]);
.withCommand(["server", ...this.args]);
}

public withConfig(config: object): this {
Expand Down Expand Up @@ -210,15 +208,78 @@ export class MatrixAuthenticationServiceContainer extends GenericContainer {
},
]);

return new StartedMatrixAuthenticationServiceContainer(await super.start(), `http://localhost:${port}`);
return new StartedMatrixAuthenticationServiceContainer(
await super.start(),
`http://localhost:${port}`,
this.args,
);
}
}

export class StartedMatrixAuthenticationServiceContainer extends AbstractStartedContainer {
private adminTokenPromise?: Promise<string>;

constructor(
container: StartedTestContainer,
public readonly baseUrl: string,
private readonly args: string[],
) {
super(container);
}

public async getAdminToken(csApi: ClientServerApi): Promise<string> {
if (this.adminTokenPromise === undefined) {
this.adminTokenPromise = this.registerUserInternal(
csApi,
"admin",
"totalyinsecureadminpassword",
undefined,
true,
).then((res) => res.accessToken);
}
return this.adminTokenPromise;
}

private async registerUserInternal(
csApi: ClientServerApi,
username: string,
password: string,
displayName?: string,
admin = false,
): Promise<Credentials> {
const args: string[] = [];
if (admin) args.push("-a");
await this.exec([
"mas-cli",
"manage",
"register-user",
...this.args,
...args,
"-y",
"-p",
password,
"-d",
displayName ?? "",
username,
]);

return csApi.loginUser(username, password);
}

public async registerUser(
csApi: ClientServerApi,
username: string,
password: string,
displayName?: string,
): Promise<Credentials> {
return this.registerUserInternal(csApi, username, password, displayName, false);
}

public async setThreepid(username: string, medium: string, address: string): Promise<void> {
if (medium !== "email") {
throw new Error("Only email threepids are supported by MAS");
}

await this.exec(["mas-cli", "manage", "add-email", ...this.args, username, address]);
}
}
105 changes: 36 additions & 69 deletions playwright/testcontainers/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { randB64Bytes } from "../plugins/utils/rand.ts";
import { Credentials } from "../plugins/homeserver";
import { deepCopy } from "../plugins/utils/object.ts";
import { HomeserverContainer, StartedHomeserverContainer } from "./HomeserverContainer.ts";
import { StartedMatrixAuthenticationServiceContainer } from "./mas.ts";
import { Api, ClientServerApi, Verb } from "./utils.ts";

const TAG = "develop@sha256:b69222d98abe9625d46f5d3cb01683d5dc173ae339215297138392cfeec935d9";

Expand Down Expand Up @@ -138,8 +140,6 @@ const DEFAULT_CONFIG = {
},
};

type Verb = "GET" | "POST" | "PUT" | "DELETE";

export type SynapseConfigOptions = Partial<typeof DEFAULT_CONFIG>;

export class SynapseContainer extends GenericContainer implements HomeserverContainer<typeof DEFAULT_CONFIG> {
Expand Down Expand Up @@ -231,18 +231,28 @@ export class SynapseContainer extends GenericContainer implements HomeserverCont

export class StartedSynapseContainer extends AbstractStartedContainer implements StartedHomeserverContainer {
private adminTokenPromise?: Promise<string>;
private _mas?: StartedMatrixAuthenticationServiceContainer;
protected _request?: APIRequestContext;
protected csApi: ClientServerApi;
protected adminApi: Api;

constructor(
container: StartedTestContainer,
public readonly baseUrl: string,
private readonly registrationSharedSecret: string,
) {
super(container);
this.csApi = new ClientServerApi(this.baseUrl);
this.adminApi = new Api(`${this.baseUrl}/_synapse/admin/`);
}

public setRequest(request: APIRequestContext): void {
this._request = request;
this.csApi.setRequest(request);
}

public setMatrixAuthenticationService(mas?: StartedMatrixAuthenticationServiceContainer): void {
this._mas = mas;
}

public async onTestFinished(testInfo: TestInfo): Promise<void> {
Expand All @@ -251,13 +261,14 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
}

protected async deletePublicRooms(): Promise<void> {
const token = await this.getAdminToken();
// We hide the rooms from the room directory to save time between tests and for portability between homeservers
const { chunk: rooms } = await this.request<{
const { chunk: rooms } = await this.csApi.request<{
chunk: { room_id: string }[];
}>("GET", "v3/publicRooms", {});
}>("GET", "v3/publicRooms", token, {});
await Promise.all(
rooms.map((room) =>
this.request("PUT", `v3/directory/list/room/${room.room_id}`, { visibility: "private" }),
this.csApi.request("PUT", `v3/directory/list/room/${room.room_id}`, token, { visibility: "private" }),
),
);
}
Expand All @@ -268,13 +279,18 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
displayName?: string,
admin = false,
): Promise<Credentials> {
const url = `${this.baseUrl}/_synapse/admin/v1/register`;
const { nonce } = await this._request.get(url).then((r) => r.json());
const path = `v1/register`;
const { nonce } = await this.adminApi.request<{ nonce: string }>("GET", path, undefined, {});
const mac = crypto
.createHmac("sha1", this.registrationSharedSecret)
.update(`${nonce}\0${username}\0${password}\0${admin ? "" : "not"}admin`)
.digest("hex");
const res = await this._request.post(url, {
const data = await this.adminApi.request<{
home_server: string;
access_token: string;
user_id: string;
device_id: string;
}>("POST", path, undefined, {
data: {
nonce,
username,
Expand All @@ -285,11 +301,6 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
},
});

if (!res.ok()) {
throw await res.json();
}

const data = await res.json();
return {
homeServer: data.home_server,
accessToken: data.access_token,
Expand All @@ -303,6 +314,10 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements

protected async getAdminToken(): Promise<string> {
if (this.adminTokenPromise === undefined) {
if (this._mas) {
return (this.adminTokenPromise = this._mas.getAdminToken(this.csApi));
}

this.adminTokenPromise = this.registerUserInternal(
"admin",
"totalyinsecureadminpassword",
Expand All @@ -317,72 +332,24 @@ export class StartedSynapseContainer extends AbstractStartedContainer implements
private async adminRequest<R extends {}>(verb: Verb, path: string, data?: object): Promise<R>;
private async adminRequest<R extends {}>(verb: Verb, path: string, data?: object): Promise<R> {
const adminToken = await this.getAdminToken();
const url = `${this.baseUrl}/_synapse/admin/${path}`;
const res = await this._request.fetch(url, {
data,
method: verb,
headers: {
Authorization: `Bearer ${adminToken}`,
},
});

if (!res.ok()) {
throw await res.json();
}

return res.json();
}

public async request<R extends {}>(verb: "GET", path: string, data?: never): Promise<R>;
public async request<R extends {}>(verb: Verb, path: string, data?: object): Promise<R>;
public async request<R extends {}>(verb: Verb, path: string, data?: object): Promise<R> {
const token = await this.getAdminToken();
const url = `${this.baseUrl}/_matrix/client/${path}`;
const res = await this._request.fetch(url, {
data,
method: verb,
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!res.ok()) {
throw await res.json();
}

return res.json();
return this.adminApi.request(verb, path, adminToken, data);
}

public registerUser(username: string, password: string, displayName?: string): Promise<Credentials> {
if (this._mas) {
return this._mas.registerUser(this.csApi, username, password, displayName);
}
return this.registerUserInternal(username, password, displayName, false);
}

public async loginUser(userId: string, password: string): Promise<Credentials> {
const json = await this.request<{
access_token: string;
user_id: string;
device_id: string;
home_server: string;
}>("POST", "v3/login", {
type: "m.login.password",
identifier: {
type: "m.id.user",
user: userId,
},
password: password,
});

return {
password,
accessToken: json.access_token,
userId: json.user_id,
deviceId: json.device_id,
homeServer: json.home_server,
username: userId.slice(1).split(":")[0],
};
return this.csApi.loginUser(userId, password);
}

public async setThreepid(userId: string, medium: string, address: string): Promise<void> {
if (this._mas) {
return this._mas.setThreepid(userId, medium, address);
}
await this.adminRequest("PUT", `v2/users/${userId}`, {
threepids: [
{
Expand Down
Loading

0 comments on commit 8f68dbb

Please sign in to comment.