Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
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
11 changes: 11 additions & 0 deletions libraries/Microsoft.Bot.Builder.Azure/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Runtime.CompilerServices;

// Allows us to access some internal methods from the Microsoft.Bot.Builder.Azure.Tests unit tests so we don't have to use reflection and we get compile checks.
#if SIGNASSEMBLY
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Azure.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
#else
[assembly: InternalsVisibleTo("Microsoft.Bot.Builder.Azure.Tests")]
#endif
19 changes: 16 additions & 3 deletions libraries/Microsoft.Bot.Builder.Azure/AzureBlobStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class AzureBlobStorage : IStorage
private readonly CloudStorageAccount _storageAccount;
private readonly string _containerName;
private int _checkforContainerExistance;
private readonly CloudBlobClient _blobClient;

/// <summary>
/// Initializes a new instance of the <see cref="AzureBlobStorage"/> class.
Expand Down Expand Up @@ -86,6 +87,18 @@ public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AzureBlobStorage"/> class.
/// </summary>
/// <param name="storageAccount">Azure CloudStorageAccount instance.</param>
/// <param name="containerName">Name of the Blob container where entities will be stored.</param>
/// <param name="blobClient">Custom implementation of CloudBlobClient.</param>
internal AzureBlobStorage(CloudStorageAccount storageAccount, string containerName, CloudBlobClient blobClient)
: this(storageAccount, containerName, JsonSerializer)
{
_blobClient = blobClient;
}

/// <summary>
/// Deletes entity blobs from the configured container.
/// </summary>
Expand All @@ -100,7 +113,7 @@ public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName
throw new ArgumentNullException(nameof(keys));
}

var blobClient = _storageAccount.CreateCloudBlobClient();
var blobClient = _blobClient ?? _storageAccount.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference(_containerName);
foreach (var key in keys)
{
Expand All @@ -124,7 +137,7 @@ public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName
throw new ArgumentNullException(nameof(keys));
}

var blobClient = _storageAccount.CreateCloudBlobClient();
var blobClient = _blobClient ?? _storageAccount.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference(_containerName);

var items = new Dictionary<string, object>();
Expand Down Expand Up @@ -168,7 +181,7 @@ public AzureBlobStorage(CloudStorageAccount storageAccount, string containerName
throw new ArgumentNullException(nameof(changes));
}

var blobClient = _storageAccount.CreateCloudBlobClient();
var blobClient = _blobClient ?? _storageAccount.CreateCloudBlobClient();
var blobContainer = blobClient.GetContainerReference(_containerName);

// this should only happen once - assuming this is a singleton
Expand Down
271 changes: 214 additions & 57 deletions tests/Microsoft.Bot.Builder.Azure.Tests/AzureBlobStorageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,100 +2,257 @@
// Licensed under the MIT License.

