Files

336 lines
15 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 StrategicGeneralLedgerControllerTests
{
private StrategicGeneralLedgerController _controller;
private Mock<IStrategicOpportunityService> _mockOpportunityService;
private Mock<IStrategicGLOpportunityDetailService> _mockDetailService;
[SetUp]
public void SetUp()
{
_mockOpportunityService = new Mock<IStrategicOpportunityService>();
_mockDetailService = new Mock<IStrategicGLOpportunityDetailService>();
_controller = new StrategicGeneralLedgerController(_mockOpportunityService.Object, _mockDetailService.Object);
}
// --- GetOpportunityGL ---
[Test]
public async Task GetOpportunityGL_ReturnsOk_WhenFound()
{
const long opportunityId = 1;
var data = new StrategicOpportunityGLEditData();
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGL(opportunityId, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetOpportunityGL(opportunityId, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetOpportunityGL_ReturnsNotFound_WhenMissing()
{
_mockOpportunityService
.Setup(s => s.GetStrategicOpportunityGL(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLEditData)null);
var result = await _controller.GetOpportunityGL(99, CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- CreateOpportunityGeneralLedger ---
[Test]
public async Task CreateOpportunityGeneralLedger_ReturnsOk_WithNewId_WhenSuccessful()
{
const long newId = 42;
var dto = new StrategicOpportunityGLDto();
_mockOpportunityService
.Setup(s => s.CreateOpportunityGeneralLedgerAsync(dto, It.IsAny<CancellationToken>()))
.ReturnsAsync(newId);
var result = await _controller.CreateOpportunityGeneralLedger(dto, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(newId);
}
[Test]
public async Task CreateOpportunityGeneralLedger_ReturnsBadRequest_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.CreateOpportunityGeneralLedgerAsync(It.IsAny<StrategicOpportunityGLDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.CreateOpportunityGeneralLedger(new StrategicOpportunityGLDto(), CancellationToken.None);
result.Result.Should().BeOfType<BadRequestResult>();
}
// --- UpdateOpportunityGeneralLedger ---
[Test]
public async Task UpdateOpportunityGeneralLedger_ReturnsOk_WhenSuccessful()
{
const long opportunityId = 5;
_mockOpportunityService
.Setup(s => s.UpdateOpportunityGeneralLedger(opportunityId, It.IsAny<StrategicOpportunityGLUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(opportunityId);
var result = await _controller.UpdateOpportunityGeneralLedger(opportunityId, new StrategicOpportunityGLUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>();
}
[Test]
public async Task UpdateOpportunityGeneralLedger_ReturnsNotFound_WhenServiceReturnsZero()
{
_mockOpportunityService
.Setup(s => s.UpdateOpportunityGeneralLedger(It.IsAny<long>(), It.IsAny<StrategicOpportunityGLUpdateDto>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0L);
var result = await _controller.UpdateOpportunityGeneralLedger(99, new StrategicOpportunityGLUpdateDto(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
// --- DeleteOpportunityGeneralLedger ---
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsNotFound_WhenOpportunityDoesNotExist()
{
_mockOpportunityService
.Setup(s => s.ContainsGLAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await _controller.DeleteOpportunityGeneralLedger(99, CancellationToken.None);
result.Should().BeOfType<NotFoundResult>();
_mockOpportunityService.Verify(s => s.DeleteGeneralLedgerOpportunityAsync(It.IsAny<long>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsNoContent_WhenDeleted()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsGLAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteGeneralLedgerOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
var result = await _controller.DeleteOpportunityGeneralLedger(opportunityId, CancellationToken.None);
result.Should().BeOfType<NoContentResult>();
}
[Test]
public async Task DeleteOpportunityGeneralLedger_ReturnsBadRequest_WhenDeleteFails()
{
const long opportunityId = 7;
_mockOpportunityService.Setup(s => s.ContainsGLAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(true);
_mockOpportunityService.Setup(s => s.DeleteGeneralLedgerOpportunityAsync(opportunityId, It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await _controller.DeleteOpportunityGeneralLedger(opportunityId, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
}
// --- GetGLDetail ---
[Test]
public async Task GetGLDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 3;
var dimensions = new[] { "system", "entity" };
var data = new StrategicOpportunityGLDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGLDetail(opportunityId, dimensions, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGLDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(It.IsAny<long>(), false, It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLDetailData)null);
var result = await _controller.GetGLDetail(99, Array.Empty<string>(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGLDetail_PassesIsTrackingFalse_ToService()
{
const long opportunityId = 3;
var dimensions = new[] { "system" };
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityGLDetailData());
await _controller.GetGLDetail(opportunityId, dimensions, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityGLDetailData(opportunityId, false, dimensions, CancellationToken.None), Times.Once);
}
// --- GetGLTrackingDetail ---
[Test]
public async Task GetGLTrackingDetail_ReturnsOk_WhenFound()
{
const long opportunityId = 4;
var dimensions = new[] { "system", "glAccount" };
var data = new StrategicOpportunityGLDetailData();
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(data);
var result = await _controller.GetGLTrackingDetail(opportunityId, dimensions, CancellationToken.None);
result.Result.Should().BeOfType<OkObjectResult>()
.Which.Value.Should().Be(data);
}
[Test]
public async Task GetGLTrackingDetail_ReturnsNotFound_WhenMissing()
{
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(It.IsAny<long>(), true, It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((StrategicOpportunityGLDetailData)null);
var result = await _controller.GetGLTrackingDetail(99, Array.Empty<string>(), CancellationToken.None);
result.Result.Should().BeOfType<NotFoundResult>();
}
[Test]
public async Task GetGLTrackingDetail_PassesIsTrackingTrue_ToService()
{
const long opportunityId = 4;
var dimensions = new[] { "system" };
_mockDetailService
.Setup(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, It.IsAny<CancellationToken>()))
.ReturnsAsync(new StrategicOpportunityGLDetailData());
await _controller.GetGLTrackingDetail(opportunityId, dimensions, CancellationToken.None);
_mockDetailService.Verify(s => s.GetStrategicOpportunityGLDetailData(opportunityId, true, dimensions, CancellationToken.None), Times.Once);
}
// --- UpdateGLDetails ---
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenDtoIsNull()
{
var result = await _controller.UpdateGLDetails(1, null, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenAdjustmentsIsEmpty()
{
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment>(),
Rows = new List<string> { "system", "entity" }
};
var result = await _controller.UpdateGLDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsBadRequest_WhenRowsIsEmpty()
{
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment> { new() { NodeKey = "101", GoalAmountValue = 5000m } },
Rows = new List<string>()
};
var result = await _controller.UpdateGLDetails(1, dto, CancellationToken.None);
result.Should().BeOfType<BadRequestResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
It.IsAny<long>(), It.IsAny<IEnumerable<StrategicOpportunityGLAdjustment>>(), It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Test]
public async Task UpdateGLDetails_ReturnsOk_AndCallsService_WhenDtoIsValid()
{
const long opportunityId = 42;
var dto = new UpdateGLDetailsDto
{
Adjustments = new List<StrategicOpportunityGLAdjustment> { new() { NodeKey = "101", GoalAmountValue = 5000m } },
Rows = new List<string> { "system", "entity", "glAccount" }
};
var result = await _controller.UpdateGLDetails(opportunityId, dto, CancellationToken.None);
result.Should().BeOfType<OkResult>();
_mockDetailService.Verify(s => s.UpdateStrategicOpportunityGLDetailsData(
opportunityId, dto.Adjustments, dto.Rows, CancellationToken.None), Times.Once);
}
// --- Route attribute assertions ---
[Test]
public void Controller_HasExpectedRouteTemplate()
{
var routeAttr = typeof(StrategicGeneralLedgerController).GetCustomAttribute<RouteAttribute>();
routeAttr.Should().NotBeNull();
routeAttr.Template.Should().Be("api/v{api-version:apiVersion}/[controller]");
}
[TestCase(nameof(StrategicGeneralLedgerController.GetOpportunityGL), typeof(HttpGetAttribute), "{opportunityId}/[action]")]
[TestCase(nameof(StrategicGeneralLedgerController.CreateOpportunityGeneralLedger), typeof(HttpPostAttribute), null)]
[TestCase(nameof(StrategicGeneralLedgerController.UpdateOpportunityGeneralLedger), typeof(HttpPutAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicGeneralLedgerController.DeleteOpportunityGeneralLedger), typeof(HttpDeleteAttribute), "{opportunityId}")]
[TestCase(nameof(StrategicGeneralLedgerController.GetGLDetail), typeof(HttpPostAttribute), "{opportunityId}/detail")]
[TestCase(nameof(StrategicGeneralLedgerController.GetGLTrackingDetail), typeof(HttpPostAttribute), "{opportunityId}/tracking-detail")]
[TestCase(nameof(StrategicGeneralLedgerController.UpdateGLDetails), typeof(HttpPutAttribute), "{opportunityId}/details")]
public void ActionRoute_HasExpectedHttpMethodAndTemplate(string methodName, Type httpAttrType, string expectedTemplate)
{
var method = typeof(StrategicGeneralLedgerController).GetMethod(methodName);
method.Should().NotBeNull($"method '{methodName}' should exist on {nameof(StrategicGeneralLedgerController)}");
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);
}
}
}