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 LegacyParityProjectLinksCutsAndResIntoOutputPublishAndMsix() { var project = XDocument.Load(ProjectPath); var contents = project.Descendants("Content").ToArray(); AssertLinkedRuntimeTree(contents, "$(LegacyCutsSourceRoot)\\**\\*", "Cuts\\"); AssertLinkedRuntimeTree(contents, "@(LegacyPackagedResAsset)", "Res\\"); 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( ["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 ActiveDatabaseIniIsPostBuildOutputOnlyAndNeverAPackagingItem() { 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)); var upToDateInput = Assert.Single(project.Descendants("UpToDateCheckInput")); Assert.Equal( "$(LegacyResSourceRoot)\\MmoneyCoder.ini", upToDateInput.Attribute("Include")?.Value); var upToDateBuilt = Assert.Single(project.Descendants("UpToDateCheckBuilt")); Assert.Equal( "$(TargetDir)Res\\MmoneyCoder.ini", upToDateBuilt.Attribute("Include")?.Value); var copyTarget = Assert.Single( project.Descendants("Target"), element => string.Equals( element.Attribute("Name")?.Value, "CopyLegacyDatabaseIniToLocalOutput", StringComparison.Ordinal)); Assert.Equal("Build", copyTarget.Attribute("AfterTargets")?.Value); Assert.Contains( "GenerateAppxPackageOnBuild", copyTarget.Attribute("Condition")?.Value, StringComparison.Ordinal); Assert.Contains( "PublishAppxPackage", copyTarget.Attribute("Condition")?.Value, StringComparison.Ordinal); Assert.Equal( "$(LegacyResSourceRoot)\\MmoneyCoder.ini", copyTarget.Attribute("Inputs")?.Value); Assert.Equal( "$(TargetDir)Res\\MmoneyCoder.ini", copyTarget.Attribute("Outputs")?.Value); var copy = Assert.Single(copyTarget.Elements("Copy")); Assert.Equal( "$(LegacyResSourceRoot)\\MmoneyCoder.ini", copy.Attribute("SourceFiles")?.Value); Assert.Equal( "$(TargetDir)Res\\MmoneyCoder.ini", copy.Attribute("DestinationFiles")?.Value); var fileWrite = Assert.Single(copyTarget.Descendants("FileWrites")); Assert.Equal( "$(TargetDir)Res\\MmoneyCoder.ini", fileWrite.Attribute("Include")?.Value); var cleanTarget = Assert.Single( project.Descendants("Target"), element => string.Equals( element.Attribute("Name")?.Value, "RemoveLegacyDatabaseIniFromLocalOutput", StringComparison.Ordinal)); Assert.Equal("Clean", cleanTarget.Attribute("BeforeTargets")?.Value); var delete = Assert.Single(cleanTarget.Elements("Delete")); Assert.Equal( "$(TargetDir)Res\\MmoneyCoder.ini", delete.Attribute("Files")?.Value); 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 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("MBN_STOCK_N\\MBN_STOCK_N\\bin\\Debug", 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)", "$(LegacyCutsSourceRoot)\\%(LegacyRequiredScene.Identity).t2s", "$(LegacyCutsSourceRoot)\\%(LegacyRequiredBuiltInAsset.Identity)", "$(LegacyResSourceRoot)\\MmoneyCoder.ini", "$(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); var databaseIniError = Assert.Single( target.Elements("Error"), error => (error.Attribute("Condition")?.Value ?? string.Empty) .Contains("$(LegacyResSourceRoot)\\MmoneyCoder.ini", StringComparison.Ordinal)); Assert.Contains( "GenerateAppxPackageOnBuild", databaseIniError.Attribute("Condition")?.Value, StringComparison.Ordinal); Assert.Contains( "PublishAppxPackage", databaseIniError.Attribute("Condition")?.Value, 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 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."); } }