using System;
using System.Reflection;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage.Core;
using Moq;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Microsoft.Bot.Builder.Azure.Tests
{
public class AzureBlobStorageTests : BlobStorageBaseTests, IAsyncLifetime
public class AzureBlobStorageTests
{
private readonly string _testName;
protected const string ConnectionString = @"UseDevelopmentStorage=true";
protected const string ContainerName = "containername";

public AzureBlobStorageTests(ITestOutputHelper testOutputHelper)
private AzureBlobStorage _blobStorage;
private Mock<CloudBlob> _mockBlob;
private Mock<CloudBlobContainer> _mockContainer;
private Mock<CloudBlobClient> _mockBlobClient;
private Mock<CloudStorageAccount> _mockAccount;
private Mock<CloudBlockBlob> _mockBlockBlob;

[Fact]
public void ConstructorValidation()
{
var helper = (TestOutputHelper)testOutputHelper;
// Should work.
var storageAccount = CloudStorageAccount.Parse(ConnectionString);

var test = (ITest)helper.GetType().GetField("test", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(helper);
Assert.NotNull(new AzureBlobStorage(ConnectionString, ContainerName));
Assert.NotNull(new AzureBlobStorage(storageAccount, ContainerName));
Assert.NotNull(new AzureBlobStorage(storageAccount, ContainerName, new JsonSerializer()));
Assert.NotNull(new AzureBlobStorage(storageAccount, ContainerName, new CloudBlobClient(new Uri("http://mytest"))));

_testName = test.TestCase.TestMethod.Method.Name;
// No JsonSerializer. Should throw.
Assert.Throws<ArgumentNullException>(() => new AzureBlobStorage(storageAccount, ContainerName, jsonSerializer: null));

if (StorageEmulatorHelper.CheckEmulator())
{
CloudStorageAccount.Parse(ConnectionString)
.CreateCloudBlobClient()
.GetContainerReference(ContainerName)
.DeleteIfExistsAsync().ConfigureAwait(false);
}
// No storageAccount. Should throw.
Assert.Throws<ArgumentNullException>(() => new AzureBlobStorage(null, ContainerName, new JsonSerializer()));

// No containerName. Should throw.
Assert.Throws<ArgumentNullException>(() => new AzureBlobStorage(storageAccount, null, new JsonSerializer()));
}

protected override string ContainerName
[Fact]
public async Task DeleteAsyncNullKeysFailure()
{
get
{
var containerName = _testName.ToLower().Replace("_", string.Empty);
NameValidator.ValidateContainerName(containerName);
return containerName;
}
_blobStorage = new AzureBlobStorage(ConnectionString, ContainerName);

await Assert.ThrowsAsync<ArgumentNullException>(() => _blobStorage.DeleteAsync(null));
}

public Task InitializeAsync()
[Fact]
public async Task DeleteAsyncEmptyKeysFailure()
{
return Task.CompletedTask;
var keys = new[] { string.Empty };

InitStorage();

await Assert.ThrowsAsync<ArgumentNullException>(() => _blobStorage.DeleteAsync(keys, CancellationToken.None));
}

public async Task DisposeAsync()
[Fact]
public async Task DeleteAsyncTwoKeys()
{
if (StorageEmulatorHelper.CheckEmulator())
{
await CloudStorageAccount.Parse(ConnectionString)
.CreateCloudBlobClient()
.GetContainerReference(ContainerName)
.DeleteIfExistsAsync().ConfigureAwait(false);
}
var keys = new[] { "key1", "key2" };

InitStorage();

await _blobStorage.DeleteAsync(keys, CancellationToken.None);

_mockBlobClient.Verify(x => x.GetContainerReference(It.IsAny<string>()), Times.Once);
_mockContainer.Verify(x => x.GetBlobReference(It.IsAny<string>()), Times.Exactly(2));
_mockBlob.Verify(x => x.DeleteIfExistsAsync(It.IsAny<CancellationToken>()), Times.Exactly(2));
}

[Fact]
public void BlobStorageParamTest()
public async Task ReadAsyncNullKeysFailure()
{
if (StorageEmulatorHelper.CheckEmulator())
{
Assert.Throws<FormatException>(() => new AzureBlobStorage("123", ContainerName));
_blobStorage = new AzureBlobStorage(ConnectionString, ContainerName);

Assert.Throws<ArgumentNullException>(() =>
new AzureBlobStorage((CloudStorageAccount)null, ContainerName));
await Assert.ThrowsAsync<ArgumentNullException>(() => _blobStorage.ReadAsync(null));
}

Assert.Throws<ArgumentNullException>(() =>
new AzureBlobStorage((string)null, ContainerName));
[Fact]
public async Task ReadAsyncOneKey()
{
var keys = new[] { "key1" };

InitStorage();

Assert.Throws<ArgumentNullException>(() =>
new AzureBlobStorage((CloudStorageAccount)null, null));
var items = await _blobStorage.ReadAsync(keys, CancellationToken.None);

Assert.Throws<ArgumentNullException>(() => new AzureBlobStorage((string)null, null));
_mockBlobClient.Verify(x => x.GetContainerReference(It.IsAny<string>()), Times.Once);
_mockContainer.Verify(x => x.GetBlobReference(It.IsAny<string>()), Times.Once);
_mockBlob.Verify(
x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>()),
Times.Once);
Assert.Equal(1, items.Count);
Assert.True(items.ContainsKey(keys[0]));
}

Assert.Throws<ArgumentNullException>(() =>
new AzureBlobStorage(CloudStorageAccount.Parse(ConnectionString), ContainerName, (JsonSerializer)null));
}
[Fact]
public async Task ReadAsyncContinueOnNotFoundException()
{
var keys = new[] { "key1" };
var result = new RequestResult { HttpStatusCode = 404 };

InitStorage();

_mockBlob.Setup(x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>())).Throws(new StorageException(result, "exception thrown", new Exception()));

var items = await _blobStorage.ReadAsync(keys, CancellationToken.None);

_mockBlobClient.Verify(x => x.GetContainerReference(It.IsAny<string>()), Times.Once);
_mockContainer.Verify(x => x.GetBlobReference(It.IsAny<string>()), Times.Once);
_mockBlob.Verify(
x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>()),
Times.Once);
Assert.Equal(0, items.Count);
}

