/**
 * Test suite for framework IO routing.
 */ import { Components , Configuration , Container , CoreTypes , IoActor , IoContext , IoSource , Runtime , } from "../.." ; import { BasicIoRouter , IoAssociationRule , IoSourceController , RuleBasedIoRouter } from "../../io-routing" ; import * as mocks from "./io-routing.mocks" ; import { delayAction , Spy } from "./utils" ; describe ( "IO Routing" , ( ) => { describe ( "Basic IO Routing within a single Container" , ( ) => { const TEST_TIMEOUT = 10000 ; const components1 = { controllers : { MockIoActorController : mocks . MockIoActorController , IoSourceController , BasicIoRouter , } , } ; const source1 = { name : "Temperature Source 1" , coreType : "IoSource" , objectType : CoreTypes . OBJECT_TYPE_IO_SOURCE , objectId : "c547e5cd-ef99-4ccd-b109-fc472fc2d421" , valueType : "coaty.test.Temperature[Celsius]" , } ; const actor1 = { name : "Temperature Actor 1" , coreType : "IoActor" , objectType : CoreTypes . OBJECT_TYPE_IO_ACTOR , objectId : "a731fc40-c0f8-486f-b5b6-b653c3cabaea" , valueType : source1 . valueType , } ; const ioContext = { name : "TemperatureMeasurement" , coreType : "IoContext" , objectType : CoreTypes . OBJECT_TYPE_IO_CONTEXT , objectId : Runtime . newUuid ( ) , } ; const configuration1 = { common : { ioContextNodes : { TemperatureMeasurement : { ioSources : [ source1 ] , ioActors : [ actor1 ] , } , } , } , communication : { shouldAutoStart : true , binding : global [ "test_binding" ] , } , controllers : { BasicIoRouter : { ioContext , } , } , } ; let container ; beforeAll ( done => { Spy . reset ( ) ; delayAction ( 0 , done , ( ) => { container = Container . resolve ( components1 , configuration1 ) ; } ) ; } ) ; afterAll ( done => { Spy . reset ( ) ; delayAction ( 1000 , done , ( ) => { } ) ; } , TEST_TIMEOUT ) ; it ( "actor receives all values from source" , ( done ) => { const actorController = container . getController < mocks . MockIoActorController > ( "MockIoActorController" ) ; const sourceController = container . getController < IoSourceController > ( "IoSourceController" ) ; const logger = new mocks . IoActorLogger ( ) ; const emittedValues = [ Math . random ( ) , Math . random ( ) , Math . random ( ) , Math . random ( ) ] ; const sourceAssociations = [ ] ; sourceController . observeAssociation ( source1 ) . subscribe ( isAssociated => sourceAssociations . push ( isAssociated ) ) ; actorController . logActor ( actor1 , logger ) ; delayAction ( 2000 , undefined , ( ) => { for ( const value of emittedValues ) { sourceController . publish < number > ( source1 , value ) ; } delayAction ( 2000 , undefined , ( ) => { expect ( logger . associations . length ) . toBe ( 2 ) ; expect ( logger . associations [ 0 ] ) . toBe ( false ) ; expect ( logger . associations [ 1 ] ) . toBe ( true ) ; expect ( logger . values . length ) . toBe ( emittedValues . length ) ; for ( let i = 0 ; i < logger . values . length ; i ++ ) { expect ( logger . values [ i ] ) . toBe ( emittedValues [ i ] ) ; } container . shutdown ( ) ; delayAction ( 2000 , done , ( ) => { expect ( sourceAssociations . length ) . toBe ( 2 ) ; expect ( sourceAssociations [ 0 ] ) . toBe ( false ) ; expect ( sourceAssociations [ 1 ] ) . toBe ( true ) ; } ) ; } ) ; } ) ; } , TEST_TIMEOUT ) ; } ) ; describe ( "IO Routing across containers" , ( ) => { const TEST_TIMEOUT = 10000 ; const components1 = { controllers : { IoSourceController , MockIoContextController : mocks . MockIoContextController , } , } ; const components2 = { controllers : { MockIoActorController : mocks . MockIoActorController , } , } ; const components3 = { controllers : { MockIoActorController : mocks . MockIoActorController , } , } ; const components4 = { controllers : { RuleBasedIoRouter , MockIoActorController : mocks . MockIoActorController , } , } ; const ioContext = { name : "TemperatureMeasurement" , coreType : "IoContext" , objectType : "coaty.test.TemperatureIoContext" , objectId : Runtime . newUuid ( ) , operatingState : "normal" , } as IoContext ; const source1 = { name : "Temperature Source 1" , coreType : "IoSource" , objectType : CoreTypes . OBJECT_TYPE_IO_SOURCE , objectId : "c547e5cd-ef99-4ccd-b109-fc472fc2d421" , valueType : "coaty.test.Temperature[Celsius]" , } ; const source2 = { name : "Temperature Source 2" , coreType : "IoSource" , objectType : CoreTypes . OBJECT_TYPE_IO_SOURCE , objectId : "2e9949f7-a8ef-435b-88a9-527c0a9414c3" , valueType : source1 . valueType , } ; const actor1 = { name : "Temperature Actor 1" , coreType : "IoActor" , objectType : CoreTypes . OBJECT_TYPE_IO_ACTOR , objectId : "a731fc40-c0f8-486f-b5b6-b653c3cabaea" , valueType : source1 . valueType , } ; const actor2 = { name : "Temperature Actor 2" , coreType : "IoActor" , objectType : CoreTypes . OBJECT_TYPE_IO_ACTOR , objectId : "a60a74f3-3d26-446f-a358-911867544944" , valueType : actor1 . valueType , } ; const actor3 = { name : "Non-Temperature Actor 3" , coreType : "IoActor" , objectType : CoreTypes . OBJECT_TYPE_IO_ACTOR , objectId : "ffcf807e-1758-4108-a53e-2428cef6fae9" , valueType : "coaty.test.Velocity[m/s]" , } ; const configuration1 = { common : { ioContextNodes : { TemperatureMeasurement : { ioSources : [ source1 , source2 ] , } , } , } , communication : { shouldAutoStart : true , binding : global [ "test_binding" ] , } , } ; const configuration2 = { common : { ioContextNodes : { TemperatureMeasurement : { ioActors : [ actor1 ] , characteristics : { isResponsibleForOperatingState : "normal" , } , } , } , } , communication : { shouldAutoStart : true , binding : global [ "test_binding" ] , } , } ; const configuration3 = { common : { ioContextNodes : { TemperatureMeasurement : { ioActors : [ actor2 ] , characteristics : { isResponsibleForOperatingState : "emergency" , } , } , } , } , communication : { shouldAutoStart : true , binding : global [ "test_binding" ] , } , } ; const configuration4 = { common : { ioContextNodes : { TemperatureMeasurement : { ioActors : [ actor3 ] , characteristics : { isResponsibleForOperatingState : "normal" , } , } , } , } , communication : { shouldAutoStart : true , binding : global [ "test_binding" ] , } , controllers : { RuleBasedIoRouter : { ioContext , rules : [ { name : "Route temperature sources to normal actors if operating state is normal" , valueType : source1 . valueType , condition : ( source , sourceNode , actor , actorNode , context , router ) => actorNode . characteristics ?. isResponsibleForOperatingState === "normal" && context [ "operatingState" ] === "normal" , } as IoAssociationRule , { name : "Route temperature sources to emergency actors if operating state is emergency" , valueType : source1 . valueType , condition : ( source , sourceNode , actor , actorNode , context , router ) => actorNode . characteristics ?. isResponsibleForOperatingState === "emergency" && context [ "operatingState" ] === "emergency" , } as IoAssociationRule , ] , } , } , } ; let container1 ; let container2 ; let container3 ; let container4 ; beforeAll ( done => { Spy . reset ( ) ; container1 = Container . resolve ( components1 , configuration1 ) ; container2 = Container . resolve ( components2 , configuration2 ) ; container3 = Container . resolve ( components3 , configuration3 ) ; container4 = Container . resolve ( components4 , configuration4 ) ; done ( ) ; } ) ; afterAll ( done => { Spy . reset ( ) ; delayAction ( 1000 , done , ( ) => { } ) ; } , TEST_TIMEOUT ) ; it ( "actors receive values depending on context" , ( done ) => { const ioContextController = container1 . getController < mocks . MockIoContextController > ( "MockIoContextController" ) ; const actor1Controller = container2 . getController < mocks . MockIoActorController > ( "MockIoActorController" ) ; const actor2Controller = container3 . getController < mocks . MockIoActorController > ( "MockIoActorController" ) ; const actor3Controller = container4 . getController < mocks . MockIoActorController > ( "MockIoActorController" ) ; const sourceController = container1 . getController < IoSourceController > ( "IoSourceController" ) ; const logger1 = new mocks . IoActorLogger ( ) ; const logger2 = new mocks . IoActorLogger ( ) ; const logger3 = new mocks . IoActorLogger ( ) ; const emittedValues1 = [ Math . random ( ) , Math . random ( ) , Math . random ( ) ] ; const emittedValues2 = [ Math . random ( ) , Math . random ( ) , Math . random ( ) ] ; const source1Associations = [ ] ; const source2Associations = [ ] ; sourceController . observeAssociation ( source1 ) . subscribe ( isAssociated => source1Associations . push ( isAssociated ) ) ; sourceController . observeAssociation ( source2 ) . subscribe ( isAssociated => source2Associations . push ( isAssociated ) ) ; actor1Controller . logActor ( actor1 , logger1 ) ; actor2Controller . logActor ( actor2 , logger2 ) ; actor3Controller . logActor ( actor3 , logger3 ) ; delayAction ( 1000 , undefined , ( ) => { ioContextController . discoverIoContext ( ioContext . objectType ) ; delayAction ( 2000 , undefined , ( ) => { for ( let i = 0 ; i < emittedValues1 . length ; i ++ ) { sourceController . publish < number > ( source1 , emittedValues1 [ i ] ) ; sourceController . publish < number > ( source2 , emittedValues2 [ i ] ) ; } delayAction ( 2000 , undefined , ( ) => { expect ( logger1 . associations . length ) . toBe ( 2 ) ; expect ( logger1 . associations [ 0 ] ) . toBe ( false ) ; expect ( logger1 . associations [ 1 ] ) . toBe ( true ) ; expect ( logger1 . values . length ) . toBe ( emittedValues1 . length + emittedValues2 . length ) ; for ( let i = 0 ; i < logger1 . values . length ; i ++ ) { expect ( logger1 . values [ i ] ) . toBe ( i % 2 === 0 ? emittedValues1 [ Math . floor ( i / 2 ) ] : emittedValues2 [ Math . floor ( i / 2 ) ] ) ; } expect ( logger2 . associations . length ) . toBe ( 1 ) ; expect ( logger2 . associations [ 0 ] ) . toBe ( false ) ; expect ( logger2 . values . length ) . toBe ( 0 ) ; expect ( logger3 . associations . length ) . toBe ( 1 ) ; expect ( logger3 . associations [ 0 ] ) . toBe ( false ) ; expect ( logger3 . values . length ) . toBe ( 0 ) ; delayAction ( 2000 , undefined , ( ) => { logger1 . reset ( ) ; logger2 . reset ( ) ; logger3 . reset ( ) ; ioContextController . changeIoContext ( "emergency" ) . then ( ( ) => { delayAction ( 2000 , undefined , ( ) => { for ( let i = 0 ; i < emittedValues1 . length ; i ++ ) { sourceController . publish < number > ( source1 , emittedValues1 [ i ] ) ; sourceController . publish < number > ( source2 , emittedValues2 [ i ] ) ; } delayAction ( 2000 , undefined , ( ) => { expect ( logger1 . associations . length ) . toBe ( 1 ) ; expect ( logger1 . associations [ 0 ] ) . toBe ( false ) ; expect ( logger1 . values . length ) . toBe ( 0 ) ; expect ( logger2 . associations . length ) . toBe ( 1 ) ; expect ( logger2 . associations [ 0 ] ) . toBe ( true ) ; expect ( logger2 . values . length ) . toBe ( emittedValues1 . length + emittedValues2 . length ) ; for ( let i = 0 ; i < logger2 . values . length ; i ++ ) { expect ( logger2 . values [ i ] ) . toBe ( i % 2 === 0 ? emittedValues1 [ Math . floor ( i / 2 ) ] : emittedValues2 [ Math . floor ( i / 2 ) ] ) ; } expect ( logger3 . associations . length ) . toBe ( 0 ) ; expect ( logger3 . values . length ) . toBe ( 0 ) ; logger1 . reset ( ) ; logger2 . reset ( ) ; logger3 . reset ( ) ; container1 . shutdown ( ) ; delayAction ( 1000 , undefined , ( ) => { expect ( source1Associations . length ) . toBe ( 4 ) ; expect ( source1Associations [ 0 ] ) . toBe ( false ) ; expect ( source1Associations [ 1 ] ) . toBe ( true ) ; expect ( source1Associations [ 2 ] ) . toBe ( false ) ; expect ( source1Associations [ 3 ] ) . toBe ( true ) ; expect ( source2Associations . length ) . toBe ( 4 ) ; expect ( source2Associations [ 0 ] ) . toBe ( false ) ; expect ( source2Associations [ 1 ] ) . toBe ( true ) ; expect ( source2Associations [ 2 ] ) . toBe ( false ) ; expect ( source2Associations [ 3 ] ) . toBe ( true ) ; container2 . shutdown ( ) ; container3 . shutdown ( ) ; container4 . shutdown ( ) ; delayAction ( 1000 , done , ( ) => { expect ( logger1 . associations . length ) . toBe ( 0 ) ; expect ( logger2 . associations . length ) . toBe ( 1 ) ; expect ( logger2 . associations [ 0 ] ) . toBe ( false ) ; expect ( logger3 . associations . length ) . toBe ( 0 ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } , 10 * TEST_TIMEOUT ) ; } ) ; } ) ;