diff --git a/node/src/test/test-Router.ts b/node/src/test/test-Router.ts index d6147cf55f..4eb0f4443e 100644 --- a/node/src/test/test-Router.ts +++ b/node/src/test/test-Router.ts @@ -1,55 +1,69 @@ import * as mediasoup from '../'; import { InvalidStateError } from '../errors'; +import * as utils from '../utils'; -let worker: mediasoup.types.Worker; +type TestContext = +{ + worker?: mediasoup.types.Worker; + mediaCodecs: mediasoup.types.RtpCodecCapability[]; +}; -beforeEach(() => worker && !worker.closed && worker.close()); -afterEach(() => worker && !worker.closed && worker.close()); +const ctx: TestContext = +{ + mediaCodecs : utils.deepFreeze( + [ + { + kind : 'audio', + mimeType : 'audio/opus', + clockRate : 48000, + channels : 2, + parameters : + { + useinbandfec : 1, + foo : 'bar' + } + }, + { + kind : 'video', + mimeType : 'video/VP8', + clockRate : 90000 + }, + { + kind : 'video', + mimeType : 'video/H264', + clockRate : 90000, + parameters : + { + 'level-asymmetry-allowed' : 1, + 'packetization-mode' : 1, + 'profile-level-id' : '4d0032' + }, + rtcpFeedback : [] // Will be ignored. + } + ] + ) +}; + +beforeEach(async () => +{ + ctx.worker = await mediasoup.createWorker(); +}); -const mediaCodecs: mediasoup.types.RtpCodecCapability[] = -[ - { - kind : 'audio', - mimeType : 'audio/opus', - clockRate : 48000, - channels : 2, - parameters : - { - useinbandfec : 1, - foo : 'bar' - } - }, - { - kind : 'video', - mimeType : 'video/VP8', - clockRate : 90000 - }, - { - kind : 'video', - mimeType : 'video/H264', - clockRate : 90000, - parameters : - { - 'level-asymmetry-allowed' : 1, - 'packetization-mode' : 1, - 'profile-level-id' : '4d0032' - }, - rtcpFeedback : [] // Will be ignored. - } -]; +afterEach(() => +{ + ctx.worker?.close(); +}); test('worker.createRouter() succeeds', async () => { - worker = await mediasoup.createWorker(); - const onObserverNewRouter = jest.fn(); - worker.observer.once('newrouter', onObserverNewRouter); + ctx.worker!.observer.once('newrouter', onObserverNewRouter); - const router = await worker.createRouter<{ foo: number; bar?: string }>( + const router = await ctx.worker!.createRouter<{ foo: number; bar?: string }>( { - mediaCodecs, - appData : { foo: 123 } + mediaCodecs : ctx.mediaCodecs, + appData : { foo: 123 } }); expect(onObserverNewRouter).toHaveBeenCalledTimes(1); @@ -63,11 +77,11 @@ test('worker.createRouter() succeeds', async () => expect(() => (router.appData = { foo: 222, bar: 'BBB' })).not.toThrow(); - await expect(worker.dump()) + await expect(ctx.worker!.dump()) .resolves .toMatchObject( { - pid : worker.pid, + pid : ctx.worker!.pid, webRtcServerIds : [], routerIds : [ router.id ], channelMessageHandlers : @@ -92,49 +106,41 @@ test('worker.createRouter() succeeds', async () => }); // Private API. - expect(worker.routersForTesting.size).toBe(1); + expect(ctx.worker!.routersForTesting.size).toBe(1); - worker.close(); + ctx.worker!.close(); expect(router.closed).toBe(true); // Private API. - expect(worker.routersForTesting.size).toBe(0); + expect(ctx.worker!.routersForTesting.size).toBe(0); }, 2000); test('worker.createRouter() with wrong arguments rejects with TypeError', async () => { - worker = await mediasoup.createWorker(); - // @ts-ignore - await expect(worker.createRouter({ mediaCodecs: {} })) + await expect(ctx.worker!.createRouter({ mediaCodecs: {} })) .rejects .toThrow(TypeError); // @ts-ignore - await expect(worker.createRouter({ appData: 'NOT-AN-OBJECT' })) + await expect(ctx.worker!.createRouter({ appData: 'NOT-AN-OBJECT' })) .rejects .toThrow(TypeError); - - worker.close(); }, 2000); test('worker.createRouter() rejects with InvalidStateError if Worker is closed', async () => { - worker = await mediasoup.createWorker(); - - worker.close(); + ctx.worker!.close(); - await expect(worker.createRouter({ mediaCodecs })) + await expect(ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs })) .rejects .toThrow(InvalidStateError); }, 2000); test('router.close() succeeds', async () => { - worker = await mediasoup.createWorker(); - - const router = await worker.createRouter({ mediaCodecs }); + const router = await ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs }); const onObserverClose = jest.fn(); router.observer.once('close', onObserverClose); @@ -146,9 +152,7 @@ test('router.close() succeeds', async () => test('Router emits "workerclose" if Worker is closed', async () => { - worker = await mediasoup.createWorker(); - - const router = await worker.createRouter({ mediaCodecs }); + const router = await ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs }); const onObserverClose = jest.fn(); router.observer.once('close', onObserverClose); @@ -156,7 +160,7 @@ test('Router emits "workerclose" if Worker is closed', async () => await new Promise((resolve) => { router.on('workerclose', resolve); - worker.close(); + ctx.worker!.close(); }); expect(onObserverClose).toHaveBeenCalledTimes(1); diff --git a/node/src/utils.ts b/node/src/utils.ts index 439f1fcaaf..f0ad39ff1e 100644 --- a/node/src/utils.ts +++ b/node/src/utils.ts @@ -197,3 +197,26 @@ export function parseStringStringArrayVector( return array; } + +/** + * Make an object or array recursively immutable. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze. + */ +export function deepFreeze(object: T): T +{ + // Retrieve the property names defined on object. + const propNames = Reflect.ownKeys(object as any); + + // Freeze properties before freezing self. + for (const name of propNames) + { + const value = (object as any)[name]; + + if ((value && typeof value === 'object') || typeof value === 'function') + { + deepFreeze(value); + } + } + + return Object.freeze(object); +}