Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unit Tests for ApiToolkitClientFactory Implementation and Interface Definition for IApiToolkitClientFactory #34

Merged
merged 2 commits into from
Aug 11, 2024
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
32 changes: 31 additions & 1 deletion ApiToolKit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json;
using static System.Web.HttpUtility;

Expand Down Expand Up @@ -184,7 +185,7 @@
}
}

public ObservingHandler APIToolkitObservingHandler(HttpContext? context = null, ATOptions? options = null)

Check warning on line 188 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 188 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
return new ObservingHandler(PublishMessageAsync, Metadata.ProjectId, context, options);
}
Expand Down Expand Up @@ -392,16 +393,16 @@
[JsonProperty("duration")]
public long Duration { get; set; }
[JsonProperty("errors")]
public List<ATError>? Errors { get; set; }

Check warning on line 396 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
[JsonProperty("tags")]
public List<string> Tags { get; set; }
[JsonProperty("service_version")]
public string ServiceVersion { get; set; }
[JsonProperty("parent_id")]
public string? ParentId { get; set; }

Check warning on line 402 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonProperty("msg_id")]
public string? MsgId { get; set; }

Check warning on line 405 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

}

Expand All @@ -424,10 +425,35 @@
public string StackTrace { get; set; }
}

public interface IApiToolkitClientFactory
{
HttpClient CreateClient(ATOptions options);
}

public class ApiToolkitClientFactory : IApiToolkitClientFactory
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServiceProvider _serviceProvider;

public ApiToolkitClientFactory(IHttpClientFactory httpClientFactory, IServiceProvider serviceProvider)
{
_httpClientFactory = httpClientFactory;
_serviceProvider = serviceProvider;
}

public HttpClient CreateClient(ATOptions options)
{
var client = _httpClientFactory.CreateClient();

var handler = _serviceProvider.GetRequiredService<ObservingHandler>();
handler.SetOptions(options);
return new HttpClient(handler);
}
}

public class ATOptions
{
public string? PathWildCard { get; set; }

Check warning on line 456 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public List<string> RedactHeaders { get; set; }
public List<string> RedactRequestBody { get; set; }
public List<string> RedactResponseBody { get; set; }
Expand All @@ -436,10 +462,14 @@
{
private readonly HttpContext _context;
private readonly Func<Payload, Task> _publishMessageAsync;
private readonly ATOptions _options;
private ATOptions _options;
private readonly string _project_id;
private readonly string? _msg_id;

Check warning on line 467 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
public void SetOptions(ATOptions options)
{
_options = options;
}
public ObservingHandler(Func<Payload, Task> publishMessage, string project_id, HttpContext? httpContext = null, ATOptions? options = null) : base(new HttpClientHandler())

Check warning on line 472 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 472 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
_context = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
_publishMessageAsync = publishMessage;
Expand Down Expand Up @@ -516,7 +546,7 @@
await _publishMessageAsync(payload);
return response;
}
catch (Exception _err)

Check warning on line 549 in ApiToolKit.cs

View workflow job for this annotation

GitHub Actions / build

The variable '_err' is declared but never used
{
return response;
}
Expand Down
75 changes: 74 additions & 1 deletion ApiToolkitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Builder;

using Moq;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
public class RedactTests
{
[Test]
Expand Down Expand Up @@ -208,3 +210,74 @@ public async Task MiddlewareTest_ReturnsNotFoundForRequest()
}
}


