Skip to content

Commit

Permalink
Split UrlRequestBase class
Browse files Browse the repository at this point in the history
  • Loading branch information
richardrandak committed Sep 21, 2020
1 parent e904559 commit 350030f
Show file tree
Hide file tree
Showing 16 changed files with 295 additions and 227 deletions.
40 changes: 32 additions & 8 deletions FortnoxAPILibrary.Tests/ConnectionTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using FortnoxAPILibrary.Connectors;
using FortnoxAPILibrary.Connectors;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FortnoxAPILibrary.Tests
Expand All @@ -8,18 +7,45 @@ namespace FortnoxAPILibrary.Tests
public class ConnectionTests
{
[TestMethod]
[ExpectedException(typeof(AggregateException))]
public void TestConnection_WithoutCredenials_Error()
public void TestConnection_NoCredenials_Error()
{
//Arrange
ConnectionCredentials.AccessToken = null;
ConnectionCredentials.ClientSecret = null;

//Act
ICustomerConnector cc = new CustomerConnector();
cc.Find();

Assert.IsTrue(cc.HasError);
}

[TestMethod]
public void TestConnection_EmptyCredenials_Error()
{
//Arrange
ConnectionCredentials.AccessToken = "";
ConnectionCredentials.ClientSecret = "";

//Act
ICustomerConnector cc = new CustomerConnector();
cc.AccessToken = "";
cc.ClientSecret = "";
cc.Find();

Assert.IsTrue(cc.HasError);
}

[TestMethod]
public void TestConnection_WrongCredenials_Error()
{
//Arrange
ConnectionCredentials.AccessToken = "ABC";
ConnectionCredentials.ClientSecret = "DEF";

//Act
ICustomerConnector cc = new CustomerConnector();
cc.Find();

Assert.IsTrue(cc.HasError);
}

[TestMethod]
Expand Down Expand Up @@ -48,8 +74,6 @@ public void TestConnection_GlobalCredentials_Set()

//Act
ICustomerConnector connector = new CustomerConnector();
connector.AccessToken = "";
connector.ClientSecret = "";

var customers = connector.Find();
MyAssert.HasNoError(connector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public void Test_AbsenceTransaction_CRUD()
var tmpCostCenter = new CostCenterConnector().Get("TMP") ?? new CostCenterConnector().Create(new CostCenter() { Code = "TMP", Description = "TmpCostCenter" });

Assert.IsNotNull(tmpEmployee, $"Temporary employee not created.");
Assert.IsNotNull(tmpProject, $"Temporary project not created.");
Assert.IsNotNull(tmpCostCenter, $"Temporary costcenter not created.");
#endregion Arrange


Expand Down
1 change: 1 addition & 0 deletions FortnoxAPILibrary.Tests/ConnectorTests/ExpenseTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public void Test_Expense_Find()
}

connector.LastModified = timeStamp; //does not seem to work
connector.Limit = 500;
var expensesCollection = connector.Find();

var filteredExpenses = expensesCollection.Entities.Where(x => x.Text == remark).ToList();
Expand Down
45 changes: 45 additions & 0 deletions FortnoxAPILibrary/AdaptableSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Runtime.Serialization;
using FortnoxAPILibrary.Serialization;

namespace FortnoxAPILibrary
{
public class AdaptableSerializer
{
private readonly JsonEntitySerializer serializer;

public Func<string, string> FixRequestContent; //Needed for fixing irregular json requests //TODO: rename to "adapt serialization" //post-serialization
public Func<string, string> FixResponseContent; //Needed for fixing irregular json responses //TODO: rename to adapt deserialization //pre-deserialization

public AdaptableSerializer()
{
serializer = new JsonEntitySerializer();
}

public string Serialize<T>(T entity)
{
var json = serializer.Serialize(entity);

if (FixRequestContent != null)
json = FixRequestContent(json);

FixRequestContent = null;
return json;
}

public T Deserialize<T>(string content)
{
try
{
if (FixResponseContent != null)
content = FixResponseContent(content);
FixResponseContent = null;
return serializer.Deserialize<T>(content);
}
catch (Exception e)
{
throw new SerializationException("An error occured while deserializing the response. Check ResponseContent.", e.InnerException);
}
}
}
}
87 changes: 87 additions & 0 deletions FortnoxAPILibrary/BaseClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using ComposableAsync;
using RateLimiter;

namespace FortnoxAPILibrary
{
public class BaseClient
{
private const string AccessTokenHeader = "Access-Token";
private const string ClientSecretHeader = "Client-Secret";

/// <summary>
/// Http client used under-the-hood for all request (except file upload due to a server-side limitation)
/// </summary>
private HttpClient HttpClient { get; }

/// <summary>
/// Optional Fortnox Access Token, if used it will override the static version.
/// </summary>
public string AccessToken { get; set; }

/// <summary>
/// Optional Fortnox Client Secret, if used it will override the static version.
/// </summary>
public string ClientSecret { get; set; }

/// <summary>
/// Timeout of requests sent to the Fortnox API in miliseconds
/// </summary>
public int Timeout
{
get => (int) HttpClient.Timeout.TotalMilliseconds;
set => HttpClient.Timeout = TimeSpan.FromMilliseconds(value);
}

/// <summary>
/// Base fortnox server URI
/// </summary>
public string BaseUrl
{
get => HttpClient.BaseAddress.AbsoluteUri;
set => HttpClient.BaseAddress = new Uri(value);
}

public bool UseRateLimiter { get; set; }

private const int LimitPerSecond = 3;
private static readonly TimeLimiter RateLimiter = TimeLimiter.GetFromMaxCountByInterval(LimitPerSecond, TimeSpan.FromSeconds(1));

public BaseClient()
{
HttpClient = new HttpClient();

AccessToken = ConnectionCredentials.AccessToken;
ClientSecret = ConnectionCredentials.ClientSecret;
BaseUrl = ConnectionSettings.FortnoxAPIServer;
Timeout = 30 * 10000;
UseRateLimiter = ConnectionSettings.UseRateLimiter;
}

public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
{
request.Headers.Add(AccessTokenHeader, AccessToken);
request.Headers.Add(ClientSecretHeader, ClientSecret);

if (UseRateLimiter)
await RateLimiter; //throttle every call to Fortnox

return await HttpClient.SendAsync(request).ConfigureAwait(false);
}

public async Task<HttpWebResponse> SendAsync(HttpWebRequest request)
{
request.Headers.Add(AccessTokenHeader, AccessToken);
request.Headers.Add(ClientSecretHeader, ClientSecret);
request.Timeout = Timeout;

if (UseRateLimiter)
await RateLimiter; //throttle every call to Fortnox

return (HttpWebResponse) await request.GetResponseAsync().ConfigureAwait(false);
}
}
}
5 changes: 4 additions & 1 deletion FortnoxAPILibrary/ConnectionCredentials.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace FortnoxAPILibrary
{
/// <remarks/>
/// <summary>
/// Default (global) connection credentials.
/// <remarks>Changes are not applied to already created connectors.</remarks>
/// </summary>
public static class ConnectionCredentials
{
/// <remarks/>
Expand Down
4 changes: 4 additions & 0 deletions FortnoxAPILibrary/ConnectionSettings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
namespace FortnoxAPILibrary
{
/// <summary>
/// Default (global) connection settings.
/// <remarks>Changes are not applied to already created connectors.</remarks>
/// </summary>
public static class ConnectionSettings
{
/// <summary>
Expand Down
12 changes: 6 additions & 6 deletions FortnoxAPILibrary/Connectors/AssetConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,33 +92,33 @@ public async Task DeleteAsync(string id)
}
public async Task<Asset> CreateAsync(Asset asset)
{
FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);
Serializer.FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);

var result = await BaseCreate(asset).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
public async Task<Asset> UpdateAsync(Asset asset)
{
var id = asset.Id;
asset.Id = null;
FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);
Serializer.FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);

var result = await BaseUpdate(asset, id).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
asset.Id = id;
return result;
}

public async Task<Asset> GetAsync(string id)
{
FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);
Serializer.FixResponseContent = (json) => new Regex("Assets").Replace(json, "Asset", 1);

var result = await BaseGet(id).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
}
Expand Down
16 changes: 8 additions & 8 deletions FortnoxAPILibrary/Connectors/AssetTypesConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public EntityCollection<AssetTypesSubset> Find()

