Merge pull request #141 from microsoft/gabog/LoadTestConfigFromAppSettings

Refactor config so we can run and debug tests from VS
This commit is contained in:
Monica Rivera 2020-10-02 10:30:27 -07:00 коммит произвёл GitHub
Родитель 2a9454d0d6 9fca12c800
Коммит 4ee52c8d61
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 403 добавлений и 424 удалений

5
.gitignore поставляемый
Просмотреть файл

@ -353,4 +353,7 @@ MigrationBackup/
# Python virtual env
venv/
virtualenv/
virtualenv/
# Local appsettings
appsettings.Development.json

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

@ -1,354 +1,349 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Bot.Connector.DirectLine;
using Newtonsoft.Json;
using SkillFunctionalTests.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace SkillFunctionalTests.Bot
{
public class TestBotClient
{
private const string OriginHeaderKey = "Origin";
private const string OriginHeaderValue = "https://carlos.test.com";
private readonly DirectLineClient _directLineClient;
private readonly IBotTestConfiguration _config;
private readonly string _user = $"dl_SkillTestUser-{ Guid.NewGuid() }";
private string _conversationId;
private readonly string _token;
private string _watermark;
public TestBotClient(IBotTestConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.DirectLineSecret))
{
throw new ArgumentNullException(nameof(config.DirectLineSecret));
}
if (string.IsNullOrEmpty(config.BotId))
{
throw new ArgumentNullException(nameof(config.BotId));
}
// Instead of generating a vanilla DirectLineClient with secret,
// we obtain a directLine token with the secrets and then we use
// that token to create the directLine client.
// What this gives us is the ability to pass TrustedOrigins when obtaining the token,
// which tests the enhanced authentication.
// This endpoint is unfortunately not supported by the directLine client which is
// why we add this custom code.
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, $"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.DirectLineSecret);
request.Content = new StringContent(JsonConvert.SerializeObject(new
{
User = new { Id = _user },
TrustedOrigins = new string[]
{
OriginHeaderValue
}
}), Encoding.UTF8, "application/json");
using (var response = client.SendAsync(request).GetAwaiter().GetResult())
{
if (response.IsSuccessStatusCode)
{
// Extract token from response
var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
_token = JsonConvert.DeserializeObject<DirectLineToken>(body).Token;
_conversationId = JsonConvert.DeserializeObject<DirectLineToken>(body).ConversationId;
// Create directLine client from token
_directLineClient = new DirectLineClient(_token);
// From now on, we'll add an Origin header in directLine calls, with
// the trusted origin we sent when acquiring the token as value.
_directLineClient.HttpClient.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
}
else
{
throw new Exception("Failed to acquire directLine token");
}
}
}
}
public Task<ResourceResponse> SendMessageAsync(string message, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(nameof(message)))
{
throw new ArgumentNullException(nameof(message));
}
// Create a message activity with the input text.
var messageActivity = new Activity
{
From = new ChannelAccount(_user),
Text = message,
Type = ActivityTypes.Message,
};
Console.WriteLine($"Sent to bot: {message}");
return SendActivityAsync(messageActivity, cancellationToken);
}
public async Task<ResourceResponse[]> SendMessagesAsync(IEnumerable<string> messages, CancellationToken cancellationToken = default(CancellationToken))
{
if (messages == null)
{
throw new ArgumentNullException(nameof(messages));
}
var resourceResponses = new List<ResourceResponse>();
foreach (var message in messages)
{
resourceResponses.Add(await SendMessageAsync(message, cancellationToken));
}
return resourceResponses.ToArray();
}
public async Task StartConversation(CancellationToken cancellationToken = default(CancellationToken))
{
var conversation = await _directLineClient.Conversations.StartConversationAsync(cancellationToken);
_conversationId = conversation?.ConversationId ?? throw new InvalidOperationException("Conversation cannot be null");
}
public Task<ResourceResponse> SendActivityAsync(Activity activity, CancellationToken cancellationToken = default(CancellationToken))
{
// Send the message activity to the bot.
return _directLineClient.Conversations.PostActivityAsync(_conversationId, activity, cancellationToken);
}
public async Task AssertReplyAsync(string expected, CancellationToken cancellationToken = default(CancellationToken))
{
var messages = await PollBotMessagesAsync(cancellationToken);
Console.WriteLine("Messages sent from bot:");
var messagesList = messages.ToList();
foreach (var m in messagesList.ToList())
{
Console.WriteLine($"Type:{m.Type}; Text:{m.Text}");
}
Assert.True(messagesList.Any(m => m.Type == ActivityTypes.Message && m.Text.Contains(expected, StringComparison.OrdinalIgnoreCase)), $"Expected: {expected}");
}
public async Task AssertReplyOneOf(IEnumerable<string> expected, CancellationToken cancellationToken = default(CancellationToken))
{
var messages = await PollBotMessagesAsync(cancellationToken);
Assert.Contains(messages, m => m.Type == ActivityTypes.Message && expected.Any(e => m.Text.Contains(e, StringComparison.OrdinalIgnoreCase)));
}
public async Task<IEnumerable<Activity>> PollBotMessagesAsync(CancellationToken cancellationToken = default)
{
// Even if we receive a cancellation token with a super long timeout,
// we set a cap on the max time this while loop can run
var maxCancellation = new CancellationTokenSource(TimeSpan.FromMinutes(2));
while (!cancellationToken.IsCancellationRequested && !maxCancellation.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(3));
var activities = await ReadBotMessagesAsync(cancellationToken);
if (activities != null && activities.Any())
{
return activities;
}
}
throw new Exception("No activities received");
}
public async Task<IEnumerable<Activity>> ReadBotMessagesAsync(CancellationToken cancellationToken = default)
{
// Retrieve activities from directLine
var activitySet = await _directLineClient.Conversations.GetActivitiesAsync(_conversationId, _watermark, cancellationToken);
_watermark = activitySet?.Watermark;
// Extract and return the activities sent from the bot.
return activitySet?.Activities?.Where(activity => activity.From.Id == _config.BotId);
}
public async Task SignInAndVerifyOAuthAsync(Activity oAuthCard, CancellationToken cancellationToken = default)
{
// We obtained what we think is an OAuthCard. Steps to follow:
// 1- Verify we have a sign in link
// 2- Get directLine session id and cookie
// 3- Follow sign in link but manually do each redirect
// 3.a- Detect the PostSignIn url in the redirect chain
// 3.b- Add cookie and challenge session id to post sign in link
// 4- Verify final redirect to token service ends up in success
// 1- Verify we have a sign in link in the activity
if (oAuthCard == null)
{
throw new Exception("OAuthCard is null");
}
else if (oAuthCard.Attachments == null)
{
throw new Exception("OAuthCard.Attachments = null");
}
var card = JsonConvert.DeserializeObject<SigninCard>(JsonConvert.SerializeObject(oAuthCard.Attachments.FirstOrDefault().Content));
if (card == null)
{
throw new Exception("No SignIn Card received in activity");
}
if (card.Buttons == null || !card.Buttons.Any())
{
throw new Exception("No buttons received in sign in card");
}
var signInUrl = card.Buttons[0].Value?.ToString();
if (string.IsNullOrEmpty(signInUrl) || !signInUrl.StartsWith("https://"))
{
throw new Exception($"Sign in url is empty or badly formatted. Url received: {signInUrl}");
}
// 2- Get directLine session id and cookie
var sessionInfo = await GetSessionInfoAsync();
// 3- Follow sign in link but manually do each redirect
// 4- Verify final redirect to token service ends up in success
await SignInAsync(sessionInfo, signInUrl);
}
private async Task SignInAsync(DirectLineSessionInfo directLineSession, string url)
{
var cookieContainer = new CookieContainer();
var handler = new HttpClientHandler()
{
AllowAutoRedirect = false,
CookieContainer = cookieContainer
};
// We have a sign in url, which will produce multiple HTTP 302 for redirects
// This will path
// token service -> other services -> auth provider -> token service (post sign in)-> response with token
// When we receive the post sign in redirect, we add the cookie passed in the directLine session info
// to test enhanced authentication. This in ther scenarios happens by itself since browsers do this for us.
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
while (!string.IsNullOrEmpty(url))
{
using (var response = await client.GetAsync(url))
{
var text = await response.Content.ReadAsStringAsync();
url = response.StatusCode == HttpStatusCode.Redirect
? response.Headers.Location.OriginalString
: null;
// Once the redirects are done, there is no more url. This means we
// did the entire loop
if (url == null)
{
Assert.True(response.IsSuccessStatusCode);
Assert.Contains("You are now signed in and can close this window.", text);
return;
}
// If this is the post sign in callback, add the cookie and code challenge
// so that the token service gets the verification.
// Here we are simulating what Webchat does along with the browser cookies.
if (url.StartsWith("https://token.botframework.com/api/oauth/PostSignInCallback"))
{
url += $"&code_challenge={ directLineSession.SessionId }";
cookieContainer.Add(directLineSession.Cookie);
}
}
}
throw new Exception("Sign in did not succeed. Set a breakpoint in TestBotClient.SignInAsync() to debug the redirect sequence.");
}
}
private async Task<DirectLineSessionInfo> GetSessionInfoAsync()
{
// Set up cookie container to obtain response cookie
var cookies = new CookieContainer();
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Connector.DirectLine;
using Newtonsoft.Json;
using SkillFunctionalTests.Configuration;
using Xunit;
namespace SkillFunctionalTests.Bot
{
public class TestBotClient
{
private const string OriginHeaderKey = "Origin";
private const string OriginHeaderValue = "https://carlos.test.com";
private readonly BotTestConfiguration _config;
private readonly DirectLineClient _directLineClient;
private readonly string _token;
private readonly string _user = $"dl_SkillTestUser-{Guid.NewGuid()}";
private string _conversationId;
private string _watermark;
public TestBotClient(BotTestConfiguration config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(config.DirectLineSecret))
{
throw new ArgumentNullException(nameof(config.DirectLineSecret));
}
if (string.IsNullOrEmpty(config.BotId))
{
throw new ArgumentNullException(nameof(config.BotId));
}
// Instead of generating a vanilla DirectLineClient with secret,
// we obtain a directLine token with the secrets and then we use
// that token to create the directLine client.
// What this gives us is the ability to pass TrustedOrigins when obtaining the token,
// which tests the enhanced authentication.
// This endpoint is unfortunately not supported by the directLine client which is
// why we add this custom code.
using (var client = new HttpClient())
{
var request = new HttpRequestMessage(HttpMethod.Post, "https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", config.DirectLineSecret);
request.Content = new StringContent(JsonConvert.SerializeObject(new
{
User = new { Id = _user },
TrustedOrigins = new[] { OriginHeaderValue }
}), Encoding.UTF8, "application/json");
using (var response = client.SendAsync(request).GetAwaiter().GetResult())
{
if (response.IsSuccessStatusCode)
{
// Extract token from response
var body = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
_token = JsonConvert.DeserializeObject<DirectLineToken>(body).Token;
_conversationId = JsonConvert.DeserializeObject<DirectLineToken>(body).ConversationId;
// Create directLine client from token
_directLineClient = new DirectLineClient(_token);
// From now on, we'll add an Origin header in directLine calls, with
// the trusted origin we sent when acquiring the token as value.
_directLineClient.HttpClient.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
}
else
{
throw new Exception("Failed to acquire directLine token");
}
}
}
}
public Task<ResourceResponse> SendMessageAsync(string message, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(nameof(message)))
{
throw new ArgumentNullException(nameof(message));
}
// Create a message activity with the input text.
var messageActivity = new Activity
{
From = new ChannelAccount(_user),
Text = message,
Type = ActivityTypes.Message,
};
Console.WriteLine($"Sent to bot: {message}");
return SendActivityAsync(messageActivity, cancellationToken);
}
public async Task<ResourceResponse[]> SendMessagesAsync(IEnumerable<string> messages, CancellationToken cancellationToken = default)
{
if (messages == null)
{
throw new ArgumentNullException(nameof(messages));
}
var resourceResponses = new List<ResourceResponse>();
foreach (var message in messages)
{
resourceResponses.Add(await SendMessageAsync(message, cancellationToken));
}
return resourceResponses.ToArray();
}
public async Task StartConversation(CancellationToken cancellationToken = default)
{
var conversation = await _directLineClient.Conversations.StartConversationAsync(cancellationToken);
_conversationId = conversation?.ConversationId ?? throw new InvalidOperationException("Conversation cannot be null");
}
public Task<ResourceResponse> SendActivityAsync(Activity activity, CancellationToken cancellationToken = default)
{
// Send the message activity to the bot.
return _directLineClient.Conversations.PostActivityAsync(_conversationId, activity, cancellationToken);
}
public async Task AssertReplyAsync(string expected, CancellationToken cancellationToken = default)
{
var messages = await PollBotMessagesAsync(cancellationToken);
Console.WriteLine("Messages sent from bot:");
var messagesList = messages.ToList();
foreach (var m in messagesList.ToList())
{
Console.WriteLine($"Type:{m.Type}; Text:{m.Text}");
}
Assert.True(messagesList.Any(m => m.Type == ActivityTypes.Message && m.Text.Contains(expected, StringComparison.OrdinalIgnoreCase)), $"Expected: {expected}");
}
public async Task AssertReplyOneOf(IEnumerable<string> expected, CancellationToken cancellationToken = default)
{
var messages = await PollBotMessagesAsync(cancellationToken);
Assert.Contains(messages, m => m.Type == ActivityTypes.Message && expected.Any(e => m.Text.Contains(e, StringComparison.OrdinalIgnoreCase)));
}
public async Task<IEnumerable<Activity>> PollBotMessagesAsync(CancellationToken cancellationToken = default)
{
// Even if we receive a cancellation token with a super long timeout,
// we set a cap on the max time this while loop can run
var maxCancellation = new CancellationTokenSource(TimeSpan.FromMinutes(2));
while (!cancellationToken.IsCancellationRequested && !maxCancellation.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(3));
var activities = await ReadBotMessagesAsync(cancellationToken);
if (activities != null && activities.Any())
{
return activities;
}
}
throw new Exception("No activities received");
}
public async Task<IEnumerable<Activity>> ReadBotMessagesAsync(CancellationToken cancellationToken = default)
{
// Retrieve activities from directLine
var activitySet = await _directLineClient.Conversations.GetActivitiesAsync(_conversationId, _watermark, cancellationToken);
_watermark = activitySet?.Watermark;
// Extract and return the activities sent from the bot.
return activitySet?.Activities?.Where(activity => activity.From.Id == _config.BotId);
}
public async Task SignInAndVerifyOAuthAsync(Activity oAuthCard, CancellationToken cancellationToken = default)
{
// We obtained what we think is an OAuthCard. Steps to follow:
// 1- Verify we have a sign in link
// 2- Get directLine session id and cookie
// 3- Follow sign in link but manually do each redirect
// 3.a- Detect the PostSignIn url in the redirect chain
// 3.b- Add cookie and challenge session id to post sign in link
// 4- Verify final redirect to token service ends up in success
// 1- Verify we have a sign in link in the activity
if (oAuthCard == null)
{
throw new Exception("OAuthCard is null");
}
if (oAuthCard.Attachments == null)
{
throw new Exception("OAuthCard.Attachments = null");
}
var card = JsonConvert.DeserializeObject<SigninCard>(JsonConvert.SerializeObject(oAuthCard.Attachments.FirstOrDefault().Content));
if (card == null)
{
throw new Exception("No SignIn Card received in activity");
}
if (card.Buttons == null || !card.Buttons.Any())
{
throw new Exception("No buttons received in sign in card");
}
var signInUrl = card.Buttons[0].Value?.ToString();
if (string.IsNullOrEmpty(signInUrl) || !signInUrl.StartsWith("https://"))
{
throw new Exception($"Sign in url is empty or badly formatted. Url received: {signInUrl}");
}
// 2- Get directLine session id and cookie
var sessionInfo = await GetSessionInfoAsync();
// 3- Follow sign in link but manually do each redirect
// 4- Verify final redirect to token service ends up in success
await SignInAsync(sessionInfo, signInUrl);
}
private async Task SignInAsync(DirectLineSessionInfo directLineSession, string url)
{
var cookieContainer = new CookieContainer();
var handler = new HttpClientHandler
{
CookieContainer = cookies
};
using (var client = new HttpClient(handler))
{
// Call the directLine session api, not supported by DirectLine client
const string getSessionUrl = "https://directline.botframework.com/v3/directline/session/getsessionid";
var request = new HttpRequestMessage(HttpMethod.Get, getSessionUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
// We want to add the Origins header to this client as well
client.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
using (var response = await client.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
// The directLine response that is relevant to us is the cookie and the session info.
// Extract cookie from cookies
var cookie = cookies.GetCookies(new Uri(getSessionUrl)).Cast<Cookie>().FirstOrDefault(c => c.Name == "webchat_session_v2");
// Extract session info from body
var body = await response.Content.ReadAsStringAsync();
var session = JsonConvert.DeserializeObject<DirectLineSession>(body);
return new DirectLineSessionInfo()
{
SessionId = session.SessionId,
Cookie = cookie
};
}
throw new Exception("Failed to obtain session id");
}
}
}
}
public class DirectLineToken
{
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("conversationId")]
public string ConversationId { get; set; }
}
public class DirectLineSession
{
[JsonProperty("sessionId")]
public string SessionId { get; set; }
}
public class DirectLineSessionInfo
{
public string SessionId { get; set; }
public Cookie Cookie { get; set; }
}
}
AllowAutoRedirect = false,
CookieContainer = cookieContainer
};
// We have a sign in url, which will produce multiple HTTP 302 for redirects
// This will path
// token service -> other services -> auth provider -> token service (post sign in)-> response with token
// When we receive the post sign in redirect, we add the cookie passed in the directLine session info
// to test enhanced authentication. This in ther scenarios happens by itself since browsers do this for us.
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
while (!string.IsNullOrEmpty(url))
{
using (var response = await client.GetAsync(url))
{
var text = await response.Content.ReadAsStringAsync();
url = response.StatusCode == HttpStatusCode.Redirect
? response.Headers.Location.OriginalString
: null;
// Once the redirects are done, there is no more url. This means we
// did the entire loop
if (url == null)
{
Assert.True(response.IsSuccessStatusCode);
Assert.Contains("You are now signed in and can close this window.", text);
return;
}
// If this is the post sign in callback, add the cookie and code challenge
// so that the token service gets the verification.
// Here we are simulating what Webchat does along with the browser cookies.
if (url.StartsWith("https://token.botframework.com/api/oauth/PostSignInCallback"))
{
url += $"&code_challenge={directLineSession.SessionId}";
cookieContainer.Add(directLineSession.Cookie);
}
}
}
throw new Exception("Sign in did not succeed. Set a breakpoint in TestBotClient.SignInAsync() to debug the redirect sequence.");
}
}
private async Task<DirectLineSessionInfo> GetSessionInfoAsync()
{
// Set up cookie container to obtain response cookie
var cookies = new CookieContainer();
var handler = new HttpClientHandler { CookieContainer = cookies };
using (var client = new HttpClient(handler))
{
// Call the directLine session api, not supported by DirectLine client
const string getSessionUrl = "https://directline.botframework.com/v3/directline/session/getsessionid";
var request = new HttpRequestMessage(HttpMethod.Get, getSessionUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
// We want to add the Origins header to this client as well
client.DefaultRequestHeaders.Add(OriginHeaderKey, OriginHeaderValue);
using (var response = await client.SendAsync(request))
{
if (response.IsSuccessStatusCode)
{
// The directLine response that is relevant to us is the cookie and the session info.
// Extract cookie from cookies
var cookie = cookies.GetCookies(new Uri(getSessionUrl)).Cast<Cookie>().FirstOrDefault(c => c.Name == "webchat_session_v2");
// Extract session info from body
var body = await response.Content.ReadAsStringAsync();
var session = JsonConvert.DeserializeObject<DirectLineSession>(body);
return new DirectLineSessionInfo
{
SessionId = session.SessionId,
Cookie = cookie
};
}
throw new Exception("Failed to obtain session id");
}
}
}
}
public class DirectLineToken
{
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("conversationId")]
public string ConversationId { get; set; }
}
public class DirectLineSession
{
[JsonProperty("sessionId")]
public string SessionId { get; set; }
}
public class DirectLineSessionInfo
{
public string SessionId { get; set; }
public Cookie Cookie { get; set; }
}
}

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

