Skip to content
Open
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
2 changes: 2 additions & 0 deletions Fitbit.Portable.Tests/FatTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using NUnit.Framework;
using AutoFixture;
using System.Threading.Tasks;
using AutoFixture.AutoMoq;
using Fitbit.Api.Portable;
using Fitbit.Models;

Expand All @@ -20,6 +21,7 @@ public class FatTests
public void Init()
{
fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
}

[Test]
Expand Down
1 change: 1 addition & 0 deletions Fitbit.Portable.Tests/Fitbit.Portable.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.6.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.6.0" />
<PackageReference Include="FluentAssertions" Version="5.5.3" />
<PackageReference Include="Moq" Version="4.10.1" />
<PackageReference Include="nunit" Version="3.11.0" />
Expand Down
16 changes: 4 additions & 12 deletions Fitbit.Portable.Tests/FitbitClientConstructorTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
using Fitbit.Api.Portable;
using Fitbit.Api.Portable.OAuth2;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;

namespace Fitbit.Portable.Tests
Expand All @@ -17,10 +12,9 @@ public class FitbitClientConstructorTests
[Category("constructor")]
public void Most_Basic()
{
var credentials = new FitbitAppCredentials() { ClientId = "SomeID", ClientSecret = "THE Secret ;)" };
var accessToken = new OAuth2AccessToken() { Token = "", TokenType = "", ExpiresIn = 3600, RefreshToken = ""};

var sut = new FitbitClient(credentials, accessToken);
var sut = new FitbitClient(accessToken);

Assert.IsNotNull(sut.HttpClient);
}
Expand All @@ -38,11 +32,10 @@ public void Use_Custom_HttpClient_Factory()
[Category("constructor")]
public void Can_Instantiate_Without_Any_Interceptors()
{
var credentials = new FitbitAppCredentials() { ClientId = "SomeID", ClientSecret = "THE Secret ;)" };
var accessToken = new OAuth2AccessToken() { Token = "", TokenType = "", ExpiresIn = 3600, RefreshToken = "" };

//Ensure not even the auto-token-refresh interceptor is active
var sut = new FitbitClient(credentials, accessToken, false);
var sut = new FitbitClient(accessToken, enableOAuth2TokenRefresh: false);

Assert.IsNotNull(sut.HttpClient);
}
Expand All @@ -51,11 +44,10 @@ public void Can_Instantiate_Without_Any_Interceptors()
[Category("constructor")]
public void Can_Use_Interceptors_Without_Autorefresh()
{
var credentials = new FitbitAppCredentials() { ClientId = "SomeID", ClientSecret = "THE Secret ;)" };
var accessToken = new OAuth2AccessToken() { Token = "", TokenType = "", ExpiresIn = 3600, RefreshToken = "" };

//Registere an interceptor, but disable the auto-token-refresh interceptor
var sut = new FitbitClient(credentials, accessToken, new InterceptorCounter(), false);
//Register an interceptor, but disable the auto-token-refresh interceptor
var sut = new FitbitClient(accessToken, new InterceptorCounter(), false);

Assert.IsNotNull(sut.HttpClient);
}
Expand Down
41 changes: 18 additions & 23 deletions Fitbit.Portable.Tests/FitbitHttpMessageHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
namespace Fitbit.Portable.Tests
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using Fitbit.Api.Portable;
using NUnit.Framework;
using Fitbit.Api.Portable.OAuth2;
using Fitbit.Portable.Tests.Helpers;
using Moq;

