using FluentAssertions; using Microsoft.AspNetCore.Mvc; using Moq; using NUnit.Framework; using Strata.ContinuousImprovement.Api.Controllers; using Strata.ContinuousImprovement.Biz.StrategicOpportunities; using Strata.ContinuousImprovement.Biz.StrategicOpportunities.Dtos; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Threading; using System.Threading.Tasks; namespace Strata.ContinuousImprovement.Api.Test.Unit.Controllers { [ExcludeFromCodeCoverage] [TestFixture, Category("Unit"), Category("Controller")] public class StrategicOpportunityControllerTests { private StrategicOpportunityController _controller; private Mock _mockOpportunityService; [SetUp] public void SetUp() { _mockOpportunityService = new Mock(); _controller = new StrategicOpportunityController(_mockOpportunityService.Object); } // --- GetGeneralInfo --- [Test] public async Task GetGeneralInfo_ReturnsOk_WhenOpportunityExists() { const long opportunityId = 1; var info = new StrategicOpportunityGeneralInfo(); _mockOpportunityService .Setup(s => s.GetStrategicOpportunityGeneralInfo(opportunityId, It.IsAny())) .ReturnsAsync(info); var result = await _controller.GetGeneralInfo(opportunityId, CancellationToken.None); result.Result.Should().BeOfType() .Which.Value.Should().Be(info); } [Test] public async Task GetGeneralInfo_ReturnsNotFound_WhenOpportunityMissing() { _mockOpportunityService .Setup(s => s.GetStrategicOpportunityGeneralInfo(It.IsAny(), It.IsAny())) .ReturnsAsync((StrategicOpportunityGeneralInfo)null); var result = await _controller.GetGeneralInfo(99, CancellationToken.None); result.Result.Should().BeOfType(); } // --- GetDrawerDetails --- [Test] public async Task GetDrawerDetails_ReturnsOk_WhenOpportunityExists() { const long opportunityId = 5; var details = new StrategicOpportunityDrawerDetails(); _mockOpportunityService .Setup(s => s.GetStrategicOpportunityDrawerDetails(opportunityId, It.IsAny())) .ReturnsAsync(details); var result = await _controller.GetDrawerDetails(opportunityId, CancellationToken.None); result.Result.Should().BeOfType() .Which.Value.Should().Be(details); } [Test] public async Task GetDrawerDetails_ReturnsNotFound_WhenOpportunityMissing() { _mockOpportunityService .Setup(s => s.GetStrategicOpportunityDrawerDetails(It.IsAny(), It.IsAny())) .ReturnsAsync((StrategicOpportunityDrawerDetails)null); var result = await _controller.GetDrawerDetails(99, CancellationToken.None); result.Result.Should().BeOfType(); } // --- Copy --- [Test] public async Task Copy_ReturnsOk_WithNewId_WhenSuccessful() { const long opportunityId = 10; const long newId = 42; _mockOpportunityService .Setup(s => s.CopyStrategicOpportunity(opportunityId, It.IsAny(), It.IsAny())) .ReturnsAsync(newId); var result = await _controller.Copy(opportunityId, "Copy of Opp", CancellationToken.None); result.Result.Should().BeOfType() .Which.Value.Should().Be(newId); } [Test] public async Task Copy_ReturnsBadRequest_WhenServiceReturnsZero() { _mockOpportunityService .Setup(s => s.CopyStrategicOpportunity(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(0L); var result = await _controller.Copy(10, "Copy of Opp", CancellationToken.None); result.Result.Should().BeOfType(); } // --- SetStatus --- [Test] public async Task SetStatus_ReturnsOk_WhenUpdateSucceeds() { const long opportunityId = 3; var dto = new StrategicOpportunityUpdateStatusDto(); _mockOpportunityService .Setup(s => s.UpdateStrategicOpportunityStatus(opportunityId, dto, It.IsAny())) .ReturnsAsync(true); var result = await _controller.SetStatus(opportunityId, dto, CancellationToken.None); result.Result.Should().BeOfType(); } [Test] public async Task SetStatus_ReturnsNotFound_WhenUpdateFails() { _mockOpportunityService .Setup(s => s.UpdateStrategicOpportunityStatus(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await _controller.SetStatus(99, new StrategicOpportunityUpdateStatusDto(), CancellationToken.None); result.Result.Should().BeOfType(); } // --- UpdateLayout --- [Test] public async Task UpdateLayout_ReturnsOk_WhenUpdateSucceeds() { const long opportunityId = 7; var layout = new StrategicOpportunityLayout(); _mockOpportunityService .Setup(s => s.UpdateStrategicOpportunityLayout(opportunityId, layout, It.IsAny())) .ReturnsAsync(true); var result = await _controller.UpdateLayout(opportunityId, layout, CancellationToken.None); result.Result.Should().BeOfType(); } [Test] public async Task UpdateLayout_ReturnsNotFound_WhenUpdateFails() { _mockOpportunityService .Setup(s => s.UpdateStrategicOpportunityLayout(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await _controller.UpdateLayout(99, new StrategicOpportunityLayout(), CancellationToken.None); result.Result.Should().BeOfType(); } // --- UpdateRoles --- [Test] public async Task UpdateRoles_ReturnsOk_WhenUpdateSucceeds() { const long opportunityId = 4; var dto = new StrategicRolesUpdate(); _mockOpportunityService .Setup(s => s.UpdateRoles(opportunityId, dto, It.IsAny())) .ReturnsAsync(true); var result = await _controller.UpdateRoles(opportunityId, dto, CancellationToken.None); result.Result.Should().BeOfType(); } [Test] public async Task UpdateRoles_ReturnsNotFound_WhenUpdateFails() { _mockOpportunityService .Setup(s => s.UpdateRoles(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await _controller.UpdateRoles(99, new StrategicRolesUpdate(), CancellationToken.None); result.Result.Should().BeOfType(); } // --- RunTrackingTask --- [Test] public async Task RunTrackingTask_ReturnsOk_WhenSuccessful() { const long opportunityId = 8; _mockOpportunityService .Setup(s => s.RunTrackingTask(opportunityId, It.IsAny())) .ReturnsAsync(true); var result = await _controller.RunTrackingTask(opportunityId, CancellationToken.None); result.Result.Should().BeOfType(); } [Test] public async Task RunTrackingTask_ReturnsNotFound_WhenOpportunityMissing() { _mockOpportunityService .Setup(s => s.RunTrackingTask(It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await _controller.RunTrackingTask(99, CancellationToken.None); result.Result.Should().BeOfType(); } // --- GetMeasures --- [Test] public async Task GetMeasures_ReturnsOk_WhenMeasuresExist() { const long opportunityId = 2; var measures = new List { new() }; _mockOpportunityService .Setup(s => s.GetStrategicOpportunityMeasures(opportunityId, It.IsAny())) .ReturnsAsync(measures); var result = await _controller.GetMeasures(opportunityId, CancellationToken.None); result.Result.Should().BeOfType() .Which.Value.Should().Be(measures); } [Test] public async Task GetMeasures_ReturnsNotFound_WhenNoMeasures() { _mockOpportunityService .Setup(s => s.GetStrategicOpportunityMeasures(It.IsAny(), It.IsAny())) .ReturnsAsync(new List()); var result = await _controller.GetMeasures(99, CancellationToken.None); result.Result.Should().BeOfType(); } // --- CompleteInitiative --- [Test] public async Task CompleteInitiative_ReturnsOk_WhenSuccessful() { const long opportunityId = 6; var data = new StrategicOpportunityCompleteInitiativeData(); _mockOpportunityService .Setup(s => s.CompleteInitiative(opportunityId, data, It.IsAny())) .ReturnsAsync(true); var result = await _controller.CompleteInitiative(opportunityId, data, CancellationToken.None); result.Result.Should().BeOfType(); } [Test] public async Task CompleteInitiative_ReturnsNotFound_WhenOpportunityMissing() { _mockOpportunityService .Setup(s => s.CompleteInitiative(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(false); var result = await _controller.CompleteInitiative(99, new StrategicOpportunityCompleteInitiativeData(), CancellationToken.None); result.Result.Should().BeOfType(); } // --- Route attribute assertions --- [Test] public void Controller_HasExpectedRouteTemplate() { var routeAttr = typeof(StrategicOpportunityController).GetCustomAttribute(); routeAttr.Should().NotBeNull(); routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]"); } [TestCase(nameof(StrategicOpportunityController.GetOpportunities), typeof(HttpGetAttribute), "")] [TestCase(nameof(StrategicOpportunityController.GetGeneralInfo), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.IsNameValid), typeof(HttpPostAttribute), "[action]")] [TestCase(nameof(StrategicOpportunityController.GetLayout), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.UpdateLayout), typeof(HttpPutAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.SetStatus), typeof(HttpPutAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.GetInitiatives), typeof(HttpGetAttribute), "[action]")] [TestCase(nameof(StrategicOpportunityController.UpdateInitiative), typeof(HttpPutAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.UpdateRoles), typeof(HttpPutAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.UpdateRampUps), typeof(HttpPutAttribute), "{opportunityId}/ramp-ups")] [TestCase(nameof(StrategicOpportunityController.GetRampUps), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.RunTrackingTask), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.CompleteInitiative), typeof(HttpPutAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.GetMeasures), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.GetPrimaryMeasure), typeof(HttpGetAttribute), "{opportunityId}/[action]")] [TestCase(nameof(StrategicOpportunityController.GetMeasureDetails), typeof(HttpGetAttribute), "{measureId}/[action]")] [TestCase(nameof(StrategicOpportunityController.GetDrawerDetails), typeof(HttpGetAttribute), "{opportunityId}")] [TestCase(nameof(StrategicOpportunityController.Copy), typeof(HttpPostAttribute), "{opportunityId}/copy")] public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate) { var method = typeof(StrategicOpportunityController).GetMethod(methodName); method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicOpportunityController)}"); var attr = method.GetCustomAttribute(httpAttrType); attr.Should().NotBeNull($"{methodName} should be decorated with [{httpAttrType.Name}]"); var template = (string)attr.GetType().GetProperty("Template").GetValue(attr); template.Should().Be(expectedTemplate); } } }