218 lines
8.8 KiB
C#
218 lines
8.8 KiB
C#
using NSubstitute;
|
|
using ScadaLink.Commons.Entities.ExternalSystems;
|
|
using ScadaLink.Commons.Entities.InboundApi;
|
|
using ScadaLink.Commons.Entities.Scripts;
|
|
using ScadaLink.Commons.Entities.Templates;
|
|
using ScadaLink.Commons.Interfaces.Repositories;
|
|
using ScadaLink.Commons.Types.Transport;
|
|
using ScadaLink.Transport.Export;
|
|
|
|
namespace ScadaLink.Transport.Tests.Export;
|
|
|
|
public sealed class DependencyResolverTests
|
|
{
|
|
private readonly ITemplateEngineRepository _templates = Substitute.For<ITemplateEngineRepository>();
|
|
private readonly IExternalSystemRepository _externalSystems = Substitute.For<IExternalSystemRepository>();
|
|
private readonly INotificationRepository _notifications = Substitute.For<INotificationRepository>();
|
|
private readonly IInboundApiRepository _inboundApi = Substitute.For<IInboundApiRepository>();
|
|
|
|
private DependencyResolver Sut() => new(_templates, _externalSystems, _notifications, _inboundApi);
|
|
|
|
private static ExportSelection SelectTemplates(params int[] ids) => new(
|
|
TemplateIds: ids,
|
|
SharedScriptIds: Array.Empty<int>(),
|
|
ExternalSystemIds: Array.Empty<int>(),
|
|
DatabaseConnectionIds: Array.Empty<int>(),
|
|
NotificationListIds: Array.Empty<int>(),
|
|
SmtpConfigurationIds: Array.Empty<int>(),
|
|
ApiKeyIds: Array.Empty<int>(),
|
|
ApiMethodIds: Array.Empty<int>(),
|
|
IncludeDependencies: true);
|
|
|
|
private static ExportSelection SelectApiMethods(params int[] ids) => new(
|
|
TemplateIds: Array.Empty<int>(),
|
|
SharedScriptIds: Array.Empty<int>(),
|
|
ExternalSystemIds: Array.Empty<int>(),
|
|
DatabaseConnectionIds: Array.Empty<int>(),
|
|
NotificationListIds: Array.Empty<int>(),
|
|
SmtpConfigurationIds: Array.Empty<int>(),
|
|
ApiKeyIds: Array.Empty<int>(),
|
|
ApiMethodIds: ids,
|
|
IncludeDependencies: true);
|
|
|
|
private void StubTemplate(Template t)
|
|
{
|
|
_templates.GetTemplateWithChildrenAsync(t.Id, Arg.Any<CancellationToken>()).Returns(t);
|
|
}
|
|
|
|
private void StubAllSharedScripts(params SharedScript[] scripts)
|
|
{
|
|
_templates.GetAllSharedScriptsAsync(Arg.Any<CancellationToken>()).Returns(scripts);
|
|
}
|
|
|
|
private void StubAllExternalSystems(params ExternalSystemDefinition[] systems)
|
|
{
|
|
_externalSystems.GetAllExternalSystemsAsync(Arg.Any<CancellationToken>()).Returns(systems);
|
|
foreach (var es in systems)
|
|
{
|
|
_externalSystems
|
|
.GetMethodsByExternalSystemIdAsync(es.Id, Arg.Any<CancellationToken>())
|
|
.Returns(Array.Empty<ExternalSystemMethod>());
|
|
}
|
|
}
|
|
|
|
private void StubAllFolders(params TemplateFolder[] folders)
|
|
{
|
|
_templates.GetAllFoldersAsync(Arg.Any<CancellationToken>()).Returns(folders);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_includes_base_template_for_composed_template()
|
|
{
|
|
var baseT = new Template("Base") { Id = 10 };
|
|
var composing = new Template("Top") { Id = 11 };
|
|
composing.Compositions.Add(new TemplateComposition("slot") { TemplateId = 11, ComposedTemplateId = 10 });
|
|
|
|
StubTemplate(composing);
|
|
StubTemplate(baseT);
|
|
StubAllSharedScripts();
|
|
StubAllExternalSystems();
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(11), CancellationToken.None);
|
|
|
|
Assert.Equal(2, result.Templates.Count);
|
|
Assert.Contains(result.Templates, t => t.Id == 10);
|
|
Assert.Contains(result.Templates, t => t.Id == 11);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_includes_shared_script_referenced_by_template()
|
|
{
|
|
var shared = new SharedScript("UtilHelper", "return 42;") { Id = 100 };
|
|
var t = new Template("UsesUtil") { Id = 1 };
|
|
t.Scripts.Add(new TemplateScript("body", "var x = UtilHelper(); return x;") { TemplateId = 1 });
|
|
|
|
StubTemplate(t);
|
|
StubAllSharedScripts(shared, new SharedScript("OtherScript", "return 0;") { Id = 101 });
|
|
StubAllExternalSystems();
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(1), CancellationToken.None);
|
|
|
|
Assert.Single(result.SharedScripts);
|
|
Assert.Equal("UtilHelper", result.SharedScripts[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_includes_external_system_referenced_by_template()
|
|
{
|
|
var es = new ExternalSystemDefinition("ErpSystem", "https://erp", "ApiKey") { Id = 7 };
|
|
var t = new Template("UsesErp") { Id = 2 };
|
|
t.Scripts.Add(new TemplateScript("call", "ErpSystem.Call(\"x\");") { TemplateId = 2 });
|
|
|
|
StubTemplate(t);
|
|
StubAllSharedScripts();
|
|
StubAllExternalSystems(es, new ExternalSystemDefinition("Other", "https://o", "Basic") { Id = 8 });
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(2), CancellationToken.None);
|
|
|
|
Assert.Single(result.ExternalSystems);
|
|
Assert.Equal("ErpSystem", result.ExternalSystems[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_includes_api_method_shared_script_dependency()
|
|
{
|
|
var shared = new SharedScript("Validator", "return true;") { Id = 50 };
|
|
var method = new ApiMethod("submit", "var ok = Validator(input); return ok;") { Id = 5 };
|
|
|
|
_inboundApi.GetApiMethodByIdAsync(5, Arg.Any<CancellationToken>()).Returns(method);
|
|
StubAllSharedScripts(shared);
|
|
StubAllExternalSystems();
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectApiMethods(5), CancellationToken.None);
|
|
|
|
Assert.Single(result.ApiMethods);
|
|
Assert.Single(result.SharedScripts);
|
|
Assert.Equal("Validator", result.SharedScripts[0].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_handles_diamond_dependency_without_duplication()
|
|
{
|
|
// A composes B and C; both B and C compose D. Selection={A}. D must appear once.
|
|
var d = new Template("D") { Id = 4 };
|
|
var b = new Template("B") { Id = 2 };
|
|
b.Compositions.Add(new TemplateComposition("d-in-b") { TemplateId = 2, ComposedTemplateId = 4 });
|
|
var c = new Template("C") { Id = 3 };
|
|
c.Compositions.Add(new TemplateComposition("d-in-c") { TemplateId = 3, ComposedTemplateId = 4 });
|
|
var a = new Template("A") { Id = 1 };
|
|
a.Compositions.Add(new TemplateComposition("b-in-a") { TemplateId = 1, ComposedTemplateId = 2 });
|
|
a.Compositions.Add(new TemplateComposition("c-in-a") { TemplateId = 1, ComposedTemplateId = 3 });
|
|
|
|
StubTemplate(a);
|
|
StubTemplate(b);
|
|
StubTemplate(c);
|
|
StubTemplate(d);
|
|
StubAllSharedScripts();
|
|
StubAllExternalSystems();
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(1), CancellationToken.None);
|
|
|
|
Assert.Equal(4, result.Templates.Count);
|
|
Assert.Single(result.Templates, t => t.Id == 4);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_includes_template_folder_for_each_selected_template()
|
|
{
|
|
var root = new TemplateFolder("Root") { Id = 1, ParentFolderId = null };
|
|
var child = new TemplateFolder("Child") { Id = 2, ParentFolderId = 1 };
|
|
var grand = new TemplateFolder("Grand") { Id = 3, ParentFolderId = 2 };
|
|
var t = new Template("T") { Id = 99, FolderId = 3 };
|
|
|
|
StubTemplate(t);
|
|
StubAllSharedScripts();
|
|
StubAllExternalSystems();
|
|
StubAllFolders(root, child, grand,
|
|
new TemplateFolder("Unrelated") { Id = 4, ParentFolderId = null });
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(99), CancellationToken.None);
|
|
|
|
Assert.Equal(3, result.TemplateFolders.Count);
|
|
// Root-first ordering: depth 0, 1, 2.
|
|
Assert.Equal("Root", result.TemplateFolders[0].Name);
|
|
Assert.Equal("Child", result.TemplateFolders[1].Name);
|
|
Assert.Equal("Grand", result.TemplateFolders[2].Name);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Resolve_returns_topological_order_base_before_derived()
|
|
{
|
|
// Top composes Middle, Middle composes Leaf. Order must be Leaf, Middle, Top.
|
|
var leaf = new Template("Leaf") { Id = 30 };
|
|
var middle = new Template("Middle") { Id = 20 };
|
|
middle.Compositions.Add(new TemplateComposition("l") { TemplateId = 20, ComposedTemplateId = 30 });
|
|
var top = new Template("Top") { Id = 10 };
|
|
top.Compositions.Add(new TemplateComposition("m") { TemplateId = 10, ComposedTemplateId = 20 });
|
|
|
|
StubTemplate(top);
|
|
StubTemplate(middle);
|
|
StubTemplate(leaf);
|
|
StubAllSharedScripts();
|
|
StubAllExternalSystems();
|
|
StubAllFolders();
|
|
|
|
var result = await Sut().ResolveAsync(SelectTemplates(10), CancellationToken.None);
|
|
|
|
Assert.Equal(3, result.Templates.Count);
|
|
Assert.Equal("Leaf", result.Templates[0].Name);
|
|
Assert.Equal("Middle", result.Templates[1].Name);
|
|
Assert.Equal("Top", result.Templates[2].Name);
|
|
}
|
|
}
|