fix(templateengine): audit projected template/script summaries instead of the tracked graph and script bodies (review 2026-10-21 08/G1)
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class TemplateAuditShapeTests
|
||||
{
|
||||
private const string Body = "return \"BODY_SENTINEL_7f3a\";";
|
||||
|
||||
private readonly Mock<ITemplateEngineRepository> _repoMock = new();
|
||||
private readonly Mock<IAuditService> _auditMock = new();
|
||||
private readonly List<object?> _afterStates = [];
|
||||
|
||||
/// <summary>Captures every audit after-state the service writes.</summary>
|
||||
public TemplateAuditShapeTests()
|
||||
{
|
||||
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1);
|
||||
_auditMock
|
||||
.Setup(a => a.LogAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<object?>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<string, string, string, string, string, object?, CancellationToken>(
|
||||
(_, _, _, _, _, 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<CancellationToken>())).ReturnsAsync(template);
|
||||
_repoMock.Setup(r => r.GetAllTemplatesForAnalysisAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<Template> { template });
|
||||
return template;
|
||||
}
|
||||
|
||||
private string SingleAfterStateJson()
|
||||
{
|
||||
var after = Assert.Single(_afterStates);
|
||||
return JsonSerializer.Serialize(after);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template rename audits a summary without script bodies.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[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 _));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a template move audits a summary without script bodies.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[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());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a script add audits the script's metadata and body hash, not its body.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[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);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a shared-script create audits its body hash, not its body.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user