UNPKG

@babblevoice/babble-drachtio-callmanager

Version:
1,182 lines (872 loc) 36.7 kB
const expect = require( "chai" ).expect const callmanager = require( "../../index.js" ) const call = require( "../../lib/call.js" ) const srf = require( "../mock/srf.js" ) const projectrtp = require( "@babblevoice/projectrtp" ).projectrtp const projectrtpmessage = require( "@babblevoice/projectrtp/lib/message.js" ) const net = require( "net" ) /* These DO NOT form part of our interface */ const clearcallmanager = require( "../../lib/callmanager.js" )._clear const callstore = require( "../../lib/store.js" ) after( async () => { await projectrtp.shutdown() } ) before( async () => { await projectrtp.run() } ) /* All call objects should be created by the framework - but we create them to test */ describe( "call object", function() { afterEach( function() { clearcallmanager() } ) beforeEach( function() { clearcallmanager() } ) it( "uas.newuac - create uas", async function() { const srfscenario = new srf.srfscenario() let newcallcalled = false srfscenario.options.em.on( "call.new", ( /*newcall*/ ) => { newcallcalled = true } ) const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( newcallcalled ).to.be.true expect( call ).to.have.property( "uuid" ).that.is.a( "string" ) expect( call ).to.have.property( "type" ).that.is.a( "string" ).to.equal( "uas" ) expect( call ).to.have.property( "state" ).that.is.a( "object" ) expect( call.state ).to.have.property( "trying" ).that.is.a( "boolean" ).to.be.false expect( call.state ).to.have.property( "ringing" ).that.is.a( "boolean" ).to.be.false expect( call.state ).to.have.property( "established" ).that.is.a( "boolean" ).to.be.false expect( call.state ).to.have.property( "canceled" ).that.is.a( "boolean" ).to.be.false expect( call.state ).to.have.property( "destroyed" ).that.is.a( "boolean" ).to.be.true expect( call ).to.have.property( "children" ) // Set - how do you test? expect( call ).to.have.property( "parent" ).that.is.undefined expect( call ).to.have.property( "vars" ).that.is.a( "object" ) expect( call ).to.have.property( "epochs" ).that.is.a( "object" ) expect( call.epochs ).to.have.property( "startat" ).that.is.a( "number" ) expect( call.epochs ).to.have.property( "answerat" ).that.is.a( "number" ) expect( call.epochs ).to.have.property( "endat" ).that.is.a( "number" ) expect( call ).to.have.property( "channels" ).that.is.a( "object" ) expect( call.channels ).to.have.property( "audio" ).to.be.undefined /* if uas */ expect( call ).to.have.property( "network" ).that.is.a( "object" ) expect( call.network ).to.have.property( "remote" ).that.is.a( "object" ) expect( call.network.remote ).to.have.property( "address" ).that.is.a( "string" ) expect( call.network.remote ).to.have.property( "port" ).that.is.a( "number" ) expect( call.network.remote ).to.have.property( "protocol" ).that.is.a( "string" ) expect( call ).to.have.property( "sip" ).that.is.a( "object" ) expect( call.sip ).to.have.property( "callid" ).that.is.a( "string" ) expect( call.sip ).to.have.property( "tags" ).that.is.a( "object" ) expect( call.sip.tags ).to.have.property( "remote" ).that.is.a( "string" ) expect( call.sip.tags ).to.have.property( "local" ).that.is.a( "string" ) expect( call.hangup_cause.reason ).to.equal( "NORMAL_CLEARING" ) expect( call.hangup_cause.sip ).to.equal( 487 ) expect( call.state.cleaned ).to.be.true /* defaults are loaded from call manager */ expect( call.options.preferedcodecs ).to.equal( "g722 ilbc pcmu pcma" ) } ) it( "uas.newuac - create uac", async function() { const srfscenario = new srf.srfscenario() const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) const child = await call.newuac( { "contact": "1000@dummy" } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 2, "storebyuuid": 2, "storebyentity": 0 } ) expect( child ).to.have.property( "uuid" ).that.is.a( "string" ) expect( child ).to.have.property( "type" ).that.is.a( "string" ).to.equal( "uac" ) expect( child ).to.have.property( "state" ).that.is.a( "object" ) expect( child.state ).to.have.property( "trying" ).that.is.a( "boolean" ).to.be.true expect( child.state ).to.have.property( "ringing" ).that.is.a( "boolean" ).to.be.false expect( child.state ).to.have.property( "established" ).that.is.a( "boolean" ).to.be.true /* becuase we awaited the newuax */ expect( child.state ).to.have.property( "canceled" ).that.is.a( "boolean" ).to.be.false expect( child.state ).to.have.property( "destroyed" ).that.is.a( "boolean" ).to.be.false expect( child ).to.have.property( "children" ) // Set - how do you test? expect( child ).to.have.property( "parent" ).that.is.a( "object" ) expect( child ).to.have.property( "vars" ).that.is.a( "object" ) expect( child ).to.have.property( "epochs" ).that.is.a( "object" ) expect( child.epochs ).to.have.property( "startat" ).that.is.a( "number" ) expect( child.epochs ).to.have.property( "answerat" ).that.is.a( "number" ) expect( child.epochs ).to.have.property( "endat" ).that.is.a( "number" ) expect( child ).to.have.property( "channels" ).that.is.a( "object" ) expect( child.channels ).to.have.property( "audio" ).that.is.a( "object" ) expect( child ).to.have.property( "sip" ).that.is.a( "object" ) /* if uac */ expect( child ).to.not.have.property( "source" ).that.is.a( "object" ) /* Hanging up child does not hangup parent */ await child.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) /* Hangup parent */ await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( child.state ).to.have.property( "destroyed" ).that.is.a( "boolean" ).to.be.true expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - create uac by entity no registrar", async function() { const srfscenario = new srf.srfscenario() const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) const child = await call.newuac( { "entity": { "uri": "1000@dummy" } } ) expect( child ).to.be.false expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( call.state.cleaned ).to.be.true } ) it( "uas.newuac - create uac by entity with registrar", async function() { const options = { "registrar": { "contacts": async ( /* entity */ ) => { return { "username": "1000", "realm": "dummy.com", "display": "Bob", "uri": "1000@dummy.com", "contacts": [ { "contact": "sip:1000@dummy.com:5060" }, { "contact": "sip:1000@dummy.com:5060;transport=blah" } ] } } } } const srfscenario = new srf.srfscenario( options ) const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) call._req.setparsedheader( "remote-party-id", { "name": "", "uri": "", "user": "0123456789", "host": "someotherrealm.com", "params": { "privacy": false }, "type": "callerid" } ) /* when newuacv returns, only one of the contacts should hacve succeeded */ const child = await call.newuac( { "entity": { "uri": "1000@dummy" } } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 2, "storebyuuid": 2, "storebyentity": 1 } ) const e = await child.entity expect( e.ccc ).to.equal( 1 ) /* mock */ let requestoptions = false child.parseallow( { get:() => { return "INVITE, UPDATE, OPTIONS" } } ) child._dialog.on( "request", ( options ) => requestoptions = options ) child.update() expect( requestoptions.method ).to.equal( "UPDATE" ) expect( requestoptions.headers[ "remote-party-id" ] ).to.equal( "\"0123456789\" <sip:0123456789@someotherrealm.com>;party=calling;screen=yes" ) /* simulate wire to propogate hangup */ await call.hangup( call.hangupcodes.NORMAL_CLEARING, false, "wire" ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( srfscenario.options.srf._createuaccount ).to.equal( 2 ) expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - create uac by entity with max limit and registrar", async function() { const options = { "registrar": { "contacts": async ( /* entity */ ) => { return { "username": "1000", "realm": "dummy.com", "display": "Bob", "uri": "1000@dummy.com", "contacts": [ { "contact": "sip:1000@dummy.com:5060" }, { "contact": "sip:1000@dummy.com:5060;transport=blah" } ] } } } } const srfscenario = new srf.srfscenario( options ) const inboundcall = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) const child = await inboundcall.newuac( { "entity": { "uri": "1000@dummy", "max": 1 } } ) const child2 = await call.newuac( { "entity": { "uri": "1000@dummy", "max": 1 } } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 2, "storebyuuid": 2, "storebyentity": 1 } ) const child3newcalls = [] await call.newuac( { "entity": { "uri": "1000@dummy" } }, { early:( c ) => child3newcalls.push( c ) } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 4, "storebyuuid": 4, "storebyentity": 1 } ) await Promise.all( [ child3newcalls[ 0 ].hangup(), child3newcalls[ 1 ].hangup() ] ) await inboundcall.hangup( call.hangupcodes.NORMAL_CLEARING, false, "wire" ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( srfscenario.options.srf._createuaccount ).to.equal( 4 ) expect( child2 ).to.be.false expect( child.state.cleaned ).to.be.true expect( child3newcalls[ 0 ].state.cleaned ).to.be.true expect( child3newcalls[ 1 ].state.cleaned ).to.be.true } ) it( "uas.newuac detatch from parent", async function() { const srfscenario = new srf.srfscenario() const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) const child = await call.newuac( { "contact": "1000@dummy" } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 2, "storebyuuid": 2, "storebyentity": 0 } ) child.detach() await child.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - 486", async function() { const srfscenario = new srf.srfscenario() srfscenario.oncreateUAC( () => { throw { "status": 486 } } ) const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) const children = [] const child = await call.newuac( { "contact": "1000@dummy" }, { "early": ( c ) => { children.push( c ) } } ) expect( children.length ).to.equal( 1 ) expect( children[ 0 ].hangup_cause.sip ).equal( 486 ) expect( children[ 0 ].hangup_cause.reason ).equal( "USER_BUSY" ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - timeout", async function() { const srfscenario = new srf.srfscenario() srfscenario.options.srf.newuactimeout = 20 /* longer than our uactimeout */ const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) const children = [] const child = await call.newuac( { "contact": "1000@dummy", "uactimeout": 10 }, { "early": ( c ) => { children.push( c ) } } ) /* overide default - very short */ expect( child.destroyed ).to.be.true expect( children.length ).to.equal( 1 ) expect( children[ 0 ].destroyed ).to.be.true expect( children[ 0 ].hangup_cause.sip ).equal( 408 ) expect( children[ 0 ].hangup_cause.reason ).equal( "REQUEST_TIMEOUT" ) expect( children[ 0 ].hangup_cause.src ).equal( "us" ) /* when we get here the clean up is on teh event loop */ await new Promise( resolve => setTimeout( resolve, 100 ) ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - child remote hangup", async function() { const srfscenario = new srf.srfscenario() const call = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) const child = await call.newuac( { "contact": "1000@dummy" } ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 2, "storebyuuid": 2, "storebyentity": 0 } ) expect( child.established ).to.be.true /* mock destroy from network */ child._dialog.destroy() await child.waitforhangup() expect( child.hangup_cause.sip ).equal( 487 ) expect( child.hangup_cause.reason ).equal( "NORMAL_CLEARING" ) expect( child.hangup_cause.src ).equal( "wire" ) expect( await callstore.stats() ).to.deep.include( { "storebycallid": 1, "storebyuuid": 1, "storebyentity": 0 } ) await call.hangup() expect( await callstore.stats() ).to.deep.include( { "storebycallid": 0, "storebyuuid": 0, "storebyentity": 0 } ) expect( child.state ).to.have.property( "destroyed" ).that.is.a( "boolean" ).to.be.true expect( call.state.cleaned ).to.be.true expect( child.state.cleaned ).to.be.true } ) it( "uas.newuac - new call event", async function() { const srfscenario = new srf.srfscenario() let eventhappened = false srfscenario.options.em.on( "call.new", ( c ) => { expect( c ).to.be.an.instanceof( call ) expect( c.type ).to.be.an( "string" ).to.be.equal( "uas" ) eventhappened = true } ) const inbound = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) expect( eventhappened ).to.be.true await inbound.hangup() } ) it( "uas.newuac - ringing event", async function() { const srfscenario = new srf.srfscenario() let eventhappened = false srfscenario.options.em.on( "call.ringing", ( c ) => { expect( c ).to.be.an.instanceof( call ) expect( c.type ).to.be.an( "string" ).to.be.equal( "uas" ) eventhappened = true } ) const c = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) /* signal ringing (this would normally come from the other side) */ c.ring() expect( eventhappened ).to.be.true await c.hangup() } ) it( "uas.newuac - destroyed event", async function() { const srfscenario = new srf.srfscenario() let eventhappened = false const c = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) c.on( "call.destroyed", ( /*c*/ ) => { eventhappened = true } ) expect( eventhappened ).to.be.false await c.hangup() expect( eventhappened ).to.be.true } ) it( "uas.newuac - answered and destroyed event", async function() { const srfscenario = new srf.srfscenario() let eventhappened = false srfscenario.options.em.on( "call.answered", ( c ) => { expect( c ).to.be.an.instanceof( call ) expect( c.type ).to.be.an( "string" ).to.be.equal( "uas" ) eventhappened = true } ) srfscenario.options.em.on( "call.destroyed", ( /*c*/ ) => { eventhappened = false } ) const c = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) /* signal answered (this could be called for a ivr or a bridged call) */ await c.answer() expect( eventhappened ).to.be.true await c.hangup() expect( eventhappened ).to.be.false } ) it( "uas.newuac - authed event", async function() { const options = { "userlookup": async ( username, realm ) => { return { "secret": "zanzibar", "username": username, "realm": realm } } } const srfscenario = new srf.srfscenario( options ) let eventhappened = false srfscenario.options.em.on( "call.authed", ( c ) => { expect( c ).to.be.an.instanceof( call ) expect( c.type ).to.be.an( "string" ).to.be.equal( "uas" ) eventhappened = true } ) const c = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { resolve( call ) } ) srfscenario.inbound() } ) let onsendcount = 0 /* mock - auth example from https://datatracker.ietf.org/doc/html/draft-smith-sipping-auth-examples-01 3.3*/ c._req.msg.uri = "sip:bob@biloxi.com" c._req.setparsedheader( "from", { "params": { "tag": "767sf76wew" }, "uri": "sip:bob@biloxi.com", "host": "biloxi.com" } ) c._res.onsend( ( code, /*msg*/ ) => { if( 407 == code ) { c._auth._nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093" c._auth._opaque = "5ccc069c403ebaf9f0171e9517f40e41" /* We would normally look here to get nonce and opaque - howevere we are frigging it */ //let request = msg.headers[ "Proxy-Authenticate" ] let request = "Digest realm=\"biloxi.com\", algorithm=MD5, qop=\"auth\", nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", opaque=\"5ccc069c403ebaf9f0171e9517f40e41\", stale=false" /* The items a uac will add */ request += ", username=\"bob\", nc=00000001,cnonce=\"0a4f113b\"," request += " uri=\"sip:bob@biloxi.com\"," request += " response=\"89eb0059246c02b2f6ee02c7961d5ea3\"" srfscenario.req.set( "Proxy-Authorization", request ) c._onauth( srfscenario.req, srfscenario.res ) } onsendcount++ } ) /* signal answered (this could be called for a ivr or a bridged call) */ let shouldnothappen = false try{ await c.auth() } catch( e ) { shouldnothappen = true } await c.hangup() expect( eventhappened ).to.be.true expect( shouldnothappen ).to.be.false expect( c.hangup_cause.reason ).to.equal( "NORMAL_CLEARING" ) expect( c.hangup_cause.src ).to.equal( "us" ) expect( c.state.destroyed ).to.equal( true ) expect( c.state.authed ).to.equal( true ) const e = await c.entity expect( e.username ).to.equal( "bob" ) expect( e.realm ).to.equal( "biloxi.com" ) expect( e.uri ).to.equal( "bob@biloxi.com" ) expect( onsendcount ).to.equal( 2 ) } ) it( "uas.newuac - auth failed", async function() { const options = { "userlookup": async ( username, realm ) => { return { "secret": "zanzibar", "username": username, "realm": realm } } } const srfscenario = new srf.srfscenario( options ) let eventhappened = false srfscenario.options.em.on( "call.authed.failed", ( c ) => { expect( c ).to.be.an.instanceof( call ) expect( c.type ).to.be.an( "string" ).to.be.equal( "uas" ) eventhappened = true } ) const c = await new Promise( ( resolve ) => { srfscenario.oncall( async ( call ) => { let onsendcount = 0 /* mock - auth example from https://datatracker.ietf.org/doc/html/draft-smith-sipping-auth-examples-01 3.3*/ call._req.msg.uri = "sip:bob@biloxi.com" call._req.setparsedheader( "from", { "params": { "tag": "767sf76wew" }, "uri": "sip:bob@biloxi.com", "host": "biloxi.com" } ) call._auth._nonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093" call._auth._opaque = "5ccc069c403ebaf9f0171e9517f40e41" call._res.onsend( ( code, msg ) => { if( 0 == onsendcount ) { let request = msg.headers[ "Proxy-Authenticate" ] /* The items a uac will add */ request += ", username=\"bob\", nc=00000001,cnonce=\"0a4f113b\"," request += " uri=\"sip:bob@biloxi.com\"," request += " response=\"89eb0059246c02b2f6ee02c7961d5ea\"" srfscenario.req.set( "Proxy-Authorization", request ) call._onauth( srfscenario.req, srfscenario.res ) } else if( 1 == onsendcount ) { resolve( call ) } onsendcount++ } ) /* signal answered (this could be called for a ivr or a bridged call) */ await call.auth() } ) srfscenario.inbound() } ) await c.waitforhangup() expect( eventhappened ).to.be.true expect( c.hangup_cause.reason ).to.equal( "FORBIDDEN" ) expect( c.hangup_cause.src ).to.equal( "us" ) expect( c.state.destroyed ).to.equal( true ) expect( c.state.authed ).to.equal( false ) } ) it( "uas.newuac - caller id set correctly", async function() { const srfscenario = new srf.srfscenario( {} ) let createuacoptions srfscenario.oncreateUAC( ( contact, options, /*callbacks*/ ) => { createuacoptions = options } ) const options = { "contact": "ourcontactstring", "late": true } await call.newuac( options, { "early": ( c ) => c.hangup() } ) expect( createuacoptions.headers[ "remote-party-id" ] ).to.equal( "\"0000000000\" <sip:0000000000@localhost.localdomain>;party=calling;screen=yes" ) expect( createuacoptions.late ).to.be.true } ) it( "uas.newuac - early callback is called", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } let earlycallbackcalled = false const c = await call.newuac( options, { "early": ( /*c*/ ) => earlycallbackcalled = true } ) c.hangup() expect( earlycallbackcalled ).to.be.true } ) it( "uas.newuac - confirmed callback is called", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } let earlycallbackcalled = false const c = await call.newuac( options, { "confirm": ( /*c*/ ) => earlycallbackcalled = true } ) c.hangup() expect( earlycallbackcalled ).to.be.true } ) it( "uas.newuac - simple update", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) /* mock */ c.parseallow( { get:() => { return "INVITE, UPDATE, OPTIONS" } } ) let requestoptions c._dialog.on( "request", ( options ) => requestoptions = options ) await c.update( { "callerid": { "name": "Kermit", "number": "kermy" } } ) c.hangup() expect( requestoptions.method ).to.equal( "UPDATE" ) expect( requestoptions.body ).to.be.a( "string" ) expect( requestoptions.headers[ "remote-party-id" ] ).to.equal( "\"Kermit\" <sip:kermy@localhost.localdomain>;party=calling;screen=yes" ) } ) it( "uas.newuac - simple update - but don't allow as not in allow", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) /* mock */ let requestoptions = false c._dialog.on( "request", ( options ) => requestoptions = options ) const returnedval = await c.update( { "remote": { "display": "Kermit", "realm": "muppetshow.com", "username": "kermy" } } ) c.hangup() expect( returnedval ).to.be.false expect( requestoptions ).to.be.false } ) it( "Test listen and emit event on call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = false c.on( "somerandomevent", ( ob ) => { if( "hello" === ob.vars.xinfo ) eventfired = true } ) c.vars.xinfo = "hello" c.emit( "somerandomevent" ) c.hangup() expect( eventfired ).to.be.true } ) it( "Test listen and emit event on and removealllisteners call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = 0 c.on( "somerandomevent", ( ob ) => { if( "hello" === ob.vars.xinfo ) eventfired++ } ) c.vars.xinfo = "hello" c.emit( "somerandomevent" ) c.removealllisteners( "somerandomevent" ) c.emit( "somerandomevent" ) c.hangup() expect( eventfired ).to.be.equal( 1 ) } ) it( "Test listen and emit event on and removealllisteners (none specified) call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = 0 c.on( "somerandomevent", ( ob ) => { if( "hello" === ob.vars.xinfo ) eventfired++ } ) c.vars.xinfo = "hello" c.emit( "somerandomevent" ) c.removealllisteners() c.emit( "somerandomevent" ) c.hangup() expect( eventfired ).to.be.equal( 1 ) } ) it( "Test listen and emit event on and off call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = 0 const ourcb = ( ob ) => { if( "hello" === ob.vars.xinfo ) eventfired++ } c.on( "somerandomevent", ourcb ) c.vars.xinfo = "hello" c.emit( "somerandomevent" ) c.off( "somerandomevent", ourcb ) c.emit( "somerandomevent" ) c.hangup() expect( eventfired ).to.be.equal( 1 ) } ) it( "Test listen and emit event once call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = 0 c.once( "somerandomevent", ( ob ) => { if( "hello" === ob.vars.xinfo ) eventfired++ } ) c.vars.xinfo = "hello" c.emit( "somerandomevent" ) c.emit( "somerandomevent" ) c.hangup() expect( eventfired ).to.be.equal( 1 ) } ) it( "Test listen and emit call.pick on call object", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) let eventfired = false c.on( "call.pick", ( /*callobject*/ ) => { eventfired = true } ) c.pick() c.hangup() expect( eventfired ).to.be.true } ) it( "Create a call and mock rtpengine and ensure we receive events", async function() { new srf.srfscenario( {} ) const rtpserver = await callmanager.projectrtp.proxy.listen() const connection = net.createConnection( 9002, "127.0.0.1" ) .on( "error", ( e ) => { console.error( e ) } ) connection.on( "connect", () => { /* announce our node */ connection.write( projectrtpmessage.createmessage( {"status":{"channel":{"available":5000,"current":0},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ) } ) const messagestate = projectrtpmessage.newstate() let msgid connection.on( "data", ( data ) => { projectrtpmessage.parsemessage( messagestate, data, ( msg ) => { if( "open" === msg.channel ) { msgid = msg.id setTimeout( () => connection.write( projectrtpmessage.createmessage( {"local":{"port":10008,"dtls":{"fingerprint":"Some fingerprint","enabled":false},"address":"192.168.0.141"},"id": msg.id, "uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"open","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ), 2 ) } else if( "close" == msg.channel ) { connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"record","file":"/tmp/voicemail/recording/03039cdb-1949-407d-91d6-15ba6894955c.wav","event":"finished.channelclosed","filesize":160684,"emailed":true,"transcription":"test recording for voicemail","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ) connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"close","reason":"requested","stats":{"in":{"mos":4.5,"count":586,"dropped":0,"skip":0},"out":{"count":303,"skip":0},"tick":{"meanus":124,"maxus":508,"count":597}},"status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ) } } ) } ) /* ensure we are connected */ await new Promise( ( resolve ) => setTimeout( () => resolve(), 100 ) ) /* this flow mimicks the flow associated with a voicemail being left */ const c = await call.newuac( { "contact": "ourcontactstring" } ) setTimeout( () => connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"play","event":"start","reason":"new","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ), 1 ) setTimeout( () => connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"play","event":"end","reason":"completed","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ), 10 ) c.channels.audio.play( { "files": [ { "wav": "/voicemail/greeting.wav", "alt": "greeting" } ] } ) let ev = await c.waitforanyevent( { "action": "play", "event": "end" } ) expect( ev.action ).to.equal( "play" ) expect( ev.event ).to.equal( "end" ) expect( ev.reason ).to.equal( "completed" ) setTimeout( () => connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"play","event":"start","reason":"new","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ) ) setTimeout( () => connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"play","event":"end","reason":"completed","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ) ) c.channels.audio.play( { "files": [ { "wav": "/voicemail/boing.wav", "alt": "" } ] } ) ev = await c.waitforanyevent( { "action": "play", "event": "end" } ) expect( ev.action ).to.equal( "play" ) expect( ev.event ).to.equal( "end" ) expect( ev.reason ).to.equal( "completed" ) setTimeout( () => connection.write( projectrtpmessage.createmessage( {"id": msgid,"uuid":"6d8ba7bb-44b9-4989-9aaf-5d938b496c49","action":"record","file":"/tmp/voicemail/recording/03039cdb-1949-407d-91d6-15ba6894955c.wav","event":"recording","status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d5c94"}} ) ), 5 ) c.channels.audio.record( { "file": "/voicemail/jhjhjgjhgjg.wav", "maxduration": 120 * 1000, "numchannels": 1, "email": { "to": "test@example.com", "foruser": "1000", "from": "012345789" }, "transcribe": true } ) setTimeout( () => { /* hangup from client */ c._onhangup( "wire" ) }, 50 ) ev = await c.waitforanyevent( { "action": "record", "event": /finished.*|\*/ }, 10000 ) expect( ev.action ).to.equal( "record" ) expect( ev.event ).to.equal( "finished.channelclosed" ) expect( ev.transcription ).to.equal( "test recording for voicemail" ) expect( ev.emailed ).to.be.true expect( ev.filesize ).to.equal( 160684 ) connection.destroy() await rtpserver.destroy() } ) it( "set get moh", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "late": true } const c = await call.newuac( options ) const s = { "loop": true, "files": [ { "wav": "some.wav" } ] } /* no default configured */ expect( c.moh ).to.be.undefined c.moh = s expect( c._moh.loop ).to.equal( true ) expect( c.moh.loop ).to.equal( true ) c._onhangup( "wire" ) } ) it( "overide caller id name", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "callerid": { "name": "Hello" } } const c = await call.newuac( options ) /* no default configured */ expect( c.options.headers[ "remote-party-id" ] ).to.equal( "\"Hello\" <sip:0000000000@localhost.localdomain>;party=calling;screen=yes" ) c._onhangup( "wire" ) } ) it( "overide caller id number", async function() { new srf.srfscenario( {} ) const options = { "contact": "ourcontactstring", "callerid": { "number": "012345789" } } const c = await call.newuac( options ) /* no default configured */ expect( c.options.headers[ "remote-party-id" ] ).to.equal( "\"012345789\" <sip:012345789@localhost.localdomain>;party=calling;screen=yes" ) c._onhangup( "wire" ) } ) } )