Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
bbed942
Fix Sonar findings
Jan 13, 2026
012c7f7
Add tests for updated endpoints
Jan 13, 2026
639ea1b
Remove legacy database scripts
Jan 13, 2026
56cfbdf
chore: Bump the nuget-dependencies group with 15 updates
dependabot[bot] Jan 14, 2026
24b4a14
Merge pull request #708 from PathfinderHonorManager/dependabot/nuget/…
github-actions[bot] Jan 14, 2026
8c12a03
chore: Bump the nuget-dependencies group with 1 update
dependabot[bot] Feb 2, 2026
e4999cb
Merge pull request #709 from PathfinderHonorManager/dependabot/nuget/…
github-actions[bot] Feb 2, 2026
94129dc
chore: Bump FluentValidation.Validators.UnitTestExtension from 1.12.0…
dependabot[bot] Apr 13, 2026
03ac700
Merge pull request #734 from PathfinderHonorManager/dependabot/nuget/…
github-actions[bot] Apr 13, 2026
ee3d5fd
ci(deps): Bump the github-actions group across 1 directory with 3 upd…
dependabot[bot] Apr 14, 2026
43f7307
Merge pull request #730 from PathfinderHonorManager/dependabot/github…
brian-cummings May 29, 2026
b1d60e1
ci(deps): Bump dependabot/fetch-metadata in the github-actions group
dependabot[bot] May 29, 2026
19addbd
Merge pull request #750 from PathfinderHonorManager/dependabot/github…
github-actions[bot] May 29, 2026
16539ec
chore: Bump coverlet.msbuild and 24 others
dependabot[bot] May 29, 2026
9be6170
Fix ApplicationInsights startup compile error
May 31, 2026
b729aba
Merge pull request #751 from PathfinderHonorManager/dependabot/nuget/…
brian-cummings May 31, 2026
d803df8
test: increase coverage for migration service and swagger auth filter
May 31, 2026
4427e16
test: add pathfinder achievement service edge-case coverage
May 31, 2026
c9f8f5e
test: fix nullable annotation warning in migration service test
May 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dependabot_automerge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.5.0
uses: dependabot/fetch-metadata@v3.1.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/main_pathfinderhonormanager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Test with the dotnet CLI (OpenCover)
run: dotnet test PathfinderHonorManager.Tests/PathfinderHonorManager.Tests.csproj /p:CollectCoverage=true /p:CoverletOutput=TestResults/coverage.opencover.xml /p:CoverletOutputFormat=opencover /p:Exclude="[PathfinderHonorManager.Tests*]*"
- name: Upload coverage report
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: coverage-report
path: PathfinderHonorManager.Tests/TestResults/coverage.opencover.xml
Expand All @@ -38,7 +38,7 @@ jobs:
fetch-depth: 0
- uses: ./.github/actions/setup-dotnet
- name: Download coverage report
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: coverage-report
path: .
Expand Down Expand Up @@ -91,7 +91,7 @@ jobs:
working-directory: ./PathfinderHonorManager

- name: Upload Migration Script
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: migration-script
path: PathfinderHonorManager/migration.sql
Expand All @@ -109,7 +109,7 @@ jobs:
App.ServerRootAddress: ${{ env.SERVER_ROOT_ADDRESS }}

- name: Upload artifact for deployment job
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: .net-app
path: ${{env.DOTNET_ROOT}}/myapp
Expand All @@ -124,7 +124,7 @@ jobs:

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
name: .net-app

Expand Down
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,9 @@ _TeamCity*
*.coverage
*.coveragexml

# Coverlet code coverage results
coverage.json
# Coverlet code coverage results
coverage.json
coverage.opencover.xml

# NCrunch
_NCrunch_*
Expand Down Expand Up @@ -448,4 +449,4 @@ ASALocalRun/
.mfractor/

# Local History for Visual Studio
.localhistory/
.localhistory/
1 change: 0 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
## Project Structure & Module Organization
- `PathfinderHonorManager/` hosts the ASP.NET Core API. Key areas: `Controllers/`, `Service/`, `DataAccess/`, `Model/`, `Dto/`, `Validators/`, `Healthcheck/`, `Migrations/`, `Swagger/`, `Mapping/`, and `Converters/`.
- `PathfinderHonorManager.Tests/` contains NUnit tests plus test helpers and seeders.
- `PathfinderHonorManager/Pathfinder-DB/` stores database SQL scripts.
- Configuration lives in `PathfinderHonorManager/appsettings.json`, `PathfinderHonorManager/appsettings.Development.json`, and `PathfinderHonorManager/Properties/launchSettings.json`.

