Skip to content

Commit d6cd2e2

Browse files
committed
Update to .NET 10, refactor build, add coverage, improve docs
.NET 10 support and dependency updates -- Updated all projects and solution to support .NET 10.0, 9.0, and 8.0 -- Upgraded ASP.NET Core and Entity Framework Core dependencies per target framework -- Updated solution file for latest Visual Studio compatibility Build and packaging system refactor -- Removed base.targets and introduced pack.targets for centralized packaging and metadata -- Updated Directory.Build.props for new target frameworks and dependency versioning Testing and code coverage improvements -- Updated test project and targets to net10.0 and latest test SDKs -- Switched from FluentAssertions to xUnit assertions -- Added Microsoft.CodeCoverage and improved coverage collection -- Enhanced GitHub Actions workflow to run tests with coverage and publish HTML reports Documentation and code quality -- Improved XML documentation for Search and Search<TEntity, TDto> constructors -- Updated README with local and CI coverage instructions -- Added new test for custom specifier filter method usage in SpecifierFunctionGeneratorTests
1 parent 79bef60 commit d6cd2e2

File tree

16 files changed

+155
-61
lines changed

16 files changed

+155
-61
lines changed

.github/workflows/smart-search.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ jobs:
1818
- name: Setup .NET
1919
uses: actions/setup-dotnet@v4
2020
with:
21-
dotnet-version: '9.0.x'
21+
dotnet-version: '10.0.x'
22+
23+
- name: Restore
24+
run: dotnet restore
2225

2326
- name: Build and pack SmartSearch Abstractions
2427
run: dotnet build ./src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj -c Release
@@ -35,5 +38,25 @@ jobs:
3538
- name: Build and pack SmartSearch AspNetCore
3639
run: dotnet build ./src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj -c Release
3740

41+
- name: Test with coverage (coverlet.collector)
42+
run: dotnet test ./src --collect:"XPlat Code Coverage" --results-directory ./TestResults --no-build
43+
44+
- name: Install ReportGenerator
45+
run: dotnet tool install --global dotnet-reportgenerator-globaltool
46+
47+
- name: Generate coverage report
48+
run: |
49+
reportgenerator \
50+
-reports:./TestResults/**/coverage.cobertura.xml \
51+
-targetdir:./TestResults/Report \
52+
-reporttypes:Html
53+
echo "\nCoverage summary:" && cat ./TestResults/Report/Summary.txt || true
54+
55+
- name: Upload coverage report artifact
56+
uses: actions/upload-artifact@v4
57+
with:
58+
name: coverage-report
59+
path: ./TestResults/Report
60+
3861
- name: Publish
3962
run: dotnet nuget push ./**/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate

src/Directory.Build.props

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
<Project>
22
<PropertyGroup>
3-
<LibTargets>net8.0;net9.0;</LibTargets>
4-
<AspTargets>net8.0;net9.0;</AspTargets>
3+
<LibTargets>net8.0;net9.0;net10.0;</LibTargets>
4+
<AspTargets>net8.0;net9.0;net10.0;</AspTargets>
55
</PropertyGroup>
66
<PropertyGroup>
7-
<SearchesVer>0.8.6</SearchesVer>
7+
<SearchesVer>0.9.0</SearchesVer>
88
<SearchesPreview></SearchesPreview>
99
</PropertyGroup>
10-
<PropertyGroup>
11-
<DotNetCoreVersion Condition="'$(TargetFramework)' == 'net8.0'">8.0.0</DotNetCoreVersion>
12-
<DotNetCoreVersion Condition="'$(TargetFramework)' == 'net9.0'">9.0.0</DotNetCoreVersion>
13-
</PropertyGroup>
1410
<PropertyGroup>
1511
<PropSelVer>1.0.2</PropSelVer>
1612
<OpHintVer>1.0.0</OpHintVer>
17-
<ProblemsVer>1.0.0-preview-4.9</ProblemsVer>
13+
<ProblemsVer>1.0.0-preview-6.0</ProblemsVer>
14+
</PropertyGroup>
15+
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
16+
<AspVer>8.0.0</AspVer>
17+
<EFVer>8.0.10</EFVer>
18+
</PropertyGroup>
19+
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
20+
<AspVer>9.0.0</AspVer>
21+
<EFVer>9.0.0</EFVer>
22+
</PropertyGroup>
23+
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
24+
<AspVer >10.0.0</AspVer>
25+
<EFVer>10.0.0</EFVer>
1826
</PropertyGroup>
1927
</Project>

