import { strict as assert } from "assert" ; import { ContainerRuntimeFactoryWithDefaultDataStore , DataObject , DataObjectFactory , } from "@fluidframework/aqueduct" ; import { Container } from "@fluidframework/container-loader" ; import { requestFluidObject } from "@fluidframework/runtime-utils" ; import { ITestObjectProvider , timeoutPromise } from "@fluidframework/test-utils" ; import { describeFullCompat } from "@fluidframework/test-version-utils" ; import { IRequest } from "@fluidframework/core-interfaces" ; import { IContainerRuntimeBase } from "@fluidframework/runtime-definitions" ; class TestDataObject extends DataObject { public get _root ( ) { return this . root ; } } describeFullCompat ( "Audience correctness" , ( getTestObjectProvider ) => { let provider ; const dataObjectFactory = new DataObjectFactory ( "TestDataObject" , TestDataObject , [ ] , [ ] ) ; const innerRequestHandler = async ( request , runtime ) => runtime . IFluidHandleContext . resolveHandle ( request ) ; const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore ( dataObjectFactory , [ [ dataObjectFactory . type , Promise . resolve ( dataObjectFactory ) ] , ] , undefined , [ innerRequestHandler ] , { summaryOptions : { disableSummaries : true } } ) ; const createContainer = async ( ) : Promise < Container > => await provider . createContainer ( runtimeFactory ) as Container ; const loadContainer = async ( ) : Promise < Container > => await provider . loadContainer ( runtimeFactory ) as Container ; async function ensureContainerConnected ( container ) { if ( ! container . connected ) { return new Promise ( ( resolve ) => container . once ( "connected" , ( ) => resolve ( ) ) ) ; } } /** Function to wait for a client with the given clientId to be added to the audience of the given container. */ async function waitForClientAdd ( container , clientId , errorMsg ) { if ( container . audience . getMember ( clientId ) === undefined ) { return timeoutPromise ( ( resolve ) => { const listener = ( newClientId ) => { if ( newClientId === clientId ) { container . audience . off ( "addMember" , listener ) ; resolve ( ) ; } } ; container . audience . on ( "addMember" , ( newClientId ) => listener ( newClientId ) ) ; } , { durationMs : 2000 , errorMsg } ) ; } } /** Function to wait for a client with the given clientId to be remove from the audience of the given container. */ async function waitForClientRemove ( container , clientId , errorMsg ) { if ( container . audience . getMember ( clientId ) !== undefined ) { return timeoutPromise ( ( resolve ) => { const listener = ( newClientId ) => { if ( newClientId === clientId ) { container . audience . off ( "removeMember" , listener ) ; resolve ( ) ; } } ; container . audience . on ( "removeMember" , ( newClientId ) => listener ( newClientId ) ) ; } , { durationMs : 2000 , errorMsg } ) ; } } beforeEach ( async ( ) => { provider = getTestObjectProvider ( ) ; } ) ; /**
     * These tests wait for a client to get connected and then wait for them to be added to the audience. Ideally,
     * by the time a client moves to connected state, its audience should have itself and all previous clients.
     * This is tracked here - https://github.com/microsoft/FluidFramework/issues/7275. Once this is fixed, the tests
     * should be updated as per the new expectations.
     */ it ( "should add clients in audience as expected" , async ( ) => { const client1Container = await createContainer ( ) ; await ensureContainerConnected ( client1Container ) ; assert ( client1Container . clientId !== undefined , "client1 does not have clientId" ) ; await waitForClientAdd ( client1Container , client1Container . clientId , "client1's audience doesn't have self" ) ; const client2Container = await loadContainer ( ) ; await ensureContainerConnected ( client2Container ) ; assert ( client2Container . clientId !== undefined , "client2 does not have clientId" ) ; await waitForClientAdd ( client2Container , client2Container . clientId , "client2's audience doesn't have self" ) ; await waitForClientAdd ( client1Container , client2Container . clientId , "client1's audience doesn't have client2" ) ; await waitForClientAdd ( client2Container , client1Container . clientId , "client2's audience doesn't have client1" ) ; } ) ; it ( "should add clients in audience as expected in write mode" , async ( ) => { const client1Container = await createContainer ( ) ; const client1DataStore = await requestFluidObject < TestDataObject > ( client1Container , "default" ) ; const client2Container = await loadContainer ( ) ; const client2DataStore = await requestFluidObject < TestDataObject > ( client2Container , "default" ) ; client1DataStore . _root . set ( "testKey1" , "testValue1" ) ; client2DataStore . _root . set ( "testKey2" , "testValue2" ) ; await ensureContainerConnected ( client1Container ) ; await ensureContainerConnected ( client2Container ) ; await provider . ensureSynchronized ( ) ; assert ( client1Container . clientId !== undefined , "client1 does not have clientId" ) ; assert ( client2Container . clientId !== undefined , "client2 does not have clientId" ) ; await waitForClientAdd ( client1Container , client1Container . clientId , "client1's audience doesn't have self" ) ; await waitForClientAdd ( client1Container , client2Container . clientId , "client1's audience doesn't have client2" ) ; await waitForClientAdd ( client2Container , client1Container . clientId , "client2's audience doesn't have client1" ) ; await waitForClientAdd ( client2Container , client2Container . clientId , "client2's audience doesn't have self" ) ; } ) ; it ( "should remove clients in audience as expected" , async ( ) => { const client1Container = await createContainer ( ) ; await ensureContainerConnected ( client1Container ) ; const client2Container = await loadContainer ( ) ; await ensureContainerConnected ( client2Container ) ; assert ( client1Container . clientId !== undefined , "client1 does not have clientId" ) ; assert ( client2Container . clientId !== undefined , "client2 does not have clientId" ) ; await waitForClientAdd ( client1Container , client2Container . clientId , "client1's audience doesn't have client2" ) ; await waitForClientAdd ( client2Container , client2Container . clientId , "client2's audience doesn't have self" ) ; client2Container . close ( ) ; await provider . ensureSynchronized ( ) ; await waitForClientRemove ( client1Container , client2Container . clientId , "client2's audience should be removed" ) ; } ) ; } ) ;