Fix existing LocalTransport tests

This commit is contained in:
Robin
2026-02-13 12:43:13 +01:00
parent 6cf859fd9e
commit 2a56830426
2 changed files with 99 additions and 140 deletions

View File

@@ -39,7 +39,6 @@ import { constant } from "../../Behavior";
import { ConnectionManagerData } from "../remoteMembers/ConnectionManager"; import { ConnectionManagerData } from "../remoteMembers/ConnectionManager";
import { ConnectionState, type Connection } from "../remoteMembers/Connection"; import { ConnectionState, type Connection } from "../remoteMembers/Connection";
import { type Publisher } from "./Publisher"; import { type Publisher } from "./Publisher";
import { type LocalTransportWithSFUConfig } from "./LocalTransport";
import { initializeWidget } from "../../../widget"; import { initializeWidget } from "../../../widget";
initializeWidget(); initializeWidget();
@@ -216,11 +215,10 @@ describe("LocalMembership", () => {
it("throws error on missing RTC config error", () => { it("throws error on missing RTC config error", () => {
withTestScheduler(({ scope, hot, expectObservable }) => { withTestScheduler(({ scope, hot, expectObservable }) => {
const localTransport$ = const localTransport$ = scope.behavior<null | LivekitTransportConfig>(
scope.behavior<null | LocalTransportWithSFUConfig>( hot("1ms #", {}, new MatrixRTCTransportMissingError("domain.com")),
hot("1ms #", {}, new MatrixRTCTransportMissingError("domain.com")), null,
null, );
);
// we do not need any connection data since we want to fail before reaching that. // we do not need any connection data since we want to fail before reaching that.
const mockConnectionManager = { const mockConnectionManager = {
@@ -279,23 +277,11 @@ describe("LocalMembership", () => {
}); });
const aTransport = { const aTransport = {
transport: { livekit_service_url: "a",
livekit_service_url: "a", } as LivekitTransportConfig;
} as LivekitTransportConfig,
sfuConfig: {
url: "sfu-url",
jwt: "sfu-token",
},
} as LocalTransportWithSFUConfig;
const bTransport = { const bTransport = {
transport: { livekit_service_url: "b",
livekit_service_url: "b", } as LivekitTransportConfig;
} as LivekitTransportConfig,
sfuConfig: {
url: "sfu-url",
jwt: "sfu-token",
},
} as LocalTransportWithSFUConfig;
const connectionTransportAConnected = { const connectionTransportAConnected = {
livekitRoom: mockLivekitRoom({ livekitRoom: mockLivekitRoom({
@@ -305,7 +291,7 @@ describe("LocalMembership", () => {
} as unknown as LocalParticipant, } as unknown as LocalParticipant,
}), }),
state$: constant(ConnectionState.LivekitConnected), state$: constant(ConnectionState.LivekitConnected),
transport: aTransport.transport, transport: aTransport,
} as unknown as Connection; } as unknown as Connection;
const connectionTransportAConnecting = { const connectionTransportAConnecting = {
...connectionTransportAConnected, ...connectionTransportAConnected,
@@ -314,7 +300,7 @@ describe("LocalMembership", () => {
} as unknown as Connection; } as unknown as Connection;
const connectionTransportBConnected = { const connectionTransportBConnected = {
state$: constant(ConnectionState.LivekitConnected), state$: constant(ConnectionState.LivekitConnected),
transport: bTransport.transport, transport: bTransport,
livekitRoom: mockLivekitRoom({}), livekitRoom: mockLivekitRoom({}),
} as unknown as Connection; } as unknown as Connection;
@@ -368,12 +354,8 @@ describe("LocalMembership", () => {
// stop the first Publisher and let the second one life. // stop the first Publisher and let the second one life.
expect(publishers[0].destroy).toHaveBeenCalled(); expect(publishers[0].destroy).toHaveBeenCalled();
expect(publishers[1].destroy).not.toHaveBeenCalled(); expect(publishers[1].destroy).not.toHaveBeenCalled();
expect(publisherFactory.mock.calls[0][0].transport).toBe( expect(publisherFactory.mock.calls[0][0].transport).toBe(aTransport);
aTransport.transport, expect(publisherFactory.mock.calls[1][0].transport).toBe(bTransport);
);
expect(publisherFactory.mock.calls[1][0].transport).toBe(
bTransport.transport,
);
scope.end(); scope.end();
await flushPromises(); await flushPromises();
// stop all tracks after ending scopes // stop all tracks after ending scopes
@@ -446,8 +428,9 @@ describe("LocalMembership", () => {
const scope = new ObservableScope(); const scope = new ObservableScope();
const connectionManagerData = new ConnectionManagerData(); const connectionManagerData = new ConnectionManagerData();
const localTransport$ = const localTransport$ = new BehaviorSubject<null | LivekitTransportConfig>(
new BehaviorSubject<null | LocalTransportWithSFUConfig>(null); null,
);
const connectionManagerData$ = new BehaviorSubject( const connectionManagerData$ = new BehaviorSubject(
new Epoch(connectionManagerData), new Epoch(connectionManagerData),
); );
@@ -519,7 +502,7 @@ describe("LocalMembership", () => {
}); });
( (
connectionManagerData2.getConnectionForTransport(aTransport.transport)! connectionManagerData2.getConnectionForTransport(aTransport)!
.state$ as BehaviorSubject<ConnectionState> .state$ as BehaviorSubject<ConnectionState>
).next(ConnectionState.LivekitConnected); ).next(ConnectionState.LivekitConnected);
expect(localMembership.localMemberState$.value).toStrictEqual({ expect(localMembership.localMemberState$.value).toStrictEqual({

View File

@@ -43,10 +43,10 @@ describe("LocalTransport", () => {
afterEach(() => scope.end()); afterEach(() => scope.end());
it("throws if config is missing", async () => { it("throws if config is missing", async () => {
const localTransport$ = createLocalTransport$({ const { advertised$, active$ } = createLocalTransport$({
scope, scope,
roomId: "!room:example.org", roomId: "!room:example.org",
useOldestMember$: constant(false), useOldestMember: false,
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -58,14 +58,15 @@ describe("LocalTransport", () => {
getDeviceId: vi.fn(), getDeviceId: vi.fn(),
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), forceJwtEndpoint: JwtEndpointVersion.Legacy,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
await flushPromises(); await flushPromises();
expect(() => localTransport$.value).toThrow( expect(() => advertised$.value).toThrow(
new MatrixRTCTransportMissingError(""), new MatrixRTCTransportMissingError(""),
); );
expect(() => active$.value).toThrow(new MatrixRTCTransportMissingError(""));
}); });
it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => { it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => {
@@ -83,10 +84,10 @@ describe("LocalTransport", () => {
); );
const observations: unknown[] = []; const observations: unknown[] = [];
const errors: Error[] = []; const errors: Error[] = [];
const localTransport$ = createLocalTransport$({ const { advertised$, active$ } = createLocalTransport$({
scope, scope,
roomId: "!example_room_id", roomId: "!example_room_id",
useOldestMember$: constant(false), useOldestMember: false,
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
baseUrl: "https://lk.example.org", baseUrl: "https://lk.example.org",
@@ -98,10 +99,10 @@ describe("LocalTransport", () => {
getDeviceId: vi.fn(), getDeviceId: vi.fn(),
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), forceJwtEndpoint: JwtEndpointVersion.Legacy,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
localTransport$.subscribe( active$.subscribe(
(o) => observations.push(o), (o) => observations.push(o),
(e) => errors.push(e), (e) => errors.push(e),
); );
@@ -111,7 +112,8 @@ describe("LocalTransport", () => {
const expectedError = new FailToGetOpenIdToken(new Error("no openid")); const expectedError = new FailToGetOpenIdToken(new Error("no openid"));
expect(observations).toStrictEqual([null]); expect(observations).toStrictEqual([null]);
expect(errors).toStrictEqual([expectedError]); expect(errors).toStrictEqual([expectedError]);
expect(() => localTransport$.value).toThrow(expectedError); expect(() => advertised$.value).toThrow(expectedError);
expect(() => active$.value).toThrow(expectedError);
}); });
it("emits preferred transport after OpenID resolves", async () => { it("emits preferred transport after OpenID resolves", async () => {
@@ -126,10 +128,10 @@ describe("LocalTransport", () => {
openIdResolver.promise, openIdResolver.promise,
); );
const localTransport$ = createLocalTransport$({ const { advertised$, active$ } = createLocalTransport$({
scope, scope,
roomId: "!room:example.org", roomId: "!room:example.org",
useOldestMember$: constant(false), useOldestMember: false,
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -140,7 +142,7 @@ describe("LocalTransport", () => {
baseUrl: "https://lk.example.org", baseUrl: "https://lk.example.org",
}, },
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), forceJwtEndpoint: JwtEndpointVersion.Legacy,
delayId$: constant("delay_id_mock"), delayId$: constant("delay_id_mock"),
}); });
@@ -150,14 +152,17 @@ describe("LocalTransport", () => {
livekitAlias: "Akph4alDMhen", livekitAlias: "Akph4alDMhen",
livekitIdentity: ownMemberMock.userId + ":" + ownMemberMock.deviceId, livekitIdentity: ownMemberMock.userId + ":" + ownMemberMock.deviceId,
}); });
expect(localTransport$.value).toBe(null); expect(advertised$.value).toBe(null);
expect(active$.value).toBe(null);
await flushPromises(); await flushPromises();
// final // final
expect(localTransport$.value).toStrictEqual({ const expectedTransport = {
transport: { livekit_service_url: "https://lk.example.org",
livekit_service_url: "https://lk.example.org", type: "livekit",
type: "livekit", };
}, expect(advertised$.value).toStrictEqual(expectedTransport);
expect(active$.value).toStrictEqual({
transport: expectedTransport,
sfuConfig: { sfuConfig: {
jwt: "jwt", jwt: "jwt",
livekitAlias: "Akph4alDMhen", livekitAlias: "Akph4alDMhen",
@@ -167,53 +172,8 @@ describe("LocalTransport", () => {
}); });
}); });
it("updates local transport when oldest member changes", async () => { // TODO: This test previously didn't test what it claims to.
// Use config so transport discovery succeeds, but delay OpenID JWT fetch it.todo("updates local transport when oldest member changes");
mockConfig({
livekit: { livekit_service_url: "https://lk.example.org" },
});
const memberships$ = new BehaviorSubject(new Epoch([]));
const openIdResolver = Promise.withResolvers<openIDSFU.SFUConfig>();
vi.spyOn(openIDSFU, "getSFUConfigWithOpenID").mockReturnValue(
openIdResolver.promise,
);
const localTransport$ = createLocalTransport$({
scope,
roomId: "!example_room_id",
useOldestMember$: constant(true),
memberships$,
client: {
getDomain: () => "",
// eslint-disable-next-line @typescript-eslint/naming-convention
_unstable_getRTCTransports: async () => Promise.resolve([]),
getOpenIdToken: vi.fn(),
getDeviceId: vi.fn(),
baseUrl: "https://lk.example.org",
},
ownMembershipIdentity: ownMemberMock,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy),
delayId$: constant("delay_id_mock"),
});
openIdResolver.resolve?.(openIdResponse);
expect(localTransport$.value).toBe(null);
await flushPromises();
// final
expect(localTransport$.value).toStrictEqual({
transport: {
livekit_service_url: "https://lk.example.org",
type: "livekit",
},
sfuConfig: {
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
livekitAlias: "Akph4alDMhen",
livekitIdentity: "@lk_user:ABCDEF",
url: "https://lk.example.org",
},
});
});
type LocalTransportProps = Parameters<typeof createLocalTransport$>[0]; type LocalTransportProps = Parameters<typeof createLocalTransport$>[0];
@@ -229,8 +189,8 @@ describe("LocalTransport", () => {
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
scope, scope,
roomId: "!example_room_id", roomId: "!example_room_id",
useOldestMember$: constant(false), useOldestMember: false,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), forceJwtEndpoint: JwtEndpointVersion.Legacy,
delayId$: constant(null), delayId$: constant(null),
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
@@ -256,15 +216,19 @@ describe("LocalTransport", () => {
mockConfig({ mockConfig({
livekit: { livekit_service_url: "https://lk.example.org" }, livekit: { livekit_service_url: "https://lk.example.org" },
}); });
const localTransport$ = createLocalTransport$(localTransportOpts); const { advertised$, active$ } =
createLocalTransport$(localTransportOpts);
openIdResolver.resolve?.(openIdResponse); openIdResolver.resolve?.(openIdResponse);
expect(localTransport$.value).toBe(null); expect(advertised$.value).toBe(null);
expect(active$.value).toBe(null);
await flushPromises(); await flushPromises();
expect(localTransport$.value).toStrictEqual({ const expectedTransport = {
transport: { livekit_service_url: "https://lk.example.org",
livekit_service_url: "https://lk.example.org", type: "livekit",
type: "livekit", };
}, expect(advertised$.value).toStrictEqual(expectedTransport);
expect(active$.value).toStrictEqual({
transport: expectedTransport,
sfuConfig: { sfuConfig: {
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
livekitAlias: "Akph4alDMhen", livekitAlias: "Akph4alDMhen",
@@ -273,13 +237,15 @@ describe("LocalTransport", () => {
}, },
}); });
}); });
it("supports getting transport via user settings", async () => { it("supports getting transport via user settings", async () => {
customLivekitUrl.setValue("https://lk.example.org"); customLivekitUrl.setValue("https://lk.example.org");
const localTransport$ = createLocalTransport$(localTransportOpts); const { advertised$, active$ } =
createLocalTransport$(localTransportOpts);
openIdResolver.resolve?.(openIdResponse); openIdResolver.resolve?.(openIdResponse);
expect(localTransport$.value).toBe(null); expect(advertised$.value).toBe(null);
await flushPromises(); await flushPromises();
expect(localTransport$.value).toStrictEqual({ expect(active$.value).toStrictEqual({
transport: { transport: {
livekit_service_url: "https://lk.example.org", livekit_service_url: "https://lk.example.org",
type: "livekit", type: "livekit",
@@ -292,19 +258,24 @@ describe("LocalTransport", () => {
}, },
}); });
}); });
it("supports getting transport via backend", async () => { it("supports getting transport via backend", async () => {
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([ localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
{ type: "livekit", livekit_service_url: "https://lk.example.org" }, { type: "livekit", livekit_service_url: "https://lk.example.org" },
]); ]);
const localTransport$ = createLocalTransport$(localTransportOpts); const { advertised$, active$ } =
createLocalTransport$(localTransportOpts);
openIdResolver.resolve?.(openIdResponse); openIdResolver.resolve?.(openIdResponse);
expect(localTransport$.value).toBe(null); expect(advertised$.value).toBe(null);
expect(active$.value).toBe(null);
await flushPromises(); await flushPromises();
expect(localTransport$.value).toStrictEqual({ const expectedTransport = {
transport: { livekit_service_url: "https://lk.example.org",
livekit_service_url: "https://lk.example.org", type: "livekit",
type: "livekit", };
}, expect(advertised$.value).toStrictEqual(expectedTransport);
expect(active$.value).toStrictEqual({
transport: expectedTransport,
sfuConfig: { sfuConfig: {
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
livekitAlias: "Akph4alDMhen", livekitAlias: "Akph4alDMhen",
@@ -313,6 +284,7 @@ describe("LocalTransport", () => {
}, },
}); });
}); });
it("fails fast if the openID request fails for backend config", async () => { it("fails fast if the openID request fails for backend config", async () => {
localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([ localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([
{ type: "livekit", livekit_service_url: "https://lk.example.org" }, { type: "livekit", livekit_service_url: "https://lk.example.org" },
@@ -320,13 +292,11 @@ describe("LocalTransport", () => {
openIdResolver.reject( openIdResolver.reject(
new FailToGetOpenIdToken(new Error("Test driven error")), new FailToGetOpenIdToken(new Error("Test driven error")),
); );
try { await expect(async () =>
await lastValueFrom(createLocalTransport$(localTransportOpts)); lastValueFrom(createLocalTransport$(localTransportOpts).active$),
throw Error("Expected test to throw"); ).rejects.toThrow(expect.any(FailToGetOpenIdToken));
} catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
}
}); });
it("supports getting transport via well-known", async () => { it("supports getting transport via well-known", async () => {
localTransportOpts.client.getDomain.mockReturnValue("example.org"); localTransportOpts.client.getDomain.mockReturnValue("example.org");
fetchMock.getOnce("https://example.org/.well-known/matrix/client", { fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
@@ -334,15 +304,19 @@ describe("LocalTransport", () => {
{ type: "livekit", livekit_service_url: "https://lk.example.org" }, { type: "livekit", livekit_service_url: "https://lk.example.org" },
], ],
}); });
const localTransport$ = createLocalTransport$(localTransportOpts); const { advertised$, active$ } =
createLocalTransport$(localTransportOpts);
openIdResolver.resolve?.(openIdResponse); openIdResolver.resolve?.(openIdResponse);
expect(localTransport$.value).toBe(null); expect(advertised$.value).toBe(null);
expect(active$.value).toBe(null);
await flushPromises(); await flushPromises();
expect(localTransport$.value).toStrictEqual({ const expectedTransport = {
transport: { livekit_service_url: "https://lk.example.org",
livekit_service_url: "https://lk.example.org", type: "livekit",
type: "livekit", };
}, expect(advertised$.value).toStrictEqual(expectedTransport);
expect(active$.value).toStrictEqual({
transport: expectedTransport,
sfuConfig: { sfuConfig: {
jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=",
livekitAlias: "Akph4alDMhen", livekitAlias: "Akph4alDMhen",
@@ -352,6 +326,7 @@ describe("LocalTransport", () => {
}); });
expect(fetchMock.done()).toEqual(true); expect(fetchMock.done()).toEqual(true);
}); });
it("fails fast if the openId request fails for the well-known config", async () => { it("fails fast if the openId request fails for the well-known config", async () => {
localTransportOpts.client.getDomain.mockReturnValue("example.org"); localTransportOpts.client.getDomain.mockReturnValue("example.org");
fetchMock.getOnce("https://example.org/.well-known/matrix/client", { fetchMock.getOnce("https://example.org/.well-known/matrix/client", {
@@ -362,20 +337,18 @@ describe("LocalTransport", () => {
openIdResolver.reject( openIdResolver.reject(
new FailToGetOpenIdToken(new Error("Test driven error")), new FailToGetOpenIdToken(new Error("Test driven error")),
); );
try { await expect(async () =>
await lastValueFrom(createLocalTransport$(localTransportOpts)); lastValueFrom(createLocalTransport$(localTransportOpts).active$),
throw Error("Expected test to throw"); ).rejects.toThrow(expect.any(FailToGetOpenIdToken));
} catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
}
}); });
it("throws if no options are available", async () => { it("throws if no options are available", async () => {
const localTransport$ = createLocalTransport$({ const { advertised$, active$ } = createLocalTransport$({
scope, scope,
ownMembershipIdentity: ownMemberMock, ownMembershipIdentity: ownMemberMock,
roomId: "!example_room_id", roomId: "!example_room_id",
useOldestMember$: constant(false), useOldestMember: false,
forceJwtEndpoint$: constant(JwtEndpointVersion.Legacy), forceJwtEndpoint: JwtEndpointVersion.Legacy,
delayId$: constant(null), delayId$: constant(null),
memberships$: constant(new Epoch<CallMembership[]>([])), memberships$: constant(new Epoch<CallMembership[]>([])),
client: { client: {
@@ -390,7 +363,10 @@ describe("LocalTransport", () => {
}); });
await flushPromises(); await flushPromises();
expect(() => localTransport$.value).toThrow( expect(() => advertised$.value).toThrow(
new MatrixRTCTransportMissingError(""),
);
expect(() => active$.value).toThrow(
new MatrixRTCTransportMissingError(""), new MatrixRTCTransportMissingError(""),
); );
}); });