Skip to content

Commit 567f133

Browse files
committed
Updated patterns using IHttpClientFactory and IOptions. Updated unit tests.
1 parent 930d33b commit 567f133

File tree

5 files changed

+89
-50
lines changed

5 files changed

+89
-50
lines changed

src/Umbraco.Forms.Integrations.Crm.Hubspot.Tests/Services/HubspotContactServiceTests.cs

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Configuration;
22
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Options;
34
using Moq;
45
using Moq.Protected;
56
using NUnit.Framework;
@@ -14,6 +15,7 @@
1415
using Umbraco.Cms.Core.Services;
1516
using Umbraco.Forms.Core;
1617
using Umbraco.Forms.Core.Persistence.Dtos;
18+
using Umbraco.Forms.Integrations.Crm.Hubspot.Configuration;
1719
using Umbraco.Forms.Integrations.Crm.Hubspot.Models;
1820
using Umbraco.Forms.Integrations.Crm.Hubspot.Services;
1921

@@ -102,10 +104,11 @@ public class HubspotContactServiceTests
102104
[Test]
103105
public async Task GetContactProperties_WithoutApiKeyConfigured_ReturnsEmptyCollectionWithLoggedWarning()
104106
{
105-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration(withApiKey: false);
107+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration(withApiKey: false);
108+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
106109
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
107110
var mockedKeyValueService = new Mock<IKeyValueService>();
108-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
111+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
109112

110113
var result = await sut.GetContactPropertiesAsync();
111114

@@ -125,13 +128,16 @@ public async Task GetContactProperties_WithoutApiKeyConfigured_ReturnsEmptyColle
125128
[Test]
126129
public async Task GetContactProperties_WithFailedRequest_ReturnsEmptyCollectionWithLoggedError()
127130
{
128-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration();
131+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration();
132+
133+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
134+
mockedHttpClientFactory
135+
.Setup<HttpClient>(x => x.CreateClient(It.IsAny<string>()))
136+
.Returns(CreateMockedHttpClient(HttpStatusCode.InternalServerError));
137+
129138
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
130139
var mockedKeyValueService = new Mock<IKeyValueService>();
131-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
132-
133-
var httpClient = CreateMockedHttpClient(HttpStatusCode.InternalServerError);
134-
HubspotContactService.ClientFactory = () => httpClient;
140+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
135141

136142
var result = await sut.GetContactPropertiesAsync();
137143

@@ -151,13 +157,16 @@ public async Task GetContactProperties_WithFailedRequest_ReturnsEmptyCollectionW
151157
[Test]
152158
public async Task GetContactProperties_WithSuccessfulRequest_ReturnsMappedAndOrderedPropertyCollection()
153159
{
154-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration();
160+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration();
161+
162+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
163+
mockedHttpClientFactory
164+
.Setup<HttpClient>(x => x.CreateClient(It.IsAny<string>()))
165+
.Returns(CreateMockedHttpClient(HttpStatusCode.OK, s_contactPropertiesResponse));
166+
155167
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
156168
var mockedKeyValueService = new Mock<IKeyValueService>();
157-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
158-
159-
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK, s_contactPropertiesResponse);
160-
HubspotContactService.ClientFactory = () => httpClient;
169+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
161170

162171
var result = await sut.GetContactPropertiesAsync();
163172

@@ -169,10 +178,11 @@ public async Task GetContactProperties_WithSuccessfulRequest_ReturnsMappedAndOrd
169178
[Test]
170179
public async Task PostContact_WithoutApiKeyConfigured_ReturnsNotConfiguredWithLoggedWarning()
171180
{
172-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration(withApiKey: false);
181+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration(withApiKey: false);
182+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
173183
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
174184
var mockedKeyValueService = new Mock<IKeyValueService>();
175-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
185+
var sut = new HubspotContactService( mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
176186

177187
var record = new Record();
178188
var fieldMappings = new List<MappedProperty>();
@@ -194,13 +204,16 @@ public async Task PostContact_WithoutApiKeyConfigured_ReturnsNotConfiguredWithLo
194204
[Test]
195205
public async Task PostContact_WithFailedRequest_ReturnsFailedWithLoggedError()
196206
{
197-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration();
207+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration();
208+
209+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
210+
mockedHttpClientFactory
211+
.Setup<HttpClient>(x => x.CreateClient(It.IsAny<string>()))
212+
.Returns(CreateMockedHttpClient(HttpStatusCode.InternalServerError));
213+
198214
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
199215
var mockedKeyValueService = new Mock<IKeyValueService>();
200-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
201-
202-
var httpClient = CreateMockedHttpClient(HttpStatusCode.InternalServerError);
203-
HubspotContactService.ClientFactory = () => httpClient;
216+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
204217

205218
var record = new Record();
206219
var fieldMappings = new List<MappedProperty>();
@@ -222,13 +235,16 @@ public async Task PostContact_WithFailedRequest_ReturnsFailedWithLoggedError()
222235
[Test]
223236
public async Task PostContact_WithSuccessfulRequest_ReturnSuccess()
224237
{
225-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration();
238+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration();
239+
240+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
241+
mockedHttpClientFactory
242+
.Setup<HttpClient>(x => x.CreateClient(It.IsAny<string>()))
243+
.Returns(CreateMockedHttpClient(HttpStatusCode.OK));
244+
226245
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
227246
var mockedKeyValueService = new Mock<IKeyValueService>();
228-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
229-
230-
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK);
231-
HubspotContactService.ClientFactory = () => httpClient;
247+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
232248

233249
var formFieldId = Guid.NewGuid();
234250
var record = new Record();
@@ -263,13 +279,16 @@ public async Task PostContact_WithSuccessfulRequest_ReturnSuccess()
263279
[Test]
264280
public async Task PostContact_WithSuccessfulRequestAndUnmappedField_ReturnSuccessWithLoggedWarning()
265281
{
266-
Mock<IConfiguration> mockedConfig = CreateMockedConfiguration();
282+
Mock<IOptions<HubspotSettings>> mockedConfig = CreateMockedConfiguration();
283+
284+
var mockedHttpClientFactory = new Mock<IHttpClientFactory>();
285+
mockedHttpClientFactory
286+
.Setup<HttpClient>(x => x.CreateClient(It.IsAny<string>()))
287+
.Returns(CreateMockedHttpClient(HttpStatusCode.OK));
288+
267289
var mockedLogger = new Mock<ILogger<HubspotContactService>>();
268290
var mockedKeyValueService = new Mock<IKeyValueService>();
269-
var sut = new HubspotContactService(mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
270-
271-
var httpClient = CreateMockedHttpClient(HttpStatusCode.OK);
272-
HubspotContactService.ClientFactory = () => httpClient;
291+
var sut = new HubspotContactService(mockedHttpClientFactory.Object, mockedConfig.Object, mockedLogger.Object, AppCaches.NoCache, mockedKeyValueService.Object);
273292

274293
var formFieldId = Guid.NewGuid();
275294
var record = new Record();
@@ -296,16 +315,11 @@ public async Task PostContact_WithSuccessfulRequestAndUnmappedField_ReturnSucces
296315
Assert.AreEqual(CommandResult.Success, result);
297316
}
298317

299-
private static Mock<IConfiguration> CreateMockedConfiguration(bool withApiKey = true)
318+
private static Mock<IOptions<HubspotSettings>> CreateMockedConfiguration(bool withApiKey = true)
300319
{
301-
var mockedConfiguration = new Mock<IConfiguration>();
302-
if (withApiKey)
303-
{
304-
mockedConfiguration
305-
.Setup(x => x[It.Is<string>(y => y == HubspotWorkflow.HubspotApiKey)])
306-
.Returns(ApiKey);
307-
}
308-
320+
var mockedConfiguration = new Mock<IOptions<HubspotSettings>>();
321+
mockedConfiguration.Setup(x => x.Value).Returns(new HubspotSettings { ApiKey = withApiKey ? ApiKey : String.Empty });
322+
309323
return mockedConfiguration;
310324
}
311325

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Umbraco.Forms.Integrations.Crm.Hubspot.Configuration
8+
{
9+
public class HubspotSettings
10+
{
11+
public string ApiKey { get; set; }
12+
}
13+
}

src/Umbraco.Forms.Integrations.Crm.Hubspot/HubspotComposer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Umbraco.Cms.Core.DependencyInjection;
55
using Umbraco.Cms.Core.Notifications;
66
using Umbraco.Forms.Core.Providers;
7+
using Umbraco.Forms.Integrations.Crm.Hubspot.Configuration;
78
using Umbraco.Forms.Integrations.Crm.Hubspot.Services;
89

910
namespace Umbraco.Forms.Integrations.Crm.Hubspot
@@ -12,6 +13,9 @@ public class HubspotComposer : IComposer
1213
{
1314
public void Compose(IUmbracoBuilder builder)
1415
{
16+
builder.Services.AddOptions<HubspotSettings>()
17+
.Bind(builder.Config.GetSection(HubspotWorkflow.HubspotSettings));
18+
1519
builder.Services.AddSingleton<IContactService, HubspotContactService>();
1620

1721
builder.AddNotificationHandler<ServerVariablesParsingNotification, HubspotServerVariablesParsingHandler>();

src/Umbraco.Forms.Integrations.Crm.Hubspot/HubspotWorkflow.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class HubspotWorkflow : WorkflowType
1717
{
1818
public const string HubspotWorkflowId = "c47ef1ef-22b1-4b9d-acf6-f57cb8961550";
1919

20-
public const string HubspotApiKey = "Umbraco:Forms:Integrations:Crm:Hubspot:Settings:ApiKey";
20+
public const string HubspotSettings = "Umbraco:Forms:Integrations:Crm:Hubspot:Settings";
2121

2222
private readonly ILogger<HubspotWorkflow> _logger;
2323
private readonly IContactService _contactService;

src/Umbraco.Forms.Integrations.Crm.Hubspot/Services/HubspotContactService.cs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Configuration;
22
using Microsoft.Extensions.Logging;
3+
using Microsoft.Extensions.Options;
34
using Newtonsoft.Json;
45
using System;
56
using System.Collections.Generic;
@@ -13,6 +14,7 @@
1314
using Umbraco.Cms.Core.Cache;
1415
using Umbraco.Cms.Core.Services;
1516
using Umbraco.Forms.Core.Persistence.Dtos;
17+
using Umbraco.Forms.Integrations.Crm.Hubspot.Configuration;
1618
using Umbraco.Forms.Integrations.Crm.Hubspot.Extensions;
1719
using Umbraco.Forms.Integrations.Crm.Hubspot.Models;
1820
using Umbraco.Forms.Integrations.Crm.Hubspot.Models.Dtos;
@@ -22,17 +24,14 @@ namespace Umbraco.Forms.Integrations.Crm.Hubspot.Services
2224
{
2325
public class HubspotContactService : IContactService
2426
{
25-
//Using a static HttpClient(see: https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/).
26-
private static readonly HttpClient s_client = new HttpClient();
27+
private readonly IHttpClientFactory _httpClientFactory;
2728

28-
//Access to the client within the class is via ClientFactory(), allowing us to mock the responses in tests.
29-
public static Func<HttpClient> ClientFactory = () => s_client;
30-
31-
private readonly IConfiguration _configuration;
3229
private readonly ILogger<HubspotContactService> _logger;
3330
private readonly AppCaches _appCaches;
3431
private readonly IKeyValueService _keyValueService;
3532

33+
private readonly HubspotSettings _settings;
34+
3635
private const string CrmApiHost = "https://api.hubapi.com";
3736
private static readonly string CrmV3ApiBaseUrl = $"{CrmApiHost}/crm/v3/";
3837
private const string InstallUrlFormat = "https://app-eu1.hubspot.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope={2}";
@@ -48,9 +47,15 @@ public class HubspotContactService : IContactService
4847

4948
private const string RefreshTokenDatabaseKey = "Umbraco.Forms.Integrations.Crm.Hubspot+RefreshToken";
5049

51-
public HubspotContactService(IConfiguration configuration, ILogger<HubspotContactService> logger, AppCaches appCaches, IKeyValueService keyValueService)
50+
public HubspotContactService(
51+
IHttpClientFactory httpClientFactory,
52+
IOptions<HubspotSettings> options,
53+
ILogger<HubspotContactService> logger,
54+
AppCaches appCaches,
55+
IKeyValueService keyValueService)
5256
{
53-
_configuration = configuration;
57+
_httpClientFactory = httpClientFactory;
58+
_settings = options.Value;
5459
_logger = logger;
5560
_appCaches = appCaches;
5661
_keyValueService = keyValueService;
@@ -68,7 +73,8 @@ public async Task<AuthorizationResult> AuthorizeAsync(string code)
6873
RedirectUrl = OAuthRedirectUrl,
6974
AuthorizationCode = code,
7075
};
71-
var response = await GetResponse(OAuthTokenProxyUrl, HttpMethod.Post, content: tokenRequest, contentType: "application/x-www-form-urlencoded").ConfigureAwait(false);
76+
var response = await GetResponse(OAuthTokenProxyUrl,
77+
HttpMethod.Post, content: tokenRequest, contentType: "application/x-www-form-urlencoded").ConfigureAwait(false);
7278
if (response.IsSuccessStatusCode)
7379
{
7480
var responseContent = await response.Content.ReadAsStringAsync();
@@ -273,7 +279,7 @@ private AuthenticationDetail GetConfiguredAuthenticationDetails()
273279

274280
private bool TryGetApiKey(out string apiKey)
275281
{
276-
apiKey = _configuration[HubspotWorkflow.HubspotApiKey];
282+
apiKey = _settings.ApiKey;
277283

278284
return !string.IsNullOrEmpty(apiKey);
279285
}
@@ -340,6 +346,8 @@ private async Task<HttpResponseMessage> GetResponse(
340346
object content = null,
341347
string contentType = null)
342348
{
349+
var httpClient = _httpClientFactory.CreateClient();
350+
343351
var requestMessage = new HttpRequestMessage
344352
{
345353
Method = httpMethod,
@@ -362,7 +370,7 @@ private async Task<HttpResponseMessage> GetResponse(
362370
}
363371
}
364372

365-
return await ClientFactory().SendAsync(requestMessage).ConfigureAwait(false);
373+
return await httpClient.SendAsync(requestMessage).ConfigureAwait(false);
366374
}
367375

368376
private static HttpContent CreateRequestContent(object data, string contentType)

0 commit comments

Comments
 (0)