Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions libraries/Microsoft.Bot.Builder/Adapters/TestAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public async Task ProcessActivityAsync(Activity activity, BotCallbackHandler cal
activity.LocalTimestamp = DateTimeOffset.Now;
}

using (var context = new TurnContext(this, activity))
using (var context = CreateTurnContext(activity))
{
await RunPipelineAsync(context, callback, cancellationToken).ConfigureAwait(false);
}
Expand Down Expand Up @@ -373,7 +373,7 @@ public Task CreateConversationAsync(string channelId, BotCallbackHandler callbac
var update = Activity.CreateConversationUpdateActivity();
update.ChannelId = channelId;
update.Conversation = new ConversationAccount { Id = Guid.NewGuid().ToString("n") };
using (var context = new TurnContext(this, (Activity)update))
using (var context = CreateTurnContext((Activity)update))
{
return callback(context, cancellationToken);
}
Expand Down Expand Up @@ -589,11 +589,9 @@ public virtual Task<TokenResponse> GetUserTokenAsync(ITurnContext turnContext, A
Token = token,
});
}
else
{
// not found
return Task.FromResult<TokenResponse>(null);
}

// not found
return Task.FromResult<TokenResponse>(null);
}

/// <summary>Attempts to retrieve the token for a user that's in a login flow, using the bot's AppCredentials.
Expand Down Expand Up @@ -883,6 +881,16 @@ public Task<TokenResponse> ExchangeTokenAsync(ITurnContext turnContext, AppCrede
return Task.FromResult<TokenResponse>(null);
}
}

/// <summary>
/// Creates the turn context for the adapter.
/// </summary>
/// <param name="activity">An <see cref="Activity"/> instance for the turn.</param>
/// <returns>A <see cref="TurnContext"/> instance to be used by the adapter.</returns>
protected virtual TurnContext CreateTurnContext(Activity activity)
{
return new TurnContext(this, activity);
}

