From 73bbabfe137f5227cdf5a8fe45a9e9c1632acb2b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 12:09:32 +0000 Subject: [PATCH 1/3] Add Dependency Injection section to README Document how to use IPDataClient with IHttpClientFactory and ASP.NET Core dependency injection for proper connection pool management. Closes #20 https://claude.ai/code/session_013C9LnDefoZu8s7V61xAfGR --- .github/README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/README.md b/.github/README.md index 3c11e4f..b181c9c 100644 --- a/.github/README.md +++ b/.github/README.md @@ -19,6 +19,7 @@ - [Currency](#currency) - [Threat](#threat) - [EU Endpoint](#eu-endpoint) +- [Dependency Injection](#dependency-injection) - [Migrating from v2 to v3](#migrating-from-v2-to-v3) - [Contributing](#contributing) - [Versioning](#versioning) @@ -143,6 +144,41 @@ var client = new IPDataClient("API_KEY", new Uri("https://eu-api.ipdata.co")); var ipInfo = await client.Lookup("8.8.8.8"); ``` +## Dependency Injection + +If you're using ASP.NET Core, you can register `IPDataClient` with `IHttpClientFactory` to benefit from managed connection pooling and handler lifetimes: + +```csharp +// In Program.cs or Startup.cs +services.AddHttpClient("ipdata"); +services.AddSingleton(sp => +{ + var factory = sp.GetRequiredService(); + var httpClient = factory.CreateClient("ipdata"); + return new IPDataClient("API_KEY", httpClient); +}); +``` + +Then inject `IIPDataClient` wherever you need it: + +```csharp +public class MyService +{ + private readonly IIPDataClient _ipDataClient; + + public MyService(IIPDataClient ipDataClient) + { + _ipDataClient = ipDataClient; + } + + public async Task GetCountry(string ip) + { + var result = await _ipDataClient.Lookup(ip); + return result.CountryName; + } +} +``` + ## Migrating from v2 to v3 v3.0.0 renames all public types to follow [.NET naming conventions](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions) for two-letter acronyms. It also adds EU endpoint support and a `Company` lookup. From c231f134851b1bcff153a01aced6fb6c68ba6948 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 12:30:39 +0000 Subject: [PATCH 2/3] Improve unit tests: reduce duplication and extract JSON test data to files - Move inline JSON test data from TestDataSource.cs into separate .json files under DataSources/TestData/, loaded as embedded resources - Consolidate 40 duplicate error-handling tests in IPDataClientTests into 11 parameterized tests using MemberData with status code/exception pairs - Merge three near-identical exception test classes (BadRequest, Forbidden, Unauthorized) into a single DerivedExceptionTests class Closes #13 https://claude.ai/code/session_013C9LnDefoZu8s7V61xAfGR --- .../DataSources/TestData/Carrier.json | 5 + .../DataSources/TestData/Company.json | 6 + .../DataSources/TestData/IPLookupResult.json | 71 ++ .../DataSources/TestDataSource.cs | 20 +- .../Exceptions/BadRequestExceptionTests.cs | 50 -- .../Exceptions/DerivedExceptionTests.cs | 58 ++ .../Exceptions/ForbiddenExceptionTests.cs | 50 -- .../Exceptions/UnauthorizedExceptionTests.cs | 50 -- test/Unit/IPData.Tests/IPData.Tests.csproj | 4 + test/Unit/IPData.Tests/IPDataClientTests.cs | 833 +++--------------- 10 files changed, 267 insertions(+), 880 deletions(-) create mode 100644 test/Unit/IPData.Tests/DataSources/TestData/Carrier.json create mode 100644 test/Unit/IPData.Tests/DataSources/TestData/Company.json create mode 100644 test/Unit/IPData.Tests/DataSources/TestData/IPLookupResult.json delete mode 100644 test/Unit/IPData.Tests/Exceptions/BadRequestExceptionTests.cs create mode 100644 test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs delete mode 100644 test/Unit/IPData.Tests/Exceptions/ForbiddenExceptionTests.cs delete mode 100644 test/Unit/IPData.Tests/Exceptions/UnauthorizedExceptionTests.cs diff --git a/test/Unit/IPData.Tests/DataSources/TestData/Carrier.json b/test/Unit/IPData.Tests/DataSources/TestData/Carrier.json new file mode 100644 index 0000000..c5692cb --- /dev/null +++ b/test/Unit/IPData.Tests/DataSources/TestData/Carrier.json @@ -0,0 +1,5 @@ +{ + "name": "T-Mobile", + "mcc": "310", + "mnc": "160" +} diff --git a/test/Unit/IPData.Tests/DataSources/TestData/Company.json b/test/Unit/IPData.Tests/DataSources/TestData/Company.json new file mode 100644 index 0000000..d9f63ee --- /dev/null +++ b/test/Unit/IPData.Tests/DataSources/TestData/Company.json @@ -0,0 +1,6 @@ +{ + "name": "Google LLC", + "domain": "google.com", + "network": "8.8.8.0/24", + "type": "business" +} diff --git a/test/Unit/IPData.Tests/DataSources/TestData/IPLookupResult.json b/test/Unit/IPData.Tests/DataSources/TestData/IPLookupResult.json new file mode 100644 index 0000000..1acfaa5 --- /dev/null +++ b/test/Unit/IPData.Tests/DataSources/TestData/IPLookupResult.json @@ -0,0 +1,71 @@ +{ + "ip": "91.225.201.108", + "is_eu": false, + "city": "Lviv", + "region": "L'vivs'ka Oblast'", + "region_code": "46", + "region_type": "oblast", + "country_name": "Ukraine", + "country_code": "UA", + "continent_name": "Europe", + "continent_code": "EU", + "latitude": 49.8486, + "longitude": 24.0323, + "postal": "79000", + "calling_code": "380", + "flag": "https://ipdata.co/flags/ua.png", + "emoji_flag": "\uD83C\uDDFA\uD83C\uDDE6", + "emoji_unicode": "U+1F1FA U+1F1E6", + "asn": { + "asn": "AS49824", + "name": "PC \"Astra-net\"", + "domain": "astra.in.ua", + "route": "91.225.200.0/22", + "type": "isp" + }, + "company": { + "name": "Astra-net", + "domain": "astra.in.ua", + "network": "91.225.200.0/22", + "type": "isp" + }, + "carrier": { + "name": "Kyivstar", + "mcc": "255", + "mnc": "03" + }, + "languages": [ + { + "name": "Ukrainian", + "native": "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", + "code": "uk" + } + ], + "currency": { + "name": "Ukrainian Hryvnia", + "code": "UAH", + "symbol": "\u20B4", + "native": "\u20B4", + "plural": "Ukrainian hryvnias" + }, + "time_zone": { + "name": "Europe/Kiev", + "abbr": "EET", + "offset": "+0200", + "is_dst": false, + "current_time": "2020-01-30T23:16:19.129316+02:00" + }, + "threat": { + "is_tor": false, + "is_icloud_relay": false, + "is_proxy": false, + "is_datacenter": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "blocklists": [] + }, + "status": 200 +} diff --git a/test/Unit/IPData.Tests/DataSources/TestDataSource.cs b/test/Unit/IPData.Tests/DataSources/TestDataSource.cs index 2ff97e2..def8814 100644 --- a/test/Unit/IPData.Tests/DataSources/TestDataSource.cs +++ b/test/Unit/IPData.Tests/DataSources/TestDataSource.cs @@ -1,4 +1,6 @@ using System.Collections.Generic; +using System.IO; +using System.Reflection; namespace IPData.Tests.DataSources { @@ -13,17 +15,29 @@ public static IEnumerable EmptyOrWhitespaceString() public static IEnumerable IPLookupResultData() { - yield return new object[] { "{\"ip\":\"91.225.201.108\",\"is_eu\":false,\"city\":\"Lviv\",\"region\":\"L'vivs'ka Oblast'\",\"region_code\":\"46\",\"region_type\":\"oblast\",\"country_name\":\"Ukraine\",\"country_code\":\"UA\",\"continent_name\":\"Europe\",\"continent_code\":\"EU\",\"latitude\":49.8486,\"longitude\":24.0323,\"postal\":\"79000\",\"calling_code\":\"380\",\"flag\":\"https:\\/\\/ipdata.co\\/flags\\/ua.png\",\"emoji_flag\":\"\uD83C\uDDFA\uD83C\uDDE6\",\"emoji_unicode\":\"U+1F1FA U+1F1E6\",\"asn\":{\"asn\":\"AS49824\",\"name\":\"PC \\\"Astra-net\\\"\",\"domain\":\"astra.in.ua\",\"route\":\"91.225.200.0\\/22\",\"type\":\"isp\"},\"company\":{\"name\":\"Astra-net\",\"domain\":\"astra.in.ua\",\"network\":\"91.225.200.0\\/22\",\"type\":\"isp\"},\"carrier\":{\"name\":\"Kyivstar\",\"mcc\":\"255\",\"mnc\":\"03\"},\"languages\":[{\"name\":\"Ukrainian\",\"native\":\"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430\",\"code\":\"uk\"}],\"currency\":{\"name\":\"Ukrainian Hryvnia\",\"code\":\"UAH\",\"symbol\":\"\u20B4\",\"native\":\"\u20B4\",\"plural\":\"Ukrainian hryvnias\"},\"time_zone\":{\"name\":\"Europe\\/Kiev\",\"abbr\":\"EET\",\"offset\":\"+0200\",\"is_dst\":false,\"current_time\":\"2020-01-30T23:16:19.129316+02:00\"},\"threat\":{\"is_tor\":false,\"is_icloud_relay\":false,\"is_proxy\":false,\"is_datacenter\":false,\"is_anonymous\":false,\"is_known_attacker\":false,\"is_known_abuser\":false,\"is_threat\":false,\"is_bogon\":false,\"blocklists\":[]},\"status\":200}" }; + yield return new object[] { ReadJsonFile("IPLookupResult.json") }; } public static IEnumerable CarrierData() { - yield return new object[] { "{\"name\":\"T-Mobile\",\"mcc\":\"310\",\"mnc\":\"160\"\r\n}" }; + yield return new object[] { ReadJsonFile("Carrier.json") }; } public static IEnumerable CompanyData() { - yield return new object[] { "{\"name\":\"Google LLC\",\"domain\":\"google.com\",\"network\":\"8.8.8.0\\/24\",\"type\":\"business\"}" }; + yield return new object[] { ReadJsonFile("Company.json") }; + } + + private static string ReadJsonFile(string fileName) + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"IPData.Tests.DataSources.TestData.{fileName}"; + + using (var stream = assembly.GetManifestResourceStream(resourceName)) + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } } } } diff --git a/test/Unit/IPData.Tests/Exceptions/BadRequestExceptionTests.cs b/test/Unit/IPData.Tests/Exceptions/BadRequestExceptionTests.cs deleted file mode 100644 index 8254fec..0000000 --- a/test/Unit/IPData.Tests/Exceptions/BadRequestExceptionTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net; -using FluentAssertions; -using IPData.Exceptions; -using Xunit; - -namespace IPData.Tests.Exceptions -{ - public class BadRequestExceptionTests - { - [Fact] - public void BadRequestException_WhenCreate_ShouldReturnStatusCode() - { - // Act - var sut = new BadRequestException(); - - // Assert - sut.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Fact] - public void BadRequestException_WhenCreateWithoutParams_ShouldReturnApiError() - { - // Act - var sut = new BadRequestException(); - - // Assert - sut.ApiError.Should().NotBeNull(); - } - - [Theory, AutoMoqData] - public void BadRequestException_WhenCreateWithContent_ShouldReturnApiErrorWithMessage(string content) - { - // Act - var sut = new BadRequestException(content); - - // Assert - sut.ApiError.Message.Should().Be(content); - } - - [Theory, AutoMoqData] - public void BadRequestException_WhenCreateWithContent_ShouldBeMessage(string content) - { - // Act - var sut = new BadRequestException(content); - - // Assert - sut.Message.Should().Be(content); - } - } -} diff --git a/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs b/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs new file mode 100644 index 0000000..914b04e --- /dev/null +++ b/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Net; +using FluentAssertions; +using IPData.Exceptions; +using Xunit; + +namespace IPData.Tests.Exceptions +{ + public class DerivedExceptionTests + { + public static IEnumerable ExceptionStatusCodeData() + { + yield return new object[] { new BadRequestException(), HttpStatusCode.BadRequest }; + yield return new object[] { new ForbiddenException(), HttpStatusCode.Forbidden }; + yield return new object[] { new UnauthorizedException(), HttpStatusCode.Unauthorized }; + } + + [Theory] + [MemberData(nameof(ExceptionStatusCodeData))] + public void Exception_WhenCreate_ShouldReturnCorrectStatusCode( + ApiException sut, HttpStatusCode expectedStatusCode) + { + sut.StatusCode.Should().Be(expectedStatusCode); + } + + [Theory] + [MemberData(nameof(ExceptionStatusCodeData))] + public void Exception_WhenCreateWithoutParams_ShouldReturnApiError( + ApiException sut, HttpStatusCode _) + { + sut.ApiError.Should().NotBeNull(); + } + + public static IEnumerable ExceptionWithContentData() + { + var content = "test error message"; + yield return new object[] { new BadRequestException(content), content }; + yield return new object[] { new ForbiddenException(content), content }; + yield return new object[] { new UnauthorizedException(content), content }; + } + + [Theory] + [MemberData(nameof(ExceptionWithContentData))] + public void Exception_WhenCreateWithContent_ShouldReturnApiErrorWithMessage( + ApiException sut, string expectedMessage) + { + sut.ApiError.Message.Should().Be(expectedMessage); + } + + [Theory] + [MemberData(nameof(ExceptionWithContentData))] + public void Exception_WhenCreateWithContent_ShouldBeMessage( + ApiException sut, string expectedMessage) + { + sut.Message.Should().Be(expectedMessage); + } + } +} diff --git a/test/Unit/IPData.Tests/Exceptions/ForbiddenExceptionTests.cs b/test/Unit/IPData.Tests/Exceptions/ForbiddenExceptionTests.cs deleted file mode 100644 index dd7fb99..0000000 --- a/test/Unit/IPData.Tests/Exceptions/ForbiddenExceptionTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net; -using FluentAssertions; -using IPData.Exceptions; -using Xunit; - -namespace IPData.Tests.Exceptions -{ - public class ForbiddenExceptionTests - { - [Fact] - public void ForbiddenException_WhenCreate_ShouldReturnStatusCode() - { - // Act - var sut = new ForbiddenException(); - - // Assert - sut.StatusCode.Should().Be(HttpStatusCode.Forbidden); - } - - [Fact] - public void ForbiddenException_WhenCreateWithoutParams_ShouldReturnApiError() - { - // Act - var sut = new ForbiddenException(); - - // Assert - sut.ApiError.Should().NotBeNull(); - } - - [Theory, AutoMoqData] - public void ForbiddenException_WhenCreateWithContent_ShouldReturnApiErrorWithMessage(string content) - { - // Act - var sut = new ForbiddenException(content); - - // Assert - sut.ApiError.Message.Should().Be(content); - } - - [Theory, AutoMoqData] - public void ForbiddenException_WhenCreateWithContent_ShouldBeMessage(string content) - { - // Act - var sut = new ForbiddenException(content); - - // Assert - sut.Message.Should().Be(content); - } - } -} diff --git a/test/Unit/IPData.Tests/Exceptions/UnauthorizedExceptionTests.cs b/test/Unit/IPData.Tests/Exceptions/UnauthorizedExceptionTests.cs deleted file mode 100644 index 7749fdd..0000000 --- a/test/Unit/IPData.Tests/Exceptions/UnauthorizedExceptionTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net; -using FluentAssertions; -using IPData.Exceptions; -using Xunit; - -namespace IPData.Tests.Exceptions -{ - public class UnauthorizedExceptionTests - { - [Fact] - public void UnauthorizedException_WhenCreate_ShouldReturnStatusCode() - { - // Act - var sut = new UnauthorizedException(); - - // Assert - sut.StatusCode.Should().Be(HttpStatusCode.Unauthorized); - } - - [Fact] - public void UnauthorizedException_WhenCreateWithoutParams_ShouldReturnApiError() - { - // Act - var sut = new UnauthorizedException(); - - // Assert - sut.ApiError.Should().NotBeNull(); - } - - [Theory, AutoMoqData] - public void UnauthorizedException_WhenCreateWithContent_ShouldReturnApiErrorWithMessage(string content) - { - // Act - var sut = new UnauthorizedException(content); - - // Assert - sut.ApiError.Message.Should().Be(content); - } - - [Theory, AutoMoqData] - public void UnauthorizedException_WhenCreateWithContent_ShouldBeMessage(string content) - { - // Act - var sut = new UnauthorizedException(content); - - // Assert - sut.Message.Should().Be(content); - } - } -} diff --git a/test/Unit/IPData.Tests/IPData.Tests.csproj b/test/Unit/IPData.Tests/IPData.Tests.csproj index 0ccea55..627a296 100644 --- a/test/Unit/IPData.Tests/IPData.Tests.csproj +++ b/test/Unit/IPData.Tests/IPData.Tests.csproj @@ -10,6 +10,10 @@ + + + + diff --git a/test/Unit/IPData.Tests/IPDataClientTests.cs b/test/Unit/IPData.Tests/IPDataClientTests.cs index 3c61333..1662abe 100644 --- a/test/Unit/IPData.Tests/IPDataClientTests.cs +++ b/test/Unit/IPData.Tests/IPDataClientTests.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -65,844 +66,222 @@ public void IPDataClient_WhenCreatedWithValidApiKeyAndHttpClient_ShouldCreateCli act.Should().NotThrow(); } - [Theory, AutoMoqData] - public async Task Lookup_WhenStatusCode400_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup().ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenStatusCode403_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) + public static IEnumerable StatusCodeExceptionData() { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup().ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenStatusCode401_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup().ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + yield return new object[] { HttpStatusCode.BadRequest, typeof(BadRequestException) }; + yield return new object[] { HttpStatusCode.Forbidden, typeof(ForbiddenException) }; + yield return new object[] { HttpStatusCode.Unauthorized, typeof(UnauthorizedException) }; + yield return new object[] { (HttpStatusCode)0, typeof(ApiException) }; } - [Theory, AutoMoqData] - public async Task Lookup_WhenUnknownStatusCode_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Lookup_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Lookup().ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("69.78.70.144").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("69.78.70.144").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("69.78.70.144").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Lookup_WhenCalledWithIpAndErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Lookup("69.78.70.144").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIpList_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - var ipList = new string[] { "1.1.1.1", "2.2.2.2", "3.3.3.3" }; - Func act = async () => { await sut.Lookup(ipList).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIpList_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - var ipList = new string[] { "1.1.1.1", "2.2.2.2", "3.3.3.3" }; - Func act = async () => { await sut.Lookup(ipList).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIpList_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - var ipList = new string[] { "1.1.1.1", "2.2.2.2", "3.3.3.3" }; - Func act = async () => { await sut.Lookup(ipList).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithIpList_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Lookup_WhenCalledWithIpListAndErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); var ipList = new string[] { "1.1.1.1", "2.2.2.2", "3.3.3.3" }; Func act = async () => { await sut.Lookup(ipList).ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelector_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.CountryName).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelector_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.CountryName).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelector_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.CountryName).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelector_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Lookup_WhenCalledWithSelectorAndErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Lookup("1.1.1.1", x => x.CountryName).ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelectors_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.Asn, x => x.City).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelectors_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.Asn, x => x.City).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelectors_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Lookup("1.1.1.1", x => x.Asn, x => x.City).ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Lookup_WhenCalledWithSelectors_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Lookup_WhenCalledWithSelectorsAndErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Lookup("1.1.1.1", x => x.Asn, x => x.City).ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Company_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Company("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Company_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Company("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Company_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Company("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Company_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Company_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Company("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Carrier_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Carrier("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Carrier_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Carrier("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Carrier_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Carrier("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Carrier_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Carrier_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Carrier("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Asn_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Asn("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Asn_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Asn("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Asn_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Asn("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Asn_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Asn_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Asn("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task TimeZone_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.TimeZone("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task TimeZone_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.TimeZone("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task TimeZone_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.TimeZone("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task TimeZone_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task TimeZone_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.TimeZone("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Currency_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Currency("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Currency_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Currency("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Currency_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Currency("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Currency_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Currency_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Currency("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } - [Theory, AutoMoqData] - public async Task Threat_WhenCalledWithIp_ShouldThrowBadRequestException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.BadRequest)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Threat("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Threat_WhenCalledWithIp_ShouldThrowForbiddenException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Forbidden)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Threat("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Threat_WhenCalledWithIp_ShouldThrowUnauthorizedException( - [Frozen] Mock httpClient, - string apiKey) - { - // Arrange - httpClient - .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.Unauthorized)); - - var sut = new IPDataClient(apiKey, httpClient.Object); - Func act = async () => { await sut.Threat("1.1.1.1").ConfigureAwait(false); }; - - // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); - } - - [Theory, AutoMoqData] - public async Task Threat_WhenCalledWithIp_ShouldThrowApiException( - [Frozen] Mock httpClient, - string apiKey) + [Theory] + [MemberData(nameof(StatusCodeExceptionData))] + public async Task Threat_WhenErrorStatusCode_ShouldThrowExpectedException( + HttpStatusCode statusCode, Type expectedExceptionType) { // Arrange + var httpClient = new Mock(); httpClient .Setup(x => x.SendAsync(It.IsAny())) - .ReturnsAsync(new HttpResponseMessage(0)); + .ReturnsAsync(new HttpResponseMessage(statusCode)); - var sut = new IPDataClient(apiKey, httpClient.Object); + var sut = new IPDataClient("test-api-key", httpClient.Object); Func act = async () => { await sut.Threat("1.1.1.1").ConfigureAwait(false); }; // Act/Assert - await act.Should() - .ThrowAsync() - .ConfigureAwait(false); + (await act.Should().ThrowAsync().ConfigureAwait(false)) + .And.Should().BeOfType(expectedExceptionType); } } } From 59438e975975d942a6a2026bad175e4511b90493 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 12:33:40 +0000 Subject: [PATCH 3/3] Fix xUnit1026 warning for unused parameter in DerivedExceptionTests https://claude.ai/code/session_013C9LnDefoZu8s7V61xAfGR --- .../IPData.Tests/Exceptions/DerivedExceptionTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs b/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs index 914b04e..bbe1d6d 100644 --- a/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs +++ b/test/Unit/IPData.Tests/Exceptions/DerivedExceptionTests.cs @@ -15,6 +15,13 @@ public static IEnumerable ExceptionStatusCodeData() yield return new object[] { new UnauthorizedException(), HttpStatusCode.Unauthorized }; } + public static IEnumerable ExceptionData() + { + yield return new object[] { new BadRequestException() }; + yield return new object[] { new ForbiddenException() }; + yield return new object[] { new UnauthorizedException() }; + } + [Theory] [MemberData(nameof(ExceptionStatusCodeData))] public void Exception_WhenCreate_ShouldReturnCorrectStatusCode( @@ -24,9 +31,9 @@ public void Exception_WhenCreate_ShouldReturnCorrectStatusCode( } [Theory] - [MemberData(nameof(ExceptionStatusCodeData))] + [MemberData(nameof(ExceptionData))] public void Exception_WhenCreateWithoutParams_ShouldReturnApiError( - ApiException sut, HttpStatusCode _) + ApiException sut) { sut.ApiError.Should().NotBeNull(); }