protected override IStorage GetStorage(bool typeNameHandlingNone = false)
[Fact]
public async Task ReadAsyncContinueOnAggregateException()
{
var storageAccount = CloudStorageAccount.Parse(ConnectionString);
if (typeNameHandlingNone)
var keys = new[] { "key1" };
var result = new RequestResult { HttpStatusCode = 404 };

InitStorage();

_mockBlob.Setup(x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>())).Throws(new AggregateException(new StorageException(result, "exception thrown", new Exception())));

var items = await _blobStorage.ReadAsync(keys, CancellationToken.None);

_mockBlobClient.Verify(x => x.GetContainerReference(It.IsAny<string>()), Times.Once);
_mockContainer.Verify(x => x.GetBlobReference(It.IsAny<string>()), Times.Once);
_mockBlob.Verify(
x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>()),
Times.Once);
Assert.Equal(0, items.Count);
}

[Fact]
public async Task WriteAsyncNullChangesFailure()
{
_blobStorage = new AzureBlobStorage(ConnectionString, ContainerName);

await Assert.ThrowsAsync<ArgumentNullException>(() => _blobStorage.WriteAsync(null));
}

[Fact]
public async Task WriteAsyncTwoChanges()
{
var changes = new Dictionary<string, object>
{
return new AzureBlobStorage(
storageAccount,
ContainerName,
new JsonSerializer() { TypeNameHandling = TypeNameHandling.None });
}
{ "key1", "value1" },
{ "key2", "value2" }
};

InitStorage();

await _blobStorage.WriteAsync(changes, CancellationToken.None);

_mockBlobClient.Verify(x => x.GetContainerReference(It.IsAny<string>()), Times.Once);
_mockContainer.Verify(x => x.GetBlockBlobReference(It.IsAny<string>()), Times.Exactly(2));
_mockBlockBlob.Verify(
x => x.UploadFromStreamAsync(
It.IsAny<MultiBufferMemoryStream>(),
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>()),
Times.Exactly(2));
}

[Fact]
public async Task WriteAsyncFailure()
{
var changes = new Dictionary<string, object>
{
{ "key1", "value1" },
{ "key2", "value2" }
};
var result = new RequestResult { HttpStatusCode = 400 };

InitStorage();

_mockBlockBlob.Setup(x => x.UploadFromStreamAsync(
It.IsAny<MultiBufferMemoryStream>(),
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>())).Throws(new StorageException(result, "exception thrown", new Exception()));

await Assert.ThrowsAsync<StorageException>(() => _blobStorage.WriteAsync(changes, CancellationToken.None));
}

private void InitStorage()
{
Stream stream = new MemoryStream(Encoding.ASCII.GetBytes("{\"Id\":0,\"Topic\":\"car\"}"));

_mockBlob = new Mock<CloudBlob>(new Uri("http://test/myaccount/blob"));
_mockBlob.Setup(x => x.DeleteIfExistsAsync(It.IsAny<CancellationToken>()));
_mockBlob.Setup(x => x.OpenReadAsync(
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>())).Returns(Task.FromResult(stream));

_mockBlockBlob = new Mock<CloudBlockBlob>(new Uri("http://test/myaccount/blob"));
_mockBlockBlob.Setup(x => x.UploadFromStreamAsync(
It.IsAny<MultiBufferMemoryStream>(),
It.IsAny<AccessCondition>(),
It.IsAny<BlobRequestOptions>(),
It.IsAny<OperationContext>(),
It.IsAny<CancellationToken>()));

_mockContainer = new Mock<CloudBlobContainer>(new Uri("https://testuri.com"));
_mockContainer.Setup(x => x.GetBlobReference(It.IsAny<string>())).Returns(_mockBlob.Object);
_mockContainer.Setup(x => x.GetBlockBlobReference(It.IsAny<string>())).Returns(_mockBlockBlob.Object);

_mockBlobClient = new Mock<CloudBlobClient>(new Uri("https://testuri.com"), null);
_mockBlobClient.Setup(x => x.GetContainerReference(It.IsAny<string>())).Returns(_mockContainer.Object);

_mockAccount = new Mock<CloudStorageAccount>(new StorageCredentials("accountName", "S2V5VmFsdWU=", "key"), false);

return new AzureBlobStorage(storageAccount, ContainerName);
_blobStorage = new AzureBlobStorage(_mockAccount.Object, ContainerName, _mockBlobClient.Object);
}
}
}