fix(transport): real stale-instance enumeration in ImportResult (M8 D2, #16) + native-alarm rename-redirect test
This commit is contained in:
+127
-1
@@ -323,7 +323,8 @@ public sealed class SiteInstanceImportTests : IDisposable
|
||||
// Counts: site + connection + instance added (template was Skipped).
|
||||
Assert.Equal(3, result.Added);
|
||||
Assert.Equal(1, result.Skipped);
|
||||
// D2 has not run yet — StaleInstanceIds stays empty.
|
||||
// No template was overwritten and the imported instance is NotDeployed, so
|
||||
// the D2 (#16) stale-instance enumeration finds nothing.
|
||||
Assert.Empty(result.StaleInstanceIds);
|
||||
}
|
||||
|
||||
@@ -866,6 +867,131 @@ public sealed class SiteInstanceImportTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// M-4 — native-alarm-source ConnectionNameOverride RENAME-REDIRECT
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyAsync_native_alarm_override_connection_name_rewritten_to_redirected_target_name()
|
||||
{
|
||||
// M-4 regression. The existing tests only cover the same-name identity case
|
||||
// (source connection name == target connection name), so a bug that fails to
|
||||
// rewrite ConnectionNameOverride on a RENAME-REDIRECT would go unnoticed.
|
||||
// Here the operator maps the source connection "SourceConn" to a
|
||||
// DIFFERENTLY-named existing target connection "TargetConn" via an explicit
|
||||
// MapToExisting. After import, the imported instance's native-alarm-source
|
||||
// override (and its connection binding) must carry the TARGET name, not the
|
||||
// source's.
|
||||
int targetConnId;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
ctx.Templates.Add(new Template("Pump") { Description = "pump tpl" });
|
||||
var site = new Site("Plant 1", "plant-1");
|
||||
ctx.Sites.Add(site);
|
||||
await ctx.SaveChangesAsync();
|
||||
// The target connection is named DIFFERENTLY from the source's.
|
||||
var conn = new DataConnection("TargetConn", "OpcUa", site.Id)
|
||||
{
|
||||
PrimaryConfiguration = "{\"endpoint\":\"opc.tcp://target\"}",
|
||||
};
|
||||
ctx.DataConnections.Add(conn);
|
||||
await ctx.SaveChangesAsync();
|
||||
targetConnId = conn.Id;
|
||||
}
|
||||
|
||||
// Hand-pack a bundle whose instance references the SOURCE connection name
|
||||
// "SourceConn" (both in its binding and its native-alarm override). The
|
||||
// bundle does NOT carry the connection — the redirect resolves it against
|
||||
// the existing target "TargetConn".
|
||||
var content = new BundleContentDto(
|
||||
TemplateFolders: Array.Empty<TemplateFolderDto>(),
|
||||
Templates: Array.Empty<TemplateDto>(),
|
||||
SharedScripts: Array.Empty<SharedScriptDto>(),
|
||||
ExternalSystems: Array.Empty<ExternalSystemDto>(),
|
||||
DatabaseConnections: Array.Empty<DatabaseConnectionDto>(),
|
||||
NotificationLists: Array.Empty<NotificationListDto>(),
|
||||
SmtpConfigs: Array.Empty<SmtpConfigDto>(),
|
||||
ApiMethods: Array.Empty<ApiMethodDto>())
|
||||
{
|
||||
Sites = new[]
|
||||
{
|
||||
new SiteDto("plant-1", "Plant 1", null, null, null, null, null),
|
||||
},
|
||||
DataConnections = Array.Empty<DataConnectionDto>(),
|
||||
Instances = new[]
|
||||
{
|
||||
new InstanceDto(
|
||||
UniqueName: "Pump-01",
|
||||
TemplateName: "Pump",
|
||||
SiteIdentifier: "plant-1",
|
||||
AreaName: null,
|
||||
State: InstanceState.Enabled,
|
||||
AttributeOverrides: Array.Empty<InstanceAttributeOverrideDto>(),
|
||||
AlarmOverrides: Array.Empty<InstanceAlarmOverrideDto>(),
|
||||
NativeAlarmSourceOverrides: new[]
|
||||
{
|
||||
new InstanceNativeAlarmSourceOverrideDto(
|
||||
SourceCanonicalName: "NativeSrc",
|
||||
ConnectionNameOverride: "SourceConn",
|
||||
SourceReferenceOverride: "ns=3;s=Pump.Alarm",
|
||||
ConditionFilterOverride: null),
|
||||
},
|
||||
ConnectionBindings: new[]
|
||||
{
|
||||
new InstanceConnectionBindingDto(
|
||||
AttributeName: "Flow",
|
||||
ConnectionName: "SourceConn",
|
||||
DataSourceReferenceOverride: "ns=3;s=Pump.Flow"),
|
||||
}),
|
||||
},
|
||||
};
|
||||
var sessionId = await PackAndLoadAsync(content);
|
||||
|
||||
// Explicit redirect: source "SourceConn" → target "TargetConn" (DIFFERENT name).
|
||||
var nameMap = new BundleNameMap(
|
||||
Sites: new[] { new SiteMapping("plant-1", MappingAction.MapToExisting, "plant-1") },
|
||||
Connections: new[]
|
||||
{
|
||||
new ConnectionMapping("plant-1", "SourceConn", MappingAction.MapToExisting, "TargetConn"),
|
||||
});
|
||||
|
||||
var result = await ApplyAsync(
|
||||
sessionId,
|
||||
new List<ImportResolution>
|
||||
{
|
||||
new("Site", "plant-1", ResolutionAction.Skip, null),
|
||||
new("Instance", "Pump-01", ResolutionAction.Add, null),
|
||||
},
|
||||
nameMap);
|
||||
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
|
||||
// No new connection created — the redirect resolved the existing target.
|
||||
var conn = Assert.Single(await ctx.DataConnections.ToListAsync());
|
||||
Assert.Equal("TargetConn", conn.Name);
|
||||
Assert.Equal(targetConnId, conn.Id);
|
||||
|
||||
var inst = await ctx.Instances
|
||||
.Include(i => i.ConnectionBindings)
|
||||
.Include(i => i.NativeAlarmSourceOverrides)
|
||||
.SingleAsync(i => i.UniqueName == "Pump-01");
|
||||
|
||||
// THE FIX: the native-alarm-source override carries the TARGET name,
|
||||
// not the source's "SourceConn".
|
||||
var native = Assert.Single(inst.NativeAlarmSourceOverrides);
|
||||
Assert.Equal("TargetConn", native.ConnectionNameOverride);
|
||||
|
||||
// The connection binding FK rewires to the redirected target connection.
|
||||
var binding = Assert.Single(inst.ConnectionBindings);
|
||||
Assert.Equal(targetConnId, binding.DataConnectionId);
|
||||
}
|
||||
|
||||
Assert.Equal(1, result.Added);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// C2 — two sites with same-named connections resolve independently
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user