## Build, Test, and Development Commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,162 @@ public async Task BulkPut_WithValidData_ReturnsMultiStatus()
Assert.That(multiStatusResult.StatusCode, Is.EqualTo(207));
}

[Test]
public async Task BulkPut_WithValidItem_ReturnsOkStatusPerItem()
{
var pathfinderId = Guid.NewGuid();
var bulkData = new List<Incoming.BulkPutPathfinderDto>
{
new Incoming.BulkPutPathfinderDto
{
Items = new List<Incoming.BulkPutPathfinderItemDto>
{
new Incoming.BulkPutPathfinderItemDto
{
PathfinderId = pathfinderId,
Grade = 6,
IsActive = true
}
}
}
};

var updatedPathfinder = new Outgoing.PathfinderDto
{
PathfinderID = pathfinderId,
FirstName = "Test",
LastName = "User",
Grade = 6,
IsActive = true
};

_pathfinderServiceMock
.Setup(x => x.UpdateAsync(pathfinderId, It.IsAny<Incoming.PutPathfinderDto>(), TestClubCode, It.IsAny<CancellationToken>()))
.ReturnsAsync(updatedPathfinder);

var result = await _controller.BulkPutPathfindersAsync(bulkData, new CancellationToken());

Assert.That(result, Is.Not.Null);
var multiStatusResult = result as ObjectResult;
Assert.That(multiStatusResult.StatusCode, Is.EqualTo(207));
var responses = ((IEnumerable<object>)multiStatusResult.Value).ToList();
Assert.That(responses.Count, Is.EqualTo(1));
Assert.That(GetAnonymousProperty<int>(responses[0], "status"), Is.EqualTo(StatusCodes.Status200OK));
}

[Test]
public async Task BulkPut_WithMissingPathfinder_ReturnsNotFoundStatusPerItem()
{
var pathfinderId = Guid.NewGuid();
var bulkData = new List<Incoming.BulkPutPathfinderDto>
{
new Incoming.BulkPutPathfinderDto
{
Items = new List<Incoming.BulkPutPathfinderItemDto>
{
new Incoming.BulkPutPathfinderItemDto
{
PathfinderId = pathfinderId,
Grade = 6,
IsActive = true
}
}
}
};

_pathfinderServiceMock
.Setup(x => x.UpdateAsync(pathfinderId, It.IsAny<Incoming.PutPathfinderDto>(), TestClubCode, It.IsAny<CancellationToken>()))
.ReturnsAsync((Outgoing.PathfinderDto)null);

var result = await _controller.BulkPutPathfindersAsync(bulkData, new CancellationToken());

Assert.That(result, Is.Not.Null);
var multiStatusResult = result as ObjectResult;
Assert.That(multiStatusResult.StatusCode, Is.EqualTo(207));
var responses = ((IEnumerable<object>)multiStatusResult.Value).ToList();
Assert.That(responses.Count, Is.EqualTo(1));
Assert.That(GetAnonymousProperty<int>(responses[0], "status"), Is.EqualTo(StatusCodes.Status404NotFound));
}

[Test]
public async Task BulkPut_WithValidationError_ReturnsBadRequestStatusPerItem()
{
var pathfinderId = Guid.NewGuid();
var bulkData = new List<Incoming.BulkPutPathfinderDto>
{
new Incoming.BulkPutPathfinderDto
{
Items = new List<Incoming.BulkPutPathfinderItemDto>
{
new Incoming.BulkPutPathfinderItemDto
{
PathfinderId = pathfinderId,
Grade = 15,
IsActive = true
}
}
}
};

var validationErrors = new List<FluentValidation.Results.ValidationFailure>
{
new FluentValidation.Results.ValidationFailure("Grade", "Grade must be between 5 and 12.")
};
var validationException = new ValidationException(validationErrors);

_pathfinderServiceMock
.Setup(x => x.UpdateAsync(pathfinderId, It.IsAny<Incoming.PutPathfinderDto>(), TestClubCode, It.IsAny<CancellationToken>()))
.ThrowsAsync(validationException);

var result = await _controller.BulkPutPathfindersAsync(bulkData, new CancellationToken());

Assert.That(result, Is.Not.Null);
var multiStatusResult = result as ObjectResult;
Assert.That(multiStatusResult.StatusCode, Is.EqualTo(207));
var responses = ((IEnumerable<object>)multiStatusResult.Value).ToList();
Assert.That(responses.Count, Is.EqualTo(1));
Assert.That(GetAnonymousProperty<int>(responses[0], "status"), Is.EqualTo(StatusCodes.Status400BadRequest));
}