@ -2,33 +2,55 @@
// Licensed under the MIT License.
using System;
using Microsoft.Extensions.Configuration;
using Xunit.Sdk;
namespace SkillFunctionalTests.Configuration
{
public class BotTestConfiguration : IBotTestConfiguration
/// <summary>
/// Load test configuration from appsettings.json, appsettings.Development.json (if present) and environment variables.
/// </summary>
/// <remarks>
/// This class will initialize the test configuration from config or environment variables.
/// The values in appsettings will be overriden by the ones in appsettings.Development (if present) and those
/// will be also overriden by the values in environment variables (if set).
/// </remarks>
public class BotTestConfiguration
{
public BotTestConfiguration(string directLineSecret, string botId)
private const string DirectLineSecretKey = "DIRECTLINE";
private const string BotIdKey = "BOTID";
private static readonly IConfiguration _configuration;
/// <summary>
/// Static constructor to initialize IConfiguration only once.
/// </summary>
static BotTestConfiguration()
{
if (string.IsNullOrEmpty(botId))
{
throw new ArgumentNullException(nameof(botId));
}
if (string.IsNullOrEmpty(directLineSecret))
{
throw new ArgumentNullException(nameof(directLineSecret));
}
BotId = botId;
DirectLineSecret = directLineSecret;
_configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", true, true)
.AddEnvironmentVariables()
.Build();
}
public BotTestConfiguration()
{
// DirectLine secret
DirectLineSecret = _configuration[DirectLineSecretKey];
if (string.IsNullOrWhiteSpace(DirectLineSecret))
{
throw new XunitException($"Configuration setting '{DirectLineSecret}' not found.");
}
BotId = _configuration[BotIdKey];
if (string.IsNullOrWhiteSpace(BotId))
{
throw new ArgumentException($"Configuration setting '{BotIdKey}' not set.");
}
}
public string BotId { get; }
public string DirectLineSecret { get; }
}
}
}

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

