Fix some tests

This commit is contained in:
Robin
2026-10-09 01:43:25 +02:00
parent 033f79975a
commit c6807fd9a2
11 changed files with 90 additions and 137 deletions
+4 -99
View File
@@ -49,11 +49,12 @@ describe("getSFUConfig", () => {
serviceUrl: "https://sfu.example.org", serviceUrl: "https://sfu.example.org",
roomId: "!example_room_id", roomId: "!example_room_id",
slotId: "m.call#room", slotId: "m.call#room",
role: "subscriber", role: "publisher",
logger, logger,
}; };
}); });
afterEach(() => { afterEach(async () => {
void (await fetchMock.flush());
vitest.clearAllMocks(); vitest.clearAllMocks();
fetchMock.reset(); fetchMock.reset();
}); });
@@ -72,7 +73,6 @@ describe("getSFUConfig", () => {
livekitIdentity: "@me:example.org:ABCDEF", livekitIdentity: "@me:example.org:ABCDEF",
livekitAlias: "!example_room_id", livekitAlias: "!example_room_id",
}); });
void (await fetchMock.flush());
}); });
it("should fail if the SFU errors", async () => { it("should fail if the SFU errors", async () => {
@@ -95,7 +95,6 @@ describe("getSFUConfig", () => {
"MatrixError: [500] Failed to look up user info from homeserver", "MatrixError: [500] Failed to look up user info from homeserver",
); );
void (await fetchMock.flush());
return; return;
} }
expect.fail("Expected test to throw;"); expect.fail("Expected test to throw;");
@@ -137,7 +136,6 @@ describe("getSFUConfig", () => {
expect(config.jwt).toBe(testJWTToken); expect(config.jwt).toBe(testJWTToken);
expect(callCount).toBe(2); expect(callCount).toBe(2);
void (await fetchMock.flush());
}); });
it("should successfully send delay parameters to the JWT service legacy endpoint", async () => { it("should successfully send delay parameters to the JWT service legacy endpoint", async () => {
@@ -150,7 +148,7 @@ describe("getSFUConfig", () => {
if ( if (
body.delay_id === "mock_delay_id" && body.delay_id === "mock_delay_id" &&
body.delay_timeout === 3600000 && body.delay_timeout === 3600000 &&
body.delay_cs_api_url === "https://homeserverserver.org/cs_api" body.delay_cs_api_url === "https://matrix.homeserverserver.org"
) { ) {
return { return {
status: 200, status: 200,
@@ -172,98 +170,6 @@ describe("getSFUConfig", () => {
jwt: testJWTToken, jwt: testJWTToken,
url: serviceUrl, url: serviceUrl,
}); });
void (await fetchMock.flush());
});
it("should try legacy and then new endpoint with delay delegation", async () => {
fetchMock.post("https://sfu.example.org/get_token", () => {
return {
status: 500,
body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
};
});
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
status: 500,
body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
};
});
try {
await getSFUConfig({ ...params, ...delayParams });
} catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
const mxError = (ex as Error).cause as MatrixError;
expect(mxError.message).toEqual(
"MatrixError: [500] Failed to look up user info from homeserver",
);
void (await fetchMock.flush());
}
const calls = fetchMock.calls();
expect(calls.length).toBe(2);
expect(calls[0][0]).toStrictEqual("https://sfu.example.org/get_token");
expect(calls[0][1]).toStrictEqual({
// check if it uses correct delayID!
body: '{"room_id":"!example_room_id","slot_id":"m.call#ROOM","member":{"id":"@alice:example.org:DEVICE","claimed_user_id":"@alice:example.org","claimed_device_id":"DEVICE"},"delay_id":"mock_delay_id","delay_timeout":3600000,"delay_cs_api_url":"https://matrix.homeserverserver.org"}',
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
expect(calls[1][0]).toStrictEqual("https://sfu.example.org/sfu/get");
expect(calls[1][1]).toStrictEqual({
body: '{"room":"!example_room_id","device_id":"DEVICE","delay_id":"mock_delay_id","delay_timeout":3600000,"delay_cs_api_url":"https://matrix.homeserverserver.org"}',
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
});
it("dont try legacy if endpoint with delay delegation is sucessful", async () => {
fetchMock.post("https://sfu.example.org/get_token", () => {
return {
status: 200,
body: { url: serviceUrl, jwt: testJWTToken },
};
});
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
status: 500,
body: { error: "Test failure" },
};
});
try {
await getSFUConfig({ ...params, ...delayParams });
} catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toEqual(
new Error("SFU Config fetch failed with status code 500"),
);
void (await fetchMock.flush());
}
const calls = fetchMock.calls();
expect(calls.length).toBe(1);
expect(calls[0][0]).toStrictEqual("https://sfu.example.org/get_token");
expect(calls[0][1]).toStrictEqual({
// check if it uses correct delayID!
body: '{"room_id":"!example_room_id","slot_id":"m.call#ROOM","member":{"id":"@alice:example.org:DEVICE","claimed_user_id":"@alice:example.org","claimed_device_id":"DEVICE"},"delay_id":"mock_delay_id","delay_timeout":3600000,"delay_cs_api_url":"https://matrix.homeserverserver.org"}',
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
}); });
it("should retry fetching the openid token", async () => { it("should retry fetching the openid token", async () => {
@@ -293,6 +199,5 @@ describe("getSFUConfig", () => {
livekitIdentity: "@me:example.org:ABCDEF", livekitIdentity: "@me:example.org:ABCDEF",
livekitAlias: "!example_room_id", livekitAlias: "!example_room_id",
}); });
void (await fetchMock.flush());
}); });
}); });
@@ -148,6 +148,7 @@ describe("DeveloperSettingsTab", () => {
getCrypto: () => ({ getVersion: (): string => "x" }), getCrypto: () => ({ getVersion: (): string => "x" }),
getUserId: () => "@u:hs", getUserId: () => "@u:hs",
getDeviceId: () => "DEVICE", getDeviceId: () => "DEVICE",
getDomain: () => "hs",
} as unknown as MatrixClient; } as unknown as MatrixClient;
it("will not update custom transport without roomId", async () => { it("will not update custom transport without roomId", async () => {
const user = userEvent.setup(); const user = userEvent.setup();
+6 -3
View File
@@ -742,9 +742,12 @@ export function createCallViewModel$(
return combineLatest( return combineLatest(
connections.map((connection) => connections.map((connection) =>
remoteMatrixLivekitMembers$.pipe( remoteMatrixLivekitMembers$.pipe(
switchMap((members) => switchMap((members) => {
combineLatest(members.value.map((m) => m.participant.value$)), if (members.value.length === 0) return of([]);
), return combineLatest(
members.value.map((m) => m.participant.value$),
);
}),
map((participants) => ({ map((participants) => ({
url: JSON.stringify(connection.transport), // TODO url: JSON.stringify(connection.transport), // TODO
livekitRoom: connection.livekitRoom, livekitRoom: connection.livekitRoom,
@@ -122,6 +122,8 @@ export function withCallViewModel(mode: MatrixRTCMode) {
}; };
const room = mockMatrixRoom({ const room = mockMatrixRoom({
client: new (class extends EventEmitter { client: new (class extends EventEmitter {
public readonly baseUrl = "https://matrix.example.com";
public getUserId(): string | undefined { public getUserId(): string | undefined {
return localRtcMember.userId; return localRtcMember.userId;
} }
@@ -337,6 +337,7 @@ describe("LocalMembership", () => {
}), }),
state$: constant(ConnectionState.LivekitConnected), state$: constant(ConnectionState.LivekitConnected),
transport: mockTransport, transport: mockTransport,
serverName: "example.org",
} as Connection; } as Connection;
const connectionTransportAConnecting = { const connectionTransportAConnecting = {
...connectionTransportAConnected, ...connectionTransportAConnected,
@@ -858,6 +859,7 @@ describe("LocalMembership", () => {
const connection = { const connection = {
state$: constant(ConnectionState.LivekitConnected), state$: constant(ConnectionState.LivekitConnected),
transport: mockTransport, transport: mockTransport,
serverName: "example.org",
livekitRoom: mockLivekitRoom({ livekitRoom: mockLivekitRoom({
localParticipant: mockLocalParticipant({ localParticipant: mockLocalParticipant({
isScreenShareEnabled: false, isScreenShareEnabled: false,
@@ -46,8 +46,11 @@ describe("LocalTransport", () => {
getDomain: () => "example.org", getDomain: () => "example.org",
}), }),
).toStrictEqual({ ).toStrictEqual({
transport: {
livekit_service_url: "https://lk.example.org", livekit_service_url: "https://lk.example.org",
type: "livekit", type: "livekit",
},
serverName: "example.org",
}); });
}); });
@@ -74,8 +77,11 @@ describe("LocalTransport", () => {
}); });
expect(await getLocalTransport(client)).toStrictEqual({ expect(await getLocalTransport(client)).toStrictEqual({
transport: {
livekit_service_url: "https://lk.example.org", livekit_service_url: "https://lk.example.org",
type: "livekit", type: "livekit",
},
serverName: "example.org",
}); });
}); });
@@ -86,8 +92,11 @@ describe("LocalTransport", () => {
}); });
expect(await getLocalTransport(client)).toStrictEqual({ expect(await getLocalTransport(client)).toStrictEqual({
transport: {
livekit_service_url: "https://lk.example.org", livekit_service_url: "https://lk.example.org",
type: "livekit", type: "livekit",
},
serverName: "example.org",
}); });
}); });
@@ -97,8 +106,11 @@ describe("LocalTransport", () => {
]); ]);
expect(await getLocalTransport(client)).toStrictEqual({ expect(await getLocalTransport(client)).toStrictEqual({
transport: {
livekit_service_url: "https://lk.example.org", livekit_service_url: "https://lk.example.org",
type: "livekit", type: "livekit",
},
serverName: "example.org",
}); });
}); });
@@ -28,9 +28,12 @@ import {
} from "livekit-client"; } from "livekit-client";
import fetchMock from "fetch-mock"; import fetchMock from "fetch-mock";
import EventEmitter from "events"; import EventEmitter from "events";
import { MatrixError, type IOpenIDToken } from "matrix-js-sdk"; import { MatrixError } from "matrix-js-sdk";
import { logger, type Logger } from "matrix-js-sdk/lib/logger"; import { logger, type Logger } from "matrix-js-sdk/lib/logger";
import { type UnstableLivekitTransport } from "matrix-js-sdk/lib/matrixrtc"; import {
type LivekitGetTokenResponse,
type UnstableLivekitTransport,
} from "matrix-js-sdk/lib/matrixrtc";
import { import {
Connection, Connection,
@@ -185,7 +188,7 @@ describe("Start connection states", () => {
expect(connection.state$.getValue()).toEqual("Initialized"); expect(connection.state$.getValue()).toEqual("Initialized");
}); });
it("fail to getOpenId token then error state", async () => { it("fail to get SFU token then error state", async () => {
setupTest(); setupTest();
vi.useFakeTimers(); vi.useFakeTimers();
@@ -209,10 +212,10 @@ describe("Start connection states", () => {
}); });
onTestFinished(() => s.unsubscribe()); onTestFinished(() => s.unsubscribe());
const deferred = Promise.withResolvers<IOpenIDToken>(); const deferred = Promise.withResolvers<LivekitGetTokenResponse>();
client.getOpenIdToken.mockImplementation( client._unstable_getLivekitToken.mockImplementation(
async (): Promise<IOpenIDToken> => { async (): Promise<LivekitGetTokenResponse> => {
return await deferred.promise; return await deferred.promise;
}, },
); );
@@ -54,9 +54,15 @@ beforeEach(() => {
vi.mocked(fakeConnectionFactory).createConnection = vi vi.mocked(fakeConnectionFactory).createConnection = vi
.fn() .fn()
.mockImplementation( .mockImplementation(
(scope: ObservableScope, transport: UnstableLivekitTransport) => { (
scope: ObservableScope,
_role: unknown,
transport: UnstableLivekitTransport,
serverName: string,
) => {
const mockConnection = { const mockConnection = {
transport, transport,
serverName,
remoteParticipants$: new BehaviorSubject([]), remoteParticipants$: new BehaviorSubject([]),
} as unknown as Connection; } as unknown as Connection;
vi.mocked(mockConnection).start = vi.fn(); vi.mocked(mockConnection).start = vi.fn();
@@ -229,14 +235,14 @@ describe("ConnectionManagerData", () => {
test("warns when a second connection to the same URL is merged", () => { test("warns when a second connection to the same URL is merged", () => {
const warn = vi.spyOn(logger, "warn").mockImplementation(() => {}); const warn = vi.spyOn(logger, "warn").mockImplementation(() => {});
const data = new ConnectionManagerData(logger); const data = new ConnectionManagerData(logger);
const connection = { transport: TRANSPORT_1 } as unknown as Connection; const connection = TRANSPORT_1 as unknown as Connection;
const p = (identity: string): RemoteParticipant => const p = (identity: string): RemoteParticipant =>
({ identity }) as unknown as RemoteParticipant; ({ identity }) as unknown as RemoteParticipant;
data.add(connection, [p("a")]); data.add(connection, [p("a")]);
data.add({ ...connection } as Connection, [p("b")]); data.add({ ...connection } as Connection, [p("b")]);
expect(warn).toHaveBeenCalledWith( expect(warn).toHaveBeenCalledWith(
expect.stringContaining( expect.stringContaining(
"second connection to https://lk.example.org: existing [a], adding [b]", 'second connection to [null,"https://lk.example.org","example.org"]: existing [a], adding [b]',
), ),
); );
expect(data.getParticipantsForTransport(TRANSPORT_1)).toHaveLength(2); expect(data.getParticipantsForTransport(TRANSPORT_1)).toHaveLength(2);
@@ -247,12 +253,14 @@ describe("connectionManagerData$ stream", () => {
// Used in test to control fake connections' remoteParticipants$ streams // Used in test to control fake connections' remoteParticipants$ streams
let fakeRemoteParticipantsStreams: Map<string, Behavior<RemoteParticipant[]>>; let fakeRemoteParticipantsStreams: Map<string, Behavior<RemoteParticipant[]>>;
function keyForTransport(transport: UnstableLivekitTransport): string { function keyForTransport({
transport,
serverName,
}: TransportLocator): string {
return JSON.stringify([ return JSON.stringify([
"url" in transport ? transport.url : undefined, "url" in transport ? transport.url : null,
"livekit_service_url" in transport "livekit_service_url" in transport ? transport.livekit_service_url : null,
? transport.livekit_service_url serverName,
: undefined,
]); ]);
} }
@@ -260,7 +268,7 @@ describe("connectionManagerData$ stream", () => {
fakeRemoteParticipantsStreams = new Map(); fakeRemoteParticipantsStreams = new Map();
function getRemoteParticipantsFor( function getRemoteParticipantsFor(
transport: UnstableLivekitTransport, transport: TransportLocator,
): Behavior<RemoteParticipant[]> { ): Behavior<RemoteParticipant[]> {
return ( return (
fakeRemoteParticipantsStreams.get(keyForTransport(transport)) ?? fakeRemoteParticipantsStreams.get(keyForTransport(transport)) ??
@@ -272,13 +280,22 @@ describe("connectionManagerData$ stream", () => {
vi.mocked(fakeConnectionFactory).createConnection = vi vi.mocked(fakeConnectionFactory).createConnection = vi
.fn() .fn()
.mockImplementation( .mockImplementation(
(scope: ObservableScope, transport: UnstableLivekitTransport) => { (
scope: ObservableScope,
_role: unknown,
transport: UnstableLivekitTransport,
serverName: string,
) => {
const fakeRemoteParticipants$ = new BehaviorSubject< const fakeRemoteParticipants$ = new BehaviorSubject<
RemoteParticipant[] RemoteParticipant[]
>([]); >([]);
const mockConnection = { const mockConnection = {
transport, transport,
remoteParticipants$: getRemoteParticipantsFor(transport), serverName,
remoteParticipants$: getRemoteParticipantsFor({
transport,
serverName,
}),
} as unknown as Connection; } as unknown as Connection;
vi.mocked(mockConnection).start = vi.fn(); vi.mocked(mockConnection).start = vi.fn();
vi.mocked(mockConnection).stop = vi.fn(); vi.mocked(mockConnection).stop = vi.fn();
@@ -288,7 +305,7 @@ describe("connectionManagerData$ stream", () => {
}); });
fakeRemoteParticipantsStreams.set( fakeRemoteParticipantsStreams.set(
keyForTransport(transport), keyForTransport({ transport, serverName }),
fakeRemoteParticipants$, fakeRemoteParticipants$,
); );
return mockConnection; return mockConnection;
@@ -301,7 +318,7 @@ describe("connectionManagerData$ stream", () => {
// Setup the fake participants streams behavior // Setup the fake participants streams behavior
// ============================== // ==============================
fakeRemoteParticipantsStreams.set( fakeRemoteParticipantsStreams.set(
keyForTransport(TRANSPORT_1.transport), keyForTransport(TRANSPORT_1),
behavior("oa-b", { behavior("oa-b", {
o: [], o: [],
a: [{ identity: "user1A" } as RemoteParticipant], a: [{ identity: "user1A" } as RemoteParticipant],
@@ -313,7 +330,7 @@ describe("connectionManagerData$ stream", () => {
); );
fakeRemoteParticipantsStreams.set( fakeRemoteParticipantsStreams.set(
keyForTransport(TRANSPORT_2.transport), keyForTransport(TRANSPORT_2),
behavior("o-a", { behavior("o-a", {
o: [], o: [],
a: [{ identity: "user2A" } as RemoteParticipant], a: [{ identity: "user2A" } as RemoteParticipant],
@@ -50,10 +50,8 @@ export class ConnectionManagerData {
// This is enough as a key because the ConnectionManager is already scoped by room. // This is enough as a key because the ConnectionManager is already scoped by room.
// We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`. // We also do not need to consider the slotId at this point since each `MatrixRTCSession` is already scoped by `slotDescription: {id, application}`.
return JSON.stringify([ return JSON.stringify([
"url" in transport ? transport.url : undefined, "url" in transport ? transport.url : null,
"livekit_service_url" in transport "livekit_service_url" in transport ? transport.livekit_service_url : null,
? transport.livekit_service_url
: undefined,
serverName, serverName,
]); ]);
} }
@@ -142,6 +142,7 @@ test("should signal participant on a connection that is publishing", async () =>
const connection = { const connection = {
transport: bobMembership.getTransport(), transport: bobMembership.getTransport(),
serverName: "example.org",
} as unknown as Connection; } as unknown as Connection;
const dataWithPublisher = new ConnectionManagerData(); const dataWithPublisher = new ConnectionManagerData();
dataWithPublisher.add(connection, [ dataWithPublisher.add(connection, [
@@ -185,6 +186,7 @@ test("should signal participant on a connection that is not publishing", async (
const connection = { const connection = {
transport: bobMembership.getTransport(), transport: bobMembership.getTransport(),
serverName: "example.org",
} as unknown as Connection; } as unknown as Connection;
const dataWithPublisher = new ConnectionManagerData(); const dataWithPublisher = new ConnectionManagerData();
dataWithPublisher.add(connection, []); dataWithPublisher.add(connection, []);
@@ -226,9 +228,11 @@ describe("Publication edge case", () => {
); );
const connectionA = { const connectionA = {
transport: transportA, transport: transportA,
serverName: "example.org",
} as unknown as Connection; } as unknown as Connection;
const connectionB = { const connectionB = {
transport: transportB, transport: transportB,
serverName: "sample.com",
} as unknown as Connection; } as unknown as Connection;
connectionWithPublisher.add(connectionA, [ connectionWithPublisher.add(connectionA, [
@@ -284,8 +288,14 @@ test("bob is publishing in the wrong connection", async () => {
bobMembership.userId, bobMembership.userId,
bobMembership.deviceId, bobMembership.deviceId,
); );
const connectionA = { transport: transportA } as unknown as Connection; const connectionA = {
const connectionB = { transport: transportB } as unknown as Connection; transport: transportA,
serverName: "example.org",
} as unknown as Connection;
const connectionB = {
transport: transportB,
serverName: "sample.com",
} as unknown as Connection;
// Bob is not publishing on A // Bob is not publishing on A
connectionWithPublisher.add(connectionA, []); connectionWithPublisher.add(connectionA, []);
+1 -1
View File
@@ -69,7 +69,7 @@ export function getBasicRTCSession(
getUserId: () => localRtcMember.userId, getUserId: () => localRtcMember.userId,
getDeviceId: () => localRtcMember.deviceId, getDeviceId: () => localRtcMember.deviceId,
getSyncState: () => SyncState.Syncing, getSyncState: () => SyncState.Syncing,
getDomain: () => null, getDomain: () => "example.com",
getAccessToken: () => "fake-token", getAccessToken: () => "fake-token",
sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }), sendEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),
redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }), redactEvent: vitest.fn().mockResolvedValue({ event_id: "$fake:event" }),