src/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,25 @@ Tests in `RoyalCode.SmartSearch.Tests` demonstrate usage scenarios such as:
118118
---
119119

120120
For more examples, see the test files in the `RoyalCode.SmartSearch.Tests` folder.
121+
122+
## Documentation Status
123+
- Libraries and patterns are described above with examples of usage.
124+
- More API-level docs and extension points can be expanded (e.g., selector resolvers, specifier generator options).
125+
- Tests illustrate typical usage; a dedicated samples directory could be added in the future.
126+
127+
## Test Coverage
128+
The test projects already reference `coverlet.collector` and `Microsoft.CodeCoverage` (see `src/tests.targets`).
129+
130+
### Local coverage report
131+
1. Run tests and collect coverage:
132+
- `dotnet test ./src --collect:"XPlat Code Coverage" --results-directory ./TestResults`
133+
2. Generate HTML report:
134+
- Install tool once: `dotnet tool install --global dotnet-reportgenerator-globaltool`
135+
- Generate: `reportgenerator -reports:./TestResults/**/coverage.cobertura.xml -targetdir:./TestResults/Report -reporttypes:Html`
136+
3. Open `./TestResults/Report/index.html` in a browser.
137+
138+
### GitHub Actions coverage
139+
The workflow at `.github/workflows/smart-search.yml` runs tests with coverage and publishes an artifact `coverage-report` containing the HTML output. After the run completes:
140+
- Download the `coverage-report` artifact from the job summary.
141+
- Open `index.html` to view coverage.
142+

src/RoyalCode.SmartSearch.Abstractions/RoyalCode.SmartSearch.Abstractions.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="..\base.targets" />
3+
<Import Project="..\pack.targets" />
44

55
<PropertyGroup>
66
<TargetFrameworks>$(LibTargets)</TargetFrameworks>

src/RoyalCode.SmartSearch.AspNetCore/RoyalCode.SmartSearch.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="..\base.targets" />
3+
<Import Project="..\pack.targets" />
44

55
<PropertyGroup>
66
<TargetFrameworks>$(LibTargets)</TargetFrameworks>

src/RoyalCode.SmartSearch.Core/Defaults/Search.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public class Search<TEntity> : ISearch<TEntity>
1818
/// <summary>
1919
/// Creates a new search.
2020
/// </summary>
21-
/// <param name="performer"></param>
22-
/// <param name="options"></param>
21+
/// <param name="performer">The criteria performer used to execute search operations for entities of type TEntity.</param>
22+
/// <param name="options">The options that configure search behavior, such as tracking and filtering settings.</param>
2323
public Search(ICriteriaPerformer<TEntity> performer, CriteriaOptions options)
2424
{
2525
this.performer = performer;
@@ -69,6 +69,11 @@ public Task<TEntity> SingleAsync(CancellationToken cancellationToken = default)
6969
=> performer.Prepare(options).SingleAsync(cancellationToken);
7070
}
7171

72+
/// <summary>
73+
/// Default implementation of <see cref="ISearch{TEntity,TDto}"/>.
74+
/// </summary>
75+
/// <typeparam name="TEntity">The entity type.</typeparam>
76+
/// <typeparam name="TDto">The dto type.</typeparam>
7277
public class Search<TEntity, TDto> : ISearch<TEntity, TDto>
7378
where TEntity : class
7479
where TDto : class
@@ -77,6 +82,13 @@ public class Search<TEntity, TDto> : ISearch<TEntity, TDto>
7782
private readonly CriteriaOptions options;
7883
private readonly ISearchSelect<TEntity, TDto> searchSelect;
7984

