fix(transport): blocker heuristic no longer hard-blocks valid imports — local declarations excluded, denylist extended, template-script findings downgraded to warnings
#05-T19. Import name-resolution findings are now split by origin: template-script (and attribute-default-expression) references become non-blocking advisory warnings (ConflictKind.Warning + ImportResult.Warnings) since the design-time deploy gate re-validates authoritatively; ApiMethod-script references stay hard errors (SemanticValidationException / ConflictKind.Blocker) as they have no downstream gate. Names declared locally in a body (Roslyn-parsed local functions / methods) are excluded so a script's own helper isn't flagged; KnownNonReferenceNames extended with common BCL/LINQ/SQL surface. DetectBlockersAsync and RunSemanticValidationAsync Pass 1 apply the identical split so preview and apply agree. Rollback/hard-fail tests retargeted to ApiMethods (the still-hard-failing vehicle). CentralUI preview renders the new Warning badge/advisory. Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
@@ -57,37 +58,38 @@ public sealed class ValidationFailureTests : IDisposable
|
||||
public async Task Semantic_validation_failure_rolls_back_all_writes()
|
||||
{
|
||||
// Arrange:
|
||||
// - Seed a template whose script body references MissingHelper(),
|
||||
// - Seed an ApiMethod whose script body references MissingHelper(),
|
||||
// a SharedScript that is NOT in the bundle (we don't seed one) and
|
||||
// also NOT in the target after the wipe.
|
||||
// - Export the template only (no helper alongside).
|
||||
// also NOT in the target after the wipe. ApiMethod name-resolution
|
||||
// findings are HARD errors (no downstream deploy gate re-validates
|
||||
// them — unlike template-script findings, which are advisory as of
|
||||
// #05-T19), so this is the vehicle that still forces a rollback.
|
||||
// - Export the ApiMethod only (no helper alongside).
|
||||
// - Wipe the target so the apply runs through Add-paths.
|
||||
// - Snapshot row counts so the rollback assertion is unambiguous.
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
var t = new Template("BrokenPump") { Description = "calls a missing helper" };
|
||||
t.Scripts.Add(new TemplateScript("init", "var x = MissingHelper();"));
|
||||
ctx.Templates.Add(t);
|
||||
ctx.ApiMethods.Add(new ApiMethod("BrokenIngest", "var x = MissingHelper();"));
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// Export only the broken template. IncludeDependencies=false guarantees
|
||||
// Export only the broken ApiMethod. IncludeDependencies=false guarantees
|
||||
// MissingHelper is NOT pulled in even if one happened to exist.
|
||||
byte[] bundleBytes;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var exporter = scope.ServiceProvider.GetRequiredService<IBundleExporter>();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
var ids = await ctx.Templates.Select(t => t.Id).ToListAsync();
|
||||
var ids = await ctx.ApiMethods.Select(m => m.Id).ToListAsync();
|
||||
var selection = new ExportSelection(
|
||||
TemplateIds: ids,
|
||||
TemplateIds: Array.Empty<int>(),
|
||||
SharedScriptIds: Array.Empty<int>(),
|
||||
ExternalSystemIds: Array.Empty<int>(),
|
||||
DatabaseConnectionIds: Array.Empty<int>(),
|
||||
NotificationListIds: Array.Empty<int>(),
|
||||
SmtpConfigurationIds: Array.Empty<int>(),
|
||||
ApiMethodIds: Array.Empty<int>(),
|
||||
ApiMethodIds: ids,
|
||||
IncludeDependencies: false);
|
||||
|
||||
var stream = await exporter.ExportAsync(selection,
|
||||
@@ -103,21 +105,20 @@ public sealed class ValidationFailureTests : IDisposable
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
ctx.TemplateScripts.RemoveRange(ctx.TemplateScripts);
|
||||
ctx.Templates.RemoveRange(ctx.Templates);
|
||||
ctx.ApiMethods.RemoveRange(ctx.ApiMethods);
|
||||
ctx.SharedScripts.RemoveRange(ctx.SharedScripts);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
int templatesBefore;
|
||||
int apiMethodsBefore;
|
||||
int sharedScriptsBefore;
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
templatesBefore = await ctx.Templates.CountAsync();
|
||||
apiMethodsBefore = await ctx.ApiMethods.CountAsync();
|
||||
sharedScriptsBefore = await ctx.SharedScripts.CountAsync();
|
||||
}
|
||||
Assert.Equal(0, templatesBefore);
|
||||
Assert.Equal(0, apiMethodsBefore);
|
||||
Assert.Equal(0, sharedScriptsBefore);
|
||||
|
||||
// Load the bundle into a session.
|
||||
@@ -139,7 +140,7 @@ public sealed class ValidationFailureTests : IDisposable
|
||||
importer.ApplyAsync(sessionId,
|
||||
new List<ImportResolution>
|
||||
{
|
||||
new("Template", "BrokenPump", ResolutionAction.Add, null),
|
||||
new("ApiMethod", "BrokenIngest", ResolutionAction.Add, null),
|
||||
},
|
||||
user: "bob"));
|
||||
}
|
||||
@@ -150,11 +151,11 @@ public sealed class ValidationFailureTests : IDisposable
|
||||
Assert.Contains(ex.Errors,
|
||||
err => err.Contains("MissingHelper", StringComparison.Ordinal));
|
||||
|
||||
// 2. No new Template or SharedScript row was committed.
|
||||
// 2. No new ApiMethod or SharedScript row was committed.
|
||||
await using (var scope = _provider.CreateAsyncScope())
|
||||
{
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<ScadaBridgeDbContext>();
|
||||
Assert.Equal(templatesBefore, await ctx.Templates.CountAsync());
|
||||
Assert.Equal(apiMethodsBefore, await ctx.ApiMethods.CountAsync());
|
||||
Assert.Equal(sharedScriptsBefore, await ctx.SharedScripts.CountAsync());
|
||||
|
||||
// 3. A BundleImportFailed audit row exists. The BundleImporter
|
||||
|
||||
Reference in New Issue
Block a user