118 lines
5.3 KiB
C#
118 lines
5.3 KiB
C#
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());
|
|
}
|
|
}
|