Merged branch with changes for last reviews

This commit is contained in:
Melanie Mendoza 2018-08-24 16:11:22 -07:00
Родитель d9a5e46f41 0f80d01b94
Коммит 17b2f2f785
7 изменённых файлов: 409 добавлений и 105 удалений

Просмотреть файл

@ -21,6 +21,10 @@ class BaseMiddleware
throw new Error('toSendable not implemented') throw new Error('toSendable not implemented')
class TextMiddleware extends BaseMiddleware class TextMiddleware extends BaseMiddleware
# TextMiddleware doesn't use invokes currently, so just return null
handleInvoke: (invokeEvent, connector) ->
return null
toReceivable: (activity) -> toReceivable: (activity) ->
@robot.logger.info "#{LogPrefix} TextMiddleware toReceivable" @robot.logger.info "#{LogPrefix} TextMiddleware toReceivable"
address = activity.address address = activity.address
@ -56,6 +60,22 @@ class TextMiddleware extends BaseMiddleware
supportsAuth: () -> supportsAuth: () ->
return false return false
# Sends an error message back to the user if authorization isn't supported for the
# channel or prepares and sends the message to hubot for reception
maybeReceive: (activity, connector, authEnabled) ->
# Return an error to the user if the message channel doesn't support authorization
# and authorization is enabled
if authEnabled
@robot.logger.info "#{LogPrefix} Authorization isn\'t supported
for the channel error"
text = "Authorization isn't supported for this channel"
payload = @constructErrorResponse(activity, text)
@send(connector, payload)
else
event = @toReceivable activity
if event?
@robot.receive event
# Sends the payload to the bot framework messaging channel # Sends the payload to the bot framework messaging channel
send: (connector, payload) -> send: (connector, payload) ->
if !Array.isArray(payload) if !Array.isArray(payload)

Просмотреть файл

@ -27,11 +27,12 @@ class BotFrameworkAdapter extends Adapter
@enableAuth = false @enableAuth = false
if process.env.HUBOT_TEAMS_ENABLE_AUTH? and process.env.HUBOT_TEAMS_ENABLE_AUTH == 'true' if process.env.HUBOT_TEAMS_ENABLE_AUTH? and process.env.HUBOT_TEAMS_ENABLE_AUTH == 'true'
@enableAuth = true @enableAuth = true
@initialAdmins = process.env.HUBOT_TEAMS_INITIAL_ADMINS
robot.logger.info "#{LogPrefix} Adapter loaded. Using appId #{@appId}" robot.logger.info "#{LogPrefix} Adapter loaded. Using appId #{@appId}"
# Initial Admins should be required when auth is enabled # Initial Admins should be required when auth is enabled
if @enableAuth if @enableAuth
if process.env.HUBOT_TEAMS_INITIAL_ADMINS if @initialAdmins?
# If there isn't a list of authorized users in the brain, populate # If there isn't a list of authorized users in the brain, populate
# it with admins from the environment variable # it with admins from the environment variable
if robot.brain.get("authorizedUsers") is null if robot.brain.get("authorizedUsers") is null
@ -50,22 +51,15 @@ class BotFrameworkAdapter extends Adapter
@connector.onEvent (events, cb) => @onBotEvents events, cb @connector.onEvent (events, cb) => @onBotEvents events, cb
@connector.onInvoke (events, cb) => @menuCardInvoke events, cb @connector.onInvoke (events, cb) => @onInvoke events, cb
# If the command for the invoke doesn't need user input, handle the command # Handles the invoke and passes an event to be handled, if needed
# normally. If it does need user input, return a prompt for user input. onInvoke: (invokeEvent, cb) ->
menuCardInvoke: (invokeEvent, cb) ->
console.log(invokeEvent)
middleware = @using(invokeEvent.source) middleware = @using(invokeEvent.source)
payload = middleware.maybeConstructUserInputPrompt(invokeEvent) event = middleware.handleInvoke(invokeEvent, @connector)
if payload == null if event != null
invokeEvent.text = invokeEvent.value.hubotMessage @handleActivity(event)
delete invokeEvent.value
@handleActivity(invokeEvent)
else
middleware.sendPayload(@robot, @connector, payload)
return
using: (name) -> using: (name) ->
MiddlewareClass = Middleware.middlewareFor(name) MiddlewareClass = Middleware.middlewareFor(name)
@ -86,26 +80,10 @@ class BotFrameworkAdapter extends Adapter
# If authorization isn't supported by the activity source, use # If authorization isn't supported by the activity source, use
# the text middleware, otherwise use the Teams middleware # the text middleware, otherwise use the Teams middleware
if not middleware.supportsAuth() if not middleware.supportsAuth()
# Return an error to the user if the message channel doesn't support authorization middleware.maybeReceive(activity, @connector, @enableAuth)
# and authorization is enabled
if @enableAuth
@robot.logger.info "#{LogPrefix} Authorization isn\'t supported
for the channel error"
text = "Authorization isn't supported for this channel"
payload = middleware.constructErrorResponse(activity, text)
middleware.send(@robot.adapter.connector, payload)
return
else
event = middleware.toReceivable activity
if event?
@robot.receive event
else else
middleware.toReceivable activity, @enableAuth, @appId, @appPassword, \ middleware.maybeReceive(activity, @connector, @enableAuth, \
(event, response) => @appId, @appPassword)
if response?
middleware.send(@robot.adapter.connector, response)
else if event?
@robot.receive event
send: (context, messages...) -> send: (context, messages...) ->
@robot.logger.info "#{LogPrefix} send" @robot.logger.info "#{LogPrefix} send"
@ -118,7 +96,7 @@ class BotFrameworkAdapter extends Adapter
activity = context.user.activity activity = context.user.activity
middleware = @using(activity.source) middleware = @using(activity.source)
payload = middleware.toSendable(context, msg) payload = middleware.toSendable(context, msg)
middleware.send(@robot.adapter.connector, payload) middleware.send(@connector, payload)
run: -> run: ->
@robot.router.post @endpoint, @connector.listen() @robot.router.post @endpoint, @connector.listen()

