Files
element-call-Github/sdk/src/media-backend/livekit/LivekitBackend.test.ts
T
Timo K. 5b13e1da81 renames and restructure
- session -> matrixrtc (folder)
 - move MatrixRtcClient to top level of sdk
 - RtcTransport -> Transport
 - backend -> media-backend
2026-10-08 13:53:57 +02:00

113 lines
4.2 KiB
TypeScript

/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details.
*/
import fetchMock from "fetch-mock";
import { logger } from "matrix-js-sdk/lib/logger";
import { type LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
import { BehaviorSubject } from "rxjs";
import { afterEach, describe, expect, it } from "vitest";
import { ObservableScope } from "../../reactive/ObservableScope";
import { testScope } from "../../utils/test";
import { MediaConnectionState } from "../api";
import { ConnectionState } from "./Connection";
import { toMediaConnectionState } from "./connectionState";
import { createTransportRegistry, supportsDelegation } from "./LivekitBackend";
import { exampleTransport } from "./test";
const otherTransport: LivekitTransport = {
type: "livekit",
livekit_service_url: "https://other.example.org",
};
describe("createTransportRegistry", () => {
it("connects to a transport once however many members are on it, and drops it with the last", () => {
const registry = createTransportRegistry(testScope());
const urls = (): string[] =>
registry.transports$.value.value.map((t) => t.livekit_service_url);
const alice = new ObservableScope();
const bob = new ObservableScope();
registry.follow(alice, new BehaviorSubject(exampleTransport));
registry.follow(bob, new BehaviorSubject(exampleTransport));
expect(urls()).toEqual([exampleTransport.livekit_service_url]);
alice.end();
expect(urls()).toEqual([exampleTransport.livekit_service_url]);
bob.end();
expect(urls()).toEqual([]);
});
it("follows a member that changes transport", () => {
const registry = createTransportRegistry(testScope());
const transport$ = new BehaviorSubject<LivekitTransport | undefined>(
exampleTransport,
);
registry.follow(testScope(), transport$);
transport$.next(otherTransport);
expect(registry.transports$.value.value).toEqual([otherTransport]);
transport$.next(undefined);
expect(registry.transports$.value.value).toEqual([]);
});
it("is safe to release after the registry's own scope has ended", () => {
const scope = new ObservableScope();
const registry = createTransportRegistry(scope);
const member = new ObservableScope();
registry.follow(member, new BehaviorSubject(exampleTransport));
scope.end();
expect(() => member.end()).not.toThrow();
});
});
describe("supportsDelegation", () => {
afterEach(() => fetchMock.reset());
const homeserver = "https://matrix.example.org";
const homeserverEndpoint = `${homeserver}/_matrix/client/unstable/io.element.msc4195/rtc/livekit/delegate_delayed_leave`;
const transportEndpoint = `${exampleTransport.livekit_service_url}/delegate_delayed_leave`;
it.each([
["the homeserver supports it", 400, 404, true],
["only the transport supports it", 404, 400, true],
["neither supports it", 404, 404, false],
])("%s", async (_name, homeserverStatus, transportStatus, expected) => {
fetchMock.post(homeserverEndpoint, homeserverStatus);
fetchMock.post(transportEndpoint, transportStatus);
await expect(
supportsDelegation(homeserver, exampleTransport, logger),
).resolves.toBe(expected);
});
it("assumes no support where the probe itself fails", async () => {
fetchMock.post(homeserverEndpoint, { throws: new TypeError("CORS") });
fetchMock.post(transportEndpoint, { throws: new TypeError("CORS") });
await expect(
supportsDelegation(homeserver, exampleTransport, logger),
).resolves.toBe(false);
});
});
describe("toMediaConnectionState", () => {
it("maps every LiveKit state and passes an error through", () => {
expect(toMediaConnectionState(ConnectionState.FetchingConfig)).toBe(
MediaConnectionState.Preparing,
);
expect(toMediaConnectionState(ConnectionState.LivekitConnected)).toBe(
MediaConnectionState.Connected,
);
expect(
toMediaConnectionState(ConnectionState.LivekitSignalReconnecting),
).toBe(MediaConnectionState.Reconnecting);
const error = new Error("gone");
expect(toMediaConnectionState(error)).toBe(error);
});
});