Skip to content

Commit

Permalink
Merge pull request #34 from githuib/FSB-1830_improved-unit-tests
Browse files Browse the repository at this point in the history
FSB-1830 - improved unit tests for API requests
  • Loading branch information
Huib Piguillet authored Nov 20, 2020
2 parents 8dd54fe + d7da24f commit 8c5d2f5
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 169 deletions.
98 changes: 38 additions & 60 deletions Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using Bynder.Sdk.Model;
using Bynder.Sdk.Api.Requests;
using Newtonsoft.Json;
using Bynder.Sdk.Query.Decoder;
using Bynder.Sdk.Query;
using Bynder.Sdk.Service.OAuth;
using Bynder.Sdk.Settings;
using Newtonsoft.Json;

namespace Bynder.Sdk.Api.RequestSender
{
Expand All @@ -24,22 +23,23 @@ internal class ApiRequestSender : IApiRequestSender
private readonly Configuration _configuration;
private readonly QueryDecoder _queryDecoder = new QueryDecoder();
private readonly ICredentials _credentials;
private readonly IOAuthService _oauthService;
private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private IHttpRequestSender _httpSender;

public const string TokenPath = "/v6/authentication/oauth2/token";

/// <summary>
/// Initializes a new instance of the <see cref="T:Sdk.Api.ApiRequestSender"/> class.
/// </summary>
/// <param name="configuration">Configuration.</param>
/// <param name="credentials">Credentials to use in authorized requests and to refresh tokens</param>
/// <param name="oauthService">OAuthService.</param>
/// <param name="httpSender">HTTP instance to send API requests</param>
internal ApiRequestSender(Configuration configuration, ICredentials credentials, IHttpRequestSender httpSender)
internal ApiRequestSender(Configuration configuration, ICredentials credentials, IOAuthService oauthService, IHttpRequestSender httpSender)
{
_configuration = configuration;
_httpSender = httpSender;
_credentials = credentials;
_oauthService = oauthService;
_httpSender = httpSender;
}

/// <summary>
Expand All @@ -48,9 +48,10 @@ internal ApiRequestSender(Configuration configuration, ICredentials credentials,
/// <returns>The instance.</returns>
/// <param name="configuration">Configuration.</param>
/// <param name="credentials">Credentials.</param>
public static IApiRequestSender Create(Configuration configuration, ICredentials credentials)
/// <param name="oauthService">OAuthService.</param>
public static IApiRequestSender Create(Configuration configuration, ICredentials credentials, IOAuthService oauthService)
{
return new ApiRequestSender(configuration, credentials, new HttpRequestSender());
return new ApiRequestSender(configuration, credentials, oauthService, new HttpRequestSender());
}

/// <summary>
Expand All @@ -69,29 +70,13 @@ public void Dispose()
/// <summary>
/// Check <see cref="t:Sdk.Api.IApiRequestSender"/>.
/// </summary>
/// <returns>Check <see cref="t:Sdk.Api.IApiRequestSender"/>.</returns>
/// <param name="request">Check <see cref="t:Sdk.Api.IApiRequestSender"/>.</param>
/// <typeparam name="T">Check <see cref="t:Sdk.Api.IApiRequestSender"/>.</typeparam>
public async Task<T> SendRequestAsync<T>(Requests.Request<T> request)
/// <returns>Check <see cref="t:Sdk.Api.IApiRequestSender"/>.</returns>
/// <exception cref="T:System.Net.Http.HttpRequestException">Check <see cref="t:Sdk.Api.IApiRequestSender"/>.</exception>
public async Task<T> SendRequestAsync<T>(Request<T> request)
{
if (request.Authenticated && !_credentials.AreValid())
{
await _semaphore.WaitAsync().ConfigureAwait(false);
if (!_credentials.AreValid())
{
try
{
await RefreshToken().ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}
}

var httpRequest = CreateHttpRequest(request);
var response = await _httpSender.SendHttpRequest(httpRequest).ConfigureAwait(false);
var response = await CreateHttpRequestAsync(request).ConfigureAwait(false);

var responseContent = response.Content;
if (response.Content == null)
Expand All @@ -100,53 +85,46 @@ public async Task<T> SendRequestAsync<T>(Requests.Request<T> request)
}

var responseString = await responseContent.ReadAsStringAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(responseString))
if (responseString == null)
{
return default(T);
}

return JsonConvert.DeserializeObject<T>(responseString);
}

private HttpRequestMessage CreateHttpRequest<T>(Requests.Request<T> request)
private async Task<HttpResponseMessage> CreateHttpRequestAsync<T>(Request<T> request)
{
var parameters = _queryDecoder.GetParameters(request.Query);
var httpRequestMessage = HttpRequestMessageFactory.Create(
_configuration.BaseUrl.ToString(),
request.HTTPMethod,
parameters,
request.Path);
_queryDecoder.GetParameters(request.Query),
request.Path
);

if (request.Authenticated)
{
httpRequestMessage.Headers.Authorization =
new AuthenticationHeaderValue(_credentials.TokenType, _credentials.AccessToken);
}

return httpRequestMessage;
}

