Files

338 lines
16 KiB
C#

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 StrategicChargeCodeControllerTests
{
private StrategicChargeCodeController _controller;
private Mock<IStrategicOpportunityService> _mockOpportunityService;
private Mock<IStrategicChargeCodeOpportunityDetailService> _mockDetailService;
[SetUp]
public void SetUp()
{
_mockOpportunityService = new Mock<IStrategicOpportunityService>();
_mockDetailService = new Mock<IStrategicChargeCodeOpportunityDetailService>();
_controller = new StrategicChargeCodeController(_mockOpportunityService.Object, _mockDetailService.Object);
}
// --- GetOpportunityChargeCode ---
[Test]
public async Task GetOpportunityChargeCode_ReturnsOk_WhenFound()
{
const long opportunityId = 1;
var data = new StrategicOpportunityChargeCodeEditData();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityChargeCode(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetOpportunityChargeCode_ReturnsNotFound_WhenMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityChargeCode(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityChargeCodeEditData)null);
var result = await _controller.GetOpportunityChargeCode(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- CreateOpportunityChargeCode ---
[Test]
public async Task CreateOpportunityChargeCode_ReturnsOk_WithNewId_WhenSuccessful()
{
const long newId = 42;
var dto = new StrategicOpportunityChargeCodeDto();
_mockOpportunityService
.Setup(s => s.CreateOpportunityChargeCodeAsync(dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(newId);
var result = await _controller.CreateOpportunityChargeCode(dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(newId);
}
[Test]
public async Task CreateOpportunityChargeCode_ReturnsBadRequest_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.CreateOpportunityChargeCodeAsync(It.IsAny<StrategicOpportunityChargeCodeDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.CreateOpportunityChargeCode(new StrategicOpportunityChargeCodeDto(), CancellationToken.None);
result.Result.Should().BeOfType<BadRequestResult>();
}
// --- UpdateOpportunityChargeCode ---
[Test]
public async Task UpdateOpportunityChargeCode_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 5;
_mockOpportunityService
.Setup(s => s.UpdateOpportunityChargeCode(opportunityId, It.IsAny<StrategicOpportunityChargeCodeUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(opportunityId);
var result = await _controller.UpdateOpportunityChargeCode(opportunityId, new StrategicOpportunityChargeCodeUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateOpportunityChargeCode_ReturnsNotFound_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.UpdateOpportunityChargeCode(It.IsAny<long>(), It.IsAny<StrategicOpportunityChargeCodeUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.UpdateOpportunityChargeCode(99, new StrategicOpportunityChargeCodeUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- DeleteOpportunityChargeCode ---
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsNotFound_WhenOpportunityDoesNotExist()
{
_mockOpportunityService
.Setup(s => s.ContainsAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.DeleteOpportunityChargeCode(99, CancellationToken.None);
result.Should().BeOfType<NotFoundResult>();
_mockOpportunityService.Verify(s => s.DeleteChargeCodeOpportunityAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsNoContent_WhenDeleted()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteChargeCodeOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
var result = await _controller.DeleteOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Should().BeOfType<NoContentResult>();
}
[Test]
public async Task DeleteOpportunityChargeCode_ReturnsBadRequest_WhenDeleteFails()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteChargeCodeOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await _controller.DeleteOpportunityChargeCode(opportunityId, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
// --- GetGoalDetail ---
[Test]
public async Task GetGoalDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 3;
var getData = new StrategicOpportunityGetDetailData();
var data = new StrategicOpportunityDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGoalDetail(opportunityId, getData, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGoalDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(It.IsAny<long>(), false, It.IsAny<StrategicOpportunityGetDetailData>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityDetailData)null);
var result = await _controller.GetGoalDetail(99, new StrategicOpportunityGetDetailData(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGoalDetail_PassesIsTrackingFalse_ToService()
{
const long opportunityId = 3;
var getData = new StrategicOpportunityGetDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityDetailData());
await _controller.GetGoalDetail(opportunityId, getData, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityDetailData(opportunityId, false, getData, CancellationToken.None), Times.Once);
}
// --- GetTrackingDetail ---
[Test]
public async Task GetTrackingDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 4;
var getData = new StrategicOpportunityGetDetailData();
var data = new StrategicOpportunityDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetTrackingDetail(opportunityId, getData, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetTrackingDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(It.IsAny<long>(), true, It.IsAny<StrategicOpportunityGetDetailData>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityDetailData)null);
var result = await _controller.GetTrackingDetail(99, new StrategicOpportunityGetDetailData(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetTrackingDetail_PassesIsTrackingTrue_ToService()
{
const long opportunityId = 4;
var getData = new StrategicOpportunityGetDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityDetailData());
await _controller.GetTrackingDetail(opportunityId, getData, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityDetailData(opportunityId, true, getData, CancellationToken.None), Times.Once);
}
// --- UpdateDetails ---
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenDtoIsNull()
{
var result = await _controller.UpdateDetails(1, null, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenAdjustmentsIsEmpty()
{
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment>(),
Rows = new List<string> { "system", "entity" }
};
var result = await _controller.UpdateDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsBadRequest_WhenRowsIsEmpty()
{
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment> { new() { NodeKey = "101", CostsValue = 5000m } },
Rows = new List<string>()
};
var result = await _controller.UpdateDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityChargeCodeAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateDetails_ReturnsOk_AndCallsService_WhenDtoIsValid()
{
const long opportunityId = 42;
var dto = new StrategicChargeCodeController.UpdateDetailsDto
{
Adjustments = new List<StrategicOpportunityChargeCodeAdjustment> { new() { NodeKey = "101", CostsValue = 5000m } },
Rows = new List<string> { "system", "entity", "chargeCode" }
};
var result = await _controller.UpdateDetails(opportunityId, dto, CancellationToken.None);
result.Should().BeOfType<OkResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityDetailsData(
opportunityId, dto.Adjustments, dto.Rows, CancellationToken.None), Times.Once);
}
// --- Route attribute assertions ---
[Test]
public void Controller_HasExpectedRouteTemplate()
{
var routeAttr = typeof(StrategicChargeCodeController).GetCustomAttribute<RouteAttribute>();
routeAttr.Should().NotBeNull();
routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]");
}
[TestCase(nameof(StrategicChargeCodeController.GetOpportunityChargeCode), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicChargeCodeController.CreateOpportunityChargeCode), typeof(HttpPostAttribute), null)]
[TestCase(nameof(StrategicChargeCodeController.UpdateOpportunityChargeCode), typeof(HttpPutAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicChargeCodeController.DeleteOpportunityChargeCode), typeof(HttpDeleteAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicChargeCodeController.GetGoalDetail), typeof(HttpPostAttribute), "{opportunityId}/goal-detail")]
[TestCase(nameof(StrategicChargeCodeController.ExportInitiative), typeof(HttpPostAttribute), "{opportunityId}/export")]
[TestCase(nameof(StrategicChargeCodeController.ExportOpportunity), typeof(HttpPostAttribute), "{opportunityId}/export-opportunity")]
[TestCase(nameof(StrategicChargeCodeController.GetTrackingDetail), typeof(HttpPostAttribute), "{opportunityId}/tracking-detail")]
[TestCase(nameof(StrategicChargeCodeController.UpdateDetails), typeof(HttpPutAttribute), "{opportunityId}/details")]
public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate)
{
var method = typeof(StrategicChargeCodeController).GetMethod(methodName);
method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicChargeCodeController)}");
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);
}
}
}