namespace Fitbit.Portable.Tests
{
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Threading;
using Fitbit.Api.Portable;
using NUnit.Framework;
using Fitbit.Api.Portable.OAuth2;
using Fitbit.Portable.Tests.Helpers;
using Moq;
[TestFixture]
public class FitbitHttpMessageHandlerTests
{
FitbitAppCredentials dummyCredentials = new FitbitAppCredentials();
OAuth2AccessToken dummyToken = new OAuth2AccessToken();

[Test]
[Category("Portable")]
[Category("Interceptor")]

public async Task CanSnifftHttpRequests()
{
//arrenge
var messageHandler = new InterceptorCounter();
var sut = new FitbitClient(dummyCredentials, dummyToken, messageHandler);
var sut = new FitbitClient(dummyToken, messageHandler);

//Act
var r = await sut.HttpClient.GetAsync("https://dev.fitbit.com/");
Expand All @@ -43,7 +41,7 @@ public async Task CanInterceptHttpRequestAndFakeResponse()
var responseFaker = new ResponseFaker(fakeResponse);

//arrenge
var sut = new FitbitClient(dummyCredentials, dummyToken, responseFaker);
var sut = new FitbitClient(dummyToken, responseFaker);

//Act
var actualResponse = await sut.HttpClient.GetAsync("https://dev.fitbit.com/");
Expand All @@ -52,8 +50,7 @@ public async Task CanInterceptHttpRequestAndFakeResponse()
//Ensure that the response handler is still invoked, even though we short circuited the request
Assert.AreEqual(EXPECT_ONE_REQUEST, responseFaker.ResponseCount);
}



[Test]
[Category("Portable")]
[Category("Interceptor")]
Expand All @@ -70,7 +67,7 @@ public async Task Correctly_Detects_Stale_Token_Refreshes_And_Retries_Original_R
//we shortcircuit the request to fake an expired token on the first request, and assuming the token is different the second time we let the request through
var fakeServer = new StaleTokenFaker();

var sut = new FitbitClient(dummyCredentials, originalToken, fakeServer, /*Explicity activate autorefresh, default is true*/true, fakeManager.Object);
var sut = new FitbitClient(originalToken, fakeServer, /*Explicity activate autorefresh, default is true*/true, fakeManager.Object);

//Act
var actualResponse = await sut.HttpClient.GetAsync("https://dev.fitbit.com/");
Expand All @@ -80,12 +77,10 @@ public async Task Correctly_Detects_Stale_Token_Refreshes_And_Retries_Original_R
//Ensure the client is updated with the refreshed token
Assert.AreEqual(refreshedToken.Token, sut.HttpClient.DefaultRequestHeaders.Authorization.Parameter);
fakeManager.Verify(m => m.RefreshTokenAsync(It.IsAny<FitbitClient>()), Times.Once);
//Expecte two interceptions. First when we get the 401 refresh, and second when we retry after refreshing the stale token
//Expected two interceptions. First when we get the 401 refresh, and second when we retry after refreshing the stale token
Assert.AreEqual(2, fakeServer.requestCount, "It looks like either the client did not retry after the token was refreshed, or the stale token was not detected");

}


[Test]
[Category("Portable")]
[Category("Interceptor")]
Expand All @@ -102,7 +97,7 @@ public void Can_Handle_Failed_Refresh_Operation()
//simulate failed refresh token.
var fakeServer = new StaleTokenFaker(10);

var sut = new FitbitClient(dummyCredentials, originalToken, fakeServer, fakeManager.Object);
var sut = new FitbitClient(originalToken, fakeServer, tokenManager: fakeManager.Object);

//Act
var r = sut.HttpClient.GetAsync("https://dev.fitbit.com/");
Expand All @@ -124,10 +119,10 @@ public async Task Disable_Automatic_Token_Refresh()
var fakeManager = new Mock<ITokenManager>();
fakeManager.Setup(m => m.RefreshTokenAsync(It.IsAny<FitbitClient>())).Returns(() => Task.Run(() => refreshedToken));

//we shortcircuit the request to return a stale token and ensure that the client lets the stale token response through
//we short circuit the request to return a stale token and ensure that the client lets the stale token response through
var fakeServer = new StaleTokenFaker();

var sut = new FitbitClient(dummyCredentials, originalToken, fakeServer, false, fakeManager.Object);
var sut = new FitbitClient(originalToken, fakeServer, false, fakeManager.Object);

//Act
var actualResponse = await sut.HttpClient.GetAsync("https://dev.fitbit.com/");
Expand Down
2 changes: 2 additions & 0 deletions Fitbit.Portable.Tests/GoalsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using NUnit.Framework;
using AutoFixture;
using System.Threading.Tasks;
using AutoFixture.AutoMoq;

namespace Fitbit.Portable.Tests
{
Expand All @@ -18,6 +19,7 @@ public class GoalsTests
public void Init()
{
fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
}

[Test] [Category("Portable")]
Expand Down
2 changes: 2 additions & 0 deletions Fitbit.Portable.Tests/WeightTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using NUnit.Framework;
using AutoFixture;
using System.Threading.Tasks;
using AutoFixture.AutoMoq;

namespace Fitbit.Portable.Tests
{
Expand All @@ -20,6 +21,7 @@ public class WeightTests
public void Init()
{
fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
}

[Test]
Expand Down
12 changes: 7 additions & 5 deletions Fitbit.Portable/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
{
internal class Constants
{
public const string BaseApiUrl = "https://api.fitbit.com/";
public const string TemporaryCredentialsRequestTokenUri = "oauth/request_token";
public const string TemporaryCredentialsAccessTokenUri = "oauth/access_token";
public const string AuthorizeUri = "oauth/authorize";
public const string LogoutAndAuthorizeUri = "oauth/logout_and_authorize";
public static string BaseApiUrl => "https://api.fitbit.com/";

/// <summary>
/// The fitbit api url for oAuth2 token request
/// </summary>
public static string OAuth2TokenUrl => $"{BaseApiUrl}oauth2/token";

public const string FloorsUnsupportedOnDeviceError = "Invalid time series resource path: /activities/floors";

public class Headers
Expand Down
95 changes: 31 additions & 64 deletions Fitbit.Portable/FitbitClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ namespace Fitbit.Api.Portable
{
public class FitbitClient : IFitbitClient
{
public FitbitAppCredentials AppCredentials { get; private set; }

private OAuth2AccessToken _accesToken;
public OAuth2AccessToken AccessToken
{
Expand All @@ -28,116 +26,85 @@ public OAuth2AccessToken AccessToken
_accesToken = value;
//If we update the AccessToken after HttpClient has been created, then reconfigure authorization header
if (HttpClient != null)
{
ConfigureAuthorizationHeader();
}
}
}

/// <summary>
/// The httpclient which will be used for the api calls through the FitbitClient instance
/// </summary>
public HttpClient HttpClient { get; private set; }

public ITokenManager TokenManager { get; private set; }
public bool OAuth2TokenAutoRefresh { get; set; }
public List<IFitbitInterceptor> FitbitInterceptorPipeline { get; private set; }


private List<IFitbitInterceptor> FitbitInterceptorPipeline { get; set; }

/// <summary>
/// Simplest constructor for OAuth2- requires the minimum information required by FitBit.Net client to make succesful calls to Fitbit Api
/// Simple constructor for OAuth2- requires the minimum information required by FitBit.Net client to make succesful calls to Fitbit Api
/// </summary>
/// <param name="credentials">Obtain this information from your developer dashboard. App credentials are required to perform token refresh</param>
/// <param name="accessToken">Authenticate with Fitbit API using OAuth2. Authenticator2 class is a helper for this process</param>
/// <param name="interceptor">An interface that enables sniffing all outgoing and incoming http requests from FitbitClient</param>
public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, IFitbitInterceptor interceptor = null, bool enableOAuth2TokenRefresh = true, ITokenManager tokenManager = null)
/// <param name="enableOAuth2TokenRefresh"></param>
/// <param name="tokenManager">Implementation of ITokenManager which is required for the scope of the FitbitClient instance.</param>
public FitbitClient(OAuth2AccessToken accessToken, IFitbitInterceptor interceptor = null, bool enableOAuth2TokenRefresh = true, ITokenManager tokenManager = null)
{
this.AppCredentials = credentials;
this.AccessToken = accessToken;

this.FitbitInterceptorPipeline = new List<IFitbitInterceptor>();


AccessToken = accessToken;
TokenManager = tokenManager;
FitbitInterceptorPipeline = new List<IFitbitInterceptor>();

if (interceptor != null)
{
this.FitbitInterceptorPipeline.Add(interceptor);
FitbitInterceptorPipeline.Add(interceptor);
}

ConfigureTokenManager(tokenManager);

//Auto refresh should always be the last handle to be registered.
ConfigureAutoRefresh(enableOAuth2TokenRefresh);

CreateHttpClientForOAuth2();
}

private void ConfigureAutoRefresh(bool enableOAuth2TokenRefresh)
{
this.OAuth2TokenAutoRefresh = enableOAuth2TokenRefresh;
OAuth2TokenAutoRefresh = enableOAuth2TokenRefresh;
if (OAuth2TokenAutoRefresh)
this.FitbitInterceptorPipeline.Add(new OAuth2AutoRefreshInterceptor());
return;
{
FitbitInterceptorPipeline.Add(new OAuth2AutoRefreshInterceptor());
}
}

/// <summary>
/// Simplest constructor for OAuth2- requires the minimum information required by FitBit.Net client to make succesful calls to Fitbit Api
/// Simple constructor for OAuth2- requires the minimum information required by FitBit.Net client to make successful calls to Fitbit Api
/// </summary>
/// <param name="credentials">Obtain this information from your developer dashboard. App credentials are required to perform token refresh</param>
/// <param name="accessToken">Authenticate with Fitbit API using OAuth2. Authenticator2 class is a helper for this process</param>
/// <param name="interceptor">An interface that enables sniffing all outgoing and incoming http requests from FitbitClient</param>
public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, List<IFitbitInterceptor> interceptors, bool enableOAuth2TokenRefresh = true, ITokenManager tokenManager = null)
/// <param name="interceptors">An interface that enables sniffing all outgoing and incoming http requests from FitbitClient</param>
/// <param name="enableOAuth2TokenRefresh"></param>
/// <param name="tokenManager">Implementation of ITokenManager which is required for the scope of the FitbitClient instance.</param>
public FitbitClient(OAuth2AccessToken accessToken, List<IFitbitInterceptor> interceptors, bool enableOAuth2TokenRefresh = true, ITokenManager tokenManager = null)
{
this.AppCredentials = credentials;
this.AccessToken = accessToken;

this.FitbitInterceptorPipeline = new List<IFitbitInterceptor>();
AccessToken = accessToken;
TokenManager = tokenManager;

if (interceptors != null && interceptors.Count > 0)
this.FitbitInterceptorPipeline.AddRange(interceptors);
FitbitInterceptorPipeline = new List<IFitbitInterceptor>();

ConfigureTokenManager(tokenManager);
if (interceptors?.Count > 0)
{
FitbitInterceptorPipeline.AddRange(interceptors);
}

//Auto refresh should always be the last handle to be registered.
ConfigureAutoRefresh(enableOAuth2TokenRefresh);
CreateHttpClientForOAuth2();
}


public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, bool enableOAuth2TokenRefresh) : this(credentials, accessToken, null, enableOAuth2TokenRefresh)
{

}

public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, List<IFitbitInterceptor> interceptors, bool enableOAuth2TokenRefresh) : this(credentials, accessToken, interceptors, enableOAuth2TokenRefresh, null)
{

}

public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, List<IFitbitInterceptor> interceptors, ITokenManager tokenManager) : this(credentials, accessToken, interceptors, true, tokenManager)
{

}

public FitbitClient(FitbitAppCredentials credentials, OAuth2AccessToken accessToken, IFitbitInterceptor interceptor, ITokenManager tokenManager) : this(credentials, accessToken, interceptor, true, tokenManager)
{

}


/// <summary>
/// Advanced mode for library usage. Allows custom creation of HttpClient to account for future authentication methods
/// </summary>
/// <param name="customFactory">A function or lambda expression who is in charge of creating th HttpClient. It takes as an argument a HttpMessageHandler which does wiring for IFitbitInterceptor. To use IFitbitInterceptor you must pass this HttpMessageHandler as anargument to the constuctor of HttpClient</param>
/// <param name="interceptor">An interface that enables sniffing all outgoing and incoming http requests from FitbitClient</param>
public FitbitClient(Func<HttpMessageHandler, HttpClient> customFactory, IFitbitInterceptor interceptor = null, ITokenManager tokenManager = null)
{
this.OAuth2TokenAutoRefresh = false;

ConfigureTokenManager(tokenManager);
this.HttpClient = customFactory(new FitbitHttpMessageHandler(this, interceptor));
}

private void ConfigureTokenManager(ITokenManager tokenManager)
{
TokenManager = tokenManager ?? new DefaultTokenManager();
TokenManager = tokenManager;
HttpClient = customFactory(new FitbitHttpMessageHandler(this, interceptor));
}

private void CreateHttpClientForOAuth2()
Expand Down
Loading