private async Task RefreshToken()
{
TokenQuery query = new TokenQuery
{
ClientId = _configuration.ClientId,
ClientSecret = _configuration.ClientSecret,
RefreshToken = _credentials.RefreshToken,
GrantType = "refresh_token"
};

var request = new OAuthRequest<Token>
{
Authenticated = false,
Query = query,
Path = TokenPath,
HTTPMethod = HttpMethod.Post
};
if (!_credentials.AreValid())
{
// Get a refesh token when the credentials are no longer valid
await _semaphore.WaitAsync().ConfigureAwait(false);
try
{
await _oauthService.GetRefreshTokenAsync().ConfigureAwait(false);
}
finally
{
_semaphore.Release();
}
}

var newToken = await SendRequestAsync(request).ConfigureAwait(false);
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
_credentials.TokenType,
_credentials.AccessToken
);
}

_credentials.Update(newToken);
return await _httpSender.SendHttpRequest(httpRequestMessage).ConfigureAwait(false);
}

private static class HttpRequestMessageFactory
Expand Down
3 changes: 2 additions & 1 deletion Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ public string UserAgent
/// <summary>
/// Sends the HTTP request and returns its response.
/// </summary>
/// <returns>The HTTP request response.</returns>
/// <param name="httpRequest">HTTP request.</param>
/// <returns>The HTTP request response.</returns>
/// <exception cref="T:System.Net.Http.HttpRequestException">Check <see cref="t:Sdk.Api.IHttpRequestSender"/>.</exception>
public async Task<HttpResponseMessage> SendHttpRequest(HttpRequestMessage httpRequest)
{
httpRequest.Headers.Add("User-Agent", UserAgent);
Expand Down
4 changes: 3 additions & 1 deletion Bynder/Sdk/Api/RequestSender/IApiRequestSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ internal interface IApiRequestSender : IDisposable
/// <summary>
/// Sends the request async.
/// </summary>
/// <returns>The deserialized response.</returns>
/// <param name="request">Request.</param>
/// <typeparam name="T">Type we want to deserialize response to.</typeparam>
/// <returns>The deserialized response.</returns>
/// <exception cref="T:System.Net.Http.HttpRequestException">The request failed due to an underlying issue
/// such as network connectivity, DNS failure, server certificate validation or timeout.</exception>
Task<T> SendRequestAsync<T>(Request<T> request);
}
}
4 changes: 3 additions & 1 deletion Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ internal interface IHttpRequestSender : IDisposable
/// <summary>
/// Sends the HTTP request and returns its response.
/// </summary>
/// <returns>The HTTP request response.</returns>
/// <param name="httpRequest">HTTP request.</param>
/// <returns>The HTTP request response.</returns>
/// <exception cref="T:System.Net.Http.HttpRequestException">The request failed due to an underlying issue
/// such as network connectivity, DNS failure, server certificate validation or timeout.</exception>
Task<HttpResponseMessage> SendHttpRequest(HttpRequestMessage httpRequest);
}
}
3 changes: 3 additions & 0 deletions Bynder/Sdk/Api/Requests/OAuthRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@ namespace Bynder.Sdk.Api.Requests
/// <typeparam name="T">Type to which the response will be deserialized</typeparam>
internal class OAuthRequest<T> : Request<T>
{
public OAuthRequest() {
Authenticated = false;
}
}
}
5 changes: 3 additions & 2 deletions Bynder/Sdk/Bynder.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Product>Bynder.Sdk</Product>
<Copyright>Copyright © Bynder</Copyright>
<PackOnBuild>true</PackOnBuild>
<PackageVersion>2.2.0</PackageVersion>
<PackageVersion>2.2.1</PackageVersion>
<Authors>BynderDevops</Authors>
<Description>The main goal of this SDK is to speed up the integration of Bynder customers who use C# making it easier to connect to the Bynder API (http://docs.bynder.apiary.io/) and executing requests on it.</Description>
<PackageIconUrl>https://bynder.com/static/3.0/img/favicon-black.ico</PackageIconUrl>
Expand All @@ -16,7 +16,8 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Owners>BynderDevops</Owners>
<PackageProjectUrl>https://github.com/Bynder/bynder-c-sharp-sdk</PackageProjectUrl>
<PackageReleaseNotes>Adds the SDK's name and version to each request in the User-Agent header, to enable better insight in the Bynder API usage.</PackageReleaseNotes>
<PackageReleaseNotes>- Performance and stability improvements.
- Fixed error with deserialization of ModifyMedia response</PackageReleaseNotes>
<Summary>The main goal of this SDK is to speed up the integration of Bynder customers who use C# making it easier to connect to the Bynder API (http://docs.bynder.apiary.io/) and executing requests on it.</Summary>
<PackageTags>Bynder API C# SDK</PackageTags>
<Title>Bynder.Sdk</Title>
Expand Down
27 changes: 0 additions & 27 deletions Bynder/Sdk/Query/Decoder/IParameterDecoder.cs