Просмотреть файл

@ -42,6 +42,18 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
@robot.logger.info("#{LogPrefix} Restricting tenants to \ @robot.logger.info("#{LogPrefix} Restricting tenants to \
#{JSON.stringify(@allowedTenants)}") #{JSON.stringify(@allowedTenants)}")
# If the invoke is due to a command that needs user input, sends a user input card
# otherwise, returns an event to handle, if needed, or null
handleInvoke: (invokeEvent, connector) ->
payload = @maybeConstructUserInputPrompt(invokeEvent)
if payload != null
@sendPayload(connector, payload)
return null
else
invokeEvent.text = invokeEvent.value.hubotMessage
delete invokeEvent.value
return invokeEvent
toReceivable: (activity, authEnabled, appId, appPassword, cb) -> toReceivable: (activity, authEnabled, appId, appPassword, cb) ->
@robot.logger.info "#{LogPrefix} toReceivable" @robot.logger.info "#{LogPrefix} toReceivable"
@ -68,7 +80,6 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
activity?.address?.conversation?.id, (err, chatMembers) => activity?.address?.conversation?.id, (err, chatMembers) =>
if err if err
return return
# Return with unauthorized error as true if auth is enabled and the user who sent # Return with unauthorized error as true if auth is enabled and the user who sent
# the message is not authorized # the message is not authorized
if authEnabled if authEnabled
@ -132,6 +143,14 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
supportsAuth: () -> supportsAuth: () ->
return true return true
maybeReceive: (activity, connector, authEnabled, appId, appPassword) ->
@toReceivable activity, authEnabled, appId, appPassword,
(event, response) =>
if response?
@send(connector, response)
else if event?
@robot.receive event
# Combines payloads then sends the combined payload to MS Teams # Combines payloads then sends the combined payload to MS Teams
send: (connector, payload) -> send: (connector, payload) ->
# The message is from Teams, so combine hubot responses # The message is from Teams, so combine hubot responses
@ -140,18 +159,18 @@ class MicrosoftTeamsMiddleware extends BaseMiddleware
if @robot.brain.get("justReceivedResponse") is null if @robot.brain.get("justReceivedResponse") is null
@robot.brain.set("teamsResponse", payload) @robot.brain.set("teamsResponse", payload)
@robot.brain.set("justReceivedResponse", true) @robot.brain.set("justReceivedResponse", true)
setTimeout(@sendPayload, 100, @robot, connector, @robot.brain.get("teamsResponse")) setTimeout(@sendPayload.bind(this), 100, connector, @robot.brain.get("teamsResponse"))
else else
@combineResponses(@robot.brain.get("teamsResponse"), payload) @combineResponses(@robot.brain.get("teamsResponse"), payload)
sendPayload: (robot, connector, payload) -> sendPayload: (connector, payload) ->
if !Array.isArray(payload) if !Array.isArray(payload)
payload = [payload] payload = [payload]
connector.send payload, (err, _) -> connector.send payload, (err, _) =>
if err if err
throw err throw err
robot.brain.remove("teamsResponse") @robot.brain.remove("teamsResponse")
robot.brain.remove("justReceivedResponse") @robot.brain.remove("justReceivedResponse")
# Combines the text and attachments of multiple hubot messages sent in succession. # Combines the text and attachments of multiple hubot messages sent in succession.
# Most of the first received response is kept, and the text and attachments of # Most of the first received response is kept, and the text and attachments of

Просмотреть файл

@ -77,6 +77,41 @@ describe 'BaseMiddleware', ->
).to.throw() ).to.throw()
describe 'TextMiddleware', -> describe 'TextMiddleware', ->
describe 'handleInvoke', ->
robot = null
event = null
connector = null
beforeEach ->
robot = new MockRobot
event =
type: 'invoke'
text: 'Bot do something and tell User about it'
agent: 'tests'
source: '*'
address:
conversation:
id: "conversation-id"
bot:
id: "bot-id"
user:
id: "user-id"
name: "user-name"
connector =
send: () -> {}
it 'should return null', ->
# Setup
middleware = new TextMiddleware(robot)
# Action
result = null
expect(() ->
result = middleware.handleInvoke(event, connector)
).to.not.throw()
# Assert
expect(result).to.be.null
describe 'toReceivable', -> describe 'toReceivable', ->
robot = null robot = null
event = null event = null
@ -191,6 +226,85 @@ describe 'TextMiddleware', ->
# Action and Assert # Action and Assert
expect(middleware.supportsAuth()).to.be.false expect(middleware.supportsAuth()).to.be.false
describe 'maybeReceive', ->
robot = null
middleware = null
authEnabled = true
connector = null
event = null
beforeEach ->
robot = new MockRobot
middleware = new TextMiddleware(robot)
connector =
send: (payload, cb) ->
robot.brain.set("payload", payload)
authEnabled = true
event =
type: 'message'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
agent: 'tests'
source: 'msteams'
entities: [
type: "mention"
text: "<at>Bot</at>"
mentioned:
id: "bot-id"
name: "bot-name"
,
type: "mention"
text: "<at>User</at>"
mentioned:
id: "user-id"
name: "user-name"
]
sourceEvent:
tenant:
id: "tenant-id"
address:
conversation:
id: "19:conversation-id"
bot:
id: "bot-id"
user:
id: "user-id"
name: "user-name"
aadObjectId: "eight888-four-4444-fore-twelve121212"
userPrincipalName: "user-UPN"
serviceUrl: 'url-serviceUrl/a-url'
it 'should return authorization not supported error when auth is enabled', ->
# Setup
# Action
expect(() ->
middleware.maybeReceive(event, connector, authEnabled)
).to.not.throw()
# Assert
resultEvent = robot.brain.get("event")
expect(resultEvent).to.be.null
resultPayload = robot.brain.get("payload")
expect(resultPayload).to.be.a('Array')
expect(resultPayload.length).to.eql 1
expect(resultPayload[0].text).to.eql "Authorization isn't supported for this channel"
it 'should work when auth is not enabled', ->
# Setup
authEnabled = false
# Action
expect(() ->
middleware.maybeReceive(event, connector, authEnabled)
).to.not.throw()
# Assert
resultEvent = robot.brain.get("event")
expect(resultEvent).to.not.be.null
expect(resultEvent).to.be.a('Object')
resultPayload = robot.brain.get("payload")
expect(resultPayload).to.be.null
describe 'constructErrorResponse', -> describe 'constructErrorResponse', ->
it 'return a proper payload with the text of the error', -> it 'return a proper payload with the text of the error', ->
# Setup # Setup

