Skip to content
This repository was archived by the owner on Aug 18, 2021. It is now read-only.

Commit 04a2848

Browse files
committed
feat(ServiceBus): added unit tests
Added some tests that should test the general behavior of ServiceBus.
1 parent f4899e4 commit 04a2848

File tree

6 files changed

+486
-23
lines changed

6 files changed

+486
-23
lines changed

src/Liquid.Activation/Worker/LightWorker.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public abstract class LightWorker : LightBackgroundTask, ILightWorker
2525
protected readonly static Dictionary<MethodInfo, QueueAttribute> _queues = new Dictionary<MethodInfo, QueueAttribute>();
2626
protected readonly static Dictionary<MethodInfo, TopicAttribute> _topics = new Dictionary<MethodInfo, TopicAttribute>();
2727
private readonly List<string> _inputValidationErrors = new List<string>();
28-
protected ILightTelemetry Telemetry { get; } = Workbench.Instance.Telemetry != null ? (ILightTelemetry)Workbench.Instance.Telemetry.CloneService() : null;
28+
protected ILightTelemetry Telemetry => Workbench.Instance.Telemetry;
2929
protected ILightCache Cache => Workbench.Instance.Cache;
3030
//Instance of CriticHandler to inject on the others classes
3131
private readonly CriticHandler _criticHandler = new CriticHandler();

src/Liquid.OnAzure/Liquid.OnAzure.csproj

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
<DebugType>full</DebugType>
1616
<DebugSymbols>true</DebugSymbols>
1717
</PropertyGroup>
18+
<ItemGroup>
19+
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
20+
</ItemGroup>
1821
<ItemGroup>
1922
<Compile Remove="Telemetry\AppInsightsTelemetryMiddleware.cs" />
2023
</ItemGroup>
@@ -29,6 +32,10 @@
2932
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.3" />
3033
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
3134
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
35+
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113">
36+
<PrivateAssets>all</PrivateAssets>
37+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
38+
</PackageReference>
3239
<PackageReference Include="WindowsAzure.Storage" Version="9.3.3" />
3340
</ItemGroup>
3441
<ItemGroup>

src/Liquid.OnAzure/MessageBuses/ServiceBus.cs

+169-20
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,125 @@
1-
using Liquid.Base.Interfaces;
1+
// Copyright (c) Avanade Inc. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using System.Threading.Tasks;
8+
using Liquid.Activation;
29
using Liquid.Domain;
310
using Liquid.Domain.Base;
4-
using Liquid.Activation;
511
using Liquid.Runtime.Configuration.Base;
612
using Liquid.Runtime.Telemetry;
713
using Microsoft.Azure.ServiceBus;
8-
using System;
9-
using System.Collections.Generic;
10-
using System.Reflection;
11-
using System.Text;
12-
using System.Threading.Tasks;
1314