@ -1,41 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using Xunit.Sdk;
namespace SkillFunctionalTests.Configuration
{
public class EnvironmentBotTestConfiguration : IBotTestConfiguration
{
private const string DirectLineSecretKey = "DIRECTLINE";
private const string BotIdKey = "BOTID";
public EnvironmentBotTestConfiguration(string directLineSecretKey, string botIdKey)
{
// Load config from environment variables
// DirectLine secret
DirectLineSecret = Environment.GetEnvironmentVariable(directLineSecretKey);
if (string.IsNullOrWhiteSpace(DirectLineSecret))
{
throw new XunitException($"Environment variable '{directLineSecretKey}' not found.");
}
BotId = Environment.GetEnvironmentVariable(botIdKey);
if (string.IsNullOrWhiteSpace(BotId))
{
throw new XunitException($"Environment variable '{botIdKey}' not found.");
}
}
public EnvironmentBotTestConfiguration()
: this(DirectLineSecretKey, BotIdKey)
{
}
public string BotId { get; }
public string DirectLineSecret { get; }
}
}

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

@ -1,12 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
namespace SkillFunctionalTests.Configuration
{
public interface IBotTestConfiguration
{
string BotId { get; }
string DirectLineSecret { get; }
}
}

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

@ -24,7 +24,7 @@ namespace FunctionalTests
// If the test takes more than two minutes, declare failure.
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var testBot = new TestBotClient(new EnvironmentBotTestConfiguration());
var testBot = new TestBotClient(new BotTestConfiguration());
await testBot.StartConversation(cancellationTokenSource.Token);
await testBot.SendMessageAsync("Hello", cancellationTokenSource.Token);

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

@ -18,7 +18,7 @@ namespace FunctionalTests
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var testBot = new TestBotClient(new EnvironmentBotTestConfiguration());
var testBot = new TestBotClient(new BotTestConfiguration());
await testBot.StartConversation(cancellationTokenSource.Token);
await testBot.SendMessageAsync("Hi", cancellationTokenSource.Token);
@ -32,7 +32,7 @@ namespace FunctionalTests
{
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var testBot = new TestBotClient(new EnvironmentBotTestConfiguration());
var testBot = new TestBotClient(new BotTestConfiguration());
await testBot.StartConversation(cancellationTokenSource.Token);
await testBot.SendMessageAsync("Hi", cancellationTokenSource.Token);

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

@ -8,6 +8,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.Bot.Connector.DirectLine" Version="3.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.8" />
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.21" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
@ -18,4 +20,10 @@
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

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

@ -0,0 +1,4 @@
{
"DIRECTLINE": "",
"BOTID": ""
}