public async Task<EntityCollection<AssetTypesSubset>> FindAsync()
{
FixResponseContent = (json) =>
Serializer.FixResponseContent = (json) =>
{
var structure = JObject.Parse(json);

Expand All @@ -86,7 +86,7 @@ public async Task<EntityCollection<AssetTypesSubset>> FindAsync()

var result = await BaseFind().ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
public async Task DeleteAsync(long? id)
Expand All @@ -95,29 +95,29 @@ public async Task DeleteAsync(long? id)
}
public async Task<AssetType> CreateAsync(AssetType assetType)
{
FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);
Serializer.FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);

var result = await BaseCreate(assetType).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
public async Task<AssetType> UpdateAsync(AssetType assetTypes)
{
FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);
Serializer.FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);

var result = await BaseUpdate(assetTypes, assetTypes.Id.ToString()).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
public async Task<AssetType> GetAsync(long? id)
{
FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);
Serializer.FixResponseContent = (json) => new Regex("Type").Replace(json, "AssetType", 1);

var result = await BaseGet(id.ToString()).ConfigureAwait(false);

FixResponseContent = null;
Serializer.FixResponseContent = null;
return result;
}
}
Expand Down
21 changes: 11 additions & 10 deletions FortnoxAPILibrary/Connectors/AuthorizationConnector.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Net;
using System.Net.Http;
using System.Net.Http;
using System.Threading.Tasks;
using FortnoxAPILibrary.Entities;
using FortnoxAPILibrary.Entities.Special;
Expand All @@ -26,37 +25,39 @@ public string GetAccessToken(string authorizationCode, string clientSecret)

