From e6706c26e6452b06b63affdfdcc702ee70d7378a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 24 May 2026 06:05:26 -0400 Subject: [PATCH] fix(transport): preserve MinTimeBetweenRuns + ExternalSystem retry fields in bundle DTOs Add TimeSpan? MinTimeBetweenRuns to TemplateScriptDto and int MaxRetries / TimeSpan RetryDelay to ExternalSystemDto; wire both directions in EntitySerializer. Extends the existing script round-trip assertion and adds Roundtrip_external_system_preserves_retry_config. --- .../Serialization/EntityDtos.cs | 5 +++- .../Serialization/EntitySerializer.cs | 8 +++++- .../Serialization/EntitySerializerTests.cs | 25 ++++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/ScadaLink.Transport/Serialization/EntityDtos.cs b/src/ScadaLink.Transport/Serialization/EntityDtos.cs index 864c1ee..58e58c2 100644 --- a/src/ScadaLink.Transport/Serialization/EntityDtos.cs +++ b/src/ScadaLink.Transport/Serialization/EntityDtos.cs @@ -87,7 +87,8 @@ public sealed record TemplateScriptDto( string? TriggerConfiguration, string? ParameterDefinitions, string? ReturnDefinition, - bool IsLocked); + bool IsLocked, + TimeSpan? MinTimeBetweenRuns); public sealed record TemplateCompositionDto( string InstanceName, @@ -103,6 +104,8 @@ public sealed record ExternalSystemDto( string Name, string BaseUrl, string AuthType, + int MaxRetries, + TimeSpan RetryDelay, IReadOnlyList Methods, SecretsBlock? Secrets); diff --git a/src/ScadaLink.Transport/Serialization/EntitySerializer.cs b/src/ScadaLink.Transport/Serialization/EntitySerializer.cs index 7e856de..3197ee7 100644 --- a/src/ScadaLink.Transport/Serialization/EntitySerializer.cs +++ b/src/ScadaLink.Transport/Serialization/EntitySerializer.cs @@ -55,7 +55,8 @@ public sealed class EntitySerializer TriggerConfiguration: s.TriggerConfiguration, ParameterDefinitions: s.ParameterDefinitions, ReturnDefinition: s.ReturnDefinition, - IsLocked: s.IsLocked)).ToList(), + IsLocked: s.IsLocked, + MinTimeBetweenRuns: s.MinTimeBetweenRuns)).ToList(), Compositions: t.Compositions.Select(c => new TemplateCompositionDto( InstanceName: c.InstanceName, ComposedTemplateName: templateNameById.TryGetValue(c.ComposedTemplateId, out var cn) ? cn : string.Empty)).ToList())).ToList(), @@ -89,6 +90,8 @@ public sealed class EntitySerializer Name: sys.Name, BaseUrl: sys.EndpointUrl, AuthType: sys.AuthType, + MaxRetries: sys.MaxRetries, + RetryDelay: sys.RetryDelay, Methods: methods, Secrets: secrets); }).ToList(), @@ -204,6 +207,7 @@ public sealed class EntitySerializer ParameterDefinitions = s.ParameterDefinitions, ReturnDefinition = s.ReturnDefinition, IsLocked = s.IsLocked, + MinTimeBetweenRuns = s.MinTimeBetweenRuns, }); } return t; @@ -253,6 +257,8 @@ public sealed class EntitySerializer { Id = ix + 1, AuthConfiguration = dto.Secrets?.Values.TryGetValue("AuthConfiguration", out var auth) == true ? auth : null, + MaxRetries = dto.MaxRetries, + RetryDelay = dto.RetryDelay, }; externalSystems.Add(sys); foreach (var m in dto.Methods) diff --git a/tests/ScadaLink.Transport.Tests/Serialization/EntitySerializerTests.cs b/tests/ScadaLink.Transport.Tests/Serialization/EntitySerializerTests.cs index 65ba701..eeed2d8 100644 --- a/tests/ScadaLink.Transport.Tests/Serialization/EntitySerializerTests.cs +++ b/tests/ScadaLink.Transport.Tests/Serialization/EntitySerializerTests.cs @@ -96,6 +96,7 @@ public sealed class EntitySerializerTests ParameterDefinitions = "[]", ReturnDefinition = "void", IsLocked = false, + MinTimeBetweenRuns = TimeSpan.FromSeconds(30), }); var assembly = new Template("Assembly") { Id = 2, FolderId = 1 }; @@ -133,6 +134,7 @@ public sealed class EntitySerializerTests Assert.Equal("OnUpdate", rtScript.Name); Assert.Equal("return 1;", rtScript.Code); Assert.Equal("Periodic", rtScript.TriggerType); + Assert.Equal(TimeSpan.FromSeconds(30), rtScript.MinTimeBetweenRuns); var rtAssembly = Assert.Single(roundTripped.Templates, t => t.Name == "Assembly"); var rtComp = Assert.Single(rtAssembly.Compositions); @@ -164,6 +166,27 @@ public sealed class EntitySerializerTests Assert.Equal(rtRoot.Id, rtChild.ParentFolderId); } + [Fact] + public void Roundtrip_external_system_preserves_retry_config() + { + var sys = new ExternalSystemDefinition("billing", "https://billing/api", "Basic") + { + Id = 1, + MaxRetries = 5, + RetryDelay = TimeSpan.FromSeconds(15), + }; + var aggregate = MakeEmptyAggregate() with { ExternalSystems = new[] { sys } }; + + var sut = new EntitySerializer(); + var dto = sut.ToBundleContent(aggregate); + var roundTripped = sut.FromBundleContent(dto); + + var rtSys = Assert.Single(roundTripped.ExternalSystems); + Assert.Equal("billing", rtSys.Name); + Assert.Equal(5, rtSys.MaxRetries); + Assert.Equal(TimeSpan.FromSeconds(15), rtSys.RetryDelay); + } + [Fact] public void FromDto_with_null_SecretsBlock_yields_entity_with_default_empty_secrets() { @@ -173,7 +196,7 @@ public sealed class EntitySerializerTests SharedScripts: Array.Empty(), ExternalSystems: new[] { - new ExternalSystemDto("erp", "https://x", "None", Array.Empty(), Secrets: null), + new ExternalSystemDto("erp", "https://x", "None", MaxRetries: 3, RetryDelay: TimeSpan.FromSeconds(5), Array.Empty(), Secrets: null), }, DatabaseConnections: Array.Empty(), NotificationLists: Array.Empty(),