import { strict as assert } from "assert" ; import { IFluidHandle } from "@fluidframework/core-interfaces" ; import { Container } from "@fluidframework/container-loader" ; import { ISharedMap , SharedMap } from "@fluidframework/map" ; import { requestFluidObject } from "@fluidframework/runtime-utils" ; import { ITestObjectProvider , ITestContainerConfig , DataObjectFactoryType , ChannelFactoryRegistry , ITestFluidObject , } from "@fluidframework/test-utils" ; import { describeFullCompat } from "@fluidframework/test-version-utils" ; const mapId = "mapKey" ; const registry = [ [ mapId , SharedMap . getFactory ( ) ] ] ; const testContainerConfig = { fluidDataObjectType : DataObjectFactoryType . Test , registry } ; describeFullCompat ( "SharedMap" , ( getTestObjectProvider ) => { let provider ; beforeEach ( ( ) => { provider = getTestObjectProvider ( ) ; } ) ; let dataObject1 ; let sharedMap1 ; let sharedMap2 ; let sharedMap3 ; beforeEach ( async ( ) => { const container1 = await provider . makeTestContainer ( testContainerConfig ) as Container ; dataObject1 = await requestFluidObject < ITestFluidObject > ( container1 , "default" ) ; sharedMap1 = await dataObject1 . getSharedObject < SharedMap > ( mapId ) ; const container2 = await provider . loadTestContainer ( testContainerConfig ) as Container ; const dataObject2 = await requestFluidObject < ITestFluidObject > ( container2 , "default" ) ; sharedMap2 = await dataObject2 . getSharedObject < SharedMap > ( mapId ) ; const container3 = await provider . loadTestContainer ( testContainerConfig ) as Container ; const dataObject3 = await requestFluidObject < ITestFluidObject > ( container3 , "default" ) ; sharedMap3 = await dataObject3 . getSharedObject < SharedMap > ( mapId ) ; sharedMap1 . set ( "testKey1" , "testValue" ) ; await provider . ensureSynchronized ( ) ; } ) ; function expectAllValues ( msg , key , value1 , value2 , value3 ) { const user1Value = sharedMap1 . get ( key ) ; assert . equal ( user1Value , value1 , `Incorrect value for ${ key } in container 1 ${ msg }` ) ; const user2Value = sharedMap2 . get ( key ) ; assert . equal ( user2Value , value2 , `Incorrect value for ${ key } in container 2 ${ msg }` ) ; const user3Value = sharedMap3 . get ( key ) ; assert . equal ( user3Value , value3 , `Incorrect value for ${ key } in container 3 ${ msg }` ) ; } function expectAllBeforeValues ( key , value1 , value2 , value3 ) { expectAllValues ( "before process" , key , value1 , value2 , value3 ) ; } function expectAllAfterValues ( key , value ) { expectAllValues ( "after process" , key , value , value , value ) ; } function expectAllSize ( size ) { const keys1 = Array . from ( sharedMap1 . keys ( ) ) ; assert . equal ( keys1 . length , size , "Incorrect number of Keys in container 1" ) ; const keys2 = Array . from ( sharedMap2 . keys ( ) ) ; assert . equal ( keys2 . length , size , "Incorrect number of Keys in container 2" ) ; const keys3 = Array . from ( sharedMap3 . keys ( ) ) ; assert . equal ( keys3 . length , size , "Incorrect number of Keys in container 3" ) ; assert . equal ( sharedMap1 . size , size , "Incorrect map size in container 1" ) ; assert . equal ( sharedMap2 . size , size , "Incorrect map size in container 2" ) ; assert . equal ( sharedMap3 . size , size , "Incorrect map size in container 3" ) ; } it ( "should set key value in three containers correctly" , async ( ) => { expectAllAfterValues ( "testKey1" , "testValue" ) ; } ) ; it ( "should set key value to undefined in three containers correctly" , async ( ) => { sharedMap2 . set ( "testKey1" , undefined ) ; sharedMap2 . set ( "testKey2" , undefined ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey1" , undefined ) ; expectAllAfterValues ( "testKey2" , undefined ) ; } ) ; it ( "Should delete values in 3 containers correctly" , async ( ) => { sharedMap2 . delete ( "testKey1" ) ; await provider . ensureSynchronized ( ) ; const hasKey1 = sharedMap1 . has ( "testKey1" ) ; assert . equal ( hasKey1 , false , "testKey1 not deleted in container 1" ) ; const hasKey2 = sharedMap2 . has ( "testKey1" ) ; assert . equal ( hasKey2 , false , "testKey1 not deleted in container 1" ) ; const hasKey3 = sharedMap3 . has ( "testKey1" ) ; assert . equal ( hasKey3 , false , "testKey1 not deleted in container 1" ) ; } ) ; it ( "Should check if three containers has same number of keys" , async ( ) => { sharedMap3 . set ( "testKey3" , true ) ; await provider . ensureSynchronized ( ) ; expectAllSize ( 2 ) ; } ) ; it ( "Should update value and trigger onValueChanged on other two containers" , async ( ) => { let user1ValueChangedCount = 0 ; let user2ValueChangedCount = 0 ; let user3ValueChangedCount = 0 ; sharedMap1 . on ( "valueChanged" , ( changed , local ) => { if ( ! local ) { assert . equal ( changed . key , "testKey1" , "Incorrect value for testKey1 in container 1" ) ; user1ValueChangedCount = user1ValueChangedCount + 1 ; } } ) ; sharedMap2 . on ( "valueChanged" , ( changed , local ) => { if ( ! local ) { assert . equal ( changed . key , "testKey1" , "Incorrect value for testKey1 in container 2" ) ; user2ValueChangedCount = user2ValueChangedCount + 1 ; } } ) ; sharedMap3 . on ( "valueChanged" , ( changed , local ) => { if ( ! local ) { assert . equal ( changed . key , "testKey1" , "Incorrect value for testKey1 in container 3" ) ; user3ValueChangedCount = user3ValueChangedCount + 1 ; } } ) ; sharedMap1 . set ( "testKey1" , "updatedValue" ) ; await provider . ensureSynchronized ( ) ; assert . equal ( user1ValueChangedCount , 0 , "Incorrect number of valueChanged op received in container 1" ) ; assert . equal ( user2ValueChangedCount , 1 , "Incorrect number of valueChanged op received in container 2" ) ; assert . equal ( user3ValueChangedCount , 1 , "Incorrect number of valueChanged op received in container 3" ) ; expectAllAfterValues ( "testKey1" , "updatedValue" ) ; } ) ; it ( "Simultaneous set should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey1" , "value1" ) ; sharedMap2 . set ( "testKey1" , "value2" ) ; sharedMap3 . set ( "testKey1" , "value0" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap3 . set ( "testKey1" , "value3" ) ; expectAllBeforeValues ( "testKey1" , "value1" , "value2" , "value3" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey1" , "value3" ) ; } ) ; it ( "Simultaneous delete/set should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey1" , "value1.1" ) ; sharedMap2 . delete ( "testKey1" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap3 . set ( "testKey1" , "value1.3" ) ; expectAllBeforeValues ( "testKey1" , "value1.1" , undefined , "value1.3" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey1" , "value1.3" ) ; } ) ; it ( "Simultaneous delete/set on same map should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey2" , "value2.1" ) ; sharedMap2 . delete ( "testKey2" ) ; sharedMap3 . set ( "testKey2" , "value2.3" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap2 . set ( "testKey2" , "value2.2" ) ; expectAllBeforeValues ( "testKey2" , "value2.1" , "value2.2" , "value2.3" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey2" , "value2.2" ) ; } ) ; it ( "Simultaneous set/delete should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey3" , "value3.1" ) ; sharedMap2 . set ( "testKey3" , "value3.2" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap3 . delete ( "testKey3" ) ; expectAllBeforeValues ( "testKey3" , "value3.1" , "value3.2" , undefined ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey3" , undefined ) ; } ) ; it ( "Simultaneous set/clear on a key should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey1" , "value1.1" ) ; sharedMap2 . set ( "testKey1" , "value1.2" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap3 . clear ( ) ; expectAllBeforeValues ( "testKey1" , "value1.1" , "value1.2" , undefined ) ; assert . equal ( sharedMap3 . size , 0 , "Incorrect map size after clear" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey1" , undefined ) ; expectAllSize ( 0 ) ; } ) ; it ( "Simultaneous clear/set on same map should reach eventual consistency with the same value" , async ( ) => { sharedMap1 . set ( "testKey2" , "value2.1" ) ; sharedMap2 . clear ( ) ; sharedMap3 . set ( "testKey2" , "value2.3" ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap2 . set ( "testKey2" , "value2.2" ) ; expectAllBeforeValues ( "testKey2" , "value2.1" , "value2.2" , "value2.3" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey2" , "value2.2" ) ; expectAllSize ( 1 ) ; } ) ; it ( "Simultaneous clear/set should reach eventual consistency and resolve to the same value" , async ( ) => { sharedMap1 . set ( "testKey3" , "value3.1" ) ; sharedMap2 . clear ( ) ; await provider . opProcessingController . processOutgoing ( ) ; sharedMap3 . set ( "testKey3" , "value3.3" ) ; expectAllBeforeValues ( "testKey3" , "value3.1" , undefined , "value3.3" ) ; await provider . ensureSynchronized ( ) ; expectAllAfterValues ( "testKey3" , "value3.3" ) ; expectAllSize ( 1 ) ; } ) ; it ( "should load new map with data from local state and can then process ops" , async ( ) => { /**
         * This tests test the scenario found in the following bug:
         * https://github.com/microsoft/FluidFramework/issues/2400
         *
         * - A SharedMap in local (detached) state set a key.
         * - The map is then attached so that it is available to remote clients.
         * - One of the remote clients sets a new value to the same key.
         * - The expected behavior is that the first SharedMap updates the key with the new value. But in the bug
         *   the first SharedMap stores the key in its pending state even though it does not send out an op. So,
         *   when it gets a remote op with the same key, it ignores it as it has a pending set with the same key.
         */ const newSharedMap1 = SharedMap . create ( dataObject1 . runtime ) ; newSharedMap1 . set ( "newKey" , "newValue" ) ; sharedMap1 . set ( "newSharedMap" , newSharedMap1 . handle ) ; await provider . ensureSynchronized ( ) ; const newSharedMap2 = await sharedMap2 . get < IFluidHandle < SharedMap > > ( "newSharedMap" ) ?. get ( ) ; assert ( newSharedMap2 ) ; assert . equal ( newSharedMap2 . get ( "newKey" ) , "newValue" , "The data set in local state is not available in map 2" ) ; newSharedMap2 . set ( "newKey" , "anotherNewValue" ) ; await provider . ensureSynchronized ( ) ; assert . equal ( newSharedMap2 . get ( "newKey" ) , "anotherNewValue" , "The new value is not updated in map 2" ) ; assert . equal ( newSharedMap1 . get ( "newKey" ) , "anotherNewValue" , "The new value is not updated in map 1" ) ; } ) ; } ) ;