public async Task<string> GetAccessTokenAsync(string authorizationCode, string clientSecret)
{
if (string.IsNullOrEmpty(authorizationCode) || string.IsNullOrEmpty(clientSecret))
{
if (string.IsNullOrEmpty(authorizationCode))
return string.Empty;
if (string.IsNullOrEmpty(clientSecret))
return string.Empty;
}

try
{
var wr = SetupRequest(ConnectionSettings.FortnoxAPIServer, authorizationCode, clientSecret);
var wr = SetupRequest(BaseUrl, authorizationCode, clientSecret);
using var response = await HttpClient.SendAsync(wr).ConfigureAwait(false);

if (!response.IsSuccessStatusCode)
{
HandleError(response);
Error = ErrorHandler.HandleError(response);
return string.Empty;
}

var json = response.Content.ReadAsStringAsync().Result;
var result = Deserialize<EntityWrapper<AuthorizationData>>(json);
var result = Serializer.Deserialize<EntityWrapper<AuthorizationData>>(json);

var accessToken = result?.Entity.AccessToken;
return accessToken;
}
catch (HttpRequestException we)
{
Error = HandleConnectionException(we);
Error = ErrorHandler.HandleConnectionException(we);
return string.Empty;
}
}

private static HttpRequestMessage SetupRequest(string requestUriString, string authorizationCode, string clientSecret)
private HttpRequestMessage SetupRequest(string requestUriString, string authorizationCode, string clientSecret)
{
Error = null;

var wr = new HttpRequestMessage(HttpMethod.Get, requestUriString);
wr.Headers.Add("authorization-code", authorizationCode);
wr.Headers.Add("client-secret", clientSecret);
Expand Down
Loading

0 comments on commit 350030f

Please sign in to comment.