using System.Text.Json;
using Moq;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
namespace ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests;
///
/// Review 2026-10-21 08/G1: the configuration audit (indefinitely retained) must
/// record a projected summary of a template — ids, names, member counts, changed
/// fields — never the whole tracked graph with every script body.
///
public class TemplateAuditShapeTests
{
private const string Body = "return \"BODY_SENTINEL_7f3a\";";
private readonly Mock _repoMock = new();
private readonly Mock _auditMock = new();
private readonly List _afterStates = [];
/// Captures every audit after-state the service writes.
public TemplateAuditShapeTests()
{
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny())).ReturnsAsync(1);
_auditMock
.Setup(a => a.LogAsync(It.IsAny(), It.IsAny(), It.IsAny(),
It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Callback(
(_, _, _, _, _, after, _) => _afterStates.Add(after))
.Returns(Task.CompletedTask);
}
private Template SeedTemplateWithScripts()
{
var template = new Template("Pump") { Id = 1 };
template.Scripts.Add(new TemplateScript("Calc", Body) { Id = 10, TemplateId = 1 });
template.Scripts.Add(new TemplateScript("Calc2", Body + "//2") { Id = 11, TemplateId = 1 });
template.Attributes.Add(new TemplateAttribute("Temp") { Id = 20, TemplateId = 1 });
_repoMock.Setup(r => r.GetTemplateByIdAsync(1, It.IsAny())).ReturnsAsync(template);
_repoMock.Setup(r => r.GetAllTemplatesForAnalysisAsync(It.IsAny()))
.ReturnsAsync(new List { template });
return template;
}
private string SingleAfterStateJson()
{
var after = Assert.Single(_afterStates);
return JsonSerializer.Serialize(after);
}
/// Verifies that a template rename audits a summary without script bodies.
/// A task that represents the asynchronous operation.
[Fact]
public async Task UpdateTemplate_AuditsSummary_WithoutScriptBodies()
{
SeedTemplateWithScripts();
var sut = new TemplateService(_repoMock.Object, _auditMock.Object);
var result = await sut.UpdateTemplateAsync(1, "Pump2", null, null, "admin");
Assert.True(result.IsSuccess);
var json = SingleAfterStateJson();
Assert.DoesNotContain("BODY_SENTINEL", json);
using var doc = JsonDocument.Parse(json);
Assert.Equal("Pump2", doc.RootElement.GetProperty("Name").GetString());
Assert.Equal(2, doc.RootElement.GetProperty("ScriptCount").GetInt32());
Assert.Equal(1, doc.RootElement.GetProperty("AttributeCount").GetInt32());
Assert.Contains("Name", doc.RootElement.GetProperty("ChangedFields").EnumerateArray().Select(e => e.GetString()));
Assert.False(doc.RootElement.TryGetProperty("Scripts", out _));
}
/// Verifies that a template move audits a summary without script bodies.
/// A task that represents the asynchronous operation.
[Fact]
public async Task MoveTemplate_AuditsSummary_WithoutScriptBodies()
{
SeedTemplateWithScripts();
var sut = new TemplateService(_repoMock.Object, _auditMock.Object);
var result = await sut.MoveTemplateAsync(1, null, "admin");
Assert.True(result.IsSuccess);
Assert.DoesNotContain("BODY_SENTINEL", SingleAfterStateJson());
}
/// Verifies that a script add audits the script's metadata and body hash, not its body.
/// A task that represents the asynchronous operation.
[Fact]
public async Task AddScript_AuditsBodyHash_NotBody()
{
SeedTemplateWithScripts();
var sut = new TemplateService(_repoMock.Object, _auditMock.Object);
var result = await sut.AddScriptAsync(1, new TemplateScript("Calc3", Body + "//3"), "admin");
Assert.True(result.IsSuccess);
var json = SingleAfterStateJson();
Assert.DoesNotContain("BODY_SENTINEL", json);
using var doc = JsonDocument.Parse(json);
Assert.Equal("Calc3", doc.RootElement.GetProperty("Name").GetString());
Assert.Equal(64, doc.RootElement.GetProperty("CodeSha256").GetString()!.Length);
}
/// Verifies that a shared-script create audits its body hash, not its body.
/// A task that represents the asynchronous operation.
[Fact]
public async Task CreateSharedScript_AuditsBodyHash_NotBody()
{
var sut = new SharedScriptService(_repoMock.Object, _auditMock.Object);
var result = await sut.CreateSharedScriptAsync("Helpers", Body, null, null, "admin");
Assert.True(result.IsSuccess, result.IsFailure ? result.Error : null);
Assert.DoesNotContain("BODY_SENTINEL", SingleAfterStateJson());
}
}