public class ApiToolkitClientFactoryTests
{
private Mock<IHttpClientFactory> _httpClientFactoryMock;
private Mock<IServiceProvider> _serviceProviderMock;
private Mock<ObservingHandler> _observingHandlerMock;
private IApiToolkitClientFactory _apiToolkitClientFactory;

[SetUp]
public void SetUp()
{
_httpClientFactoryMock = new Mock<IHttpClientFactory>();
_serviceProviderMock = new Mock<IServiceProvider>();
_observingHandlerMock = new Mock<ObservingHandler>();

// Set up the service provider to return the observing handler mock
_serviceProviderMock
.Setup(sp => sp.GetService(typeof(ObservingHandler)))
.Returns(_observingHandlerMock.Object);

// Initialize the ApiToolkitClientFactory with mocks
_apiToolkitClientFactory = new ApiToolkitClientFactory(_httpClientFactoryMock.Object, _serviceProviderMock.Object);
}

[Test]
public void CreateClient_ShouldReturnHttpClient_WithConfiguredHandler()
{
// Arrange
var options = new ATOptions
{
PathWildCard = "/posts/{id}",
RedactHeaders = new List<string> { "User-Agent" },
RedactRequestBody = new List<string> { "$.user.password" },
RedactResponseBody = new List<string> { "$.user.data.email" }
};

// Act
var client = _apiToolkitClientFactory.CreateClient(options);

// Assert
Assert.NotNull(client);
_observingHandlerMock.Verify(h => h.SetOptions(options), Times.Once);
}

[Test]
public void CreateClient_ShouldUseObservingHandler_FromServiceProvider()
{
// Act
var client = _apiToolkitClientFactory.CreateClient(new ATOptions());

// Assert
_serviceProviderMock.Verify(sp => sp.GetService(typeof(ObservingHandler)), Times.Once);
}

[Test]
public void CreateClient_ShouldThrowException_WhenHandlerNotRegistered()
{
// Arrange
var invalidServiceProvider = new Mock<IServiceProvider>();
invalidServiceProvider.Setup(sp => sp.GetService(typeof(ObservingHandler)))
.Returns(null); // Simulate missing handler registration

var factory = new ApiToolkitClientFactory(_httpClientFactoryMock.Object, invalidServiceProvider.Object);

// Act & Assert
Assert.Throws<NullReferenceException>(() => factory.CreateClient(new ATOptions()));
}
}



65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ Next, initialize APItoolkit in your application's entry point (e.g., `Program.cs
using ApiToolkit.Net;

// Initialize the APItoolkit client
builder.Services.AddTransient<ObservingHandler>();

// Register the custom API Toolkit Client Factory
builder.Services.AddSingleton<IApiToolkitClientFactory, ApiToolkitClientFactory>();

var config = new Config
{
ApiKey = "{ENTER_YOUR_API_KEY_HERE}",
Expand All @@ -60,6 +65,66 @@ app.Use(async (context, next) =>
# ...
```

## Usage

You can now use the IApiToolKitClientFactory Interface to directly make your Http requests

```csharp
public class MyService
{
private readonly IApiToolkitClientFactory _apiToolkitClientFactory;

public MyService(IApiToolkitClientFactory apiToolkitClientFactory)
{
_apiToolkitClientFactory = apiToolkitClientFactory;
}

public async Task<string> GetPostAsync()
{
var options = new ATOptions
{
PathWildCard = "/posts/{id}",
RedactHeaders = new[] { "User-Agent" },
RedactRequestBody = new[] { "$.user.password" },
RedactResponseBody = new[] { "$.user.data.email" }
};

var client = _apiToolkitClientFactory.CreateClient(options);
var response = await client.GetAsync("https://jsonplaceholder.typicode.com/posts/1");
return await response.Content.ReadAsStringAsync();
}
}
```

Traditional Middleware Setup
If you prefer to set up the middleware traditionally, here's how you can initialize APItoolkit in your application's entry point (e.g., Program.cs):

```csharp
using ApiToolkit.Net;

// Initialize the APItoolkit client
var config = new Config
{
ApiKey = "{ENTER_YOUR_API_KEY_HERE}",
Debug = false,
Tags = new List<string> { "environment: production", "region: us-east-1" },
ServiceVersion: "v2.0",
};
var client = await APIToolkit.NewClientAsync(config);

// Register the middleware to use the initialized client
app.Use(async (context, next) =>
{
var apiToolkit = new APIToolkit(next, client);
await apiToolkit.InvokeAsync(context);
});

// app.UseEndpoint(..)
// other middleware and logic
// ...
```


> [!NOTE]
>
> - Please make sure the APItoolkit middleware is added before `UseEndpoint` and other middleware are initialized.
Expand Down
1 change: 1 addition & 0 deletions apitoolkit-dotnet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.5" />
<!-- Deprecated -->
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
<PackageReference Include="NUnit" Version="3.13.3" />
Expand Down
Loading