shoc-backend/Api.SeaHavenIndustries.Tests/WorkOrderReconciliationControllerTests.cs

112 lines
4.2 KiB
C#
Raw Normal View History

using Api.SeaHavenIndustries.Controllers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using SeaHaven.Services.Interfaces;
using System.Security.Claims;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public sealed class WorkOrderReconciliationControllerTests
{
[Fact]
public async Task Admin_trigger_returns_durable_run_and_status_is_queryable()
{
var runId = Guid.NewGuid();
var service = new StubService(runId);
var controller = new WorkOrderReconciliationController(service);
var trigger = Assert.IsType<AcceptedResult>(
await controller.Trigger(CancellationToken.None));
Assert.Equal(StatusCodes.Status202Accepted, trigger.StatusCode);
Assert.Equal(runId, service.LastRunId);
var status = Assert.IsType<OkObjectResult>(
await controller.Status(CancellationToken.None));
Assert.IsType<WorkOrderReconciliationStatus>(status.Value);
}
[Fact]
public async Task Disabled_trigger_returns_sanitized_unavailable_response()
{
var controller = new WorkOrderReconciliationController(new StubService(Guid.Empty));
var result = Assert.IsType<ObjectResult>(
await controller.Trigger(CancellationToken.None));
Assert.Equal(StatusCodes.Status503ServiceUnavailable, result.StatusCode);
Assert.DoesNotContain("exception", result.Value!.ToString(), StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Framework_action_descriptors_expose_only_admin_get_and_post_reconciliation_routes()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddMvcCore()
.AddApplicationPart(typeof(WorkOrderReconciliationController).Assembly);
using var provider = services.BuildServiceProvider();
var descriptors = provider
.GetRequiredService<IActionDescriptorCollectionProvider>()
.ActionDescriptors
.Items
.OfType<ControllerActionDescriptor>()
.Where(d => d.ControllerTypeInfo == typeof(WorkOrderReconciliationController))
.ToList();
var routes = descriptors
.SelectMany(d => (d.ActionConstraints ?? Array.Empty<IActionConstraintMetadata>())
.OfType<HttpMethodActionConstraint>()
.SelectMany(c => c.HttpMethods)
.Select(method => $"{method} {d.AttributeRouteInfo!.Template}"))
.ToHashSet(StringComparer.Ordinal);
Assert.True(routes.SetEquals(new[]
{
"GET api/admin/work-order-reconciliation",
"POST api/admin/work-order-reconciliation"
}));
Assert.All(descriptors, descriptor =>
Assert.Contains(
descriptor.ControllerTypeInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true)
.OfType<AuthorizeAttribute>(),
attribute => attribute.Roles == "Admin"));
}
private sealed class StubService : IWorkOrderReconciliationService
{
private readonly Guid _runId;
public StubService(Guid runId) => _runId = runId;
public Guid LastRunId { get; private set; }
public Task<WorkOrderReconciliationTriggerResult> TriggerAsync(
ClaimsPrincipal user,
string reason,
CancellationToken cancellationToken)
{
LastRunId = _runId;
return Task.FromResult(new WorkOrderReconciliationTriggerResult(
_runId != Guid.Empty,
_runId));
}
public Task<WorkOrderReconciliationStatus> GetStatusAsync(
ClaimsPrincipal user,
CancellationToken cancellationToken) =>
Task.FromResult(new WorkOrderReconciliationStatus(
_runId,
"Pending",
"admin",
DateTimeOffset.UtcNow,
null,
null,
0,
0,
null));
}
}