diff --git a/Directory.Build.props b/Directory.Build.props
index 749af9f..66c247a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -68,6 +68,7 @@
+
@@ -88,6 +89,31 @@
+
+
+ true
+ lcov,opencover,cobertura
+ $(MSBuildThisFileDirectory)TestResults/coverage/$(MSBuildProjectName).
+ GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute
+ **/*Program.cs;**/*Startup.cs;**/*GlobalUsings.cs
+ true
+
+ 100
+ line
+ total
+
+
+
+
+ [MyCSharp.HttpUserAgentParser]*
+
+
+ [MyCSharp.HttpUserAgentParser.MemoryCache]*
+
+
+ [MyCSharp.HttpUserAgentParser.AspNetCore]*
+
+
all
diff --git a/Directory.Packages.props b/Directory.Packages.props
index eaa97d7..ef3e22e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -2,77 +2,71 @@
true
-
-
-
-
-
-
-
+
+
-
-
+
all
runtime; build; native; contentfiles; analyzers
-
-
-
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpContextExtensionsTests.cs b/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpContextExtensionsTests.cs
new file mode 100644
index 0000000..065cfb7
--- /dev/null
+++ b/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpContextExtensionsTests.cs
@@ -0,0 +1,37 @@
+// Copyright © https://myCSharp.de - all rights reserved
+
+using Microsoft.AspNetCore.Http;
+using MyCSharp.HttpUserAgentParser.AspNetCore;
+using MyCSharp.HttpUserAgentParser.Providers;
+using NSubstitute;
+using Xunit;
+
+namespace MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests;
+
+public class HttpContextExtensionsTests
+{
+ [Fact]
+ public void GetUserAgentString_Returns_Value_When_Present()
+ {
+ HttpContext ctx = HttpContextTestHelpers.GetHttpContext("UA");
+ Assert.Equal("UA", ctx.GetUserAgentString());
+ }
+
+ [Fact]
+ public void GetUserAgentString_Returns_Null_When_Absent()
+ {
+ DefaultHttpContext ctx = new();
+ Assert.Null(ctx.GetUserAgentString());
+ }
+
+ [Fact]
+ public void Accessor_Get_Returns_Null_When_Header_Missing()
+ {
+ var provider = Substitute.For();
+ HttpUserAgentParserAccessor accessor = new(provider);
+ DefaultHttpContext ctx = new();
+
+ Assert.Null(accessor.Get(ctx));
+ provider.DidNotReceiveWithAnyArgs().Parse(default!);
+ }
+}
diff --git a/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParser.AspNetCore.UnitTests.csproj b/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParser.AspNetCore.UnitTests.csproj
index 269119a..f24713d 100644
--- a/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParser.AspNetCore.UnitTests.csproj
+++ b/tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParser.AspNetCore.UnitTests.csproj
@@ -13,4 +13,11 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParser.MemoryCache.UnitTests.csproj b/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParser.MemoryCache.UnitTests.csproj
index b68d12f..4c330fe 100644
--- a/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParser.MemoryCache.UnitTests.csproj
+++ b/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParser.MemoryCache.UnitTests.csproj
@@ -13,4 +13,11 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderAdditionalTests.cs b/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderAdditionalTests.cs
new file mode 100644
index 0000000..45e9753
--- /dev/null
+++ b/tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParserMemoryCachedProviderAdditionalTests.cs
@@ -0,0 +1,31 @@
+// Copyright © https://myCSharp.de - all rights reserved
+
+using Microsoft.Extensions.Caching.Memory;
+using Xunit;
+
+namespace MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests;
+
+public class HttpUserAgentParserMemoryCachedProviderAdditionalTests
+{
+ [Fact]
+ public void Options_Defaults_Are_Set()
+ {
+ HttpUserAgentParserMemoryCachedProviderOptions options = new();
+ Assert.NotNull(options.CacheOptions);
+ Assert.NotNull(options.CacheEntryOptions);
+ Assert.True(options.CacheOptions.SizeLimit is null || options.CacheOptions.SizeLimit >= 0);
+ Assert.NotEqual(default, options.CacheEntryOptions.SlidingExpiration);
+ }
+
+ [Fact]
+ public void Provider_Caches_Entries_And_Resolves_Twice()
+ {
+ HttpUserAgentParserMemoryCachedProvider provider = new(new HttpUserAgentParserMemoryCachedProviderOptions(new MemoryCacheOptions { SizeLimit = 10 }));
+ string ua = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36";
+ HttpUserAgentInformation a = provider.Parse(ua);
+ HttpUserAgentInformation b = provider.Parse(ua);
+
+ Assert.Equal(a.Name, b.Name);
+ Assert.Equal(a.Version, b.Version);
+ }
+}
diff --git a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParser.UnitTests.csproj b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParser.UnitTests.csproj
index cdf4e3d..7366e0c 100644
--- a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParser.UnitTests.csproj
+++ b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParser.UnitTests.csproj
@@ -12,4 +12,11 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs
index b778535..972f42a 100644
--- a/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs
+++ b/tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs
@@ -223,4 +223,79 @@ public void InvalidUserAgent(string userAgent)
Assert.False(info.IsBrowser());
Assert.False(info.IsRobot());
}
+
+ [Fact]
+ public void Cleanup_Trims_Input()
+ {
+ string input = " Mozilla/5.0 ";
+ Assert.Equal("Mozilla/5.0", HttpUserAgentParser.Cleanup(input));
+ }
+
+ [Fact]
+ public void TryGetPlatform_True_And_False()
+ {
+ bool ok = HttpUserAgentParser.TryGetPlatform("Mozilla/5.0 (Windows NT 10.0)", out HttpUserAgentPlatformInformation? platform);
+ Assert.True(ok);
+ Assert.NotNull(platform);
+ Assert.Equal(HttpUserAgentPlatformType.Windows, platform!.Value.PlatformType);
+
+ ok = HttpUserAgentParser.TryGetPlatform("UnknownAgent", out platform);
+ Assert.False(ok);
+ Assert.Null(platform);
+ }
+
+ [Fact]
+ public void TryGetRobot_True_And_False()
+ {
+ bool ok = HttpUserAgentParser.TryGetRobot("Googlebot/2.1 (+http://www.google.com/bot.html)", out string? robot);
+ Assert.True(ok);
+ Assert.Equal("Googlebot", robot);
+
+ ok = HttpUserAgentParser.TryGetRobot("NoBotHere", out robot);
+ Assert.False(ok);
+ Assert.Null(robot);
+ }
+
+ [Fact]
+ public void TryGetMobileDevice_True_And_False()
+ {
+ bool ok = HttpUserAgentParser.TryGetMobileDevice("(iPhone; CPU iPhone OS)", out string? device);
+ Assert.True(ok);
+ Assert.Equal("Apple iPhone", device);
+
+ ok = HttpUserAgentParser.TryGetMobileDevice("Desktop Machine", out device);
+ Assert.False(ok);
+ Assert.Null(device);
+ }
+
+ [Fact]
+ public void TryGetBrowser_False_When_Token_Without_Slash()
+ {
+ // Contains DetectToken (Edg) but not followed by '/', should be ignored by fast-path and no regex fallback here
+ (string Name, string? Version)? browser;
+ bool ok = HttpUserAgentParser.TryGetBrowser("Mozilla Edg 123 something", out browser);
+ Assert.False(ok);
+ Assert.Null(browser);
+ }
+
+ [Fact]
+ public void GetBrowser_Trident_Without_RV_Falls_Back_To_Detect_Token()
+ {
+ // Trident present but no rv:, fallback should extract version after DetectToken (Trident/7.0)
+ (string Name, string? Version)? browser = HttpUserAgentParser.GetBrowser("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Trident/7.0 like Gecko");
+ Assert.NotNull(browser);
+ Assert.Equal("Internet Explorer", browser!.Value.Name);
+ Assert.Equal("7.0", browser.Value.Version);
+ }
+
+ [Fact]
+ public void GetBrowser_LongToken_NoDigits_Within_Window_Does_Not_Parse_Version()
+ {
+ // Build UA: Detect token present (Chrome), but after '/' there are no digits within first 200 chars
+ string longJunk = new('a', 200);
+ string ua = $"Mozilla/5.0 Chrome/{longJunk} versionafterwindow1.2";
+
+ (string Name, string? Version)? browser = HttpUserAgentParser.GetBrowser(ua);
+ Assert.Null(browser); // Should fail to extract version and continue, ending with no browser match
+ }
}