Skip to content

Commit b1b98cc

Browse files
authored
feat(tests): add coverage settings and new unit tests (#77)
1 parent 3006600 commit b1b98cc

File tree

8 files changed

+205
-21
lines changed

8 files changed

+205
-21
lines changed

Directory.Build.props

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
</PropertyGroup>
6969

7070
<ItemGroup Label="Default Test Dependencies" Condition="'$(IsTestProject)' == 'true'">
71+
<PackageReference Include="coverlet.msbuild" />
7172
<PackageReference Include="Microsoft.NET.Test.Sdk" />
7273
<PackageReference Include="Microsoft.Extensions.Diagnostics.Testing" />
7374
<PackageReference Include="NSubstitute" />
@@ -88,6 +89,31 @@
8889
</PackageReference>
8990
</ItemGroup>
9091

92+
<!-- Global coverage settings applied to all test projects -->
93+
<PropertyGroup Condition="'$(IsTestProject)' == 'true'" Label="Coverage">
94+
<CollectCoverage>true</CollectCoverage>
95+
<CoverletOutputFormat>lcov,opencover,cobertura</CoverletOutputFormat>
96+
<CoverletOutput>$(MSBuildThisFileDirectory)TestResults/coverage/$(MSBuildProjectName).</CoverletOutput>
97+
<ExcludeByAttribute>GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
98+
<ExcludeByFile>**/*Program.cs;**/*Startup.cs;**/*GlobalUsings.cs</ExcludeByFile>
99+
<UseSourceLink>true</UseSourceLink>
100+
<!-- Enforce 100% line coverage; branch coverage is informative only -->
101+
<Threshold>100</Threshold>
102+
<ThresholdType>line</ThresholdType>
103+
<ThresholdStat>total</ThresholdStat>
104+
</PropertyGroup>
105+
106+
<!-- Limit coverage to the target assemblies for each test project -->
107+
<PropertyGroup Condition="'$(MSBuildProjectName)' == 'HttpUserAgentParser.UnitTests'" Label="CoverageFilter">
108+
<Include>[MyCSharp.HttpUserAgentParser]*</Include>
109+
</PropertyGroup>
110+
<PropertyGroup Condition="'$(MSBuildProjectName)' == 'HttpUserAgentParser.MemoryCache.UnitTests'" Label="CoverageFilter">
111+
<Include>[MyCSharp.HttpUserAgentParser.MemoryCache]*</Include>
112+
</PropertyGroup>
113+
<PropertyGroup Condition="'$(MSBuildProjectName)' == 'HttpUserAgentParser.AspNetCore.UnitTests'" Label="CoverageFilter">
114+
<Include>[MyCSharp.HttpUserAgentParser.AspNetCore]*</Include>
115+
</PropertyGroup>
116+
91117
<ItemGroup Label="Default Analyzers">
92118
<PackageReference Include="Roslynator.Analyzers">
93119
<PrivateAssets>all</PrivateAssets>

Directory.Packages.props

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,71 @@
22
<PropertyGroup>
33
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
44
</PropertyGroup>
5-
65
<ItemGroup Label="Default Dependencies">
76
<PackageVersion Include="NaughtyStrings" Version="2.4.1" />
87
</ItemGroup>
9-
108
<ItemGroup Label="Dependencies">
119
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
1210
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
1311
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
1412
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
15-
1613
</ItemGroup>
17-
1814
<ItemGroup Label="Libraries for comparison">
1915
<PackageVersion Include="UAParser" Version="3.1.47" />
2016
<PackageVersion Include="DeviceDetector.NET" Version="6.4.2" />
2117
<PackageVersion Include="Ng.UserAgentService" Version="3.0.0" />
2218
</ItemGroup>
23-
2419
<ItemGroup Label="Benchmarks">
25-
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
26-
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" />
20+
<PackageVersion Include="BenchmarkDotNet" Version="0.15.2" />
21+
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.2" />
2722
</ItemGroup>
28-
2923
<ItemGroup Label="Tests">
30-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
24+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
3125
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.10.0" />
3226
<PackageVersion Include="NSubstitute" Version="5.3.0" />
3327
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
3428
<PrivateAssets>all</PrivateAssets>
3529
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
3630
</PackageVersion>
37-
<PackageVersion Include="xunit.v3" Version="2.0.1" />
38-
<PackageVersion Include="xunit.v3.extensibility.core" Version="2.0.1" />
39-
<PackageVersion Include="xunit.v3.assert" Version="2.0.1" />
31+
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
32+
<PackageVersion Include="xunit.v3" Version="3.0.1" />
33+
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.0.1" />
34+
<PackageVersion Include="xunit.v3.assert" Version="3.0.1" />
4035
<PackageVersion Include="xunit.runner.console" Version="2.9.3">
4136
<PrivateAssets>all</PrivateAssets>
4237
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4338
</PackageVersion>
44-
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2">
39+
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4">
4540
<PrivateAssets>all</PrivateAssets>
4641
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4742
</PackageVersion>
4843
</ItemGroup>
49-
5044
<ItemGroup Label="Analyzers">
51-
<PackageVersion Include="Roslynator.Analyzers" Version="4.13.1">
45+
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.0">
5246
<PrivateAssets>all</PrivateAssets>
5347
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
5448
</PackageVersion>
55-
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.13.1">
49+
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.0">
5650
<PrivateAssets>all</PrivateAssets>
5751
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
5852
</PackageVersion>
59-
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.13.1">
53+
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.0">
6054
<PrivateAssets>all</PrivateAssets>
6155
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
6256
</PackageVersion>
63-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0">
57+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.14.0">
6458
<PrivateAssets>all</PrivateAssets>
6559
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
6660
</PackageVersion>
67-
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.13.0">
61+
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0">
6862
<PrivateAssets>all</PrivateAssets>
6963
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
7064
</PackageVersion>
71-
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0">
65+
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0">
7266
<PrivateAssets>all</PrivateAssets>
7367
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
7468
</PackageVersion>
75-
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.196">
69+
<PackageVersion Include="Meziantou.Analyzer" Version="2.0.212">
7670
<PrivateAssets>all</PrivateAssets>
7771
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
7872
</PackageVersion>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using Microsoft.AspNetCore.Http;
4+
using MyCSharp.HttpUserAgentParser.AspNetCore;
5+
using MyCSharp.HttpUserAgentParser.Providers;
6+
using NSubstitute;
7+
using Xunit;
8+
9+
namespace MyCSharp.HttpUserAgentParser.AspNetCore.UnitTests;
10+
11+
public class HttpContextExtensionsTests
12+
{
13+
[Fact]
14+
public void GetUserAgentString_Returns_Value_When_Present()
15+
{
16+
HttpContext ctx = HttpContextTestHelpers.GetHttpContext("UA");
17+
Assert.Equal("UA", ctx.GetUserAgentString());
18+
}
19+
20+
[Fact]
21+
public void GetUserAgentString_Returns_Null_When_Absent()
22+
{
23+
DefaultHttpContext ctx = new();
24+
Assert.Null(ctx.GetUserAgentString());
25+
}
26+
27+
[Fact]
28+
public void Accessor_Get_Returns_Null_When_Header_Missing()
29+
{
30+
var provider = Substitute.For<IHttpUserAgentParserProvider>();
31+
HttpUserAgentParserAccessor accessor = new(provider);
32+
DefaultHttpContext ctx = new();
33+
34+
Assert.Null(accessor.Get(ctx));
35+
provider.DidNotReceiveWithAnyArgs().Parse(default!);
36+
}
37+
}

tests/HttpUserAgentParser.AspNetCore.UnitTests/HttpUserAgentParser.AspNetCore.UnitTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@
1313
<ProjectReference Include="..\..\tests\HttpUserAgentParser.TestHelpers\HttpUserAgentParser.TestHelpers.csproj" />
1414
</ItemGroup>
1515

16+
<ItemGroup>
17+
<PackageReference Update="coverlet.msbuild">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
21+
</ItemGroup>
22+
1623
</Project>

tests/HttpUserAgentParser.MemoryCache.UnitTests/HttpUserAgentParser.MemoryCache.UnitTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@
1313
<ProjectReference Include="..\..\src\HttpUserAgentParser.MemoryCache\HttpUserAgentParser.MemoryCache.csproj" />
1414
</ItemGroup>
1515

16+
<ItemGroup>
17+
<PackageReference Update="coverlet.msbuild">
18+
<PrivateAssets>all</PrivateAssets>
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
</PackageReference>
21+
</ItemGroup>
22+
1623
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright © https://myCSharp.de - all rights reserved
2+
3+
using Microsoft.Extensions.Caching.Memory;
4+
using Xunit;
5+
6+
namespace MyCSharp.HttpUserAgentParser.MemoryCache.UnitTests;
7+
8+
public class HttpUserAgentParserMemoryCachedProviderAdditionalTests
9+
{
10+
[Fact]
11+
public void Options_Defaults_Are_Set()
12+
{
13+
HttpUserAgentParserMemoryCachedProviderOptions options = new();
14+
Assert.NotNull(options.CacheOptions);
15+
Assert.NotNull(options.CacheEntryOptions);
16+
Assert.True(options.CacheOptions.SizeLimit is null || options.CacheOptions.SizeLimit >= 0);
17+
Assert.NotEqual(default, options.CacheEntryOptions.SlidingExpiration);
18+
}
19+
20+
[Fact]
21+
public void Provider_Caches_Entries_And_Resolves_Twice()
22+
{
23+
HttpUserAgentParserMemoryCachedProvider provider = new(new HttpUserAgentParserMemoryCachedProviderOptions(new MemoryCacheOptions { SizeLimit = 10 }));
24+
string ua = "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36";
25+
HttpUserAgentInformation a = provider.Parse(ua);
26+
HttpUserAgentInformation b = provider.Parse(ua);
27+
28+
Assert.Equal(a.Name, b.Name);
29+
Assert.Equal(a.Version, b.Version);
30+
}
31+
}

tests/HttpUserAgentParser.UnitTests/HttpUserAgentParser.UnitTests.csproj

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,11 @@
1212
<ProjectReference Include="..\..\src\HttpUserAgentParser\HttpUserAgentParser.csproj" />
1313
</ItemGroup>
1414

15+
<ItemGroup>
16+
<PackageReference Update="coverlet.msbuild">
17+
<PrivateAssets>all</PrivateAssets>
18+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
19+
</PackageReference>
20+
</ItemGroup>
21+
1522
</Project>

tests/HttpUserAgentParser.UnitTests/HttpUserAgentParserTests.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,79 @@ public void InvalidUserAgent(string userAgent)
223223
Assert.False(info.IsBrowser());
224224
Assert.False(info.IsRobot());
225225
}
226+
227+
[Fact]
228+
public void Cleanup_Trims_Input()
229+
{
230+
string input = " Mozilla/5.0 ";
231+
Assert.Equal("Mozilla/5.0", HttpUserAgentParser.Cleanup(input));
232+
}
233+
234+
[Fact]
235+
public void TryGetPlatform_True_And_False()
236+
{
237+
bool ok = HttpUserAgentParser.TryGetPlatform("Mozilla/5.0 (Windows NT 10.0)", out HttpUserAgentPlatformInformation? platform);
238+
Assert.True(ok);
239+
Assert.NotNull(platform);
240+
Assert.Equal(HttpUserAgentPlatformType.Windows, platform!.Value.PlatformType);
241+
242+
ok = HttpUserAgentParser.TryGetPlatform("UnknownAgent", out platform);
243+
Assert.False(ok);
244+
Assert.Null(platform);
245+
}
246+
247+
[Fact]
248+
public void TryGetRobot_True_And_False()
249+
{
250+
bool ok = HttpUserAgentParser.TryGetRobot("Googlebot/2.1 (+http://www.google.com/bot.html)", out string? robot);
251+
Assert.True(ok);
252+
Assert.Equal("Googlebot", robot);
253+
254+
ok = HttpUserAgentParser.TryGetRobot("NoBotHere", out robot);
255+
Assert.False(ok);
256+
Assert.Null(robot);
257+
}
258+
259+
[Fact]
260+
public void TryGetMobileDevice_True_And_False()
261+
{
262+
bool ok = HttpUserAgentParser.TryGetMobileDevice("(iPhone; CPU iPhone OS)", out string? device);
263+
Assert.True(ok);
264+
Assert.Equal("Apple iPhone", device);
265+
266+
ok = HttpUserAgentParser.TryGetMobileDevice("Desktop Machine", out device);
267+
Assert.False(ok);
268+
Assert.Null(device);
269+
}
270+
271+
[Fact]
272+
public void TryGetBrowser_False_When_Token_Without_Slash()
273+
{
274+
// Contains DetectToken (Edg) but not followed by '/', should be ignored by fast-path and no regex fallback here
275+
(string Name, string? Version)? browser;
276+
bool ok = HttpUserAgentParser.TryGetBrowser("Mozilla Edg 123 something", out browser);
277+
Assert.False(ok);
278+
Assert.Null(browser);
279+
}
280+
281+
[Fact]
282+
public void GetBrowser_Trident_Without_RV_Falls_Back_To_Detect_Token()
283+
{
284+
// Trident present but no rv:, fallback should extract version after DetectToken (Trident/7.0)
285+
(string Name, string? Version)? browser = HttpUserAgentParser.GetBrowser("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Trident/7.0 like Gecko");
286+
Assert.NotNull(browser);
287+
Assert.Equal("Internet Explorer", browser!.Value.Name);
288+
Assert.Equal("7.0", browser.Value.Version);
289+
}
290+
291+
[Fact]
292+
public void GetBrowser_LongToken_NoDigits_Within_Window_Does_Not_Parse_Version()
293+
{
294+
// Build UA: Detect token present (Chrome), but after '/' there are no digits within first 200 chars
295+
string longJunk = new('a', 200);
296+
string ua = $"Mozilla/5.0 Chrome/{longJunk} versionafterwindow1.2";
297+
298+
(string Name, string? Version)? browser = HttpUserAgentParser.GetBrowser(ua);
299+
Assert.Null(browser); // Should fail to extract version and continue, ending with no browser match
300+
}
226301
}

0 commit comments

Comments
 (0)