Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
10 changes: 5 additions & 5 deletions PathfinderHonorManager.Tests/PathfinderHonorManager.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
<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="Microsoft.Data.Sqlite.Core" Version="10.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageReference Include="FluentValidation.Validators.UnitTestExtension" Version="1.12.0.2" />
<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="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.2" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 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,7 @@ 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));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.OpenApi;
using NUnit.Framework;
using PathfinderHonorManager.Swagger;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace PathfinderHonorManager.Tests.Swagger
{
public class AuthorizeCheckDocumentFilterTests
{
[Test]
public void Apply_AddsSecurityRequirement_ForAuthorizedEndpoint()
{
var swaggerDoc = new OpenApiDocument
{
Components = new OpenApiComponents
{
SecuritySchemes = new Dictionary<string, IOpenApiSecurityScheme>
{
["oauth2"] = new OpenApiSecurityScheme()
}
},
Paths = new OpenApiPaths()
};

swaggerDoc.Paths.Add("/api/test", new OpenApiPathItem
{
Operations = new Dictionary<HttpMethod, OpenApiOperation>
{
[HttpMethod.Get] = new OpenApiOperation()
}
});

var apiDescription = new ApiDescription
{
RelativePath = "api/test",
HttpMethod = "GET",
ActionDescriptor = new ActionDescriptor
{
EndpointMetadata = new List<object> { new AuthorizeAttribute() }
}
};

var schemaGenerator = new SchemaGenerator(
new SchemaGeneratorOptions(),
new JsonSerializerDataContractResolver(new JsonSerializerOptions()));
var context = new DocumentFilterContext(
new List<ApiDescription> { apiDescription },
schemaGenerator,
new SchemaRepository());

var filter = new AuthorizeCheckDocumentFilter();

filter.Apply(swaggerDoc, context);

var operation = swaggerDoc.Paths["/api/test"].Operations[HttpMethod.Get];
Assert.That(operation.Security, Is.Not.Null);
Assert.That(operation.Security.Count, Is.EqualTo(1));
}
}
}
Loading