Template
336 lines
15 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|