using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FrameworkBLL.GOP;
using FrameworkBLL.Promotion;
using GB5Shared.Deployment;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.DTO.GOP;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
namespace FrameworkTests;
///
/// PromotionFlowProvisioningBLL authors the real "PromotionIngest" GOP flow (Ingest -> Approval ->
/// Import) via IGopFlowBLL's design-time API rather than hand-inserted snapshot rows, so
/// GopPublishService's own DAG/Approval-role validation actually runs. Covers idempotency (the
/// common re-run case) and the happy-path shape of what gets authored (3 steps, 2 edges, the
/// Approval step's required AssignedRole, and that PublishGopFlow/SaveGopSourceBinding both use the
/// FlowId that SaveGopFlow assigned).
///
public class PromotionFlowProvisioningBLLTests
{
private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 };
private static IGB5Environment DevEnvironment() => Mock.Of(e => e.GopEnvironmentCode == "DEV");
[Fact]
public async Task AlreadyProvisioned_IsANoOp()
{
var gopFlowBLL = new Mock(MockBehavior.Strict);
gopFlowBLL.Setup(b => b.GetAllSourceBindings(It.IsAny()))
.ReturnsAsync(new List
{
new() { SourceCode = "PromotionIngest", IsActive = true, FlowId = 501, SnapshotVersion = "v1" }
});
var bll = new PromotionFlowProvisioningBLL(
gopFlowBLL.Object, DevEnvironment(), NullLogger.Instance);
var result = await bll.EnsureIngestFlowProvisionedAsync(Login(), CancellationToken.None);
Assert.Contains("501", result);
// Strict mock with only GetAllSourceBindings set up — reaching GetGopFlowList/SaveGopFlow/etc.
// would have thrown a MockException instead, proving this really is a no-op.
}
[Fact]
public async Task NotYetProvisioned_CreatesFlowStepsEdgesPublishesAndBinds()
{
var gopFlowBLL = new Mock();
gopFlowBLL.Setup(b => b.GetAllSourceBindings(It.IsAny()))
.ReturnsAsync(new List());
gopFlowBLL.Setup(b => b.GetGopFlowList(It.IsAny(), It.IsAny()))
.ReturnsAsync(new List());
gopFlowBLL.Setup(b => b.SaveGopFlow(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => dto.FlowId = 501)
.ReturnsAsync("Saved 501");
gopFlowBLL.Setup(b => b.GetGopFlowGraph(501, It.IsAny(), It.IsAny()))
.ReturnsAsync(new GopFlowGraphDTO()); // empty — no steps yet
var stepIdCounter = 1000;
gopFlowBLL.Setup(b => b.SaveGopFlowStep(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => dto.FlowStepId = ++stepIdCounter)
.ReturnsAsync("Saved step");
var savedEdges = new List();
gopFlowBLL.Setup(b => b.SaveGopFlowStepEdge(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => savedEdges.Add(dto))
.ReturnsAsync("Saved edge");
gopFlowBLL.Setup(b => b.PublishGopFlow(501, "v1", It.IsAny(), It.IsAny()))
.ReturnsAsync(GopPublishResultDTO.Ok(9001, "v1"));
GopSourceBindingDTO? capturedBinding = null;
gopFlowBLL.Setup(b => b.SaveGopSourceBinding(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => capturedBinding = dto)
.ReturnsAsync("Saved binding");
var bll = new PromotionFlowProvisioningBLL(
gopFlowBLL.Object, DevEnvironment(), NullLogger.Instance);
var result = await bll.EnsureIngestFlowProvisionedAsync(Login(), CancellationToken.None);
Assert.Contains("501", result);
Assert.Contains("v1", result);
gopFlowBLL.Verify(b => b.SaveGopFlowStep(
It.Is(s => s.NodeType == "PromotionIngest" && s.FlowId == 501),
It.IsAny(), It.IsAny()), Times.Once);
gopFlowBLL.Verify(b => b.SaveGopFlowStep(
It.Is(s => s.NodeType == "Approval" && !string.IsNullOrWhiteSpace(s.AssignedRole)),
It.IsAny(), It.IsAny()), Times.Once);
gopFlowBLL.Verify(b => b.SaveGopFlowStep(
It.Is(s => s.NodeType == "PromotionImport"),
It.IsAny(), It.IsAny()), Times.Once);
Assert.Equal(2, savedEdges.Count);
Assert.All(savedEdges, e => Assert.Equal("OnSuccess", e.EdgeCondition));
Assert.NotNull(capturedBinding);
Assert.Equal("PromotionIngest", capturedBinding!.SourceCode);
Assert.Equal(501, capturedBinding.FlowId);
Assert.Equal("v1", capturedBinding.SnapshotVersion);
}
[Fact]
public async Task ExistingFlowWithNoSteps_ReusesFlowIdRatherThanCreatingANewFlow()
{
var gopFlowBLL = new Mock();
gopFlowBLL.Setup(b => b.GetAllSourceBindings(It.IsAny()))
.ReturnsAsync(new List());
gopFlowBLL.Setup(b => b.GetGopFlowList(It.IsAny(), It.IsAny()))
.ReturnsAsync(new List { new() { FlowId = 777, FlowCode = "PromotionIngest" } });
gopFlowBLL.Setup(b => b.GetGopFlowGraph(777, It.IsAny(), It.IsAny()))
.ReturnsAsync(new GopFlowGraphDTO());
var stepIdCounter = 2000;
gopFlowBLL.Setup(b => b.SaveGopFlowStep(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => dto.FlowStepId = ++stepIdCounter)
.ReturnsAsync("Saved step");
gopFlowBLL.Setup(b => b.SaveGopFlowStepEdge(It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync("Saved edge");
gopFlowBLL.Setup(b => b.PublishGopFlow(777, "v1", It.IsAny(), It.IsAny()))
.ReturnsAsync(GopPublishResultDTO.Ok(9002, "v1"));
gopFlowBLL.Setup(b => b.SaveGopSourceBinding(It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync("Saved binding");
var bll = new PromotionFlowProvisioningBLL(
gopFlowBLL.Object, DevEnvironment(), NullLogger.Instance);
var result = await bll.EnsureIngestFlowProvisionedAsync(Login(), CancellationToken.None);
Assert.Contains("777", result);
gopFlowBLL.Verify(b => b.SaveGopFlow(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
[Fact]
public async Task PublishFailure_ThrowsWithTheValidationErrors()
{
var gopFlowBLL = new Mock();
gopFlowBLL.Setup(b => b.GetAllSourceBindings(It.IsAny()))
.ReturnsAsync(new List());
gopFlowBLL.Setup(b => b.GetGopFlowList(It.IsAny(), It.IsAny()))
.ReturnsAsync(new List());
gopFlowBLL.Setup(b => b.SaveGopFlow(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((dto, _, _) => dto.FlowId = 501)
.ReturnsAsync("Saved");
gopFlowBLL.Setup(b => b.GetGopFlowGraph(501, It.IsAny(), It.IsAny()))
.ReturnsAsync(new GopFlowGraphDTO());
gopFlowBLL.Setup(b => b.SaveGopFlowStep(It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync("Saved step");
gopFlowBLL.Setup(b => b.SaveGopFlowStepEdge(It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync("Saved edge");
gopFlowBLL.Setup(b => b.PublishGopFlow(501, "v1", It.IsAny(), It.IsAny()))
.ReturnsAsync(GopPublishResultDTO.Failure(new List { "Approval step 'approve' requires an AssignedRole" }));
var bll = new PromotionFlowProvisioningBLL(
gopFlowBLL.Object, DevEnvironment(), NullLogger.Instance);
var ex = await Assert.ThrowsAsync(
() => bll.EnsureIngestFlowProvisionedAsync(Login(), CancellationToken.None));
Assert.Contains("AssignedRole", ex.Message);
gopFlowBLL.Verify(b => b.SaveGopSourceBinding(
It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
}