[Test]
public async Task BulkPut_WithDatabaseError_ReturnsBadRequestStatusPerItem()
{
var pathfinderId = Guid.NewGuid();
var bulkData = new List<Incoming.BulkPutPathfinderDto>
{
new Incoming.BulkPutPathfinderDto
{
Items = new List<Incoming.BulkPutPathfinderItemDto>
{
new Incoming.BulkPutPathfinderItemDto
{
PathfinderId = pathfinderId,
Grade = 6,
IsActive = true
}
}
}
};

_pathfinderServiceMock
.Setup(x => x.UpdateAsync(pathfinderId, It.IsAny<Incoming.PutPathfinderDto>(), TestClubCode, It.IsAny<CancellationToken>()))
.ThrowsAsync(new DbUpdateException("Database error"));

var result = await _controller.BulkPutPathfindersAsync(bulkData, new CancellationToken());

Assert.That(result, Is.Not.Null);
var multiStatusResult = result as ObjectResult;
Assert.That(multiStatusResult.StatusCode, Is.EqualTo(207));
var responses = ((IEnumerable<object>)multiStatusResult.Value).ToList();
Assert.That(responses.Count, Is.EqualTo(1));
Assert.That(GetAnonymousProperty<int>(responses[0], "status"), Is.EqualTo(StatusCodes.Status400BadRequest));
}

private static T GetAnonymousProperty<T>(object instance, string propertyName)
{
return (T)instance.GetType().GetProperty(propertyName)!.GetValue(instance);
}

[TearDown]
public async Task TearDown()
{
Expand All @@ -358,4 +514,4 @@ public async Task OneTimeTearDown()
await _dbContext.DisposeAsync();
}
}
}
}
3 changes: 3 additions & 0 deletions PathfinderHonorManager.Tests/Integration/TestAuthHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public TestAuthHandler(
IOptionsMonitor<TestAuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
// TODO: Switch to TimeProvider when AuthenticationHandler supports it for this target framework.
#pragma warning disable CS0618
ISystemClock clock)
: base(options, logger, encoder, clock)
#pragma warning restore CS0618
{
}

Expand Down
23 changes: 12 additions & 11 deletions PathfinderHonorManager.Tests/PathfinderHonorManager.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.msbuild" Version="6.0.4">
<PackageReference Include="coverlet.msbuild" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FluentValidation" Version="12.1.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="10.0.8" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="FluentValidation.Validators.UnitTestExtension" Version="1.12.0.2" />
<PackageReference Include="NUnit" Version="4.6.1" />
<PackageReference Include="NUnit3TestAdapter" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
<PackageReference Include="FluentValidation.Validators.UnitTestExtension" Version="1.12.0.5" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.8" />
</ItemGroup>

<ItemGroup>
Expand Down
25 changes: 23 additions & 2 deletions PathfinderHonorManager.Tests/Service/MigrationServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void StartAsync_MissingConnectionString_Throws()
}

[Test]
public async Task StopAsync_ReturnsCompletedTask()
public void StopAsync_ReturnsCompletedTask()
{
var services = new ServiceCollection().BuildServiceProvider();
var configuration = new ConfigurationBuilder()
Expand All @@ -39,7 +39,28 @@ public async Task StopAsync_ReturnsCompletedTask()
var logger = NullLogger<MigrationService>.Instance;
var service = new MigrationService(services, logger, configuration);

await service.StopAsync(CancellationToken.None);
Assert.DoesNotThrowAsync(() => service.StopAsync(CancellationToken.None));
}

[Test]
public void StartAsync_InvalidDatabaseConnection_ThrowsWrappedInvalidOperationException()
{
var services = new ServiceCollection().BuildServiceProvider();
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string>
{
["ConnectionStrings:PathfinderMigrationCS"] =
"Host=127.0.0.1;Port=1;Database=testdb;Username=test;Password=test;Timeout=1;Command Timeout=1"
})
.Build();
var logger = NullLogger<MigrationService>.Instance;
var service = new MigrationService(services, logger, configuration);

var ex = Assert.ThrowsAsync<InvalidOperationException>(
() => service.StartAsync(CancellationToken.None));

Assert.That(ex!.Message, Does.Contain("Database migration failed during application startup"));
Assert.That(ex.InnerException, Is.Not.Null);
}
}
}
Loading
Loading