Просмотреть файл

@ -62,67 +62,67 @@ describe 'Main Adapter', ->
'authorized_user@email.la': true 'authorized_user@email.la': true
} }
describe 'Test Authorization Not Suppported Error', -> # describe 'Test Authorization Not Supported Error', ->
robot = null # robot = null
adapter = null # adapter = null
event = null # event = null
beforeEach -> # beforeEach ->
process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'an-1_20@em.ail,authorized_user@email.la' # process.env.HUBOT_TEAMS_INITIAL_ADMINS = 'an-1_20@em.ail,authorized_user@email.la'
process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id' # process.env.BOTBUILDER_APP_ID = 'botbuilder-app-id'
process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password' # process.env.BOTBUILDER_APP_PASSWORD = 'botbuilder-app-password'
process.env.HUBOT_TEAMS_ENABLE_AUTH = 'true' # process.env.HUBOT_TEAMS_ENABLE_AUTH = 'true'
robot = new MockRobot # robot = new MockRobot
adapter = BotFrameworkAdapter.use(robot) # adapter = BotFrameworkAdapter.use(robot)
robot.adapter = adapter # robot.adapter = adapter
adapter.connector.send = (payload, cb) -> # adapter.connector.send = (payload, cb) ->
robot.brain.set("payload", payload) # robot.brain.set("payload", payload)
event = # event =
type: 'message' # type: 'message'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it' # text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
agent: 'tests' # agent: 'tests'
source: 'msteams' # source: 'msteams'
entities: [ # entities: [
type: "mention" # type: "mention"
text: "<at>Bot</at>" # text: "<at>Bot</at>"
mentioned: # mentioned:
id: "bot-id" # id: "bot-id"
name: "bot-name" # name: "bot-name"
, # ,
type: "mention" # type: "mention"
text: "<at>User</at>" # text: "<at>User</at>"
mentioned: # mentioned:
id: "user-id" # id: "user-id"
name: "user-name" # name: "user-name"
] # ]
attachments: [] # attachments: []
sourceEvent: # sourceEvent:
tenant: # tenant:
id: "tenant-id" # id: "tenant-id"
address: # address:
conversation: # conversation:
id: "19:conversation-id" # id: "19:conversation-id"
bot: # bot:
id: "bot-id" # id: "bot-id"
user: # user:
id: "user-id" # id: "user-id"
name: "user-name" # name: "user-name"
aadObjectId: "eight888-four-4444-fore-twelve121212" # aadObjectId: "eight888-four-4444-fore-twelve121212"
userPrincipalName: "user-UPN" # userPrincipalName: "user-UPN"
serviceUrl: 'url-serviceUrl/a-url' # serviceUrl: 'url-serviceUrl/a-url'
it 'should return authorization not supported error for non-Teams channels', -> # it 'should return authorization not supported error for non-Teams channels', ->
# Setup # # Setup
event.source = 'authorization-not-supported-source' # event.source = 'authorization-not-supported-source'
# Action # # Action
expect(() -> # expect(() ->
adapter.handleActivity(event) # adapter.handleActivity(event)
).to.not.throw() # ).to.not.throw()
# Assert # # Assert
result = robot.brain.get("payload") # result = robot.brain.get("payload")
expect(result).to.be.a('Array') # expect(result).to.be.a('Array')
expect(result.length).to.eql 1 # expect(result.length).to.eql 1
expect(result[0].text).to.eql "Authorization isn't supported for this channel" # expect(result[0].text).to.eql "Authorization isn't supported for this channel"

