Skip to content

Commit

Permalink
Merge pull request #44 from FromDoppler/DD-1292-dkims-sdk-integration
Browse files Browse the repository at this point in the history
SDK DKIMs integration
  • Loading branch information
blancfabian authored Nov 2, 2023
2 parents dfd43e4 + 673ff89 commit dc376a6
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 2 deletions.
156 changes: 156 additions & 0 deletions GreenArrow.Engine.Test/DKIMKeysApi/DKIMKeysApiTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using GreenArrow.Engine.DKIMKeysApi;
using GreenArrow.Engine.Extensions;
using GreenArrow.Engine.HttpSubmissionApi;
using GreenArrow.Engine.RestApi;
using Microsoft.Extensions.Options;
using Moq.Protected;
using System.Net;

namespace GreenArrow.Engine.Test.HttpSubmissionApi
{
public class DKIMKeysApiTest
{
private static readonly Fixture specimens = new();

private static IDKIMKeysApi CreateSut(
GreenArrowEngineSettings? settings = null,
IHttpClientFactory? httpClientFactory = null
)
{
settings ??= new GreenArrowEngineSettings { ServerUri = $"https://localhost/" };

return new DKIMKeysApiClient(
options: Options.Create(settings),
httpFactory: httpClientFactory ?? Mock.Of<IHttpClientFactory>()
);
}

private static Mock<HttpMessageHandler> CreateHttpMessageHandlerMock(HttpResponseMessage httpResponseMessage)
{
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(nameof(HttpClient.SendAsync), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(httpResponseMessage);

return httpMessageHandlerMock;
}

private static Mock<HttpMessageHandler> CreateHttpMessageHandlerMock(Exception exception)
{
var httpMessageHandlerMock = new Mock<HttpMessageHandler>();
httpMessageHandlerMock.Protected()
.Setup<Task<HttpResponseMessage>>(nameof(HttpClient.SendAsync), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ThrowsAsync(exception);

return httpMessageHandlerMock;
}

private static Mock<IHttpClientFactory> CreateHttpClientFactoryMock(HttpMessageHandler httpMessageHandler)
{
var httpClient = new HttpClient(httpMessageHandler);

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

return httpClientFactoryMock;
}

[Fact]
public async Task Should_request_to_the_configured_endpoint_name()
{
// Arrange
var settings = new GreenArrowEngineSettings { ServerUri = $"https://localhost/", DKIMKeysAPIEndpoint = "/api/v3/eng/dkim_keys" };
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = specimens.Create<HttpStatusCode>(),
};

var httpMessageHandlerMock = CreateHttpMessageHandlerMock(httpResponseMessage);
var httpClientFactoryMock = CreateHttpClientFactoryMock(httpMessageHandlerMock.Object);
var sut = CreateSut(settings, httpClientFactory: httpClientFactoryMock.Object);

var request = specimens.Create<DKIMKeysRequest>();

// Act
await sut.PostAsync(request, CancellationToken.None);

// Assert
httpMessageHandlerMock.Protected().Verify(
nameof(HttpClient.SendAsync),
Times.Exactly(1),
ItExpr.Is<HttpRequestMessage>(request => request.RequestUri.AbsolutePath.EndsWith(settings.DKIMKeysAPIEndpoint)),
ItExpr.IsAny<CancellationToken>()
);
}

[Fact]
public async Task Post_should_return_http_status_code_on_sucessfull()
{
// Arrange
var httpResponseMessage = new HttpResponseMessage
{
StatusCode = specimens.Create<HttpStatusCode>(),
};

var httpMessageHandlerMock = CreateHttpMessageHandlerMock(httpResponseMessage);
var httpClientFactoryMock = CreateHttpClientFactoryMock(httpMessageHandlerMock.Object);
var sut = CreateSut(httpClientFactory: httpClientFactoryMock.Object);

var request = specimens.Create<DKIMKeysRequest>();

// Act
var result = await sut.PostAsync(request, CancellationToken.None);

// Assert
Assert.Equal(httpResponseMessage.StatusCode, result.HttpStatusCode);
}

[Fact]
public async Task Post_should_throw_RestApiException_upon_an_exception_in_the_implementation()
{
// Arrange
var exception = specimens.Create<Exception>();

var httpMessageHandlerMock = CreateHttpMessageHandlerMock(exception);
var httpClientFactoryMock = CreateHttpClientFactoryMock(httpMessageHandlerMock.Object);
var sut = CreateSut(httpClientFactory: httpClientFactoryMock.Object);

var request = specimens.Create<DKIMKeysRequest>();
var cancellationToken = specimens.Create<CancellationToken>();

// Act
var result = await Assert.ThrowsAsync<RestApiException>(() => sut.PostAsync(request, cancellationToken));

// Assert
Assert.Equal(exception, result.InnerException);
}

[Fact]
public async Task Post_should_return_deserialized_response_content_on_sucessfull()
{
// Arrange
var DKIMKeysResponse = specimens.Create<DKIMKeysResponse>();
var httpContent = new StringContent(DKIMKeysResponse.ToJson(true));

var httpResponseMessage = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = httpContent,
};

var httpMessageHandlerMock = CreateHttpMessageHandlerMock(httpResponseMessage);
var httpClientFactoryMock = CreateHttpClientFactoryMock(httpMessageHandlerMock.Object);
var sut = CreateSut(httpClientFactory: httpClientFactoryMock.Object);

var request = specimens.Create<DKIMKeysRequest>();

// Act
var result = await sut.PostAsync(request, CancellationToken.None);

// Assert
Assert.Equal(DKIMKeysResponse.Success, result.Content.Success);
}
}
}
79 changes: 79 additions & 0 deletions GreenArrow.Engine/DKIMKeysApi/DKIMKeysApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using GreenArrow.Engine.Extensions;
using GreenArrow.Engine.RestApi;
using Microsoft.Extensions.Options;
using System.Net;
using System.Net.Http.Headers;

