fix(transport): connection map Pass-2 (FK) + site-qualified connection resolution (M8 D1-FIX, C1+C2)
This commit is contained in:
+59
-4
@@ -569,7 +569,8 @@ public sealed class BundleImporterPreviewTests : IDisposable
|
||||
// missing template would block — but we wiped the template too, so the
|
||||
// instance also blocks; assert the New site/connection at minimum).
|
||||
Assert.Contains(preview.Items, i => i.EntityType == "Site" && i.Name == "plant-1" && i.Kind == ConflictKind.New);
|
||||
Assert.Contains(preview.Items, i => i.EntityType == "DataConnection" && i.Name == "OpcUaPrimary" && i.Kind == ConflictKind.New);
|
||||
// C2: DataConnection preview items are site-qualified ({site}/{name}).
|
||||
Assert.Contains(preview.Items, i => i.EntityType == "DataConnection" && i.Name == "plant-1/OpcUaPrimary" && i.Kind == ConflictKind.New);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -600,13 +601,66 @@ public sealed class BundleImporterPreviewTests : IDisposable
|
||||
// The site + connection match the target exactly → Identical, not New.
|
||||
var siteItem = Assert.Single(preview.Items, i => i.EntityType == "Site" && i.Name == "plant-1");
|
||||
Assert.Equal(ConflictKind.Identical, siteItem.Kind);
|
||||
var connItem = Assert.Single(preview.Items, i => i.EntityType == "DataConnection" && i.Name == "OpcUaPrimary");
|
||||
// C2: DataConnection preview items are site-qualified ({site}/{name}).
|
||||
var connItem = Assert.Single(preview.Items, i => i.EntityType == "DataConnection" && i.Name == "plant-1/OpcUaPrimary");
|
||||
Assert.Equal(ConflictKind.Identical, connItem.Kind);
|
||||
|
||||
// No blocker — the template + connection both resolve in the target.
|
||||
Assert.DoesNotContain(preview.Items, i => i.Kind == ConflictKind.Blocker && i.EntityType == "Instance");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreviewAsync_two_sites_same_connection_name_emit_two_distinct_site_qualified_items()
|
||||
{
|
||||
// C2: a bundle with plant-1/OpcUaPrimary + plant-2/OpcUaPrimary must surface
|
||||
// TWO distinct DataConnection preview items, each site-qualified
|
||||
// ({site}/{name}) — NOT a single collapsed "OpcUaPrimary" item. This is what
|
||||
// lets the operator (and the apply path) resolve each site's connection
|
||||
// independently. Hand-pack the bundle so both same-named connections are
|
||||
// present without a heavy two-site export.
|
||||
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),
|
||||
new SiteDto("plant-2", "Plant 2", null, null, null, null, null),
|
||||
},
|
||||
DataConnections = new[]
|
||||
{
|
||||
new DataConnectionDto("plant-1", "OpcUaPrimary", "OpcUa", 3, null),
|
||||
new DataConnectionDto("plant-2", "OpcUaPrimary", "OpcUa", 3, null),
|
||||
},
|
||||
Instances = Array.Empty<InstanceDto>(),
|
||||
};
|
||||
var bytes = await PackBundleAsync(content);
|
||||
|
||||
ImportPreview preview;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var importer = scope.ServiceProvider.GetRequiredService<IBundleImporter>();
|
||||
var session = await importer.LoadAsync(new MemoryStream(bytes), passphrase: null);
|
||||
preview = await importer.PreviewAsync(session.SessionId);
|
||||
}
|
||||
|
||||
var connItems = preview.Items
|
||||
.Where(i => i.EntityType == "DataConnection")
|
||||
.Select(i => i.Name)
|
||||
.OrderBy(n => n, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
// Two distinct site-qualified items — NOT one bare "OpcUaPrimary".
|
||||
Assert.Equal(new[] { "plant-1/OpcUaPrimary", "plant-2/OpcUaPrimary" }, connItems);
|
||||
Assert.DoesNotContain(preview.Items, i => i.EntityType == "DataConnection" && i.Name == "OpcUaPrimary");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreviewAsync_modified_instance_against_hydrated_target_shows_child_diff_not_all_added()
|
||||
{
|
||||
@@ -714,10 +768,11 @@ public sealed class BundleImporterPreviewTests : IDisposable
|
||||
preview = await importer.PreviewAsync(session.SessionId);
|
||||
}
|
||||
|
||||
// The unresolved connection blocks…
|
||||
// The unresolved connection blocks… (C2: blocker Name is site-qualified
|
||||
// {site}/{name} so two sites' same-named missing connections don't collide).
|
||||
Assert.Contains(preview.Items, i =>
|
||||
i.Kind == ConflictKind.Blocker
|
||||
&& i.Name == "PhantomConn"
|
||||
&& i.Name == "plant-1/PhantomConn"
|
||||
&& i.BlockerReason is not null
|
||||
&& i.BlockerReason.Contains("PhantomConn", StringComparison.Ordinal));
|
||||
// …but the template resolves in the target, so the instance is NOT a
|
||||
|
||||
Reference in New Issue
Block a user