1115 lines
46 KiB
C#
1115 lines
46 KiB
C#
using System.Xml.Linq;
|
|
|
|
namespace MBN_STOCK_WEBVIEW.LegacyWeb.Tests;
|
|
|
|
public sealed class LegacyRuntimeAssetDeploymentContractTests
|
|
{
|
|
private static readonly string RepositoryRoot = FindRepositoryRoot();
|
|
private static readonly string ProjectPath = Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp.csproj");
|
|
|
|
[Fact]
|
|
public void LegacyParityProjectUsesCutsInPlaceAndLinksOnlyAllowlistedRes()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
var contents = project.Descendants("Content").ToArray();
|
|
|
|
Assert.DoesNotContain(
|
|
contents,
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
"$(LegacyCutsSourceRoot)\\**\\*",
|
|
StringComparison.Ordinal));
|
|
AssertLinkedRuntimeTree(contents, "@(LegacyPackagedResAsset)", "Res\\");
|
|
var cutsScrub = Assert.Single(
|
|
project.Descendants("Target"),
|
|
target => string.Equals(
|
|
target.Attribute("Name")?.Value,
|
|
"ScrubCopiedCutsFromBuildOutput",
|
|
StringComparison.Ordinal));
|
|
Assert.Contains(
|
|
"$(TargetDir)Cuts",
|
|
Assert.Single(cutsScrub.Elements("RemoveDir"))
|
|
.Attribute("Directories")?.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var packagedResNames = project.Descendants("LegacyPackagedResAsset")
|
|
.Select(element => Path.GetFileName(element.Attribute("Include")?.Value))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
string[] requiredUiMenus =
|
|
[
|
|
"setup.ini",
|
|
"기타.ini",
|
|
"업종.ini",
|
|
"업종_코스닥.ini",
|
|
"업종_코스피.ini",
|
|
"종목.ini",
|
|
"종목_0111.ini",
|
|
"종목비교.ini",
|
|
"지수.ini",
|
|
"지수_0318.ini",
|
|
"지수_250401.ini",
|
|
"해외.ini",
|
|
"환율.ini"
|
|
];
|
|
|
|
Assert.Equal(34, packagedResNames.Count);
|
|
Assert.All(requiredUiMenus, name => Assert.Contains(name, packagedResNames));
|
|
Assert.DoesNotContain("MmoneyCoder.ini", packagedResNames);
|
|
Assert.DoesNotContain("MmoneyCoder - 복사본.ini", packagedResNames);
|
|
Assert.DoesNotContain("MmoneyCoder.ini.bak", packagedResNames);
|
|
Assert.DoesNotContain("MmoneyCoder.zip", packagedResNames);
|
|
Assert.DoesNotContain("afiedt.buf.txt", packagedResNames);
|
|
}
|
|
|
|
[Fact]
|
|
public void GraphEPreviewsExposeOnlyFourAllowlistedBitmapsThroughTheTrustedWebRoot()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
var previewNames = project.Descendants("LegacyFinancialPreviewAsset")
|
|
.Select(element => Path.GetFileName(element.Attribute("Include")?.Value)!)
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
|
|
Assert.Equal(
|
|
new HashSet<string>(
|
|
["pie.bmp", "Grow.bmp", "sell.bmp", "profit.bmp"],
|
|
StringComparer.OrdinalIgnoreCase),
|
|
previewNames);
|
|
|
|
var previewContent = Assert.Single(
|
|
project.Descendants("Content"),
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
"@(LegacyFinancialPreviewAsset)",
|
|
StringComparison.Ordinal));
|
|
Assert.Equal(
|
|
"Web\\Previews\\%(Filename)%(Extension)",
|
|
previewContent.Element("Link")?.Value);
|
|
Assert.Equal(
|
|
"Web\\Previews\\%(Filename)%(Extension)",
|
|
previewContent.Element("TargetPath")?.Value);
|
|
Assert.Equal(
|
|
"PreserveNewest",
|
|
previewContent.Element("CopyToOutputDirectory")?.Value);
|
|
Assert.Equal(
|
|
"PreserveNewest",
|
|
previewContent.Element("CopyToPublishDirectory")?.Value);
|
|
|
|
Assert.DoesNotContain(
|
|
project.Descendants("Content"),
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
"$(LegacyResSourceRoot)\\**\\*",
|
|
StringComparison.OrdinalIgnoreCase));
|
|
|
|
var window = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"MainWindow.xaml.cs"));
|
|
Assert.Equal(
|
|
1,
|
|
window.Split(
|
|
"SetVirtualHostNameToFolderMapping(",
|
|
StringSplitOptions.None).Length - 1);
|
|
Assert.Contains("LegacyBridgeProtocol.TrustedHost", window, StringComparison.Ordinal);
|
|
Assert.Contains("webRoot", window, StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"SetVirtualHostNameToFolderMapping(\"res",
|
|
window,
|
|
StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void CustomerWordmarkIsProjectedFromTheAllowlistedLegacyAssetIntoTheTrustedWebRoot()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
var brandAsset = Assert.Single(project.Descendants("LegacyBrandAsset"));
|
|
Assert.Equal(
|
|
"$(LegacyResSourceRoot)\\logo.png",
|
|
brandAsset.Attribute("Include")?.Value);
|
|
|
|
var brandContent = Assert.Single(
|
|
project.Descendants("Content"),
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
"@(LegacyBrandAsset)",
|
|
StringComparison.Ordinal));
|
|
Assert.Equal(
|
|
"Web\\Brand\\%(Filename)%(Extension)",
|
|
brandContent.Element("Link")?.Value);
|
|
Assert.Equal(
|
|
"Web\\Brand\\%(Filename)%(Extension)",
|
|
brandContent.Element("TargetPath")?.Value);
|
|
Assert.Equal(
|
|
"PreserveNewest",
|
|
brandContent.Element("CopyToOutputDirectory")?.Value);
|
|
Assert.Equal(
|
|
"PreserveNewest",
|
|
brandContent.Element("CopyToPublishDirectory")?.Value);
|
|
|
|
var packagedResNames = project.Descendants("LegacyPackagedResAsset")
|
|
.Select(element => Path.GetFileName(element.Attribute("Include")?.Value))
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
Assert.Contains("logo.png", packagedResNames);
|
|
}
|
|
|
|
[Fact]
|
|
public void WebViewIgnoresItsPersistentCacheBeforeExactTrustedNavigation()
|
|
{
|
|
var window = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"MainWindow.xaml.cs"));
|
|
|
|
var mapping = window.IndexOf(
|
|
"SetVirtualHostNameToFolderMapping(",
|
|
StringComparison.Ordinal);
|
|
var disableCache = window.IndexOf(
|
|
"\"Network.setCacheDisabled\"",
|
|
StringComparison.Ordinal);
|
|
var navigation = window.IndexOf(
|
|
"Browser.Source = new Uri(",
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.True(mapping >= 0);
|
|
Assert.True(disableCache > mapping);
|
|
Assert.True(navigation > disableCache);
|
|
Assert.Contains(
|
|
"CoreWebView2HostResourceAccessKind.DenyCors",
|
|
window,
|
|
StringComparison.Ordinal);
|
|
Assert.Equal(
|
|
1,
|
|
window.Split(
|
|
"\"Network.setCacheDisabled\"",
|
|
StringSplitOptions.None).Length - 1);
|
|
Assert.Contains(
|
|
"\"{\\\"cacheDisabled\\\":true}\"",
|
|
window,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$\"https://{LegacyBridgeProtocol.TrustedHost}/index.html\"",
|
|
window,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"$\"https://{LegacyBridgeProtocol.TrustedHost}/index.html?",
|
|
window,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void DatabaseCredentialsStayInUserLocalConfigurationAndNeverEnterBuildOutputs()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
Assert.DoesNotContain(
|
|
project.Descendants().Where(element =>
|
|
element.Name.LocalName is "Content" or "None"),
|
|
element => (element.Attribute("Include")?.Value ?? string.Empty)
|
|
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
|
|
|
Assert.DoesNotContain(
|
|
project.Descendants("UpToDateCheckInput"),
|
|
element => (element.Attribute("Include")?.Value ?? string.Empty)
|
|
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(
|
|
project.Descendants("UpToDateCheckBuilt"),
|
|
element => (element.Attribute("Include")?.Value ?? string.Empty)
|
|
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
|
Assert.DoesNotContain(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"CopyLegacyDatabaseIniToLocalOutput",
|
|
StringComparison.Ordinal));
|
|
Assert.DoesNotContain(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"RemoveLegacyDatabaseIniFromLocalOutput",
|
|
StringComparison.Ordinal));
|
|
|
|
var scrubTarget = Assert.Single(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"ScrubLegacyDatabaseIniFromBuildOutput",
|
|
StringComparison.Ordinal));
|
|
Assert.Equal("PrepareForBuild", scrubTarget.Attribute("BeforeTargets")?.Value);
|
|
var scrubDelete = Assert.Single(scrubTarget.Elements("Delete"));
|
|
Assert.Equal(
|
|
"$(TargetDir)Res\\MmoneyCoder.ini",
|
|
scrubDelete.Attribute("Files")?.Value);
|
|
|
|
var validationTarget = Assert.Single(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"ValidateLegacyRuntimeAssets",
|
|
StringComparison.Ordinal));
|
|
Assert.DoesNotContain(
|
|
validationTarget.Elements("Error"),
|
|
element => (element.Attribute("Condition")?.Value ?? string.Empty)
|
|
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
|
|
|
var rejectionTarget = Assert.Single(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"RejectLegacyDatabaseIniFromPackagePayload",
|
|
StringComparison.Ordinal));
|
|
Assert.Equal(
|
|
"_ComputeAppxPackagePayload",
|
|
rejectionTarget.Attribute("BeforeTargets")?.Value);
|
|
var forbiddenPayload = Assert.Single(
|
|
rejectionTarget.Descendants("_ForbiddenLegacyDatabasePackagePayload"));
|
|
Assert.Contains(
|
|
"%(PackagingOutputs.Filename)%(PackagingOutputs.Extension)",
|
|
forbiddenPayload.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
Assert.Single(rejectionTarget.Elements("Error"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FreshCloneUsesIsolatedSourceOnlyOutputWithoutWeakeningFullBuilds()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
|
|
var sourceRootWasSpecified = Assert.Single(
|
|
project.Descendants("LegacyRuntimeSourceRootWasSpecified"));
|
|
Assert.Equal(
|
|
"'$(LegacyRuntimeSourceRoot)' != ''",
|
|
sourceRootWasSpecified.Attribute("Condition")?.Value);
|
|
|
|
var modes = project.Descendants("LegacyRuntimeAssetsMode").ToArray();
|
|
Assert.Equal(2, modes.Length);
|
|
var requiredMode = Assert.Single(
|
|
modes,
|
|
mode => string.Equals(mode.Value, "Required", StringComparison.Ordinal));
|
|
var requiredCondition = requiredMode.Attribute("Condition")?.Value;
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeSourceRootWasSpecified)",
|
|
requiredCondition,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"'$(Configuration)' == 'Release'",
|
|
requiredCondition,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$(GenerateAppxPackageOnBuild)",
|
|
requiredCondition,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("$(PublishAppxPackage)", requiredCondition, StringComparison.Ordinal);
|
|
|
|
var autoMode = Assert.Single(
|
|
modes,
|
|
mode => string.Equals(mode.Value, "Auto", StringComparison.Ordinal));
|
|
Assert.Equal(
|
|
"'$(LegacyRuntimeAssetsMode)' == ''",
|
|
autoMode.Attribute("Condition")?.Value);
|
|
|
|
var assetsEnabled = Assert.Single(
|
|
project.Descendants("LegacyRuntimeAssetsEnabled"),
|
|
element => (element.Attribute("Condition")?.Value ?? string.Empty)
|
|
.Contains("Exists('$(LegacyRuntimeSourceRoot)')", StringComparison.Ordinal));
|
|
Assert.Equal("true", assetsEnabled.Value);
|
|
|
|
var sourceOnlyOutput = Assert.Single(project.Descendants("BaseOutputPath"));
|
|
Assert.Equal(@"bin\SourceOnly\", sourceOnlyOutput.Value);
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsEnabled)",
|
|
sourceOnlyOutput.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
var sourceOnlyIntermediate = Assert.Single(
|
|
project.Descendants("IntermediateOutputPath"));
|
|
Assert.Equal(
|
|
@"obj\SourceOnly\$(Platform)\$(Configuration)\",
|
|
sourceOnlyIntermediate.Value);
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsEnabled)",
|
|
sourceOnlyIntermediate.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
var sourceOnlyConstants = Assert.Single(project.Descendants("DefineConstants"));
|
|
Assert.Contains(
|
|
"SOURCE_ONLY_RUNTIME",
|
|
sourceOnlyConstants.Value,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsEnabled)",
|
|
sourceOnlyConstants.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var validationTarget = Assert.Single(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"ValidateLegacyRuntimeAssets",
|
|
StringComparison.Ordinal));
|
|
var sourceOnlyMessage = Assert.Single(
|
|
validationTarget.Elements("Message"),
|
|
message => (message.Attribute("Condition")?.Value ?? string.Empty)
|
|
.Contains("$(LegacyRuntimeAssetsMode)", StringComparison.Ordinal));
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsMode)",
|
|
sourceOnlyMessage.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"safe first-run setup app",
|
|
sourceOnlyMessage.Attribute("Text")?.Value,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"select the existing Cuts and Res folders",
|
|
sourceOnlyMessage.Attribute("Text")?.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var fullRuntimeMessage = Assert.Single(
|
|
validationTarget.Elements("Message"),
|
|
message => (message.Attribute("Text")?.Value ?? string.Empty)
|
|
.Contains(
|
|
"External legacy runtime paths are enabled",
|
|
StringComparison.Ordinal));
|
|
Assert.Contains(
|
|
"Cuts files remain in the selected folder",
|
|
fullRuntimeMessage.Attribute("Text")?.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var runtimeValidationErrors = validationTarget.Elements("Error")
|
|
.Where(error => (error.Attribute("Condition")?.Value ?? string.Empty)
|
|
.Contains("Exists(", StringComparison.Ordinal))
|
|
.ToArray();
|
|
Assert.NotEmpty(runtimeValidationErrors);
|
|
Assert.All(runtimeValidationErrors, error =>
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsValidationEnabled)",
|
|
error.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal));
|
|
Assert.Contains(
|
|
runtimeValidationErrors,
|
|
error => (error.Attribute("Text")?.Value ?? string.Empty).Contains(
|
|
"Initialize-ExistingDevelopmentPc.ps1",
|
|
StringComparison.Ordinal));
|
|
|
|
string[] externalContentIncludes =
|
|
[
|
|
"@(LegacyPackagedResAsset)",
|
|
"@(LegacyFinancialPreviewAsset)",
|
|
"@(LegacyBrandAsset)"
|
|
];
|
|
Assert.All(externalContentIncludes, include =>
|
|
{
|
|
var content = Assert.Single(
|
|
project.Descendants("Content"),
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
include,
|
|
StringComparison.Ordinal));
|
|
Assert.Contains(
|
|
"$(LegacyRuntimeAssetsEnabled)",
|
|
content.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
});
|
|
Assert.DoesNotContain(
|
|
project.Descendants("Content"),
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
@"$(LegacyCutsSourceRoot)\**\*",
|
|
StringComparison.Ordinal));
|
|
|
|
Assert.DoesNotContain(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"CopyLegacyDatabaseIniToLocalOutput",
|
|
StringComparison.Ordinal));
|
|
|
|
var mainWindow = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"MainWindow.xaml.cs"));
|
|
Assert.Contains("#if SOURCE_ONLY_RUNTIME", mainWindow, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Source-only builds do not initialize a database runtime.",
|
|
mainWindow,
|
|
StringComparison.Ordinal);
|
|
|
|
var playout = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"MainWindow.Playout.cs"));
|
|
Assert.Contains("forceSafeDryRun: IsSourceOnlyBuild", playout,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void LegacyRuntimeSourceRootIsOverrideableAndValidatedBeforeBuild()
|
|
{
|
|
var project = XDocument.Load(ProjectPath);
|
|
var sourceRoot = Assert.Single(
|
|
project.Descendants("LegacyRuntimeSourceRoot"),
|
|
element => element.Attribute("Condition") is not null);
|
|
|
|
Assert.Equal("'$(LegacyRuntimeSourceRoot)' == ''", sourceRoot.Attribute("Condition")!.Value);
|
|
Assert.Contains(".first-run-runtime-not-configured-0F72B3C8", sourceRoot.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var target = Assert.Single(
|
|
project.Descendants("Target"),
|
|
element => string.Equals(
|
|
element.Attribute("Name")?.Value,
|
|
"ValidateLegacyRuntimeAssets",
|
|
StringComparison.Ordinal));
|
|
Assert.Equal("PrepareForBuild", target.Attribute("BeforeTargets")?.Value);
|
|
|
|
var conditions = target.Elements("Error")
|
|
.Select(error => error.Attribute("Condition")?.Value)
|
|
.Where(condition => condition is not null)
|
|
.ToArray();
|
|
string[] requiredSentinels =
|
|
[
|
|
"$(LegacyRuntimeSourceRoot)",
|
|
"$(LegacyCutsSourceRoot)",
|
|
"$(LegacyResSourceRoot)",
|
|
"$(LegacyResSourceRoot)\\logo.png",
|
|
"$(LegacyResSourceRoot)\\종목.ini",
|
|
"$(LegacyResSourceRoot)\\업종_코스피.ini",
|
|
"$(LegacyResSourceRoot)\\업종_코스닥.ini",
|
|
"$(LegacyResSourceRoot)\\해외.ini",
|
|
"$(LegacyResSourceRoot)\\환율.ini",
|
|
"$(LegacyResSourceRoot)\\지수.ini"
|
|
];
|
|
|
|
Assert.All(requiredSentinels, sentinel =>
|
|
Assert.Contains(conditions, condition =>
|
|
condition!.Contains(sentinel, StringComparison.Ordinal)));
|
|
|
|
var requiredScenes = Assert.Single(project.Descendants("LegacyRequiredScene"))
|
|
.Attribute("Include")!.Value
|
|
.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
|
Assert.Equal(45, requiredScenes.Length);
|
|
Assert.Equal(requiredScenes.Length,
|
|
requiredScenes.Distinct(StringComparer.Ordinal).Count());
|
|
Assert.Contains("5001", requiredScenes);
|
|
Assert.Contains("5076", requiredScenes);
|
|
Assert.Contains("5079", requiredScenes);
|
|
Assert.Contains("6001", requiredScenes);
|
|
|
|
var requiredAssets = Assert.Single(
|
|
project.Descendants("LegacyRequiredBuiltInAsset"))
|
|
.Attribute("Include")!.Value
|
|
.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
|
Assert.Equal(9, requiredAssets.Length);
|
|
Assert.Contains(@"images\주유기merge.png", requiredAssets);
|
|
Assert.Contains(@"images\35752913_l.jpg", requiredAssets);
|
|
Assert.Contains(@"Images\KRX.png", requiredAssets);
|
|
Assert.Contains(@"Images\NXT.png", requiredAssets);
|
|
|
|
Assert.DoesNotContain(
|
|
target.Elements("Error"),
|
|
error => (error.Attribute("Condition")?.Value ?? string.Empty)
|
|
.Contains("MmoneyCoder.ini", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void DevelopmentLiveHandoffUsesAnIgnoredVerifiedRuntimeRootAndSafeLocalFiles()
|
|
{
|
|
var directoryPropsPath = Path.Combine(RepositoryRoot, "Directory.Build.props");
|
|
var directoryProps = XDocument.Load(directoryPropsPath);
|
|
var localImport = Assert.Single(directoryProps.Descendants("Import"));
|
|
Assert.Equal(
|
|
"$(MSBuildThisFileDirectory)Directory.Build.local.props",
|
|
localImport.Attribute("Project")?.Value);
|
|
Assert.Contains(
|
|
"Exists(",
|
|
localImport.Attribute("Condition")?.Value,
|
|
StringComparison.Ordinal);
|
|
|
|
var gitIgnore = File.ReadAllLines(Path.Combine(RepositoryRoot, ".gitignore"));
|
|
Assert.Contains("Directory.Build.local.props", gitIgnore);
|
|
|
|
var initializer = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"scripts",
|
|
"Initialize-DevelopmentLiveConfig.ps1"));
|
|
Assert.Contains(
|
|
"[ValidateSet('127.0.0.1', '::1')]",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("mode = 'DryRun'", initializer, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"trustedLiveOutputEnabled = $true",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"maximumReconnectAttempts = 0",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("reconnectEnabled = $false", initializer, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"maximumAutomaticRefreshesPerTakeIn = 0",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"SetAccessRuleProtection($true, $false)",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
var authorizationInvalidation = initializer.IndexOf(
|
|
"Invalidate-ExistingAuthorization -Path $authorizationPath",
|
|
StringComparison.Ordinal);
|
|
var baseConfigurationWrite = initializer.IndexOf(
|
|
"Write-ProtectedJson $playoutPath",
|
|
StringComparison.Ordinal);
|
|
Assert.True(authorizationInvalidation >= 0);
|
|
Assert.True(baseConfigurationWrite > authorizationInvalidation);
|
|
Assert.Contains(
|
|
"Write-ProtectedJson $authorizationPath",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void DevelopmentRuntimeBundleRequiresIndependentHashAndClosedPayloadValidation()
|
|
{
|
|
var bundleCreator = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"scripts",
|
|
"New-LegacyRuntimeBundle.ps1"));
|
|
var bundleInitializer = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"scripts",
|
|
"Initialize-LegacyRuntimeBundle.ps1"));
|
|
|
|
Assert.Contains("$expectedResAssetCount = 34", bundleCreator, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Get-GitRepositoryAncestor -Path $sourceRoot",
|
|
bundleCreator,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"MmoneyCoder material is forbidden",
|
|
bundleCreator,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"LegacyRuntimeBundle.zip.sha256",
|
|
bundleCreator,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains(
|
|
"[string] $ExpectedSha256",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"ExpectedSha256 must be a separately delivered",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
var archiveHashValidation = bundleInitializer.IndexOf(
|
|
"$actualArchiveHash.Equals(",
|
|
StringComparison.Ordinal);
|
|
var archiveOpen = bundleInitializer.IndexOf(
|
|
"New-Object IO.Compression.ZipArchive(",
|
|
StringComparison.Ordinal);
|
|
Assert.True(archiveHashValidation >= 0);
|
|
Assert.True(archiveOpen > archiveHashValidation);
|
|
Assert.Contains(
|
|
"Archive entry expanded beyond its declared length",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$actualCutsFileCount -le 0",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[Environment+SpecialFolder]::LocalApplicationData",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain(
|
|
"Get-NormalizedFullPath -Path $env:LOCALAPPDATA",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Existing Directory.Build.local.props is conditional, duplicated",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Assert-NoReparsePointInTree",
|
|
bundleInitializer,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExistingDevelopmentPcInitializerUsesLocalAssetsWithoutPerformingLiveActions()
|
|
{
|
|
var initializer = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"scripts",
|
|
"Initialize-ExistingDevelopmentPc.ps1"));
|
|
|
|
Assert.Contains("[string] $LegacyRuntimeSourceRoot", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[switch] $NoFolderPicker", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
@"..\MBN_STOCK_N\MBN_STOCK_N\bin\Debug",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Test-DirectCutsAndResRootCandidate",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"-Path $siblingCandidate",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"System.Windows.Forms.FolderBrowserDialog",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$selectedLeaf.Equals('Cuts'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$selectedLeaf.Equals('Res'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"single common parent of Cuts and Res",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"must be on a ready local fixed drive",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"must be outside and must not overlap this repository",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Assert-NoReparsePointInExistingAncestry", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Legacy runtime source root contains characters reserved by MSBuild.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("$bundleCreatorPath =", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("$bundleInitializerPath =", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("& $bundleCreatorPath", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("& $bundleInitializerPath", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"function New-DirectRuntimeBindingFile",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"function Get-RuntimeFoldersPreflight",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"runtime-folders.local.json",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[switch] $ReplaceRuntimeBinding", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"The selected Cuts/Res path differs from the existing runtime binding.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Set-RuntimeBindingAtomically", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[IO.File]::Replace($temporary, $destination, $backup)",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
var directBinding = initializer.LastIndexOf(
|
|
"New-DirectRuntimeBindingFile",
|
|
StringComparison.Ordinal);
|
|
var bindingReplace = initializer.LastIndexOf(
|
|
"Set-RuntimeBindingAtomically",
|
|
StringComparison.Ordinal);
|
|
Assert.True(directBinding >= 0);
|
|
Assert.True(bindingReplace > directBinding);
|
|
|
|
Assert.Contains("[string] $DatabaseIniPath", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[IO.Path]::IsPathRooted($DatabaseIniPath)", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[switch] $SkipDatabaseProfile", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[switch] $ReplaceDatabaseProfile", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(@"Res\MmoneyCoder.ini", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("$maximumDatabaseIniLength = 1MB", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Copy-DatabaseProfileAtomically", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[IO.File]::Replace($temporaryPath, $Destination, $backupPath)",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("SetAccessRuleProtection($true, $false)", initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("'Inspect-K3DRegistration.ps1'", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("Get-VerifiedK3DRegistration -Reports $registrationReports",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[string] $registration.RegistryHive -cne 'HKLM'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[string] $registration.RegistryView -cne 'Registry64'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[string] $registration.CurrentUserOverrides -cne 'None'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"[string] $classReport.ThreadingModel -cne 'Apartment'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("Activator.CreateInstance", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("Connect(", initializer, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("TAKE IN", initializer, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("TAKE OUT", initializer, StringComparison.Ordinal);
|
|
|
|
Assert.Contains("[switch] $ConfigureDevelopmentLive", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("[switch] $ReplaceLiveConfig", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"-ConfigureDevelopmentLive requires the default Debug x64 build verification.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Independent native and interop SHA-256 approvals are both required.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Get-FileHash -LiteralPath $nativePath -Algorithm SHA256",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Get-FileHash -LiteralPath $interopPath -Algorithm SHA256",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$PlayoutHost -cne '127.0.0.1'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Existing Development Live files differ from the exact requested",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("& $DotNetPath build $ProjectPath",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("'--no-restore'", initializer, StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"'-p:LegacyRuntimeAssetsMode=Required'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$runtimeRootProperty = \"-p:LegacyRuntimeSourceRoot=$ExpectedRuntimeRoot\"",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Get-Command -Name 'dotnet.exe' -CommandType Application",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"'-getProperty:LegacyRuntimeAssetsEnabled'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"unexpectedly resolved as SourceOnly",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Debug x64 copied Cuts instead of using the selected external path.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Credential-bearing MmoneyCoder.ini entered the Debug build output.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
var runtimeWrite = initializer.LastIndexOf(
|
|
"Set-RuntimeBindingAtomically",
|
|
StringComparison.Ordinal);
|
|
var pinWrite = initializer.IndexOf(
|
|
"-Description 'K3D persistent pin'",
|
|
StringComparison.Ordinal);
|
|
var databaseWrite = initializer.LastIndexOf(
|
|
"Copy-DatabaseProfileAtomically",
|
|
StringComparison.Ordinal);
|
|
var runtimeFoldersWrite = initializer.LastIndexOf(
|
|
"-Description 'runtime folder configuration'",
|
|
StringComparison.Ordinal);
|
|
var build = initializer.LastIndexOf(
|
|
"Invoke-DebugBuildAndVerify",
|
|
StringComparison.Ordinal);
|
|
var finalK3dVerification = initializer.LastIndexOf(
|
|
"$finalNativeHash = (",
|
|
StringComparison.Ordinal);
|
|
var liveWrite = initializer.LastIndexOf(
|
|
"& $liveInitializerPath @liveArguments",
|
|
StringComparison.Ordinal);
|
|
var cleanup = initializer.LastIndexOf(
|
|
"-Path $rollbackRoot",
|
|
StringComparison.Ordinal);
|
|
Assert.True(pinWrite >= 0);
|
|
Assert.True(build > pinWrite);
|
|
Assert.True(databaseWrite > build);
|
|
Assert.True(runtimeFoldersWrite > databaseWrite);
|
|
Assert.True(finalK3dVerification > runtimeFoldersWrite);
|
|
Assert.True(liveWrite > finalK3dVerification);
|
|
Assert.True(runtimeWrite > liveWrite);
|
|
Assert.True(cleanup > runtimeWrite);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExistingDevelopmentPcInitializerRollsBackAndReissuesLiveFailClosed()
|
|
{
|
|
var initializer = File.ReadAllText(Path.Combine(
|
|
RepositoryRoot,
|
|
"scripts",
|
|
"Initialize-ExistingDevelopmentPc.ps1"));
|
|
|
|
Assert.Contains("function New-FileRollbackSnapshot", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("function Restore-FileRollbackSnapshot", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("OriginalBytes = [IO.File]::ReadAllBytes($backupPath)",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("AccessSddl = $accessSddl", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$Description rollback backup failed its immediate ACL/hash verification.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Previous database profile backup failed immediate ACL/hash verification.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$Description rollback did not restore the exact bytes and ACL.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"'Local\\MBN_STOCK_WEBVIEW.ExistingDevelopmentPcInitialization.v1'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Another existing-development-PC initialization is already running.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Development Live base configuration rollback failed:",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"The durable pin remains at",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"anchor could not be restored:",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Set-UserOnlyDirectoryAcl -Path $rollbackRoot",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
var databaseMutation = initializer.LastIndexOf(
|
|
"$databaseMutationStarted = $true",
|
|
StringComparison.Ordinal);
|
|
var databaseWrite = initializer.LastIndexOf(
|
|
"Copy-DatabaseProfileAtomically",
|
|
StringComparison.Ordinal);
|
|
var databaseRollback = initializer.LastIndexOf(
|
|
"-Snapshot $databaseSnapshot",
|
|
StringComparison.Ordinal);
|
|
var runtimeFoldersMutation = initializer.LastIndexOf(
|
|
"$runtimeFoldersMutationStarted = $true",
|
|
StringComparison.Ordinal);
|
|
var runtimeFoldersRollback = initializer.LastIndexOf(
|
|
"-Snapshot $runtimeFoldersSnapshot",
|
|
StringComparison.Ordinal);
|
|
var runtimeMutation = initializer.LastIndexOf(
|
|
"$runtimeBindingMutationStarted = $true",
|
|
StringComparison.Ordinal);
|
|
var runtimeWrite = initializer.LastIndexOf(
|
|
"Set-RuntimeBindingAtomically",
|
|
StringComparison.Ordinal);
|
|
var runtimeRollback = initializer.LastIndexOf(
|
|
"-Snapshot $propsSnapshot",
|
|
StringComparison.Ordinal);
|
|
Assert.True(databaseMutation >= 0 && databaseMutation < databaseWrite);
|
|
Assert.True(runtimeFoldersMutation >= 0);
|
|
Assert.True(runtimeMutation >= 0 && runtimeMutation < runtimeWrite);
|
|
Assert.True(databaseRollback > databaseWrite);
|
|
Assert.True(runtimeFoldersRollback > runtimeFoldersMutation);
|
|
Assert.True(runtimeRollback > runtimeWrite);
|
|
Assert.Contains("Original failure:", initializer, StringComparison.Ordinal);
|
|
Assert.Contains("Secondary recovery failure(s):", initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("function Assert-ExactObjectProperties", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"-Description 'K3D registration inspection result'",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"function New-DirectRuntimeBindingFile",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"Runtime folder configuration rollback failed:",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
Assert.Contains("function Test-ExactUtf8Text", initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"case changes, type coercion, reordered properties",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.DoesNotContain("Get-JsonContractText", initializer,
|
|
StringComparison.Ordinal);
|
|
var livePreflightStart = initializer.IndexOf(
|
|
"function Get-DevelopmentLivePreflight",
|
|
StringComparison.Ordinal);
|
|
var livePreflightEnd = initializer.IndexOf(
|
|
"function Copy-DatabaseProfileAtomically",
|
|
livePreflightStart,
|
|
StringComparison.Ordinal);
|
|
Assert.True(livePreflightStart >= 0 && livePreflightEnd > livePreflightStart);
|
|
var livePreflightFunction = initializer[livePreflightStart..livePreflightEnd];
|
|
Assert.DoesNotContain("NeedsWrite = $false", livePreflightFunction,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains("DevelopmentLiveReused = $false", initializer,
|
|
StringComparison.Ordinal);
|
|
|
|
var firstPersistentLiveInvalidation = initializer.IndexOf(
|
|
"# Invalidate the previous one-launch authorization before database/runtime",
|
|
StringComparison.Ordinal);
|
|
var setupDirectoryCreation = initializer.IndexOf(
|
|
"[IO.Directory]::CreateDirectory($setupParent)",
|
|
StringComparison.Ordinal);
|
|
var buildVerification = initializer.LastIndexOf(
|
|
"Invoke-DebugBuildAndVerify",
|
|
StringComparison.Ordinal);
|
|
var pinIssue = initializer.IndexOf(
|
|
"-Description 'K3D persistent pin'",
|
|
StringComparison.Ordinal);
|
|
var finalK3dVerification = initializer.LastIndexOf(
|
|
"$finalNativeHash = (",
|
|
StringComparison.Ordinal);
|
|
var liveIssue = initializer.LastIndexOf(
|
|
"$liveResults = @(& $liveInitializerPath @liveArguments)",
|
|
StringComparison.Ordinal);
|
|
var liveJsonVerification = initializer.LastIndexOf(
|
|
"-ExpectedJson ([string] $livePreflight.AuthorizationJson)",
|
|
StringComparison.Ordinal);
|
|
var bindingCommit = initializer.LastIndexOf(
|
|
"Set-RuntimeBindingAtomically",
|
|
StringComparison.Ordinal);
|
|
var setupCleanup = initializer.LastIndexOf(
|
|
"-Path $rollbackRoot",
|
|
StringComparison.Ordinal);
|
|
var failClosedLiveRemoval = initializer.LastIndexOf(
|
|
"Remove-DevelopmentLiveAuthorizationFailClosed",
|
|
StringComparison.Ordinal);
|
|
Assert.True(pinIssue >= 0);
|
|
Assert.True(pinIssue < firstPersistentLiveInvalidation);
|
|
Assert.True(firstPersistentLiveInvalidation < setupDirectoryCreation);
|
|
Assert.True(setupDirectoryCreation < buildVerification);
|
|
Assert.True(buildVerification < finalK3dVerification);
|
|
Assert.True(finalK3dVerification < liveIssue);
|
|
Assert.True(liveIssue < liveJsonVerification);
|
|
Assert.True(liveJsonVerification < bindingCommit);
|
|
Assert.True(bindingCommit < setupCleanup);
|
|
Assert.True(liveJsonVerification < failClosedLiveRemoval);
|
|
Assert.Contains(
|
|
"Development Live initializer returned unexpected success-stream output.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
Assert.Contains(
|
|
"$Description failed its exact JSON/ACL verification.",
|
|
initializer,
|
|
StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void RepositoryDoesNotContainCopiedLegacyRuntimeTrees()
|
|
{
|
|
Assert.False(Directory.Exists(Path.Combine(RepositoryRoot, "Cuts")));
|
|
Assert.False(Directory.Exists(Path.Combine(RepositoryRoot, "Res")));
|
|
Assert.False(Directory.Exists(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"Cuts")));
|
|
Assert.False(Directory.Exists(Path.Combine(
|
|
RepositoryRoot,
|
|
"src",
|
|
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
|
"Res")));
|
|
}
|
|
|
|
private static void AssertLinkedRuntimeTree(
|
|
IReadOnlyCollection<XElement> contents,
|
|
string include,
|
|
string targetPrefix)
|
|
{
|
|
var content = Assert.Single(
|
|
contents,
|
|
element => string.Equals(
|
|
element.Attribute("Include")?.Value,
|
|
include,
|
|
StringComparison.Ordinal));
|
|
|
|
Assert.StartsWith(targetPrefix, content.Element("Link")?.Value, StringComparison.Ordinal);
|
|
Assert.StartsWith(
|
|
targetPrefix,
|
|
content.Element("TargetPath")?.Value,
|
|
StringComparison.Ordinal);
|
|
Assert.Equal("PreserveNewest", content.Element("CopyToOutputDirectory")?.Value);
|
|
Assert.Equal("PreserveNewest", content.Element("CopyToPublishDirectory")?.Value);
|
|
}
|
|
|
|
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("Could not locate the repository root.");
|
|
}
|
|
}
|