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( await controller.Trigger(CancellationToken.None)); Assert.Equal(StatusCodes.Status202Accepted, trigger.StatusCode); Assert.Equal(runId, service.LastRunId); var status = Assert.IsType( await controller.Status(CancellationToken.None)); Assert.IsType(status.Value); } [Fact] public async Task Disabled_trigger_returns_sanitized_unavailable_response() { var controller = new WorkOrderReconciliationController(new StubService(Guid.Empty)); var result = Assert.IsType( 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() .ActionDescriptors .Items .OfType() .Where(d => d.ControllerTypeInfo == typeof(WorkOrderReconciliationController)) .ToList(); var routes = descriptors .SelectMany(d => (d.ActionConstraints ?? Array.Empty()) .OfType() .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(), 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 TriggerAsync( ClaimsPrincipal user, string reason, CancellationToken cancellationToken) { LastRunId = _runId; return Task.FromResult(new WorkOrderReconciliationTriggerResult( _runId != Guid.Empty, _runId)); } public Task GetStatusAsync( ClaimsPrincipal user, CancellationToken cancellationToken) => Task.FromResult(new WorkOrderReconciliationStatus( _runId, "Pending", "admin", DateTimeOffset.UtcNow, null, null, 0, 0, null)); } }