From ab5a92b60e24ba998bd15f91f0d808fcf01a9423 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 13:43:09 -0800 Subject: [PATCH 01/10] Update targets --- .github/workflows/codeql-analysis.yml | 3 +-- .github/workflows/test.yml | 3 +-- MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj | 4 ++-- MaxMind.MinFraud/MaxMind.MinFraud.csproj | 2 +- releasenotes.md | 4 ++++ 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 61bccb9..b1a0cf0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,9 +33,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 7.0.x 8.0.x + 9.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22a177a..9f23424 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,9 +21,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 7.0.x 8.0.x + 9.0.x - name: Build run: | diff --git a/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj b/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj index f2f7983..238f190 100644 --- a/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj +++ b/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj @@ -3,8 +3,8 @@ Test project for minFraud API 0.6.0 - net8.0;net7.0;net6.0;net472 - net8.0;net7.0;net6.0 + net9.0;net8.0;net481 + net9.0;net8.0 MaxMind.MinFraud.UnitTest MaxMind.MinFraud.UnitTest Apache-2.0 diff --git a/MaxMind.MinFraud/MaxMind.MinFraud.csproj b/MaxMind.MinFraud/MaxMind.MinFraud.csproj index 6878eff..e32eb27 100644 --- a/MaxMind.MinFraud/MaxMind.MinFraud.csproj +++ b/MaxMind.MinFraud/MaxMind.MinFraud.csproj @@ -3,7 +3,7 @@ API for MaxMind minFraud Score and Insights web services 5.1.0-beta.1 - net8.0;net7.0;net6.0;netstandard2.1;netstandard2.0 + net9.0;net8.0;netstandard2.1;netstandard2.0 true MaxMind.MinFraud ../MaxMind.snk diff --git a/releasenotes.md b/releasenotes.md index dec671c..dcdbcfe 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -4,6 +4,10 @@ Release Notes 5.1.0 ------------------ +* .NET 6.0 and .NET 7.0 have been removed as targets as they have both + reach their end of support from Microsoft. If you are using these versions, + the .NET Standard 2.1 target should continue working for you. +* .NET 9.0 has been added as a target. * The minFraud Factors subscores have been deprecated. They will be removed in March 2025. Please see [our release notes](https://dev.maxmind.com/minfraud/release-notes/2024/#deprecation-of-risk-factor-scoressubscores) for more information. From 3862adf5fd415f736cd1730a35bc0f1722d16287 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 13:44:33 -0800 Subject: [PATCH 02/10] Update language version to 13 --- MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj | 2 +- MaxMind.MinFraud/MaxMind.MinFraud.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj b/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj index 238f190..69ba368 100644 --- a/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj +++ b/MaxMind.MinFraud.UnitTest/MaxMind.MinFraud.UnitTest.csproj @@ -17,7 +17,7 @@ false false false - 12.0 + 13.0 enable latest true diff --git a/MaxMind.MinFraud/MaxMind.MinFraud.csproj b/MaxMind.MinFraud/MaxMind.MinFraud.csproj index e32eb27..66be586 100644 --- a/MaxMind.MinFraud/MaxMind.MinFraud.csproj +++ b/MaxMind.MinFraud/MaxMind.MinFraud.csproj @@ -24,7 +24,7 @@ false false false - 12.0 + 13.0 enable latest true From 66a7b27f4f4ef382b856ed78723491e5cf55baf0 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 13:47:13 -0800 Subject: [PATCH 03/10] Fix namespaces for internal classes --- MaxMind.MinFraud.UnitTest/WebServiceClientTest.cs | 1 + MaxMind.MinFraud/Request/Device.cs | 3 ++- MaxMind.MinFraud/Request/Event.cs | 3 ++- MaxMind.MinFraud/Request/Payment.cs | 3 ++- MaxMind.MinFraud/Request/Shipping.cs | 3 ++- MaxMind.MinFraud/Request/TransactionReport.cs | 3 ++- MaxMind.MinFraud/Util/EnumMemberValueConverter.cs | 2 +- MaxMind.MinFraud/Util/IPAddressConverter.cs | 2 +- MaxMind.MinFraud/Util/NetworkConverter.cs | 2 +- 9 files changed, 14 insertions(+), 8 deletions(-) diff --git a/MaxMind.MinFraud.UnitTest/WebServiceClientTest.cs b/MaxMind.MinFraud.UnitTest/WebServiceClientTest.cs index 378486c..9098cb1 100644 --- a/MaxMind.MinFraud.UnitTest/WebServiceClientTest.cs +++ b/MaxMind.MinFraud.UnitTest/WebServiceClientTest.cs @@ -1,5 +1,6 @@ using MaxMind.MinFraud.Exception; using MaxMind.MinFraud.Request; +using MaxMind.MinFraud.Util; using Microsoft.Extensions.Options; using RichardSzalay.MockHttp; using System; diff --git a/MaxMind.MinFraud/Request/Device.cs b/MaxMind.MinFraud/Request/Device.cs index 430e57e..7818eb3 100644 --- a/MaxMind.MinFraud/Request/Device.cs +++ b/MaxMind.MinFraud/Request/Device.cs @@ -1,4 +1,5 @@ -using System; +using MaxMind.MinFraud.Util; +using System; using System.Net; using System.Text.Json.Serialization; diff --git a/MaxMind.MinFraud/Request/Event.cs b/MaxMind.MinFraud/Request/Event.cs index 6c8fffb..00e4abb 100644 --- a/MaxMind.MinFraud/Request/Event.cs +++ b/MaxMind.MinFraud/Request/Event.cs @@ -1,4 +1,5 @@ -using System; +using MaxMind.MinFraud.Util; +using System; using System.Runtime.Serialization; using System.Text.Json.Serialization; diff --git a/MaxMind.MinFraud/Request/Payment.cs b/MaxMind.MinFraud/Request/Payment.cs index 00d67d7..669ca3a 100644 --- a/MaxMind.MinFraud/Request/Payment.cs +++ b/MaxMind.MinFraud/Request/Payment.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using MaxMind.MinFraud.Util; +using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace MaxMind.MinFraud.Request diff --git a/MaxMind.MinFraud/Request/Shipping.cs b/MaxMind.MinFraud/Request/Shipping.cs index 7e4c19a..b7c6847 100644 --- a/MaxMind.MinFraud/Request/Shipping.cs +++ b/MaxMind.MinFraud/Request/Shipping.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using MaxMind.MinFraud.Util; +using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace MaxMind.MinFraud.Request diff --git a/MaxMind.MinFraud/Request/TransactionReport.cs b/MaxMind.MinFraud/Request/TransactionReport.cs index 1ab3be6..bd625d3 100644 --- a/MaxMind.MinFraud/Request/TransactionReport.cs +++ b/MaxMind.MinFraud/Request/TransactionReport.cs @@ -1,4 +1,5 @@ -using System; +using MaxMind.MinFraud.Util; +using System; using System.Net; using System.Runtime.Serialization; using System.Text.Json.Serialization; diff --git a/MaxMind.MinFraud/Util/EnumMemberValueConverter.cs b/MaxMind.MinFraud/Util/EnumMemberValueConverter.cs index c3d2e82..39e33f1 100644 --- a/MaxMind.MinFraud/Util/EnumMemberValueConverter.cs +++ b/MaxMind.MinFraud/Util/EnumMemberValueConverter.cs @@ -4,7 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace MaxMind.MinFraud +namespace MaxMind.MinFraud.Util { // See https://github.com/dotnet/runtime/issues/31081. We could also switch // to a snakecase naming policy of that is added: diff --git a/MaxMind.MinFraud/Util/IPAddressConverter.cs b/MaxMind.MinFraud/Util/IPAddressConverter.cs index 5003e69..5be5a4f 100644 --- a/MaxMind.MinFraud/Util/IPAddressConverter.cs +++ b/MaxMind.MinFraud/Util/IPAddressConverter.cs @@ -3,7 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace MaxMind.MinFraud +namespace MaxMind.MinFraud.Util { internal class IPAddressConverter : JsonConverter { diff --git a/MaxMind.MinFraud/Util/NetworkConverter.cs b/MaxMind.MinFraud/Util/NetworkConverter.cs index af6bde7..f612f37 100644 --- a/MaxMind.MinFraud/Util/NetworkConverter.cs +++ b/MaxMind.MinFraud/Util/NetworkConverter.cs @@ -4,7 +4,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace MaxMind.MinFraud +namespace MaxMind.MinFraud.Util { /// /// A JsonConverter for MaxMind.Db.Network. From 69e373a58b224ba09137f9e4b1ace36d9f9c0a61 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 13:56:32 -0800 Subject: [PATCH 04/10] Avoid duplicate lookups --- MaxMind.MinFraud/Request/Email.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MaxMind.MinFraud/Request/Email.cs b/MaxMind.MinFraud/Request/Email.cs index ef32b73..cb75c9f 100644 --- a/MaxMind.MinFraud/Request/Email.cs +++ b/MaxMind.MinFraud/Request/Email.cs @@ -454,20 +454,20 @@ private static string CleanDomain(string domain) if (idx != -1) { var tld = domain.Substring(idx + 1); - if (_typoTlds.ContainsKey(tld)) + if (_typoTlds.TryGetValue(tld, out var typoTld)) { - domain = domain.Substring(0, idx) + "." + _typoTlds[tld]; + domain = domain.Substring(0, idx) + "." + typoTld; } } - if (_typoDomains.ContainsKey(domain)) + if (_typoDomains.TryGetValue(domain, out var typoDomain)) { - domain = _typoDomains[domain]; + domain = typoDomain; } - if (_equivalentDomains.ContainsKey(domain)) + if (_equivalentDomains.TryGetValue(domain, out var equivalentDomain)) { - domain = _equivalentDomains[domain]; + domain = equivalentDomain; } return domain; From 8e751528417e9ee0b0f85d6a510781a45d79d863 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:14:24 -0800 Subject: [PATCH 05/10] Use raw strings --- .../JsonElementComparer.cs | 4 +- .../Request/CustomInputsTest.cs | 16 +++--- .../Request/EmailTest.cs | 24 ++++----- .../Request/EventTest.cs | 12 ++--- .../Response/BillingAddressTest.cs | 14 ++--- .../Response/CreditCardTest.cs | 20 +++---- .../Response/DeviceTest.cs | 16 +++--- .../Response/DispositionTest.cs | 14 ++--- .../Response/EmailDomainTest.cs | 2 +- .../Response/EmailTest.cs | 18 +++---- .../Response/GeoIP2CountryTest.cs | 12 ++--- .../Response/GeoIP2LocationTest.cs | 2 +- .../Response/IPAddressTest.cs | 54 +++++++++---------- .../Response/InsightsTest.cs | 44 +++++++-------- .../Response/IssuerTest.cs | 16 +++--- .../Response/MultiplierReasonTest.cs | 12 ++--- .../Response/PhoneTest.cs | 16 +++--- .../Response/RiskScoreReasonTest.cs | 22 ++++---- .../Response/ScoreTest.cs | 22 ++++---- .../Response/ShippingAddressTest.cs | 18 +++---- .../Response/SubscoresTest.cs | 44 +++++++-------- .../Response/WarningTest.cs | 14 ++--- 22 files changed, 208 insertions(+), 208 deletions(-) diff --git a/MaxMind.MinFraud.UnitTest/JsonElementComparer.cs b/MaxMind.MinFraud.UnitTest/JsonElementComparer.cs index 3fc5e84..da9365b 100644 --- a/MaxMind.MinFraud.UnitTest/JsonElementComparer.cs +++ b/MaxMind.MinFraud.UnitTest/JsonElementComparer.cs @@ -75,7 +75,7 @@ public bool Equals(JsonElement x, JsonElement y) } default: - throw new JsonException(string.Format("Unknown JsonValueKind {0}", x.ValueKind)); + throw new JsonException($"Unknown JsonValueKind {x.ValueKind}"); } } @@ -128,7 +128,7 @@ private void ComputeHashCode(JsonElement obj, ref HashCode hash, int depth) break; default: - throw new JsonException(string.Format("Unknown JsonValueKind {0}", obj.ValueKind)); + throw new JsonException($"Unknown JsonValueKind {obj.ValueKind}"); } } diff --git a/MaxMind.MinFraud.UnitTest/Request/CustomInputsTest.cs b/MaxMind.MinFraud.UnitTest/Request/CustomInputsTest.cs index 6da787b..a199651 100644 --- a/MaxMind.MinFraud.UnitTest/Request/CustomInputsTest.cs +++ b/MaxMind.MinFraud.UnitTest/Request/CustomInputsTest.cs @@ -24,16 +24,16 @@ public void TestJson() var comparer = new JsonElementComparer(); Assert.True(comparer.JsonEquals( JsonDocument.Parse( - @" + """ { - ""string_input_1"": ""test string"", - ""int_input"": 19, - ""long_input"": 12, - ""float_input"": 3.20000005, - ""double_input"": 32.122999999999998, - ""bool_input"": true + "string_input_1": "test string", + "int_input": 19, + "long_input": 12, + "float_input": 3.20000005, + "double_input": 32.122999999999998, + "bool_input": true } - "), + """), JsonDocument.Parse(json) ), json diff --git a/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs b/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs index 3ed46dc..dfcbe58 100644 --- a/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs +++ b/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs @@ -23,12 +23,12 @@ public void TestAddress() Assert.True( comparer.JsonEquals( JsonDocument.Parse( - $@" - {{ - ""address"": ""{address}"", - ""domain"": ""{domain}"" - }} - "), + $$""" + { + "address": "{{address}}", + "domain": "{{domain}}" + } + """), JsonDocument.Parse(json) ), json @@ -52,12 +52,12 @@ public void TestAddressWithHashing() Assert.True( comparer.JsonEquals( JsonDocument.Parse( - $@" - {{ - ""address"": ""{md5}"", - ""domain"": ""{domain}"" - }} - "), + $$""" + { + "address": "{{md5}}", + "domain": "{{domain}}" + } + """), JsonDocument.Parse(json) ) ); diff --git a/MaxMind.MinFraud.UnitTest/Request/EventTest.cs b/MaxMind.MinFraud.UnitTest/Request/EventTest.cs index 8450507..6f85b13 100644 --- a/MaxMind.MinFraud.UnitTest/Request/EventTest.cs +++ b/MaxMind.MinFraud.UnitTest/Request/EventTest.cs @@ -53,14 +53,14 @@ public void TestSerialization() Assert.True( comparer.JsonEquals( JsonDocument.Parse( - @" + """ { - ""transaction_id"": ""txn123"", - ""shop_id"": ""shop123"", - ""time"": ""2020-07-12T15:30:00+02:00"", - ""type"": ""account_creation"" + "transaction_id": "txn123", + "shop_id": "shop123", + "time": "2020-07-12T15:30:00+02:00", + "type": "account_creation" } - "), + """), JsonDocument.Parse(json) ), json diff --git a/MaxMind.MinFraud.UnitTest/Response/BillingAddressTest.cs b/MaxMind.MinFraud.UnitTest/Response/BillingAddressTest.cs index b1a27be..330787b 100644 --- a/MaxMind.MinFraud.UnitTest/Response/BillingAddressTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/BillingAddressTest.cs @@ -11,15 +11,15 @@ public class BillingAddressTest public void TestBillingAddress() { var address = JsonSerializer.Deserialize( - @" + """ { - ""is_in_ip_country"": true, - ""latitude"": 43.1, - ""longitude"": 32.1, - ""distance_to_ip_location"": 100, - ""is_postal_in_city"": true + "is_in_ip_country": true, + "latitude": 43.1, + "longitude": 32.1, + "distance_to_ip_location": 100, + "is_postal_in_city": true } - "); + """); TestAddress(address!); } diff --git a/MaxMind.MinFraud.UnitTest/Response/CreditCardTest.cs b/MaxMind.MinFraud.UnitTest/Response/CreditCardTest.cs index f4c188d..b6246a9 100644 --- a/MaxMind.MinFraud.UnitTest/Response/CreditCardTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/CreditCardTest.cs @@ -10,18 +10,18 @@ public class CreditCardTest public void TestCreditCard() { var cc = JsonSerializer.Deserialize( - @" + """ { - ""issuer"": {""name"": ""Bank""}, - ""brand"": ""Visa"", - ""country"": ""US"", - ""is_business"": true, - ""is_issued_in_billing_address_country"": true, - ""is_prepaid"": true, - ""is_virtual"": true, - ""type"": ""credit"" + "issuer": {"name": "Bank"}, + "brand": "Visa", + "country": "US", + "is_business": true, + "is_issued_in_billing_address_country": true, + "is_prepaid": true, + "is_virtual": true, + "type": "credit" } - ")!; + """)!; Assert.Equal("Bank", cc.Issuer.Name); Assert.Equal("US", cc.Country); diff --git a/MaxMind.MinFraud.UnitTest/Response/DeviceTest.cs b/MaxMind.MinFraud.UnitTest/Response/DeviceTest.cs index bfc1417..4f57806 100644 --- a/MaxMind.MinFraud.UnitTest/Response/DeviceTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/DeviceTest.cs @@ -14,14 +14,14 @@ public void TestDevice() var lastSeen = "2016-06-08T14:16:38+00:00"; var localTime = "2016-06-10T14:19:10-08:00"; var device = JsonSerializer.Deserialize( - @$" - {{ - ""confidence"": 99, - ""id"": ""{id}"", - ""last_seen"": ""{lastSeen}"", - ""local_time"": ""{localTime}"" - }} - ")!; + $$""" + { + "confidence": 99, + "id": "{{id}}", + "last_seen": "{{lastSeen}}", + "local_time": "{{localTime}}" + } + """)!; Assert.Equal(99, device.Confidence); Assert.Equal(new Guid(id), device.Id); diff --git a/MaxMind.MinFraud.UnitTest/Response/DispositionTest.cs b/MaxMind.MinFraud.UnitTest/Response/DispositionTest.cs index 8e991c6..5b0299b 100644 --- a/MaxMind.MinFraud.UnitTest/Response/DispositionTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/DispositionTest.cs @@ -10,13 +10,13 @@ public class DispositionTest public void TestDisposition() { var disposition = JsonSerializer.Deserialize( - @" - { - ""action"": ""manual_review"", - ""reason"": ""custom_rule"", - ""rule_label"": ""the rule's label"" - } - ")!; + """ + { + "action": "manual_review", + "reason": "custom_rule", + "rule_label": "the rule's label" + } + """)!; Assert.Equal("manual_review", disposition.Action); Assert.Equal("custom_rule", disposition.Reason); diff --git a/MaxMind.MinFraud.UnitTest/Response/EmailDomainTest.cs b/MaxMind.MinFraud.UnitTest/Response/EmailDomainTest.cs index f3d2ed0..bf505c4 100644 --- a/MaxMind.MinFraud.UnitTest/Response/EmailDomainTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/EmailDomainTest.cs @@ -10,7 +10,7 @@ public class EmailDomainTest public void TestEmailDomain() { var domain = JsonSerializer.Deserialize( - @"{""first_seen"": ""2017-01-02""}")!; + """{"first_seen": "2017-01-02"}""")!; Assert.Equal("2017-01-02", domain.FirstSeen?.ToString("yyyy-MM-dd")); } diff --git a/MaxMind.MinFraud.UnitTest/Response/EmailTest.cs b/MaxMind.MinFraud.UnitTest/Response/EmailTest.cs index 2fa5d23..108473f 100644 --- a/MaxMind.MinFraud.UnitTest/Response/EmailTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/EmailTest.cs @@ -10,15 +10,15 @@ public class EmailTest public void TestEmail() { var email = JsonSerializer.Deserialize( - @" - { - ""domain"": { ""first_seen"": ""2014-02-03"" }, - ""first_seen"": ""2017-01-02"", - ""is_disposable"": true, - ""is_free"": true, - ""is_high_risk"": true - } - ")!; + """ + { + "domain": { "first_seen": "2014-02-03" }, + "first_seen": "2017-01-02", + "is_disposable": true, + "is_free": true, + "is_high_risk": true + } + """)!; Assert.Equal("2014-02-03", email.Domain.FirstSeen?.ToString("yyyy-MM-dd")); Assert.Equal("2017-01-02", email.FirstSeen?.ToString("yyyy-MM-dd")); diff --git a/MaxMind.MinFraud.UnitTest/Response/GeoIP2CountryTest.cs b/MaxMind.MinFraud.UnitTest/Response/GeoIP2CountryTest.cs index 4f5e85f..a514723 100644 --- a/MaxMind.MinFraud.UnitTest/Response/GeoIP2CountryTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/GeoIP2CountryTest.cs @@ -10,12 +10,12 @@ public class GeoIP2CountryTest public void TestIsHighRisk() { var country = JsonSerializer.Deserialize( - @" - { - ""is_high_risk"": true, - ""is_in_european_union"": true - } - ")!; + """ + { + "is_high_risk": true, + "is_in_european_union": true + } + """)!; Assert.True(country.IsInEuropeanUnion); } diff --git a/MaxMind.MinFraud.UnitTest/Response/GeoIP2LocationTest.cs b/MaxMind.MinFraud.UnitTest/Response/GeoIP2LocationTest.cs index d38c8ef..154f23b 100644 --- a/MaxMind.MinFraud.UnitTest/Response/GeoIP2LocationTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/GeoIP2LocationTest.cs @@ -11,7 +11,7 @@ public void TestGetLocalTime() { var time = "2015-04-19T12:59:23-01:00"; var location = JsonSerializer.Deserialize( - @$"{{""local_time"": ""{time}"" }}")!; + $$"""{"local_time": "{{time}}" }""")!; Assert.Equal(time, location.LocalTime?.ToString("yyyy-MM-ddTHH:mm:ssK")); } diff --git a/MaxMind.MinFraud.UnitTest/Response/IPAddressTest.cs b/MaxMind.MinFraud.UnitTest/Response/IPAddressTest.cs index 01329e7..e72263e 100644 --- a/MaxMind.MinFraud.UnitTest/Response/IPAddressTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/IPAddressTest.cs @@ -12,33 +12,33 @@ public void TestIPAddress() var time = "2015-04-19T12:59:23-01:00"; var address = JsonSerializer.Deserialize( - $@" - {{ - ""risk"": 99, - ""risk_reasons"": [ - {{ - ""code"": ""ANONYMOUS_IP"", - ""reason"": ""The IP address belongs to an anonymous network. See /ip_address/traits for more details."" - }}, - {{ - ""code"": ""MINFRAUD_NETWORK_ACTIVITY"", - ""reason"": ""Suspicious activity has been seen on this IP address across minFraud customers."" - }} - ], - ""country"": {{""is_high_risk"": true}}, - ""location"": {{""local_time"": ""{time}""}}, - ""traits"": {{ - ""is_anonymous"": true, - ""is_anonymous_vpn"": true, - ""is_hosting_provider"": true, - ""is_public_proxy"": true, - ""is_residential_proxy"": true, - ""is_tor_exit_node"": true, - ""mobile_country_code"" : ""310"", - ""mobile_network_code"" : ""004"" - }} - }} - ")!; + $$""" + { + "risk": 99, + "risk_reasons": [ + { + "code": "ANONYMOUS_IP", + "reason": "The IP address belongs to an anonymous network. See /ip_address/traits for more details." + }, + { + "code": "MINFRAUD_NETWORK_ACTIVITY", + "reason": "Suspicious activity has been seen on this IP address across minFraud customers." + } + ], + "country": {"is_high_risk": true}, + "location": {"local_time": "{{time}}"}, + "traits": { + "is_anonymous": true, + "is_anonymous_vpn": true, + "is_hosting_provider": true, + "is_public_proxy": true, + "is_residential_proxy": true, + "is_tor_exit_node": true, + "mobile_country_code" : "310", + "mobile_network_code" : "004" + } + } + """)!; Assert.Equal(99, address.Risk); Assert.Equal("ANONYMOUS_IP", address.RiskReasons[0].Code); diff --git a/MaxMind.MinFraud.UnitTest/Response/InsightsTest.cs b/MaxMind.MinFraud.UnitTest/Response/InsightsTest.cs index d7eabd1..ef6c298 100644 --- a/MaxMind.MinFraud.UnitTest/Response/InsightsTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/InsightsTest.cs @@ -11,28 +11,28 @@ public void TestInsights() { var id = "b643d445-18b2-4b9d-bad4-c9c4366e402a"; var insights = JsonSerializer.Deserialize( - $@" - {{ - ""id"": ""{id}"", - ""ip_address"": {{""country"": {{""iso_code"": ""US""}}}}, - ""credit_card"": {{""is_business"": true, ""is_prepaid"": true}}, - ""device"": {{""id"": ""{id}""}}, - ""disposition"": {{""action"": ""accept""}}, - ""email"": - {{ - ""domain"": {{ ""first_seen"": ""2014-02-03""}}, - ""is_free"": true - }}, - ""shipping_address"": {{""is_in_ip_country"": true}}, - ""shipping_phone"": {{""is_voip"": true}}, - ""billing_address"": {{""is_in_ip_country"": true}}, - ""billing_phone"": {{""is_voip"": false}}, - ""funds_remaining"": 1.20, - ""queries_remaining"": 123, - ""risk_score"": 0.01, - ""warnings"": [{{""code"": ""INVALID_INPUT""}}] - }} - ")!; + $$$""" + { + "id": "{{{id}}}", + "ip_address": {"country": {"iso_code": "US"}}, + "credit_card": {"is_business": true, "is_prepaid": true}, + "device": {"id": "{{{id}}}"}, + "disposition": {"action": "accept"}, + "email": + { + "domain": { "first_seen": "2014-02-03"}, + "is_free": true + }, + "shipping_address": {"is_in_ip_country": true}, + "shipping_phone": {"is_voip": true}, + "billing_address": {"is_in_ip_country": true}, + "billing_phone": {"is_voip": false}, + "funds_remaining": 1.20, + "queries_remaining": 123, + "risk_score": 0.01, + "warnings": [{"code": "INVALID_INPUT"}] + } + """)!; Assert.Equal("2014-02-03", insights.Email.Domain.FirstSeen?.ToString("yyyy-MM-dd")); Assert.Equal("US", insights.IPAddress.Country.IsoCode); diff --git a/MaxMind.MinFraud.UnitTest/Response/IssuerTest.cs b/MaxMind.MinFraud.UnitTest/Response/IssuerTest.cs index 04d05ac..e4f9100 100644 --- a/MaxMind.MinFraud.UnitTest/Response/IssuerTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/IssuerTest.cs @@ -12,14 +12,14 @@ public void TestIssuer() var phone = "132-342-2131"; var issuer = JsonSerializer.Deserialize( - $@" - {{ - ""name"": ""Bank"", - ""matches_provided_name"": true, - ""phone_number"": ""{phone}"", - ""matches_provided_phone_number"": true - }} - ")!; + $$""" + { + "name": "Bank", + "matches_provided_name": true, + "phone_number": "{{phone}}", + "matches_provided_phone_number": true + } + """)!; Assert.Equal("Bank", issuer.Name); Assert.True(issuer.MatchesProvidedName); diff --git a/MaxMind.MinFraud.UnitTest/Response/MultiplierReasonTest.cs b/MaxMind.MinFraud.UnitTest/Response/MultiplierReasonTest.cs index 2de70cb..83bb658 100644 --- a/MaxMind.MinFraud.UnitTest/Response/MultiplierReasonTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/MultiplierReasonTest.cs @@ -13,12 +13,12 @@ public void TestMultiplierReason() var msg = "Risk due to IP being an Anonymous IP"; var reason = JsonSerializer.Deserialize( - $@" - {{ - ""code"": ""{code}"", - ""reason"": ""{msg}"" - }} - ")!; + $$""" + { + "code": "{{code}}", + "reason": "{{msg}}" + } + """)!; Assert.Equal(code, reason.Code); Assert.Equal(msg, reason.Reason); diff --git a/MaxMind.MinFraud.UnitTest/Response/PhoneTest.cs b/MaxMind.MinFraud.UnitTest/Response/PhoneTest.cs index ca0a225..9ed094d 100644 --- a/MaxMind.MinFraud.UnitTest/Response/PhoneTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/PhoneTest.cs @@ -13,14 +13,14 @@ public void TestPhone() var networkOperator = "Verizon"; var numberType = "mobile"; var phone = JsonSerializer.Deserialize( - @$" - {{ - ""country"": ""{country}"", - ""is_voip"": true, - ""network_operator"": ""{networkOperator}"", - ""number_type"": ""{numberType}"" - }} - ")!; + $$""" + { + "country": "{{country}}", + "is_voip": true, + "network_operator": "{{networkOperator}}", + "number_type": "{{numberType}}" + } + """)!; Assert.Equal(country, phone.Country); Assert.True(phone.IsVoip); diff --git a/MaxMind.MinFraud.UnitTest/Response/RiskScoreReasonTest.cs b/MaxMind.MinFraud.UnitTest/Response/RiskScoreReasonTest.cs index 8d6c2a3..e802d16 100644 --- a/MaxMind.MinFraud.UnitTest/Response/RiskScoreReasonTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/RiskScoreReasonTest.cs @@ -10,17 +10,17 @@ public class RiskScoreReasonTest public void TestRiskScoreReason() { var reason = JsonSerializer.Deserialize( - $@" - {{ - ""multiplier"": 45.0, - ""reasons"": [ - {{ - ""code"": ""ANONYMOUS_IP"", - ""reason"": ""Risk due to IP being an Anonymous IP"" - }} - ] - }} - ")!; + $$""" + { + "multiplier": 45.0, + "reasons": [ + { + "code": "ANONYMOUS_IP", + "reason": "Risk due to IP being an Anonymous IP" + } + ] + } + """)!; Assert.Equal(45.0, reason.Multiplier); Assert.Equal("ANONYMOUS_IP", reason.Reasons[0].Code); diff --git a/MaxMind.MinFraud.UnitTest/Response/ScoreTest.cs b/MaxMind.MinFraud.UnitTest/Response/ScoreTest.cs index 1c83b91..32f42db 100644 --- a/MaxMind.MinFraud.UnitTest/Response/ScoreTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/ScoreTest.cs @@ -11,17 +11,17 @@ public void TestScore() { var id = "b643d445-18b2-4b9d-bad4-c9c4366e402a"; var score = JsonSerializer.Deserialize( - $@" - {{ - ""id"": ""{id}"", - ""funds_remaining"": 1.20, - ""queries_remaining"": 123, - ""disposition"": {{""action"": ""accept""}}, - ""ip_address"": {{""risk"": 0.01}}, - ""risk_score"": 0.01, - ""warnings"": [{{""code"": ""INVALID_INPUT""}}] - }} - ")!; + $$""" + { + "id": "{{id}}", + "funds_remaining": 1.20, + "queries_remaining": 123, + "disposition": {"action": "accept"}, + "ip_address": {"risk": 0.01}, + "risk_score": 0.01, + "warnings": [{"code": "INVALID_INPUT"}] + } + """)!; Assert.Equal(id, score.Id.ToString()); Assert.Equal(1.20m, score.FundsRemaining); diff --git a/MaxMind.MinFraud.UnitTest/Response/ShippingAddressTest.cs b/MaxMind.MinFraud.UnitTest/Response/ShippingAddressTest.cs index 68808a5..e59a941 100644 --- a/MaxMind.MinFraud.UnitTest/Response/ShippingAddressTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/ShippingAddressTest.cs @@ -11,17 +11,17 @@ public class ShippingAddressTest public void TestShippingAddress() { var address = JsonSerializer.Deserialize( - @" + """ { - ""is_in_ip_country"": true, - ""latitude"": 43.1, - ""longitude"": 32.1, - ""distance_to_ip_location"": 100, - ""is_postal_in_city"": true, - ""is_high_risk"": false, - ""distance_to_billing_address"": 200 + "is_in_ip_country": true, + "latitude": 43.1, + "longitude": 32.1, + "distance_to_ip_location": 100, + "is_postal_in_city": true, + "is_high_risk": false, + "distance_to_billing_address": 200 } - ")!; + """)!; TestAddress(address); diff --git a/MaxMind.MinFraud.UnitTest/Response/SubscoresTest.cs b/MaxMind.MinFraud.UnitTest/Response/SubscoresTest.cs index 1aa80e9..855f8a5 100644 --- a/MaxMind.MinFraud.UnitTest/Response/SubscoresTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/SubscoresTest.cs @@ -11,30 +11,30 @@ public class SubScoresTest public void TestSubscores() { var subscores = JsonSerializer.Deserialize( - @" + """ { - ""avs_result"": 0.01, - ""billing_address"": 0.02, - ""billing_address_distance_to_ip_location"": 0.03, - ""browser"": 0.04, - ""chargeback"": 0.05, - ""country"": 0.06, - ""country_mismatch"": 0.07, - ""cvv_result"": 0.08, - ""device"": 0.09, - ""email_address"": 0.10, - ""email_domain"": 0.11, - ""email_local_part"": 0.12, - ""email_tenure"": 0.13, - ""ip_tenure"": 0.14, - ""issuer_id_number"": 0.15, - ""order_amount"": 0.16, - ""phone_number"": 0.17, - ""shipping_address"": 0.18, - ""shipping_address_distance_to_ip_location"": 0.19, - ""time_of_day"": 0.20 + "avs_result": 0.01, + "billing_address": 0.02, + "billing_address_distance_to_ip_location": 0.03, + "browser": 0.04, + "chargeback": 0.05, + "country": 0.06, + "country_mismatch": 0.07, + "cvv_result": 0.08, + "device": 0.09, + "email_address": 0.10, + "email_domain": 0.11, + "email_local_part": 0.12, + "email_tenure": 0.13, + "ip_tenure": 0.14, + "issuer_id_number": 0.15, + "order_amount": 0.16, + "phone_number": 0.17, + "shipping_address": 0.18, + "shipping_address_distance_to_ip_location": 0.19, + "time_of_day": 0.20 } - ")!; + """)!; Assert.Equal(0.01, subscores.AvsResult); Assert.Equal(0.02, subscores.BillingAddress); diff --git a/MaxMind.MinFraud.UnitTest/Response/WarningTest.cs b/MaxMind.MinFraud.UnitTest/Response/WarningTest.cs index 630b9ad..aaaab12 100644 --- a/MaxMind.MinFraud.UnitTest/Response/WarningTest.cs +++ b/MaxMind.MinFraud.UnitTest/Response/WarningTest.cs @@ -13,13 +13,13 @@ public void TestWarning() var msg = "Input invalid"; var warning = JsonSerializer.Deserialize( - $@" - {{ - ""code"": ""{code}"", - ""warning"": ""{msg}"", - ""input_pointer"": ""/first/second"" - }} - ")!; + $$""" + { + "code": "{{code}}", + "warning": "{{msg}}", + "input_pointer": "/first/second" + } + """)!; Assert.Equal(code, warning.Code); Assert.Equal(msg, warning.Message); From 930c16d302ed19ff145026d8d4c1c91033d7ba45 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:18:11 -0800 Subject: [PATCH 06/10] Make some fields readonly --- MaxMind.MinFraud/Request/CreditCard.cs | 8 ++++---- MaxMind.MinFraud/Request/Device.cs | 4 ++-- MaxMind.MinFraud/Request/Email.cs | 4 ++-- MaxMind.MinFraud/Request/Location.cs | 2 +- MaxMind.MinFraud/Request/Order.cs | 2 +- MaxMind.MinFraud/Request/TransactionReport.cs | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/MaxMind.MinFraud/Request/CreditCard.cs b/MaxMind.MinFraud/Request/CreditCard.cs index a7d7cfb..b240ec0 100644 --- a/MaxMind.MinFraud/Request/CreditCard.cs +++ b/MaxMind.MinFraud/Request/CreditCard.cs @@ -16,10 +16,10 @@ public sealed class CreditCard private static readonly Regex TokenRe = new("^(?![0-9]{1,19}$)[\\x21-\\x7E]{1,255}$", RegexOptions.Compiled); - private string? _country; - private string? _issuerIdNumber; - private string? _lastDigits; - private string? _token; + private readonly string? _country; + private readonly string? _issuerIdNumber; + private readonly string? _lastDigits; + private readonly string? _token; /// /// Constructor. diff --git a/MaxMind.MinFraud/Request/Device.cs b/MaxMind.MinFraud/Request/Device.cs index 7818eb3..8cf31e7 100644 --- a/MaxMind.MinFraud/Request/Device.cs +++ b/MaxMind.MinFraud/Request/Device.cs @@ -11,8 +11,8 @@ namespace MaxMind.MinFraud.Request /// public sealed class Device { - private double? _sessionAge; - private string? _sessionId; + private readonly double? _sessionAge; + private readonly string? _sessionId; /// /// Constructor. diff --git a/MaxMind.MinFraud/Request/Email.cs b/MaxMind.MinFraud/Request/Email.cs index cb75c9f..ae22ea0 100644 --- a/MaxMind.MinFraud/Request/Email.cs +++ b/MaxMind.MinFraud/Request/Email.cs @@ -274,8 +274,8 @@ public sealed class Email "ymail.com", ]; - private string? _address; - private string? _domain; + private readonly string? _address; + private readonly string? _domain; /// /// Constructor. diff --git a/MaxMind.MinFraud/Request/Location.cs b/MaxMind.MinFraud/Request/Location.cs index ae59bff..3fe3980 100644 --- a/MaxMind.MinFraud/Request/Location.cs +++ b/MaxMind.MinFraud/Request/Location.cs @@ -11,7 +11,7 @@ namespace MaxMind.MinFraud.Request public abstract class Location { private static readonly Regex CountryRe = new("^[A-Z]{2}$", RegexOptions.Compiled); - private string? _country; + private readonly string? _country; /// /// Constructor. diff --git a/MaxMind.MinFraud/Request/Order.cs b/MaxMind.MinFraud/Request/Order.cs index 8dbd385..51674cf 100644 --- a/MaxMind.MinFraud/Request/Order.cs +++ b/MaxMind.MinFraud/Request/Order.cs @@ -11,7 +11,7 @@ namespace MaxMind.MinFraud.Request public sealed class Order { private static readonly Regex CurrencyRe = new("^[A-Z]{3}$", RegexOptions.Compiled); - private string? _currency; + private readonly string? _currency; /// /// Constructor. diff --git a/MaxMind.MinFraud/Request/TransactionReport.cs b/MaxMind.MinFraud/Request/TransactionReport.cs index bd625d3..7e26b5c 100644 --- a/MaxMind.MinFraud/Request/TransactionReport.cs +++ b/MaxMind.MinFraud/Request/TransactionReport.cs @@ -44,7 +44,7 @@ public enum TransactionReportTag /// public sealed class TransactionReport { - private string? _maxmindId; + private readonly string? _maxmindId; /// /// Constructor with validation. From 347651c00249227b845c35db2e24a2aedff5a031 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:19:48 -0800 Subject: [PATCH 07/10] Use switch statement for clarity --- MaxMind.MinFraud/WebServiceClient.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/MaxMind.MinFraud/WebServiceClient.cs b/MaxMind.MinFraud/WebServiceClient.cs index ca7ab38..c257e76 100644 --- a/MaxMind.MinFraud/WebServiceClient.cs +++ b/MaxMind.MinFraud/WebServiceClient.cs @@ -227,15 +227,15 @@ private static async Task HandleError(HttpResponseMessage response) var uri = response.RequestMessage?.RequestUri; var status = (int)response.StatusCode; - if (status >= 400 && status < 500) + switch (status) { - await Handle4xxStatus(response).ConfigureAwait(false); - } - else if (status >= 500 && status < 600) - { - throw new HttpException( - $"Received a server ({status}) error for {uri}", response.StatusCode, - uri); + case >= 400 and < 500: + await Handle4xxStatus(response).ConfigureAwait(false); + break; + case >= 500 and < 600: + throw new HttpException( + $"Received a server ({status}) error for {uri}", response.StatusCode, + uri); } var errorMessage = From e7ffef34b24cc38eb1788bb7db69d51b32eb21a6 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:21:04 -0800 Subject: [PATCH 08/10] Use pattern in a couple of places --- MaxMind.MinFraud/Request/CustomInputs.cs | 6 +++--- MaxMind.MinFraud/Request/Device.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/MaxMind.MinFraud/Request/CustomInputs.cs b/MaxMind.MinFraud/Request/CustomInputs.cs index 9587060..0eef80f 100644 --- a/MaxMind.MinFraud/Request/CustomInputs.cs +++ b/MaxMind.MinFraud/Request/CustomInputs.cs @@ -97,7 +97,7 @@ public void Add(string key, int value) /// public void Add(string key, long value) { - if (value <= -NumMax || value >= NumMax) + if (value is <= -NumMax or >= NumMax) { throw new ArgumentException($"The custom input number {value} is invalid. " + $"The number must be between -{NumMax} and {NumMax}, exclusive"); @@ -116,7 +116,7 @@ public void Add(string key, long value) /// public void Add(string key, float value) { - if (value <= -NumMax || value >= NumMax) + if (value is <= -NumMax or >= NumMax) { throw new ArgumentException($"The custom input number {value} is invalid. " + $"The number must be between -{NumMax} and {NumMax}, exclusive"); @@ -135,7 +135,7 @@ public void Add(string key, float value) /// public void Add(string key, double value) { - if (value <= -NumMax || value >= NumMax) + if (value is <= -NumMax or >= NumMax) { throw new ArgumentException($"The custom input number {value} is invalid. " + $"The number must be between -{NumMax} and {NumMax}, exclusive"); diff --git a/MaxMind.MinFraud/Request/Device.cs b/MaxMind.MinFraud/Request/Device.cs index 8cf31e7..a9be3ce 100644 --- a/MaxMind.MinFraud/Request/Device.cs +++ b/MaxMind.MinFraud/Request/Device.cs @@ -79,7 +79,7 @@ public double? SessionAge get => _sessionAge; init { - if (value != null && value < 0) + if (value is < 0) { throw new ArgumentException($"{nameof(value)} must be non-negative."); } @@ -97,7 +97,7 @@ public string? SessionId get => _sessionId; init { - if (value != null && value.Length > 255) + if (value is { Length: > 255 }) { throw new ArgumentException($"{nameof(value)} must be less than 255 characters long."); } From 3df355d333a711ac33f9a00f004d9e4bf7080d0b Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:21:52 -0800 Subject: [PATCH 09/10] Use collection expressions --- MaxMind.MinFraud/WebServiceClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MaxMind.MinFraud/WebServiceClient.cs b/MaxMind.MinFraud/WebServiceClient.cs index c257e76..1f6d417 100644 --- a/MaxMind.MinFraud/WebServiceClient.cs +++ b/MaxMind.MinFraud/WebServiceClient.cs @@ -91,7 +91,7 @@ internal WebServiceClient( HttpClient httpClient ) { - _locales = locales == null ? new List { "en" } : new List(locales); + _locales = locales == null ? ["en"] : [..locales]; httpClient.BaseAddress = new UriBuilder("https", host, -1, BasePath).Uri; From ece5a2a73f39290668e91ac7e82f816a59a16ab5 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Thu, 21 Nov 2024 14:25:00 -0800 Subject: [PATCH 10/10] Use var --- MaxMind.MinFraud.UnitTest/Request/EmailTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs b/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs index dfcbe58..48ab9bf 100644 --- a/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs +++ b/MaxMind.MinFraud.UnitTest/Request/EmailTest.cs @@ -66,7 +66,7 @@ public void TestAddressWithHashing() [Fact] public void TestNormalizing() { - Email e = new Email(address: "test@maxmind.com", hashAddress: true); + var e = new Email(address: "test@maxmind.com", hashAddress: true); Assert.Equal("977577b140bfb7c516e4746204fbdb01", e.AddressMD5); Assert.Equal("maxmind.com", e.Domain);