This file was deleted.

4 changes: 2 additions & 2 deletions Bynder/Sdk/Query/Decoder/QueryDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using Bynder.Sdk.Api.Converters;

namespace Bynder.Sdk.Query.Decoder
{
Expand Down Expand Up @@ -79,7 +79,7 @@ private string ConvertPropertyValue(ApiField apiField, Type propertyType, object
bool isConverted = false;
if (apiField.Converter != null)
{
IParameterDecoder converter = Activator.CreateInstance(apiField.Converter) as IParameterDecoder;
ITypeToStringConverter converter = Activator.CreateInstance(apiField.Converter) as ITypeToStringConverter;

if (converter != null
&& converter.CanConvert(propertyType))
Expand Down
1 change: 1 addition & 0 deletions Bynder/Sdk/Service/Asset/IAssetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface IAssetService
/// Gets Brands Async
/// </summary>
/// <returns>Task with list of brands</returns>
/// <exception cref="HttpRequestException">Can be thrown when requests to server can't be completed or HTTP code returned by server is an error</exception>
Task<IList<Brand>> GetBrandsAsync();

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Bynder/Sdk/Service/BynderClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public BynderClient(Configuration configuration)
{
_credentials = new Credentials(configuration.Token);
}
_requestSender = ApiRequestSender.Create(_configuration, _credentials);
_requestSender = ApiRequestSender.Create(_configuration, _credentials, _oauthService);
}

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions Bynder/Sdk/Service/OAuth/IOAuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@ public interface IOAuthService
/// <param name="code">Code received after the redirect</param>
/// <param name="scopes">The authorization scopes</param>
Task GetAccessTokenAsync(string code, string scopes);

/// <summary>
/// Gets a refresh token.
/// </summary>
/// <returns>The task to get the refresh token</returns>
Task GetRefreshTokenAsync();
}
}
36 changes: 32 additions & 4 deletions Bynder/Sdk/Service/OAuth/OAuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ internal class OAuthService : IOAuthService
/// </summary>
private IApiRequestSender _requestSender;

public const string AuthPath = "/v6/authentication/oauth2/auth";
public const string TokenPath = "/v6/authentication/oauth2/token";

/// <summary>
/// Initializes a new instance of the class
/// </summary>
Expand Down Expand Up @@ -53,7 +56,7 @@ public string GetAuthorisationUrl(string state, string scopes)
};

var builder = new UriBuilder(_configuration.BaseUrl);
builder.Path = "/v6/authentication/oauth2/auth";
builder.Path = AuthPath;

builder.Query = Utils.Url.ConvertToQuery(authoriseParams);

Expand All @@ -73,7 +76,7 @@ public async Task GetAccessTokenAsync(string code, string scopes)
throw new ArgumentNullException(code);
}

TokenQuery query = new TokenQuery
var query = new TokenQuery
{
ClientId = _configuration.ClientId,
ClientSecret = _configuration.ClientSecret,
Expand All @@ -86,14 +89,39 @@ public async Task GetAccessTokenAsync(string code, string scopes)
var request = new OAuthRequest<Token>
{
Query = query,
Path = "/v6/authentication/oauth2/token",
Path = TokenPath,
HTTPMethod = HttpMethod.Post,
Authenticated = false
};

var token = await _requestSender.SendRequestAsync(request).ConfigureAwait(false);
token.SetAccessTokenExpiration();
_credentials.Update(token);
}

/// <summary>
/// Check <see cref="IOAuthService"/>.
/// </summary>
/// <returns>Check <see cref="IOAuthService"/>.</returns>
public async Task GetRefreshTokenAsync()
{
var query = new TokenQuery
{
ClientId = _configuration.ClientId,
ClientSecret = _configuration.ClientSecret,
RefreshToken = _credentials.RefreshToken,
GrantType = "refresh_token"
};

var request = new OAuthRequest<Token>
{
Query = query,
Path = TokenPath,
HTTPMethod = HttpMethod.Post,
};

var token = await _requestSender.SendRequestAsync(request).ConfigureAwait(false);
_credentials.Update(token);
}

}
}
Loading

0 comments on commit 8c5d2f5

Please sign in to comment.