85+
/// <summary>
86+
/// Initializes a new instance of the Search class with the specified criteria performer, options, and search
87+
/// selector.
88+
/// </summary>
89+
/// <param name="performer">The criteria performer used to execute search operations for entities of type TEntity.</param>
90+
/// <param name="options">The options that configure search behavior, such as tracking and filtering settings.</param>
91+
/// <param name="searchSelect">The selector that defines how search results of type TEntity are projected to DTOs of type TDto.</param>
8092
public Search(
8193
ICriteriaPerformer<TEntity> performer,
8294
CriteriaOptions options,

src/RoyalCode.SmartSearch.Core/RoyalCode.SmartSearch.Core.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="..\base.targets" />
3+
<Import Project="..\pack.targets" />
44

55
<PropertyGroup>
66
<TargetFrameworks>$(AspTargets)</TargetFrameworks>
@@ -18,7 +18,7 @@
1818

1919
<ItemGroup>
2020
<PackageReference Include="RoyalCode.Extensions.PropertySelection" Version="$(PropSelVer)" />
21-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(DotNetCoreVersion)" />
21+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(AspVer)" />
2222
</ItemGroup>
2323

2424
<ItemGroup>

src/RoyalCode.SmartSearch.EntityFramework/RoyalCode.SmartSearch.EntityFramework.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="..\base.targets" />
3+
<Import Project="..\pack.targets" />
44

55
<PropertyGroup>
66
<TargetFrameworks>$(AspTargets)</TargetFrameworks>
@@ -17,7 +17,7 @@
1717
</PropertyGroup>
1818

1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="$(DotNetCoreVersion)" />
20+
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="$(EFVer)" />
2121
<PackageReference Include="RoyalCode.OperationHint.Abstractions" Version="$(OpHintVer)" />
2222
</ItemGroup>
2323

src/RoyalCode.SmartSearch.Linq/RoyalCode.SmartSearch.Linq.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Import Project="..\base.targets" />
3+
<Import Project="..\pack.targets" />
44

55
<PropertyGroup>
66
<TargetFrameworks>$(AspTargets)</TargetFrameworks>
@@ -17,7 +17,7 @@
1717

1818
<ItemGroup>
1919
<PackageReference Include="RoyalCode.Extensions.PropertySelection" Version="$(PropSelVer)" />
20-
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(DotNetCoreVersion)" />
20+
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(AspVer)" />
2121
</ItemGroup>
2222

2323
<ItemGroup>

src/RoyalCode.SmartSearch.Tests/AllEntitiesTests.cs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using FluentAssertions;
2-
using Microsoft.EntityFrameworkCore;
1+
using Microsoft.EntityFrameworkCore;
32
using Microsoft.Extensions.DependencyInjection;
43

54
namespace RoyalCode.SmartSearch.Tests;
@@ -43,7 +42,7 @@ public void Must_CollectAll_WhenNoFilterOrSorting()
4342
IReadOnlyList<SimpleModel> result = all.Collect();
4443

4544
// assert
46-
result.Should().HaveCount(3);
45+
Assert.Equal(3, result.Count);
4746
}
4847

4948
[Fact]
@@ -66,7 +65,7 @@ public async Task Must_CollectAll_WhenNoFilterOrSortingAsync()
6665
IReadOnlyList<SimpleModel> result = await all.CollectAsync();
6766

6867
// assert
69-
result.Should().HaveCount(3);
68+
Assert.Equal(3, result.Count);
7069
}
7170

7271
[Fact]
@@ -90,8 +89,8 @@ public void Must_CollectOne_WhenFilterByName()
9089
IReadOnlyList<SimpleModel> result = all.FilterBy(filter).Collect();
9190

9291
// assert
93-
result.Should().HaveCount(1);
94-
result.Should().ContainSingle(x => x.Id == 2);
92+
Assert.Single(result);
93+
Assert.Equal(2, result.Single().Id);
9594
}
9695

9796
[Fact]
@@ -115,7 +114,8 @@ public async Task Must_CollectOne_WhenFilterByNameAsync()
115114
IReadOnlyList<SimpleModel> result = await all.FilterBy(filter).CollectAsync();
116115

117116
// assert
118-
result.Should().HaveCount(1).And.ContainSingle(x => x.Id == 2);
117+
Assert.Single(result);
118+
Assert.Equal(2, result.Single().Id);
119119
}
120120

121121
[Fact]
@@ -139,7 +139,7 @@ public void Must_Exists_WhenFilterByName()
139139
bool result = all.FilterBy(filter).Exists();
140140

141141
// assert
142-
result.Should().BeTrue();
142+
Assert.True(result);
143143
}
144144

145145
[Fact]
@@ -163,7 +163,7 @@ public async Task Must_Exists_WhenFilterByNameAsync()
163163
bool result = await all.FilterBy(filter).ExistsAsync();
164164