Просмотреть файл

@ -26,9 +26,6 @@ class TeamsChatConnector
] ]
callback false, members callback false, members
send: (payload) ->
robot.brain.set("payload", payload)
BotBuilderTeams = { BotBuilderTeams = {
TeamsChatConnector: TeamsChatConnector TeamsChatConnector: TeamsChatConnector

Просмотреть файл

@ -3,10 +3,103 @@ expect = chai.expect
{ TextMessage, Message, User } = require 'hubot' { TextMessage, Message, User } = require 'hubot'
rewiremock = require('rewiremock/node').default rewiremock = require('rewiremock/node').default
MockRobot = require './mock-robot' MockRobot = require './mock-robot'
BotFrameworkAdapter = require '../src/adapter'
MicrosoftTeamsMiddleware = require '../src/msteams-middleware' MicrosoftTeamsMiddleware = require '../src/msteams-middleware'
describe 'MicrosoftTeamsMiddleware', -> describe 'MicrosoftTeamsMiddleware', ->
describe 'handleInvoke', ->
rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams'))
BotBuilderTeams = null
robot = null
event = null
options = null
teamsChatConnector = null
authEnabled = false
cb = () -> {}
beforeEach ->
rewiremock.enable()
BotBuilderTeams = require 'botbuilder-teams'
MicrosoftTeamsMiddleware = require '../src/msteams-middleware'
robot = new MockRobot
options = {
appId: 'botframework-app-id'
appPassword: 'botframework-app-password'
}
teamsChatConnector = new BotBuilderTeams.TeamsChatConnector(options)
teamsChatConnector.send = (payload) ->
robot.brain.set("payload", payload)
event =
type: 'invoke'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
value:
hubotMessage: 'gho list (teams|repos|members)'
source: 'msteams'
entities: [
type: "mention"
text: "<at>Bot</at>"
mentioned:
id: "bot-id"
name: "bot-name"
,
type: "mention"
text: "<at>User</at>"
mentioned:
id: "user-id"
name: "user-name"
]
sourceEvent:
tenant:
id: "tenant-id"
address:
conversation:
isGroup: 'true'
conversationType: 'channel'
id: "19:conversation-id"
bot:
id: "bot-id"
user:
id: "user-id"
name: "user-name"
aadObjectId: 'eight888-four-4444-fore-twelve121212'
userPrincipalName: 'em@ai.l'
afterEach ->
rewiremock.disable()
it 'should send user input card for specific queries', ->
# Setup
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# Action
result = null
expect(() ->
result = teamsMiddleware.handleInvoke(event, teamsChatConnector)
).to.not.throw()
# Assert
expect(result).to.be.null
response = robot.brain.get("payload")
expect(response).to.be.a('Array')
expect(response.length).to.eql(2)
it 'should return event to handle', ->
# Setup
event.value.hubotMessage = "gho list public repos"
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
# Action
result = null
expect(() ->
result = teamsMiddleware.handleInvoke(event, teamsChatConnector)
).to.not.throw()
# Assert
expect(result).to.not.be.null
expect(result).to.be.a('Object')
response = robot.brain.get("payload")
expect(response).to.be.null
describe 'toReceivable', -> describe 'toReceivable', ->
rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams')) rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams'))
@ -834,6 +927,89 @@ describe 'MicrosoftTeamsMiddleware', ->
# Action and Assert # Action and Assert
expect(teamsMiddleware.supportsAuth()).to.be.true expect(teamsMiddleware.supportsAuth()).to.be.true
describe 'maybeReceive', ->
rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams'))
BotBuilderTeams = null
robot = null
teamsMiddleware = null
connector = null
authEnabled = true
event = null
payload = null
cb = () -> {}
beforeEach ->
rewiremock.enable()
MicrosoftTeamsMiddleware = require '../src/msteams-middleware'
BotBuilderTeams = require 'botbuilder-teams'
robot = new MockRobot
robot.brain.set("authorizedUsers", {
'an-1_20@em.ail': true
'em@ai.l': false
'user-UPN': true
})
teamsMiddleware = new MicrosoftTeamsMiddleware(robot)
connector = new BotBuilderTeams.TeamsChatConnector({
appId: 'a-app-id'
appPassword: 'a-app-password'
})
connector.send = (payload, cb) ->
robot.brain.set("payload", payload)
authEnabled = true
event =
type: 'message'
text: '<at>Bot</at> do something <at>Bot</at> and tell <at>User</at> about it'
agent: 'tests'
source: 'msteams'
entities: [
type: "mention"
text: "<at>Bot</at>"
mentioned:
id: "bot-id"
name: "bot-name"
,
type: "mention"
text: "<at>User</at>"
mentioned:
id: "user-id"
name: "user-name"
]
attachments: []
sourceEvent:
tenant:
id: "tenant-id"
address:
conversation:
isGroup: 'true'
conversationType: 'channel'
id: "19:conversation-id"
bot:
id: "bot-id"
user:
id: "user-id"
name: "user-name"
aadObjectId: 'eight888-four-4444-fore-twelve121212'
userPrincipalName: 'em@ai.l'
afterEach ->
rewiremock.disable()
it 'hubot can receive a message', ->
# Setup
# Action
expect(() ->
teamsMiddleware.maybeReceive(event, connector, authEnabled, \
'a-app-id', 'a-app-password')
).to.not.throw()
# Assert
resultEvent = robot.brain.get("event")
expect(resultEvent).to.not.be.null
expect(resultEvent).to.be.a('Object')
describe 'send', -> describe 'send', ->
rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams')) rewiremock('botbuilder-teams').with(require('./mock-botbuilder-teams'))
BotBuilderTeams = null BotBuilderTeams = null
@ -1106,7 +1282,7 @@ describe 'MicrosoftTeamsMiddleware', ->
# Action # Action
expect(() -> expect(() ->
teamsMiddleware.sendPayload(robot, connector, payload) teamsMiddleware.sendPayload(connector, payload)
).to.not.throw() ).to.not.throw()
# Assert # Assert
@ -1135,7 +1311,7 @@ describe 'MicrosoftTeamsMiddleware', ->
# Action # Action
expect(() -> expect(() ->
teamsMiddleware.sendPayload(robot, connector, payload) teamsMiddleware.sendPayload(connector, payload)
).to.not.throw() ).to.not.throw()
# Assert # Assert