1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

MatrixRTC: MembershipManager test cases and deprecation of MatrixRTCSession.room (#4713)

* WIP doodles on MembershipManager test cases

* .

* initial membership manager test setup.

* Updates from discussion

* revert renaming comments

* remove unused import

* fix leave delayed event resend test.
It was missing a flush.

* comment out and remove unused variables

* es lint

* use jsdom instead of node test environment

* remove unused variables

* remove unused export

* temp

* review

* fixup tests

* more review

* remove wait for expect dependency

* flatten tests and add comments

* add more leave test cases

* use defer

* remove @jest/environment dependency

* Cleanup awaits and Make mock types more correct.
Make every mock return a Promise if the real implementation does return a pormise.

* remove flush promise dependency

* add linting to matrixrtc tests

* Add fix async lints and use matrix rtc logger for test environment.

* prettier

* change to MatrixRTCSession logger

* make accessing the full room deprecated

* remove deprecated usage of full room

* Clean up the deprecation

---------

Co-authored-by: Hugh Nimmo-Smith <hughns@matrix.org>
This commit is contained in:
Timo
2025-02-27 10:55:09 +01:00
committed by GitHub
parent d81929de4c
commit a3bbc49e02
9 changed files with 758 additions and 151 deletions

View File

@@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { encodeBase64, EventType, MatrixClient, MatrixError, type MatrixEvent, type Room } from "../../../src";
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
import { KnownMembership } from "../../../src/@types/membership";
import { DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
import { secureRandomString } from "../../../src/randomstring";
import { flushPromises } from "../../test-utils/flushPromises";
import { makeMockRoom, makeMockRoomState, membershipTemplate } from "./mocks";
const mockFocus = { type: "mock" };
@@ -37,10 +36,10 @@ describe("MatrixRTCSession", () => {
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
});
afterEach(() => {
afterEach(async () => {
client.stopClient();
client.matrixRTC.stop();
if (sess) sess.stop();
if (sess) await sess.stop();
sess = undefined;
});
@@ -322,11 +321,9 @@ describe("MatrixRTCSession", () => {
let sendStateEventMock: jest.Mock;
let sendDelayedStateMock: jest.Mock;
let sendEventMock: jest.Mock;
let updateDelayedEventMock: jest.Mock;
let sentStateEvent: Promise<void>;
let sentDelayedState: Promise<void>;
let updatedDelayedEvent: Promise<void>;
beforeEach(() => {
sentStateEvent = new Promise((resolve) => {
@@ -340,15 +337,12 @@ describe("MatrixRTCSession", () => {
};
});
});
updatedDelayedEvent = new Promise((r) => {
updateDelayedEventMock = jest.fn(r);
});
sendEventMock = jest.fn();
client.sendStateEvent = sendStateEventMock;
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
client.sendEvent = sendEventMock;
client._unstable_updateDelayedEvent = updateDelayedEventMock;
client._unstable_updateDelayedEvent = jest.fn();
mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
@@ -432,120 +426,6 @@ describe("MatrixRTCSession", () => {
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
describe("calls", () => {
const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" };
const activeFocus = { type: "livekit", focus_selection: "oldest_membership" };
async function testJoin(useOwnedStateEvents: boolean): Promise<void> {
if (useOwnedStateEvents) {
mockRoom.getVersion = jest.fn().mockReturnValue("org.matrix.msc3757.default");
}
jest.useFakeTimers();
// preparing the delayed disconnect should handle the delay being too long
const sendDelayedStateExceedAttempt = new Promise<void>((resolve) => {
const error = new MatrixError({
"errcode": "M_UNKNOWN",
"org.matrix.msc4140.errcode": "M_MAX_DELAY_EXCEEDED",
"org.matrix.msc4140.max_delay": 7500,
});
sendDelayedStateMock.mockImplementationOnce(() => {
resolve();
return Promise.reject(error);
});
});
const userStateKey = `${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA`;
// preparing the delayed disconnect should handle ratelimiting
const sendDelayedStateAttempt = new Promise<void>((resolve) => {
const error = new MatrixError({ errcode: "M_LIMIT_EXCEEDED" });
sendDelayedStateMock.mockImplementationOnce(() => {
resolve();
return Promise.reject(error);
});
});
// setting the membership state should handle ratelimiting (also with a retry-after value)
const sendStateEventAttempt = new Promise<void>((resolve) => {
const error = new MatrixError(
{ errcode: "M_LIMIT_EXCEEDED" },
429,
undefined,
undefined,
new Headers({ "Retry-After": "1" }),
);
sendStateEventMock.mockImplementationOnce(() => {
resolve();
return Promise.reject(error);
});
});
sess!.joinRoomSession([activeFocusConfig], activeFocus, {
membershipServerSideExpiryTimeout: 9000,
});
await sendDelayedStateExceedAttempt.then(); // needed to resolve after the send attempt catches
await sendDelayedStateAttempt;
const callProps = (d: number) => {
return [mockRoom!.roomId, { delay: d }, "org.matrix.msc3401.call.member", {}, userStateKey];
};
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(1, ...callProps(9000));
expect(client._unstable_sendDelayedStateEvent).toHaveBeenNthCalledWith(2, ...callProps(7500));
jest.advanceTimersByTime(5000);
await sendStateEventAttempt.then(); // needed to resolve after resendIfRateLimited catches
jest.advanceTimersByTime(1000);
await sentStateEvent;
expect(client.sendStateEvent).toHaveBeenCalledWith(
mockRoom!.roomId,
EventType.GroupCallMemberPrefix,
{
application: "m.call",
scope: "m.room",
call_id: "",
expires: 14400000,
device_id: "AAAAAAA",
foci_preferred: [activeFocusConfig],
focus_active: activeFocus,
} satisfies SessionMembershipData,
userStateKey,
);
await sentDelayedState;
// should have prepared the heartbeat to keep delaying the leave event while still connected
await updatedDelayedEvent;
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
// ensures that we reach the code that schedules the timeout for the next delay update before we advance the timers.
await flushPromises();
jest.advanceTimersByTime(5000);
// should update delayed disconnect
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(2);
jest.useRealTimers();
}
it("sends a membership event with session payload when joining a call", async () => {
await testJoin(false);
});
it("does not prefix the state key with _ for rooms that support user-owned state events", async () => {
await testJoin(true);
});
});
it("does nothing if join called when already joined", async () => {
sess!.joinRoomSession([mockFocus], mockFocus);
await sentStateEvent;
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
sess!.joinRoomSession([mockFocus], mockFocus);
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
});
});
describe("onMembershipsChanged", () => {
@@ -616,9 +496,9 @@ describe("MatrixRTCSession", () => {
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
});
afterEach(() => {
afterEach(async () => {
// stop the timers
sess!.leaveRoomSession();
await sess!.leaveRoomSession();
});
it("creates a key when joining", () => {
@@ -715,7 +595,7 @@ describe("MatrixRTCSession", () => {
}
});
it("cancels key send event that fail", async () => {
it("cancels key send event that fail", () => {
const eventSentinel = {} as unknown as MatrixEvent;
client.cancelPendingEvent = jest.fn();