feat(esg): per-system TimeoutSeconds column on ExternalSystemDefinition (spec Component-ExternalSystemGateway Timeout) — entity + EF + migration

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 03:55:26 -04:00
parent 153b82c880
commit 2767e4bca1
7 changed files with 2097 additions and 0 deletions
@@ -17,6 +17,13 @@ public class ExternalSystemDefinition
/// <summary>Fixed delay between retry attempts.</summary>
public TimeSpan RetryDelay { get; set; }
/// <summary>
/// Per-system HTTP round-trip timeout in seconds for all method calls
/// (spec: Component-ExternalSystemGateway "Timeout"). 0 = unset — the gateway's
/// configured DefaultHttpTimeout applies.
/// </summary>
public int TimeoutSeconds { get; set; }
/// <summary>
/// Initializes a new <see cref="ExternalSystemDefinition"/>.
/// </summary>
@@ -29,6 +29,8 @@ public class ExternalSystemDefinitionConfiguration : IEntityTypeConfiguration<Ex
builder.Property(e => e.AuthConfiguration)
.HasMaxLength(8000);
builder.Property(e => e.TimeoutSeconds).HasDefaultValue(0);
builder.HasMany<ExternalSystemMethod>()
.WithOne()
.HasForeignKey(m => m.ExternalSystemDefinitionId)
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddExternalSystemTimeoutSeconds : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "TimeoutSeconds",
table: "ExternalSystemDefinitions",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TimeoutSeconds",
table: "ExternalSystemDefinitions");
}
}
}
@@ -418,6 +418,11 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
b.Property<TimeSpan>("RetryDelay")
.HasColumnType("time");
b.Property<int>("TimeoutSeconds")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasDefaultValue(0);
b.HasKey("Id");
b.HasIndex("Name")
@@ -0,0 +1,19 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Entities;
/// <summary>
/// Locks the <see cref="ExternalSystemDefinition"/> construction invariants — in particular
/// that the per-system <see cref="ExternalSystemDefinition.TimeoutSeconds"/> defaults to 0,
/// meaning "unset — use the gateway's configured DefaultHttpTimeout".
/// </summary>
public class ExternalSystemDefinitionTests
{
[Fact]
public void ExternalSystemDefinition_TimeoutSeconds_DefaultsToZeroMeaningUseGatewayDefault()
{
var system = new ExternalSystemDefinition("sys", "http://x", "none");
Assert.Equal(0, system.TimeoutSeconds);
}
}
@@ -45,6 +45,21 @@ public class ExternalSystemRepositoryTests : IDisposable
Assert.Equal("Sys", loaded!.Name);
}
[Fact]
public async Task AddExternalSystem_TimeoutSeconds_RoundTrips()
{
var def = new ExternalSystemDefinition("Sys", "https://example.test", "ApiKey")
{
TimeoutSeconds = 15,
};
await _repository.AddExternalSystemAsync(def);
await _repository.SaveChangesAsync();
var loaded = await _repository.GetExternalSystemByIdAsync(def.Id);
Assert.NotNull(loaded);
Assert.Equal(15, loaded!.TimeoutSeconds);
}
[Fact]
public async Task GetMethodsByExternalSystemId_FiltersByParent()
{