1415
namespace Liquid.OnAzure
1516
{
1617
/// <summary>
17-
/// Implementation of the communication component between queues and topics of the Azure, this class is specific to azure
18+
/// Defines an object capable of creating instances of <see cref="IQueueClient"/>.
19+
/// </summary>
20+
public interface IQueueClientFactory
21+
{
22+
/// <summary>
23+
/// Creates a new instance of <see cref="IQueueClient"/>.
24+
/// </summary>
25+
/// <param name="connectionString">The connection string for the client.</param>
26+
/// <param name="queueName">The name of the queue.</param>
27+
/// <param name="receiveMode">The receive mode that the client will connect to the queue.</param>
28+
/// <returns>A new instance of <see cref="IQueueClient"/>.</returns>
29+
IQueueClient CreateClient(string connectionString, string queueName, ReceiveMode receiveMode);
30+
}
31+
32+
/// <summary>
33+
/// Defines an object capable of creating instances of <see cref="ISubscriptionClient"/>.
34+
/// </summary>
35+
public interface ISubscriptionClientFactory
36+
{
37+
/// <summary>
38+
/// Creates a new instance of <see cref="ISubscriptionClient"/>.
39+
/// </summary>
40+
/// <param name="connectionString">The connection string for the client.</param>
41+
/// <param name="topicName">The name of the topic to connect to.</param>
42+
/// <param name="subscriptionName">Identifies the subscription to this topic.</param>
43+
/// <param name="receiveMode">The receive mode that the client will connect to the queue.</param>
44+
/// <returns>A new instance of <see cref="ISubscriptionClient"/>.</returns>
45+
ISubscriptionClient CreateClient(string connectionString, string topicName, string subscriptionName, ReceiveMode receiveMode);
46+
}
47+
48+
/// <summary>
49+
/// Configuration source for <see cref="ServiceBus"/>.
50+
/// </summary>
51+
// TODO: should remove this class once we move to .NET configuration system
52+
public interface IServiceBusConfigurationProvider
53+
{
54+
/// <summary>
55+
/// Gets the configuration for a <see cref="ServiceBus"/>.
56+
/// </summary>
57+
/// <returns>
58+
/// The current configuration for a service bus.
59+
/// </returns>
60+
ServiceBusConfiguration GetConfiguration();
61+
62+
/// <summary>
63+
/// Gets the configuration for a <see cref="ServiceBus"/>.
64+
/// </summary>
65+
/// <param name="connectionName">
66+
/// Identifies which connection should be retrieved from the file.
67+
/// </param>
68+
/// <returns>
69+
/// The current configuration for a service bus.
70+
/// </returns>
71+
ServiceBusConfiguration GetConfiguration(string connectionName);
72+
}
73+
74+
/// <summary>
75+
/// Implementation of the communication component between queues and topics of the Azure, this class is specific to azure.
1876
/// </summary>
1977
public class ServiceBus : LightWorker, IWorkbenchService
2078
{
79+
/// <summary>
80+
/// Factory used to create a <see cref="IQueueClient"/>.
81+
/// </summary>
82+
private readonly IQueueClientFactory _queueClientFactory = new DefaultQueueClientFactory();
83+
84+
/// <summary>
85+
/// Factory used to create a <see cref="ISubscriptionClient"/>.
86+
/// </summary>
87+
private readonly ISubscriptionClientFactory _subscriptionClientFactory = new DefaultSubscriptionClientFactory();
88+
89+
/// <summary>
90+
/// Service that retrives a <see cref="ServiceBusConfiguration"/>.
91+
/// </summary>
92+
private readonly IServiceBusConfigurationProvider _configurationProvider = new DefaultServiceBusConfigurationProvider();
93+
94+
/// <summary>
95+
/// Initializes a new instance of the <see cref="ServiceBus"/> class.
96+
/// </summary>
97+
public ServiceBus()
98+
{
99+
}
100+
101+
/// <summary>
102+
/// Initializes a new instance of the <see cref="ServiceBus"/> class.
103+
/// </summary>
104+
/// <param name="queueClientFactory">
105+
/// Dependency. Used to obtain new instances of a <see cref="IQueueClient"/>.
106+
/// </param>
107+
/// <param name="subscriptionClientFactory">
108+
/// Dependency. Used to obtain new instances of a <see cref="ISubscriptionClient"/>.
109+
/// </param>
110+
/// <param name="configurationProvider">
111+
/// Dependency. Used to retrieve a configuration for this class.
112+
/// </param>
113+
public ServiceBus(
114+
IQueueClientFactory queueClientFactory,
115+
ISubscriptionClientFactory subscriptionClientFactory,
116+
IServiceBusConfigurationProvider configurationProvider)
117+
{
118+
_queueClientFactory = queueClientFactory ?? throw new ArgumentNullException(nameof(queueClientFactory));
119+
_subscriptionClientFactory = subscriptionClientFactory ?? throw new ArgumentNullException(nameof(subscriptionClientFactory));
120+
_configurationProvider = configurationProvider ?? throw new ArgumentNullException(nameof(configurationProvider));
121+
}
122+
21123
/// <summary>
22124
/// Implementation of the start process queue and process topic. It must be called parent before start processes.
23125
/// </summary>
@@ -36,22 +138,22 @@ public override void Initialize()
36138
/// <returns>StringConnection of the ServiceBus</returns>
37139
private string GetConnection<T>(KeyValuePair<MethodInfo, T> item)
38140
{
39-
MethodInfo method = item.Key;
40-
string connectionKey = GetKeyConnection(method);
41-
ServiceBusConfiguration config = null;
141+
var method = item.Key;
142+
var connectionKey = GetKeyConnection(method);
143+
144+
ServiceBusConfiguration config;
42145
if (string.IsNullOrEmpty(connectionKey)) // Load specific settings if provided
43146
{
44-
config = LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}");
147+
config = _configurationProvider.GetConfiguration();//LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}");
45148
}
46149
else
47150
{
48-
config = LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}_{connectionKey}");
151+
config = _configurationProvider.GetConfiguration(connectionKey);//LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}_{connectionKey}");
49152
}
50153

51154
return config.ConnectionString;
52155
}
53156

