Skip to content
Merged
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
23 changes: 17 additions & 6 deletions src/CheckoutSdk/OAuthAccessToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@ namespace Checkout
public sealed class OAuthAccessToken
{
public string Token { get; }
public string TokenType { get; }
private readonly DateTime? _expirationDate;

public static OAuthAccessToken FromOAuthServiceResponse(OAuthServiceResponse response)
{
return new OAuthAccessToken(response.AccessToken,
DateTime.Now.Add(TimeSpan.FromSeconds(response.ExpiresIn)));
if (!response.IsValid())
{
throw new ArgumentException("Invalid OAuth response");
}

return new OAuthAccessToken(
response.AccessToken,
response.TokenType,
DateTime.UtcNow.Add(TimeSpan.FromSeconds(response.ExpiresIn)));
}

private OAuthAccessToken(string token, DateTime expirationDate)
private OAuthAccessToken(string token, string tokenType, DateTime expirationDate)
{
Token = token;
_expirationDate = expirationDate;
Token = !string.IsNullOrWhiteSpace(token) ? token : throw new ArgumentException("Token cannot be empty");
TokenType = !string.IsNullOrWhiteSpace(tokenType)
? tokenType
: throw new ArgumentException("TokenType cannot be empty");
_expirationDate = expirationDate.ToUniversalTime();
}

public bool IsValid()
Expand All @@ -26,7 +37,7 @@ public bool IsValid()
return false;
}

return _expirationDate > DateTime.Now;
return _expirationDate > DateTime.UtcNow;
}
}
}
3 changes: 1 addition & 2 deletions src/CheckoutSdk/OAuthSdkCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public sealed class OAuthSdkCredentials : SdkCredentials
#if (NETSTANDARD2_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER)
private readonly ILogger _log = LogProvider.GetLogger(typeof(OAuthSdkCredentials));
#endif

private readonly string _clientId;
private readonly string _clientSecret;
private readonly JsonSerializer _serializer = new JsonSerializer();
Expand Down Expand Up @@ -80,9 +79,9 @@ private OAuthServiceResponse Request()
var httpRequest = new HttpRequestMessage(HttpMethod.Post, string.Empty);
var data = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", _clientId),
new KeyValuePair<string, string>("client_secret", _clientSecret),
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("scope", GetScopes())
};
httpRequest.Content = new FormUrlEncodedContent(data);
Expand Down
11 changes: 7 additions & 4 deletions src/CheckoutSdk/OAuthServiceResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ public sealed class OAuthServiceResponse
{
public string AccessToken { get; set; }

public string TokenType { get; set; }

public long ExpiresIn { get; set; }

public string Error { get; set; }

public bool IsValid()
{
return AccessToken != null && ExpiresIn != 0 && Error == null;
}
public bool IsValid() =>
!string.IsNullOrWhiteSpace(AccessToken) &&
!string.IsNullOrWhiteSpace(TokenType) &&
ExpiresIn > 0 &&
string.IsNullOrWhiteSpace(Error);
}
}
38 changes: 38 additions & 0 deletions test/CheckoutSdkTest/OAuthAccessTokenTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Xunit;

namespace Checkout
{
public class OAuthAccessTokenTests
{
[Fact]
public void ShouldReturnTrueAndTokenWhenResponseIsValid()
{
var response = new OAuthServiceResponse
{
AccessToken = "valid_token",
TokenType = "Bearer",
ExpiresIn = 3600
};

var token = OAuthAccessToken.FromOAuthServiceResponse(response);

Assert.NotNull(token);
Assert.Equal("valid_token", token.Token);
Assert.True(token.IsValid());
}

[Fact]
public void ShouldThrowExceptionWhenResponseIsInvalid()
{
var response = new OAuthServiceResponse
{
AccessToken = null,
TokenType = "Bearer",
ExpiresIn = 3600
};

Assert.Throws<ArgumentException>(() => OAuthAccessToken.FromOAuthServiceResponse(response));
}
}
}
91 changes: 91 additions & 0 deletions test/CheckoutSdkTest/OAuthSdkCredentialsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using Moq;
using Moq.Protected;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Checkout
{
public class OAuthSdkCredentialsTests
{
private OAuthSdkCredentials CreateSdkCredentials(HttpResponseMessage mockResponse)
{
var mockHttpMessageHandler = new Mock<HttpMessageHandler>(MockBehavior.Strict);

mockHttpMessageHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(
"SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(mockResponse)
.Verifiable();

var httpClient = new HttpClient(mockHttpMessageHandler.Object)
{
BaseAddress = new Uri("https://fake-auth.com")
};

var httpClientFactoryMock = new Mock<IHttpClientFactory>();
httpClientFactoryMock
.Setup(_ => _.CreateClient())
.Returns(httpClient);

return new OAuthSdkCredentials(
httpClientFactoryMock.Object,
new Uri("https://fake-auth.com"),
"test_client_id",
"test_client_secret",
new HashSet<OAuthScope>()
);
}

[Fact]
public void ShouldReturnAuthorizationHeaderWhenTokenIsValid()
{
using var mockResponse = new HttpResponseMessage();
mockResponse.StatusCode = HttpStatusCode.OK;
mockResponse.Content = new StringContent(
"{\"access_token\": \"valid_token\", \"token_type\": \"Bearer\", \"expires_in\": 3600}",
Encoding.UTF8,
"application/json");
var sdk = CreateSdkCredentials(mockResponse);
sdk.InitAccess();

var authorization = sdk.GetSdkAuthorization(SdkAuthorizationType.OAuth);
Assert.NotNull(authorization);

string expectedHeader = $"Bearer valid_token";
string actualHeader = authorization.GetAuthorizationHeader();

Assert.Equal(expectedHeader, actualHeader);
}

[Fact]
public void ShouldThrowExceptionWhenApiReturnsError()
{
using var mockResponse = new HttpResponseMessage();
mockResponse.StatusCode = HttpStatusCode.BadRequest;
mockResponse.Content = new StringContent(
"{\"error\": \"invalid_client\"}",
Encoding.UTF8,
"application/json");
var sdk = CreateSdkCredentials(mockResponse);

Assert.Throws<CheckoutAuthorizationException>(() => sdk.InitAccess());
}

[Fact]
public void ShouldThrowExceptionWhenResponseHasInvalidToken()
{
var response = new OAuthServiceResponse { AccessToken = null, TokenType = "Bearer", ExpiresIn = 3600 };

Assert.Throws<ArgumentException>(() => OAuthAccessToken.FromOAuthServiceResponse(response));
}
}
}
49 changes: 49 additions & 0 deletions test/CheckoutSdkTest/OAuthServiceResponseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Xunit;

namespace Checkout
{
public class OAuthServiceResponseTests
{
[Fact]
public void ShouldReturnTrueWhenResponseIsValid()
{
var response = new OAuthServiceResponse
{
AccessToken = "valid_token",
TokenType = "Bearer",
ExpiresIn = 3600,
Error = null
};

Assert.True(response.IsValid());
}

[Fact]
public void ShouldReturnFalseWhenResponseIsInvalid()
{
var response = new OAuthServiceResponse
{
AccessToken = null,
TokenType = "Bearer",
ExpiresIn = 3600,
Error = null
};

Assert.False(response.IsValid());
}

[Fact]
public void ShouldReturnFalseWhenExpiresInIsNegative()
{
var response = new OAuthServiceResponse
{
AccessToken = "valid_token",
TokenType = "Bearer",
ExpiresIn = -1,
Error = null
};

Assert.False(response.IsValid());
}
}
}
Loading