feat: add audited one-shot prepare gate

This commit is contained in:
2026-07-18 12:29:09 +09:00
parent 1302b1b92f
commit 5379511b8a
25 changed files with 6188 additions and 41 deletions

View File

@@ -0,0 +1,361 @@
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacyGateAAppIntegrationContractTests
{
private static readonly string RepositoryRoot = FindRepositoryRoot();
private static readonly string AppRoot = Path.Combine(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.LegacyParityApp");
private static readonly string Window = Read(AppRoot, "MainWindow.xaml.cs");
private static readonly string Playout = Read(AppRoot, "MainWindow.Playout.cs");
private static readonly string IntentParser = Read(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.LegacyBridge",
"LegacyPlayoutUiIntents.cs");
private static readonly string Factory = Read(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.Playout",
"PlayoutEngineFactory.cs");
private static readonly string LaunchAuthorization = Read(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.Playout",
"Safety",
"PlayoutLaunchAuthorization.cs");
private static readonly string InfrastructureRoot = Path.Combine(
RepositoryRoot,
"src",
"MBN_STOCK_WEBVIEW.Infrastructure",
"Execution");
[Fact]
public void Launch_authorization_is_captured_before_WebView_creation_and_forwarded_unchanged()
{
var constructor = Slice(
Window,
" public MainWindow()",
" private async void OnRootLoaded(");
var capture = RequiredIndex(
constructor,
"PlayoutLaunchAuthorization.CaptureFromEnvironment()");
var initializeComponent = RequiredIndex(constructor, "InitializeComponent();");
Assert.True(capture < initializeComponent);
Assert.Contains(
"_playoutLaunchAuthorization =\n" +
" PlayoutLaunchAuthorization.CaptureFromEnvironment();",
Normalize(constructor),
StringComparison.Ordinal);
var initialization = Normalize(Slice(
Playout,
" private void InitializePlayoutRuntime()",
" private async Task ConnectPlayoutAsync("));
Assert.Contains(
"_playoutEngine = PlayoutEngineFactory.Create(\n" +
" _playoutOptions,\n" +
" _playoutLaunchAuthorization);",
initialization,
StringComparison.Ordinal);
var factoryCreate = Slice(
Normalize(Factory),
" public static IPlayoutEngine Create(\n" +
" PlayoutOptions options,\n" +
" PlayoutLaunchAuthorization launchAuthorization)",
" internal static void ValidateGateAOptions(");
Assert.Contains("dispatcher,\n launchAuthorization,", factoryCreate,
StringComparison.Ordinal);
Assert.Contains("launchAuthorization: launchAuthorization", factoryCreate,
StringComparison.Ordinal);
}
[Fact]
public void Gate_A_has_a_dedicated_capability_intent_and_exact_Samsung_5001_prepare_handler()
{
Assert.Contains(
"public sealed record LegacyGateAPrepareIntent(string Capability)",
IntentParser,
StringComparison.Ordinal);
Assert.Contains("\"gate-a-prepare\" => ParseGateAPrepare(payload)", IntentParser,
StringComparison.Ordinal);
var parser = Slice(
IntentParser,
" private static LegacyUiIntent? ParseGateAPrepare(JsonElement payload)",
" private static LegacyUiIntent? ParseFadeDuration(JsonElement payload)");
Assert.Contains("payload.EnumerateObject().Count() != 1", parser,
StringComparison.Ordinal);
Assert.Contains("capability.Length != 64", parser, StringComparison.Ordinal);
Assert.Contains("not (>= 'A' and <= 'F')", parser, StringComparison.Ordinal);
var dispatch = Normalize(Slice(
Window,
" private async Task<LegacyOperatorSnapshot> HandleIntentAsync(",
" private async Task<LegacyOperatorSnapshot> RefreshNamedPlaylistsForDispatchAsync("));
Assert.Contains(
"LegacyGateAPrepareIntent prepare =>\n" +
" await ExecuteGateAPrepareAsync(\n" +
" prepare.Capability,",
dispatch,
StringComparison.Ordinal);
var handler = Slice(
Playout,
" private async Task<LegacyOperatorSnapshot> ExecuteGateAPrepareAsync(",
" private bool TryCreateExactGateAPrepareRequest(");
Assert.Contains("TryClaimGateAPrepare(capability)", handler,
StringComparison.Ordinal);
Assert.Contains("TryCreateExactGateAPrepareRequest(out var request)", handler,
StringComparison.Ordinal);
Assert.Contains("gateAPrepareClaimed: true", handler, StringComparison.Ordinal);
Assert.Contains("gateAPrepareRequest: request", handler, StringComparison.Ordinal);
Assert.Contains("CompleteGateAPrepare()", handler, StringComparison.Ordinal);
var exact = Slice(
Playout,
" private bool TryCreateExactGateAPrepareRequest(",
" private async Task<LegacyOperatorSnapshot> SetFadeDurationAsync(");
foreach (var required in new[]
{
"snapshot.Playlist.Count != 1",
"composition.FadeDuration != 6",
"composition.BackgroundKind != LegacySceneBackgroundKind.None",
"row.MarketText, \"코스피\"",
"row.StockName, \"삼성전자\"",
"row.PageText, \"1/1\"",
"row.StockCode, \"005930\"",
"_controller.CreatePlayoutRequest(6)",
"candidate.SelectedIndexZeroBased != 0",
"entry.CutCode, \"5001\"",
"entry.FadeDuration != 6",
"entry.PageNavigation?.IsEnabled == true",
"selection.GroupCode, \"코스피\"",
"selection.Subject, \"삼성전자\"",
"selection.DataCode, \"005930\""
})
{
Assert.Contains(required, exact, StringComparison.Ordinal);
}
}
[Fact]
public void Ordinary_playout_intents_are_rejected_in_Gate_A_before_workflow_access()
{
var execute = Slice(
Playout,
" private async Task<LegacyOperatorSnapshot> ExecuteOperatorPlayoutAsync(",
" private async Task<LegacyOperatorSnapshot> ExecuteGateAPrepareAsync(");
var workflowRead = RequiredIndex(execute, "var workflow = _playoutWorkflow;");
var preWorkflowGuard = execute[..workflowRead];
Assert.Contains("_playoutLaunchAuthorization.IsGateA", preWorkflowGuard,
StringComparison.Ordinal);
Assert.Contains("!gateAPrepareClaimed", preWorkflowGuard, StringComparison.Ordinal);
Assert.Contains("command != LegacyOperatorPlayoutCommand.Prepare", preWorkflowGuard,
StringComparison.Ordinal);
Assert.Contains("gateAPrepareRequest is null", preWorkflowGuard,
StringComparison.Ordinal);
Assert.Contains("return _controller.ReportError(", preWorkflowGuard,
StringComparison.Ordinal);
Assert.DoesNotContain("_playoutWorkflow", preWorkflowGuard,
StringComparison.Ordinal);
Assert.DoesNotContain("CreatePlayoutRequest", preWorkflowGuard,
StringComparison.Ordinal);
Assert.DoesNotContain("_playoutCommandGate.WaitAsync", preWorkflowGuard,
StringComparison.Ordinal);
var dispatch = Slice(
Window,
" private async Task<LegacyOperatorSnapshot> HandleIntentAsync(",
" private async Task<LegacyOperatorSnapshot> RefreshNamedPlaylistsForDispatchAsync(");
Assert.Contains("LegacyExecutePlayoutIntent playout =>", dispatch,
StringComparison.Ordinal);
Assert.Contains("ExecuteOperatorPlayoutAsync(\n playout.Command,",
Normalize(dispatch), StringComparison.Ordinal);
}
[Fact]
public void All_database_mutation_executors_receive_the_same_launch_denial_policy()
{
var constructor = Slice(
Window,
" public MainWindow()",
" private async void OnRootLoaded(");
Assert.Contains(
"new LaunchDatabaseMutationAuthorization(_playoutLaunchAuthorization)",
constructor,
StringComparison.Ordinal);
Assert.True(
CountOccurrences(
constructor,
"mutationAuthorization: databaseMutationAuthorization") == 3,
"The same launch authorization must be injected into all three DB mutation executors.");
var adapter = Slice(
Window,
" private sealed class LaunchDatabaseMutationAuthorization",
" private void PostState(");
Assert.Contains(": IDatabaseMutationAuthorization", adapter,
StringComparison.Ordinal);
Assert.Contains("return _authorization.AllowsDatabaseWrites;", adapter,
StringComparison.Ordinal);
Assert.Contains("_authorization = authorization ??", adapter,
StringComparison.Ordinal);
foreach (var executorFile in new[]
{
"OracleManualFinancialMutationExecutor.cs",
"OracleNamedPlaylistMutationExecutor.cs",
"OperatorCatalogMutationExecutor.cs"
})
{
var executor = File.ReadAllText(Path.Combine(InfrastructureRoot, executorFile));
var demand = RequiredIndex(executor, "DatabaseMutationAuthorizationGuard.Demand(");
var open = RequiredIndex(executor, "await connection.OpenAsync(");
Assert.True(demand < open, $"{executorFile} must deny before opening a DB connection.");
Assert.Contains("_mutationAuthorization", executor, StringComparison.Ordinal);
Assert.Contains("IDatabaseMutationAuthorization mutationAuthorization", executor,
StringComparison.Ordinal);
}
}
[Fact]
public void Gate_A_blocks_persistent_write_intents_before_dispatch()
{
var process = Normalize(Slice(
Window,
" private async Task ProcessIntentAsync(LegacyUiIntent intent)",
" private static bool IsNamedPlaylistModalIntent("));
var guard = RequiredIndex(
process,
"_playoutLaunchAuthorization.IsGateA &&\n IsPersistentWriteIntent(intent)");
var dispatchStarted = RequiredIndex(process, "dispatchStarted = true;");
var dispatch = RequiredIndex(process, "HandleIntentAsync(intent");
Assert.True(guard < dispatchStarted);
Assert.True(guard < dispatch);
var guardEnd = process.IndexOf("return;", guard, StringComparison.Ordinal);
Assert.True(guardEnd > guard);
Assert.Contains("ReportIntentFailure(", process[guard..guardEnd],
StringComparison.Ordinal);
var persistent = Slice(
Playout,
" private static bool IsPersistentWriteIntent(LegacyUiIntent intent)",
" private static bool IsFixedSectionBatchMutationIntent(");
foreach (var intent in new[]
{
"LegacyDeleteUc4SelectedThemeIntent",
"LegacyDeleteUc6SelectedExpertIntent",
"LegacySaveOperatorCatalogIntent",
"LegacyConfirmNativeDialogIntent",
"LegacyAddComparisonPairIntent",
"LegacySaveManualFinancialIntent",
"LegacySaveManualNetSellIntent",
"LegacySaveManualViIntent",
"LegacyImportManualListsIntent",
"LegacyCreateNamedPlaylistIntent",
"LegacySaveCurrentNamedPlaylistIntent",
"LegacyDeleteSelectedNamedPlaylistIntent"
})
{
Assert.Contains(intent, persistent, StringComparison.Ordinal);
}
}
[Fact]
public void Gate_A_blocks_fade_and_background_mutation_before_any_command_gate()
{
Assert.Contains("public bool AllowsCompositionMutation => !IsGateA;",
LaunchAuthorization, StringComparison.Ordinal);
var fade = Slice(
Playout,
" private async Task<LegacyOperatorSnapshot> SetFadeDurationAsync(",
" private LegacyOperatorSnapshot SelectPlaylistBoundaryAndStopRefresh(");
AssertCompositionMutationGuard(
fade,
"Gate A 검증 회차에서는 Fade 설정을 변경할 수 없습니다.");
var toggle = Slice(
Playout,
" private async Task<LegacyOperatorSnapshot> ToggleBackgroundAsync(",
" private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(");
AssertCompositionMutationGuard(
toggle,
"Gate A 검증 회차에서는 배경 설정을 변경할 수 없습니다.");
var choose = Slice(
Playout,
" private async Task<LegacyOperatorSnapshot> ChooseBackgroundAsync(",
" private bool CanChangeComposition()");
AssertCompositionMutationGuard(
choose,
"Gate A 검증 회차에서는 배경 파일을 선택할 수 없습니다.");
}
private static void AssertCompositionMutationGuard(string method, string message)
{
var authorization = RequiredIndex(
method,
"if (!_playoutLaunchAuthorization.AllowsCompositionMutation)");
var commandGate = RequiredIndex(method, "_playoutCommandGate.WaitAsync");
Assert.True(authorization < commandGate);
Assert.Contains(message, method[authorization..commandGate],
StringComparison.Ordinal);
Assert.Contains("return _controller.ReportError(", method[authorization..commandGate],
StringComparison.Ordinal);
}
private static int CountOccurrences(string source, string marker)
{
var count = 0;
var index = 0;
while ((index = source.IndexOf(marker, index, StringComparison.Ordinal)) >= 0)
{
count++;
index += marker.Length;
}
return count;
}
private static string Slice(string source, string startMarker, string endMarker)
{
var start = RequiredIndex(source, startMarker);
var end = RequiredIndex(source, endMarker, start + startMarker.Length);
return source[start..end];
}
private static int RequiredIndex(string source, string marker, int startIndex = 0)
{
var index = source.IndexOf(marker, startIndex, StringComparison.Ordinal);
Assert.True(index >= 0, $"Required source marker was not found: {marker}");
return index;
}
private static string Normalize(string source) =>
source.Replace("\r\n", "\n", StringComparison.Ordinal);
private static string Read(params string[] parts) =>
File.ReadAllText(Path.Combine(parts));
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
}

View File

@@ -0,0 +1,318 @@
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
public sealed class LegacyGateAHarnessContractTests
{
private const string CurrentMsixSha256 =
"0E44F87FDD31852B7DDFCD6CF3A6325DA674F1F5A23AD39CB262A725AEB84367";
private static readonly string RepositoryRoot = FindRepositoryRoot();
private static readonly string Runner = ReadScript("Invoke-LegacyPgmPrepareGate.ps1");
private static readonly string PlanBuilder = ReadScript("New-LegacyPgmPreparePlan.ps1");
private static readonly string NodeHelper = ReadScript("Test-LegacyPgmPrepareGate.mjs");
[Fact]
public void Node_receives_the_bearer_only_from_the_parent_owned_JIT_pipe()
{
Assert.Contains("import net from \"node:net\";", NodeHelper, StringComparison.Ordinal);
Assert.Contains("\"capability-sha256\", \"permit-pipe\"", NodeHelper,
StringComparison.Ordinal);
Assert.Contains(
"name.startsWith(\"MBN_LEGACY_PGM_GATE_A_\")",
NodeHelper,
StringComparison.Ordinal);
Assert.DoesNotContain(
"process.env[capabilityName]",
NodeHelper,
StringComparison.Ordinal);
var receive = Slice(
NodeHelper,
"async function receiveJitCapability(expiresAt)",
"async function prepareOnce(expiresAt)");
Assert.Contains("net.createConnection(pipePath)", receive, StringComparison.Ordinal);
Assert.Contains("bytes.length !== 64", receive, StringComparison.Ordinal);
Assert.Contains("sha256(bytes) !== configuration.capabilitySha256", receive,
StringComparison.Ordinal);
Assert.Contains("jitPermitReceived = true", receive, StringComparison.Ordinal);
var prepare = Slice(
NodeHelper,
"async function prepareOnce(expiresAt)",
"async function captureScreenshot()");
var boundary = RequiredIndex(prepare, "const preRevision = before.state.revision;");
var permit = RequiredIndex(prepare, "await receiveJitCapability(expiresAt)");
var arm = RequiredIndex(prepare, "__legacyPgmPrepareGate.arm(");
var dispatchPossible = RequiredIndex(prepare, "prepareDispatchPossible = true;");
Assert.True(boundary < permit && permit < arm && arm < dispatchPossible);
var execute = Slice(NodeHelper, "async function execute()", "try {");
Assert.Contains(
"installGate(configuration.planSha256, configuration.capabilitySha256)",
execute,
StringComparison.Ordinal);
}
[Fact]
public void Runner_authenticates_the_exact_Node_client_before_one_JIT_delivery()
{
var start = Slice(
Runner,
"function Start-NodeHelper([object] $Plan)",
"function Write-Evidence([string] $Path)");
Assert.Contains("New-CurrentUserOneShotPipe 'Node'", start, StringComparison.Ordinal);
Assert.Contains("'--capability-sha256'", start, StringComparison.Ordinal);
Assert.Contains("'--permit-pipe'", start, StringComparison.Ordinal);
Assert.DoesNotContain(
"$info.EnvironmentVariables[$capabilityName]",
start,
StringComparison.Ordinal);
Assert.Contains("MBN_LEGACY_PGM_GATE_A_*", start, StringComparison.Ordinal);
Assert.Contains("Assert-NodePermitClient", start, StringComparison.Ordinal);
var client = Slice(
Runner,
"function Assert-NodePermitClient(",
"function Stop-NodeHelperFailStop(");
Assert.Contains("ReadNamedPipeClientProcessId", client, StringComparison.Ordinal);
Assert.Contains("$clientProcessId -ne $Process.Id", client, StringComparison.Ordinal);
Assert.Contains("$Process.StartTime.ToUniversalTime().Ticks", client,
StringComparison.Ordinal);
Assert.Contains("[int]$row.ParentProcessId -ne $PID", client,
StringComparison.Ordinal);
Assert.Contains("SamePath ([string]$row.ExecutablePath) $ExpectedExecutable", client,
StringComparison.Ordinal);
var delivery = RequiredIndex(start, "$permitPipe.Stream.Write($bytes,0,$bytes.Length)");
foreach (var marker in new[]
{
"Assert-NodePermitClient",
"Assert-Authorization $script:authorization $Plan",
"Assert-Application $script:application",
"Assert-Pgm $Plan.pgm",
"Assert-Cdp ([int]$Plan.webView.port)"
})
{
Assert.True(RequiredIndex(start, marker) < delivery, marker);
}
var stop = Slice(
Runner,
"function Stop-NodeHelperFailStop(",
"function Start-NodeHelper([object] $Plan)");
Assert.Contains("Close-OneShotPipe $Pipe", stop, StringComparison.Ordinal);
Assert.Contains("$Process.Kill()", stop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(0)", stop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(5000)", stop, StringComparison.Ordinal);
Assert.Contains("was not proven stopped", stop, StringComparison.Ordinal);
Assert.Contains("only the control-plane Node helper", stop, StringComparison.Ordinal);
Assert.DoesNotContain("$script:application", stop, StringComparison.Ordinal);
Assert.DoesNotContain("Assert-Pgm", stop, StringComparison.Ordinal);
var catchBlock = start[RequiredIndex(start, " catch {")..];
Assert.Contains("Stop-NodeHelperFailStop $process $permitPipe", catchBlock,
StringComparison.Ordinal);
Assert.Contains("exceeded its outer timeout; parent fail-stop is required", start,
StringComparison.Ordinal);
var monitorTry = Slice(start, " try {\n $process.Refresh()", " catch {");
Assert.True(
RequiredIndex(monitorTry, "$process.StartTime.ToUniversalTime().Ticks") <
RequiredIndex(monitorTry, "while (-not $process.HasExited"));
Assert.Contains("if (-not $process.HasExited)", monitorTry, StringComparison.Ordinal);
Assert.Contains("$process.WaitForExit(0)", monitorTry, StringComparison.Ordinal);
}
[Fact]
public void Package_wrapper_receives_launch_environment_over_an_authenticated_pipe_not_its_command_line()
{
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
Assert.DoesNotContain("$encodedEnvironment", package, StringComparison.Ordinal);
Assert.Contains("New-CurrentUserOneShotPipe 'Package'", package,
StringComparison.Ordinal);
Assert.Contains("Assert-PackagePermitClient", package, StringComparison.Ordinal);
Assert.Contains("[LegacyPgmPreparePackageIdentity]::Read($process.Handle)",
Runner,
StringComparison.Ordinal);
Assert.Contains("$payload = $utf8NoBom.GetBytes(($environment | ConvertTo-Json -Compress))",
package,
StringComparison.Ordinal);
Assert.Contains("$launchPipe.Stream.Write($payload,0,$payload.Length)", package,
StringComparison.Ordinal);
Assert.Contains("$client.ReadByte() -ne -1", package, StringComparison.Ordinal);
var wrapperStop = Slice(
Runner,
"function Stop-PackageWrapperFailStop(",
"function Start-Package([object] $Plan)");
Assert.Contains("$Process.Kill()", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(0)", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$Process.WaitForExit(5000)", wrapperStop, StringComparison.Ordinal);
Assert.Contains("was not proven stopped", wrapperStop, StringComparison.Ordinal);
Assert.DoesNotContain("catch { return }", wrapperStop, StringComparison.Ordinal);
Assert.Contains("$wrapper.WaitForExit(0)", package, StringComparison.Ordinal);
Assert.Contains("without proving the exact wrapper exited", package,
StringComparison.Ordinal);
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
Assert.DoesNotContain("$encodedEnvironment", child, StringComparison.Ordinal);
Assert.DoesNotContain("$script:gateCapability", child, StringComparison.Ordinal);
Assert.DoesNotContain("$capabilityName", child, StringComparison.Ordinal);
Assert.DoesNotContain("$environment", child, StringComparison.Ordinal);
var pipes = Slice(
Runner,
"function New-CurrentUserOneShotPipe([string] $Purpose)",
"function Close-OneShotPipe([object] $Pipe)");
Assert.Contains("[IO.Pipes.PipeDirection]::InOut", pipes, StringComparison.Ordinal);
Assert.Contains("[IO.Pipes.PipeSecurity]::new()", pipes, StringComparison.Ordinal);
Assert.Contains("$identity.User", pipes, StringComparison.Ordinal);
Assert.Contains("SetAccessRuleProtection($true, $false)", pipes,
StringComparison.Ordinal);
}
[Fact]
public void Managed_runtime_injection_environment_is_rejected_and_removed_from_children()
{
var preflight = Slice(
Runner,
"function Assert-NoUnsafeEnvironment",
"function Assert-Pgm([object] $Pgm)");
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
var node = Slice(
Runner,
"function Start-NodeHelper([object] $Plan)",
"function Write-Evidence([string] $Path)");
foreach (var prefix in new[] { "DOTNET_*", "CORECLR_*", "COR_*", "COMPlus_*" })
{
Assert.Contains(prefix, preflight, StringComparison.Ordinal);
Assert.True(Count(child, prefix) >= 2, $"{prefix} must be removed before and after app launch.");
Assert.Contains(prefix, node, StringComparison.Ordinal);
}
foreach (var scope in new[]
{
"[EnvironmentVariableTarget]::Process",
"[EnvironmentVariableTarget]::User",
"[EnvironmentVariableTarget]::Machine"
})
{
Assert.Contains(scope, preflight, StringComparison.Ordinal);
}
}
[Fact]
public void Database_source_is_pinned_leased_and_cannot_be_overridden_by_environment()
{
Assert.Contains("approvedDatabaseConfigPath", PlanBuilder, StringComparison.Ordinal);
Assert.Contains(
"database = New-FilePin $approvedDatabaseConfigPath 'database config'",
PlanBuilder,
StringComparison.Ordinal);
Assert.Contains(
"Assert-FilePin $script:plan.configuration.database 'database config'",
Runner,
StringComparison.Ordinal);
Assert.Contains(
"Add-ReadLease ([string]$script:plan.configuration.database.path) 'database config'",
Runner,
StringComparison.Ordinal);
Assert.Contains("filePinsValidated = 12", Runner, StringComparison.Ordinal);
Assert.Contains("const databasePin = plan?.configuration?.database;", NodeHelper,
StringComparison.Ordinal);
Assert.Contains("!isSha256(databasePin?.sha256)", NodeHelper,
StringComparison.Ordinal);
var preflight = Slice(
Runner,
"function Assert-NoUnsafeEnvironment",
"function Assert-Pgm([object] $Pgm)");
var package = Slice(
Runner,
"function Start-Package([object] $Plan)",
"function Quote-Argument([string] $Value)");
var child = Slice(package, " $child = @\"", "\"@\n $encodedChild");
foreach (var prefix in new[]
{
"MBN_STOCK_ORACLE_*", "MBN_STOCK_MARIADB_*", "MBN_STOCK_DB_*"
})
{
Assert.Contains(prefix, preflight, StringComparison.Ordinal);
Assert.True(Count(child, prefix) >= 2, $"{prefix} must be removed before and after app launch.");
}
}
[Fact]
public void Plan_contract_requires_helper_fail_stop_and_the_current_package_pin()
{
foreach (var source in new[] { Runner, PlanBuilder })
{
Assert.Contains(CurrentMsixSha256, source, StringComparison.Ordinal);
}
Assert.Contains("nodeHelperFailStopRequired = $true", PlanBuilder,
StringComparison.Ordinal);
Assert.Contains("packageWrapperFailStopRequired = $true", PlanBuilder,
StringComparison.Ordinal);
Assert.Contains("'nodeHelperFailStopRequired'", Runner, StringComparison.Ordinal);
Assert.Contains("'packageWrapperFailStopRequired'", Runner, StringComparison.Ordinal);
Assert.Contains("plan?.safety?.nodeHelperFailStopRequired !== true", NodeHelper,
StringComparison.Ordinal);
Assert.Contains("plan?.safety?.packageWrapperFailStopRequired !== true", NodeHelper,
StringComparison.Ordinal);
Assert.Contains(
"Assert-JsonInt $Plan.safety.nodeHelperInvocationMaximum 1",
Runner,
StringComparison.Ordinal);
}
private static string Slice(string source, string startMarker, string endMarker)
{
var start = RequiredIndex(source, startMarker);
var end = RequiredIndex(source, endMarker, start + startMarker.Length);
return source[start..end];
}
private static int RequiredIndex(string source, string marker, int startIndex = 0)
{
var index = source.IndexOf(marker, startIndex, StringComparison.Ordinal);
Assert.True(index >= 0, $"Required source marker was not found: {marker}");
return index;
}
private static int Count(string source, string marker)
{
var count = 0;
var index = 0;
while ((index = source.IndexOf(marker, index, StringComparison.Ordinal)) >= 0)
{
count++;
index += marker.Length;
}
return count;
}
private static string ReadScript(string fileName) =>
File.ReadAllText(Path.Combine(RepositoryRoot, "scripts", fileName));
private static string FindRepositoryRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
current = current.Parent;
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
}