54-
55157
/// <summary>
56158
/// If an error occurs in the processing, this method going to called
57159
/// </summary>
@@ -80,7 +182,7 @@ public void ProcessQueue()
80182
int takeQuantity = queue.Value.TakeQuantity;
81183

82184
//Register Trace on the telemetry
83-
QueueClient queueReceiver = new QueueClient(GetConnection(queue), queueName, receiveMode);
185+
var queueReceiver = _queueClientFactory.CreateClient(GetConnection(queue), queueName, receiveMode);
84186

85187
//Register the method to process receive message
86188
//The RegisterMessageHandler is validate for all register exist on the queue, without need loop for items
@@ -96,7 +198,7 @@ public void ProcessQueue()
96198
{
97199
Exception moreInfo = new Exception($"Exception reading message from queue {queueName}. See inner exception for details. Message={exRegister.Message}", exRegister);
98200
//Use the class instead of interface because tracking exceptions directly is not supposed to be done outside AMAW (i.e. by the business code)
99-
((LightTelemetry)Workbench.Instance.Telemetry).TrackException(moreInfo);
201+
(Workbench.Instance.Telemetry as LightTelemetry)?.TrackException(moreInfo);
100202

101203
//If there is a error , set DeadLetter on register
102204
if (queueReceiver.ReceiveMode == ReceiveMode.PeekLock)
@@ -114,7 +216,7 @@ await queueReceiver.DeadLetterAsync(message.SystemProperties.LockToken,
114216
{
115217
Exception moreInfo = new Exception($"Error setting up queue consumption from service bus. See inner exception for details. Message={exception.Message}", exception);
116218
//Use the class instead of interface because tracking exceptions directly is not supposed to be done outside AMAW (i.e. by the business code)
117-
((LightTelemetry)Workbench.Instance.Telemetry).TrackException(moreInfo);
219+
(Workbench.Instance.Telemetry as LightTelemetry)?.TrackException(moreInfo);
118220
}
119221
}
120222

@@ -132,14 +234,17 @@ private void ProcessSubscription()
132234
string topicName = topic.Value.TopicName;
133235
string subscriptName = topic.Value.Subscription;
134236
ReceiveMode receiveMode = ReceiveMode.PeekLock;
237+
135238
if (topic.Value.DeleteAfterRead)
136239
{
137240
receiveMode = ReceiveMode.ReceiveAndDelete;
138241
}
242+
139243
int takeQuantity = topic.Value.TakeQuantity;
140244

141245
//Register Trace on the telemetry
142-
SubscriptionClient subscriptionClient = new SubscriptionClient(GetConnection(topic), topicName, subscriptName, receiveMode, null);
246+
var subscriptionClient = _subscriptionClientFactory.CreateClient(
247+
GetConnection(topic), topicName, subscriptName, receiveMode);
143248

144249
//Register the method to process receive message
145250
//The RegisterMessageHandler is validate for all register exist on the queue, without need loop for items
@@ -155,7 +260,7 @@ private void ProcessSubscription()
155260
{
156261
Exception moreInfo = new Exception($"Exception reading message from topic {topicName} and subscriptName {subscriptName}. See inner exception for details. Message={exRegister.Message}", exRegister);
157262
//Use the class instead of interface because tracking exceptions directly is not supposed to be done outside AMAW (i.e. by the business code)
158-
((LightTelemetry)Workbench.Instance.Telemetry).TrackException(moreInfo);
263+
(Workbench.Instance.Telemetry as LightTelemetry)?.TrackException(moreInfo);
159264

160265
var exceptionDetails = $"{exRegister.Message}";
161266

@@ -200,13 +305,57 @@ await subscriptionClient.DeadLetterAsync(message.SystemProperties.LockToken,
200305
{
201306
Exception moreInfo = new Exception($"Error setting up subscription consumption from service bus. See inner exception for details. Message={exception.Message}", exception);
202307
//Use the class instead of interface because tracking exceptions directly is not supposed to be done outside AMAW (i.e. by the business code)
203-
((LightTelemetry)Workbench.Instance.Telemetry).TrackException(moreInfo);
308+
(Workbench.Instance.Telemetry as LightTelemetry)?.TrackException(moreInfo);
204309
}
205310
}
206311

207312
protected override Task ProcessAsync()
208313
{
209314
throw new NotImplementedException();
210315
}
316+
317+
/// <summary>
318+
/// Default implementation for <see cref="IQueueClientFactory"/>,
319+
/// creates instances of <see cref="IQueueClient"/>.
320+
/// </summary>
321+
private class DefaultQueueClientFactory : IQueueClientFactory
322+
{
323+
/// <inheritdoc/>
324+
public IQueueClient CreateClient(string connectionString, string queueName, ReceiveMode receiveMode)
325+
{
326+
return new QueueClient(connectionString, queueName, receiveMode);
327+
}
328+
}
329+
330+
/// <summary>
331+
/// Default implementation for <see cref="ISubscriptionClientFactory"/>,
332+
/// creates instances of <see cref="SubscriptionClient"/>.
333+
/// </summary>
334+
private class DefaultSubscriptionClientFactory : ISubscriptionClientFactory
335+
{
336+
/// <inheritdoc/>
337+
public ISubscriptionClient CreateClient(string connectionString, string topicName, string subscriptionName, ReceiveMode mode)
338+
{
339+
return new SubscriptionClient(connectionString, topicName, subscriptionName, mode, null);
340+
}
341+
}
342+
343+
/// <summary>
344+
/// Retrieves configuration using <see cref="LightConfigurator"/>.
345+
/// </summary>
346+
private class DefaultServiceBusConfigurationProvider : IServiceBusConfigurationProvider
347+
{
348+
/// <inheritdoc/>
349+
public ServiceBusConfiguration GetConfiguration()
350+
{
351+
return LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}");
352+
}
353+
354+
/// <inheritdoc/>
355+
public ServiceBusConfiguration GetConfiguration(string connectionKey)
356+
{
357+
return LightConfigurator.Config<ServiceBusConfiguration>($"{nameof(ServiceBus)}_{connectionKey}");
358+
}
359+
}
211360
}
212361
}

test/Liquid.OnAzure.Tests/AzureBlobTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public class AzureBlobTests : IDisposable
3636

3737
public AzureBlobTests()
3838
{
39-
Workbench.Instance.Reset();
40-
4139
Workbench.Instance.AddToCache(WorkbenchServiceType.Repository, _fakeLightRepository);
4240

4341
_sut = new AzureBlob(new MediaStorageConfiguration
@@ -156,6 +154,8 @@ protected virtual void Dispose(bool isDisposing)
156154
if (isDisposing)
157155
{
158156
_stream?.Dispose();
157+
158+
Workbench.Instance.Reset();
159159
}
160160
}
161161

test/Liquid.OnAzure.Tests/Liquid.OnAzure.Tests.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
<ItemGroup>
1414
<PackageReference Include="AutoFixture.AutoNSubstitute" Version="4.11.0" />
1515
<PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" />
16+
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
</PackageReference>
1620
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
1721
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.113">
1822
<PrivateAssets>all</PrivateAssets>

0 commit comments

Comments
 (0)