private void Enqueue(Activity activity)
{
Expand Down
8 changes: 4 additions & 4 deletions libraries/Microsoft.Bot.Builder/ShowTypingMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, Cance
Task typingTask = null;
try
{
// If the incoming activity is a MessageActivity, start a timer to periodically send the typing activity.
if (IsNotRunningAsSkill(turnContext) && turnContext.Activity.Type == ActivityTypes.Message)
// Start a timer to periodically send the typing activity (bots running as skills should not send typing activity)
if (!IsSkillBot(turnContext) && turnContext.Activity.Type == ActivityTypes.Message)
{
// do not await task - we want this to run in the background and we will cancel it when its done
typingTask = SendTypingAsync(turnContext, _delay, _period, cts.Token);
Expand All @@ -83,10 +83,10 @@ public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, Cance
}
}

private static bool IsNotRunningAsSkill(ITurnContext turnContext)
private static bool IsSkillBot(ITurnContext turnContext)
{
return turnContext.TurnState.Get<IIdentity>(BotAdapter.BotIdentityKey) is ClaimsIdentity claimIdentity
&& SkillValidation.IsSkillClaim(claimIdentity.Claims) == false;
&& SkillValidation.IsSkillClaim(claimIdentity.Claims);
}

private static async Task SendTypingAsync(ITurnContext turnContext, TimeSpan delay, TimeSpan period, CancellationToken cancellationToken)
Expand Down
159 changes: 159 additions & 0 deletions tests/Microsoft.Bot.Builder.Tests/ShowTypingMiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Adapters;
using Microsoft.Bot.Connector.Authentication;
using Microsoft.Bot.Schema;
using Xunit;

namespace Microsoft.Bot.Builder.Tests
{
public class ShowTypingMiddlewareTests
{
[Fact]
public void ConstructorValidation()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new ShowTypingMiddleware(-100, 1000));
Assert.Throws<ArgumentOutOfRangeException>(() => new ShowTypingMiddleware(100, -1000));
}

[Fact]
public async Task OneSecondInterval()
{
var adapter = new TestAdapter(TestAdapter.CreateConversation("One_Second_Interval"))
.Use(new ShowTypingMiddleware(100, 1000));

await new TestFlow(adapter, async (context, cancellationToken) =>
{
await Task.Delay(TimeSpan.FromMilliseconds(2800), cancellationToken);

// note the ShowTypingMiddleware should not cause the Responded flag to be set
Assert.False(context.Responded);

await context.SendActivityAsync("Message sent after delay", cancellationToken: cancellationToken);
await Task.CompletedTask;
})
.Send("foo")
.AssertReply(ValidateTypingActivity, "check typing activity")
.AssertReply(ValidateTypingActivity, "check typing activity")
.AssertReply(ValidateTypingActivity, "check typing activity")
.AssertReply("Message sent after delay")
.StartTestAsync();
}

[Fact]
public async Task ContextCompletesBeforeTypingInterval()
{
var adapter = new TestAdapter(TestAdapter.CreateConversation("Context_Completes_Before_Typing_Interval"))
.Use(new ShowTypingMiddleware(100, 5000));

await new TestFlow(adapter, async (context, cancellationToken) =>
{
await Task.Delay(TimeSpan.FromMilliseconds(2000), cancellationToken);
await context.SendActivityAsync("Message sent after delay", cancellationToken: cancellationToken);
await Task.CompletedTask;
})
.Send("foo")
.AssertReply(ValidateTypingActivity, "check typing activity")
.AssertReply("Message sent after delay")
.StartTestAsync();
}

[Fact]
public async Task ImmediateResponseFiveSecondInterval()
{
var adapter = new TestAdapter(TestAdapter.CreateConversation("ImmediateResponse_5SecondInterval"))
.Use(new ShowTypingMiddleware(2000, 5000));

await new TestFlow(adapter, async (context, cancellationToken) =>
{
await context.SendActivityAsync("Message sent after delay", cancellationToken: cancellationToken);
await Task.CompletedTask;
})
.Send("foo")
.AssertReply("Message sent after delay")
.StartTestAsync();
}

[Fact]
public async Task ImmediateResponseWhenRunningAsSkill()
{
var adapter = new SkillTestAdapter(TestAdapter.CreateConversation("1_Second_Interval"))
.Use(new ShowTypingMiddleware(100, 1000));

await new TestFlow(adapter, async (context, cancellationToken) =>
{
await Task.Delay(TimeSpan.FromMilliseconds(2800), cancellationToken);

// note the ShowTypingMiddleware should not cause the Responded flag to be set
Assert.False(context.Responded);

await context.SendActivityAsync("Message sent after delay", cancellationToken: cancellationToken);
await Task.CompletedTask;
})
.Send("foo")
.AssertReply("Message sent after delay")
.StartTestAsync();
}

[Fact]
public void ZeroFrequency()
{
try
{
_ = new TestAdapter(TestAdapter.CreateConversation("ZeroFrequency"))
.Use(new ShowTypingMiddleware(-100, 0));
}
catch (Exception ex)
{
Assert.IsType<ArgumentOutOfRangeException>(ex);
}
}

private void ValidateTypingActivity(IActivity obj)
{
var activity = obj.AsTypingActivity();
if (activity != null)
{
return;
}

throw new Exception("Activity was not of type TypingActivity");
}

/// <summary>
/// A helper TestAdapter that injects skill claims in the turn so we can test skill use cases.
/// </summary>
private class SkillTestAdapter : TestAdapter
{
// An App ID for a parent bot.
private static readonly string _parentBotId = Guid.NewGuid().ToString();

// An App ID for a skill bot.
private static readonly string _skillBotId = Guid.NewGuid().ToString();

public SkillTestAdapter(ConversationReference conversation = null)
: base(conversation)
{
}

protected override TurnContext CreateTurnContext(Activity activity)
{
// Get the default turnContext from the base.
var turnContext = base.CreateTurnContext(activity);

// Create a skill ClaimsIdentity and put it in TurnState so SkillValidation.IsSkillClaim() returns true.
var claimsIdentity = new ClaimsIdentity();
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0"));
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _parentBotId));
claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _skillBotId));
turnContext.TurnState.Add(BotIdentityKey, claimsIdentity);

return turnContext;
}
}
}
}
Loading