namespace GreenArrow.Engine.DKIMKeysApi
{
/// <summary>
/// DKIM Keys API client implementation
/// </summary>
public class DKIMKeysApiClient : IDKIMKeysApi
{
private readonly GreenArrowEngineSettings _settings;
private readonly IHttpClientFactory _httpFactory;

private readonly string _endpoint;

/// <summary>
/// Initializes a new instance of Green Arrow DKIM Keys API Client
/// </summary>
/// <param name="options">Green Arrow settings with API Url and Authorization Token</param>
/// <param name="httpFactory">HttpClienFactory for create HttpClient objects</param>
public DKIMKeysApiClient(
IOptions<GreenArrowEngineSettings> options,
IHttpClientFactory httpFactory)
{
_settings = options.Value;
_httpFactory = httpFactory;
_endpoint = GetEndPoint();
}

private HttpClient CreateHttpClient()
{
var client = _httpFactory.CreateClient();
return client;
}

private string GetEndPoint()
{
var baseUri = new Uri(_settings.ServerUri);
var endpointUri = new Uri(baseUri, _settings.DKIMKeysAPIEndpoint);
return endpointUri.ToString();
}

/// <inheritdoc />
public async Task<IRestApiResponse<DKIMKeysResponse>> PostAsync(DKIMKeysRequest request, CancellationToken cancellationToken = default)
{
try
{
var jsonContent = request.ToJson();

var client = CreateHttpClient();

var authenticationString = $"{request.Username}:{request.Password}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);

var httpContent = new StringContent(jsonContent, encoding: default, mediaType: "application/json");
var httpResponse = await client.PostAsync(_endpoint, httpContent, cancellationToken);

if (httpResponse.StatusCode == HttpStatusCode.OK)
{
var result = await httpResponse.Content.ReadAsStringAsync(cancellationToken);
var content = result.ToObject<DKIMKeysResponse>();
return new RestApiResponse<DKIMKeysResponse>(httpResponse.StatusCode, content);
}

return new RestApiResponse<DKIMKeysResponse>(httpResponse.StatusCode);

}
catch (Exception exception)
{
throw new RestApiException("Unexpected exception", exception);
}
}
}
}
29 changes: 29 additions & 0 deletions GreenArrow.Engine/DKIMKeysApi/DKIMKeysRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using GreenArrow.Engine.Model;
using GreenArrow.Engine.RestApi;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace GreenArrow.Engine.DKIMKeysApi
{
/// <summary>
/// Create a DKIM Key Request
/// </summary>
[JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy), ItemNullValueHandling = NullValueHandling.Ignore)]
public class DKIMKeysRequest : IRestApiModel
{
/// <summary>
/// Username that is authorized to log in to GreenArrow Engine’s web interface.
/// </summary>
public string Username { get; set; }

/// <summary>
/// Password that is authorized to log in to GreenArrow Engine’s web interface.
/// </summary>
public string Password { get; set; }

/// <summary>
/// To create the private key
/// </summary>
public DkimKey DkimKey { get; set; }
}
}
42 changes: 42 additions & 0 deletions GreenArrow.Engine/DKIMKeysApi/DKIMKeysResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using GreenArrow.Engine.Model;
using GreenArrow.Engine.RestApi;