165165
// assert
166-
result.Should().BeTrue();
166+
Assert.True(result);
167167
}
168168

169169
[Fact]
@@ -187,7 +187,7 @@ public void Must_NotExists_WhenFilterByName()
187187
bool result = all.FilterBy(filter).Exists();
188188

189189
// assert
190-
result.Should().BeFalse();
190+
Assert.False(result);
191191
}
192192

193193
[Fact]
@@ -211,7 +211,7 @@ public async Task Must_NotExists_WhenFilterByNameAsync()
211211
bool result = await all.FilterBy(filter).ExistsAsync();
212212

213213
// assert
214-
result.Should().BeFalse();
214+
Assert.False(result);
215215
}
216216

217217
[Fact]
@@ -237,7 +237,8 @@ public void Must_First_WhenFilterByName()
237237
// assert
238238

239239
// assert
240-
result.Should().NotBeNull().And.Match<SimpleModel>(x => x.Id == 2);
240+
Assert.NotNull(result);
241+
Assert.Equal(2, result.Id);
241242
}
242243

243244
[Fact]
@@ -261,7 +262,8 @@ public async Task Must_First_WhenFilterByNameAsync()
261262
SimpleModel? result = await all.FilterBy(filter).FirstOrDefaultAsync();
262263

263264
// assert
264-
result.Should().NotBeNull().And.Match<SimpleModel>(x => x.Id == 2);
265+
Assert.NotNull(result);
266+
Assert.Equal(2, result.Id);
265267
}
266268

267269
[Fact]
@@ -285,7 +287,7 @@ public void Must_FirstBeNull_WhenFilterByName()
285287
SimpleModel? result = all.FilterBy(filter).FirstOrDefault();
286288

287289
// assert
288-
result.Should().BeNull();
290+
Assert.Null(result);
289291
}
290292

291293
[Fact]
@@ -309,7 +311,7 @@ public async Task Must_FirstBeNull_WhenFilterByNameAsync()
309311
SimpleModel? result = await all.FilterBy(filter).FirstOrDefaultAsync();
310312

311313
// assert
312-
result.Should().BeNull();
314+
Assert.Null(result);
313315
}
314316

315317
[Fact]
@@ -333,7 +335,7 @@ public void Must_Single_WhenFilterByName()
333335
SimpleModel result = all.FilterBy(filter).Single();
334336

335337
// assert
336-
result.Id.Should().Be(2);
338+
Assert.Equal(2, result.Id);
337339
}
338340

339341
[Fact]
@@ -357,7 +359,7 @@ public async Task Must_Single_WhenFilterByNameAsync()
357359
SimpleModel result = await all.FilterBy(filter).SingleAsync();
358360

359361
// assert
360-
result.Id.Should().Be(2);
362+
Assert.Equal(2, result.Id);
361363
}
362364

363365
[Fact]
@@ -380,7 +382,7 @@ public void Must_Throw_WhenSingle_HasMoreThenOne()
380382
Action act = () => all.Single();
381383

382384
// assert
383-
act.Should().Throw<InvalidOperationException>();
385+
Assert.Throws<InvalidOperationException>(act);
384386
}
385387

386388
[Fact]
@@ -403,7 +405,7 @@ public async Task Must_Throw_WhenSingle_HasMoreThenOneAsync()
403405
Func<Task> act = () => all.SingleAsync();
404406

405407
// assert
406-
await act.Should().ThrowAsync<InvalidOperationException>();
408+
await Assert.ThrowsAsync<InvalidOperationException>(act);
407409
}
408410

409411
[Fact]
@@ -427,7 +429,7 @@ public void Must_Throw_WhenSingle_HasNoOne()
427429
Action act = () => all.FilterBy(filter).Single();
428430

429431
// assert
430-
act.Should().Throw<InvalidOperationException>();
432+
Assert.Throws<InvalidOperationException>(act);
431433
}
432434

433435
[Fact]
@@ -451,7 +453,7 @@ public async Task Must_Throw_WhenSingle_HasNoOneAsync()
451453
Func<Task> act = () => all.FilterBy(filter).SingleAsync();
452454

453455
// assert
454-
await act.Should().ThrowAsync<InvalidOperationException>();
456+
await Assert.ThrowsAsync<InvalidOperationException>(act);
455457
}
456458
}
457459

0 commit comments

Comments
 (0)