namespace GreenArrow.Engine.DKIMKeysApi
{
/// <summary>
/// GreenArrow Response is the full DKIM Key record data
/// </summary>
public class DKIMKeysResponse : IRestApiModel
{
/// <summary>
/// When request was succesful created
/// </summary>
public bool Success { get; init; }

/// <summary>
/// Full DKIM Key record data
/// </summary>
public DKIMKeysResponseData Data { get; init; }

/// <summary>
/// Error code when request was not accepted
/// </summary>
public string ErrorCode { get; init; }

/// <summary>
/// Error message when request was not accepted
/// </summary>
public string ErrorMessages { get; init; }
}

/// <summary>
/// Full DKIM Key record data
/// </summary>
public class DKIMKeysResponseData
{
/// <summary>
/// Full DKIM Key record data
/// </summary>
public DkimKey DkimKey { get; init; }
}
}
19 changes: 19 additions & 0 deletions GreenArrow.Engine/DKIMKeysApi/IDKIMKeysApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GreenArrow.Engine.RestApi;

namespace GreenArrow.Engine.DKIMKeysApi
{
/// <summary>
/// Represent the actions available in Green Arrow Engine DKIM Keys API
/// </summary>
/// <remarks><see href="https://www.greenarrowemail.com/docs/greenarrow-engine/API-V3/Engine/DKIM-Keys"/></remarks>
public interface IDKIMKeysApi
{
/// <summary>
/// Create a DKIM Key
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>A generic rest api response with the deserialized DKIM Keys API response when success</returns>
Task<IRestApiResponse<DKIMKeysResponse>> PostAsync(DKIMKeysRequest request, CancellationToken cancellationToken = default);
}
}
5 changes: 5 additions & 0 deletions GreenArrow.Engine/GreenArrowEngineSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ public class GreenArrowEngineSettings
/// The HTTP Submission API Endpoint
/// </summary>
public string HTTPSubmissionAPIEndpoint { get; set; } = "/api/v1/send.json";

/// <summary>
/// The DKIM API Endpoint
/// </summary>
public string DKIMKeysAPIEndpoint { get; set; } = "/api/v3/eng/dkim_keys";
}
}
4 changes: 2 additions & 2 deletions GreenArrow.Engine/HttpSubmissionApi/IHttpSubmissionApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ public interface IHttpSubmissionApi
/// Submit messages for delivery
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken">The cancellation toekn</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>A generic rest api response with the deserialized Http Submission API response when success</returns>
Task<IRestApiResponse<HttpSubmissionResponse>> PostAsync(HttpSubmissionRequest request, CancellationToken cancellationToken = default);

/// <summary>
/// Submit messages for delivery
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken">The cancellation toekn</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>A generic rest api response with the deserialized Http Submission API response when success</returns>
Task<IRestApiResponse<HttpSubmissionResponse>> PutAsync(HttpSubmissionRequest request, CancellationToken cancellationToken = default);
}
Expand Down
Loading

0 comments on commit dc376a6

Please sign in to comment.