Files

710 lines
29 KiB
C#

using System.Text;
using System.Text.Json;
using MMoneyCoderSharp.Data;
namespace MBN_STOCK_WEBVIEW.LegacyApplication.Tests;
public sealed class LegacyOperatorSettingsTests
{
private static readonly Encoding Cp949 = CreateCp949Encoding();
[Fact]
public void DefaultPath_IsTheBoundedLocalApplicationConfigurationPath()
{
var expected = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MBN_STOCK_WEBVIEW",
"Config",
"runtime-folders.local.json");
Assert.Equal(expected, LegacyOperatorSettingsStore.DefaultPath);
Assert.True(Path.IsPathFullyQualified(LegacyOperatorSettingsStore.DefaultPath));
}
[Fact]
public void SceneValidation_RequiresEveryActiveSceneAndBuiltInAssetAndAllowsMissingExternalVideo()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("Cuts");
WriteValidSceneFiles(sceneDirectory);
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
sceneDirectory + Path.DirectorySeparatorChar);
Assert.True(result.IsValid);
Assert.Equal(Path.GetFullPath(sceneDirectory), result.Folder!.CanonicalPath);
Assert.Equal("fixture-scene", File.ReadAllText(
Path.Combine(result.Folder.CanonicalPath, "5001.t2s")));
Assert.Contains("Cuts", result.Folder.AbbreviatedPath);
Assert.InRange(
result.Folder.AbbreviatedPath.Length,
1,
LegacyOperatorFolderValidator.DefaultDisplayCharacters);
Assert.False(result.Folder.DatabaseIniDetected);
Assert.Equal(
LegacyOperatorFolderValidator.OptionalExternalAssetRelativePaths.Count,
result.Folder.MissingOptionalExternalAssetCount);
Assert.Null(result.WarningMessage);
}
[Fact]
public void SceneValidation_BaselineOnlyIsRejectedInsteadOfBreakingOtherScenesAfterRestart()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("partial-Cuts");
File.WriteAllText(Path.Combine(sceneDirectory, "5001.t2s"), "baseline");
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
sceneDirectory);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.RequiredFileMissing,
result.Failure);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, sceneDirectory);
}
[Fact]
public void SceneValidation_MissingOneActiveSceneOrBuiltInAssetIsRejected()
{
using var directory = new TemporaryDirectory();
var missingSceneDirectory = directory.CreateDirectory("missing-scene");
WriteValidSceneFiles(missingSceneDirectory);
File.Delete(Path.Combine(missingSceneDirectory, "5079.t2s"));
var missingScene = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
missingSceneDirectory);
var missingAssetDirectory = directory.CreateDirectory("missing-asset");
WriteValidSceneFiles(missingAssetDirectory);
File.Delete(Path.Combine(missingAssetDirectory, "Images", "KRX.png"));
var missingAsset = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
missingAssetDirectory);
Assert.False(missingScene.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileMissing,
missingScene.Failure);
Assert.False(missingAsset.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileMissing,
missingAsset.Failure);
}
[Fact]
public void SceneValidation_RejectsEmptyRequiredOrPresentOptionalFiles()
{
using var directory = new TemporaryDirectory();
var emptyRequiredDirectory = directory.CreateDirectory("empty-required");
WriteValidSceneFiles(emptyRequiredDirectory);
File.WriteAllBytes(Path.Combine(emptyRequiredDirectory, "5076.t2s"), []);
var emptyRequired = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
emptyRequiredDirectory);
var emptyOptionalDirectory = directory.CreateDirectory("empty-optional");
WriteValidSceneFiles(emptyOptionalDirectory);
var optionalPath = Path.Combine(emptyOptionalDirectory, "Video", "큐브배경.vrv");
Directory.CreateDirectory(Path.GetDirectoryName(optionalPath)!);
File.WriteAllBytes(optionalPath, []);
var emptyOptional = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
emptyOptionalDirectory);
Assert.False(emptyRequired.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileUnsafe,
emptyRequired.Failure);
Assert.False(emptyOptional.IsValid);
Assert.Equal(LegacyOperatorFolderValidationFailure.RequiredFileUnsafe,
emptyOptional.Failure);
}
[Fact]
public void SceneValidationContract_MatchesTheReachableCutRequirementManifest()
{
var manifestPath = Path.Combine(
FindRepositoryRoot(),
"scripts",
"LegacyCutRequirements.json");
using var document = JsonDocument.Parse(File.ReadAllBytes(manifestPath));
var root = document.RootElement;
var manifestScenes = root.GetProperty("activeAliases")
.EnumerateArray()
.Select(value => string.Concat(value.GetString(), ".t2s"))
.ToArray();
var manifestAssets = root.GetProperty("assets")
.EnumerateArray()
.Select(value => new
{
Kind = value.GetProperty("kind").GetString(),
Path = value.GetProperty("path").GetString()!
})
.ToArray();
Assert.Equal(
manifestScenes.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.RequiredSceneFileNames
.OrderBy(value => value, StringComparer.Ordinal));
Assert.Equal(
manifestAssets.Where(value => value.Kind != "Video")
.Select(value => value.Path)
.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.RequiredBuiltInAssetRelativePaths
.OrderBy(value => value, StringComparer.Ordinal));
Assert.Equal(
manifestAssets.Where(value => value.Kind == "Video")
.Select(value => value.Path)
.OrderBy(value => value, StringComparer.Ordinal),
LegacyOperatorFolderValidator.OptionalExternalAssetRelativePaths
.OrderBy(value => value, StringComparer.Ordinal));
}
[Fact]
public void SceneValidation_MissingBaselineFailsWithoutLeakingThePath()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("sensitive-design-folder");
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Scene,
sceneDirectory);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.RequiredFileMissing,
result.Failure);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, sceneDirectory);
}
[Fact]
public void BackgroundValidation_AcceptsAnEmptyFixedDriveDirectory()
{
using var directory = new TemporaryDirectory();
var backgroundDirectory = directory.CreateDirectory("Background");
Assert.True(LegacyOperatorFolderValidator.TryValidate(
LegacyOperatorSettingsFolderKind.Background,
backgroundDirectory,
out var folder,
out var warning));
Assert.Equal(Path.GetFullPath(backgroundDirectory), folder!.CanonicalPath);
Assert.Null(warning);
}
[Fact]
public void ResourceValidation_ParsesEveryClosedUiIniAndOnlyFlagsDatabaseIni()
{
using var directory = new TemporaryDirectory();
var resourceDirectory = directory.CreateDirectory("Res");
WriteValidResourceFiles(resourceDirectory);
File.WriteAllText(
Path.Combine(resourceDirectory, "MmoneyCoder.ini"),
"secret-content-that-must-not-be-returned");
var stockMenu = LegacyRuntimeStockCutMenuLoader
.ResolveFromResourceDirectory(resourceDirectory);
Assert.False(stockMenu.UsesBuiltInCatalog);
var uiCatalogs = LegacyRuntimeUiCatalogLoader
.ResolveFromResourceDirectory(resourceDirectory);
Assert.Empty(uiCatalogs.Warnings);
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Resource,
resourceDirectory);
Assert.True(result.IsValid, $"{result.Failure}: {result.WarningMessage}");
Assert.True(result.Folder!.DatabaseIniDetected);
Assert.DoesNotContain("secret", result.Folder.AbbreviatedPath,
StringComparison.OrdinalIgnoreCase);
Assert.Null(result.WarningMessage);
}
[Fact]
public void ResourceValidation_InvalidClosedIniFailsWithoutPartialAcceptance()
{
using var directory = new TemporaryDirectory();
var resourceDirectory = directory.CreateDirectory("private-runtime-resources");
WriteValidResourceFiles(resourceDirectory);
WriteCp949(
Path.Combine(resourceDirectory, "종목.ini"),
["[종목]", "_않은_컷"]);
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Resource,
resourceDirectory);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.InvalidRuntimeCatalog,
result.Failure);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, resourceDirectory);
}
[Fact]
public void ResourceValidation_RejectsAnUnsafeDatabaseIniWithoutReadingIt()
{
using var directory = new TemporaryDirectory();
var resourceDirectory = directory.CreateDirectory("Res");
WriteValidResourceFiles(resourceDirectory);
Directory.CreateDirectory(Path.Combine(resourceDirectory, "MmoneyCoder.ini"));
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Resource,
resourceDirectory);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.RequiredFileUnsafe,
result.Failure);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, resourceDirectory);
}
[Fact]
public void Validation_RejectsRelativeMissingRootAndControlContainingPaths()
{
using var directory = new TemporaryDirectory();
var missing = Path.Combine(directory.Path, "missing-private-folder");
string?[] candidates =
[
null,
string.Empty,
"relative-folder",
missing,
Path.GetPathRoot(directory.Path),
directory.Path + "\nprivate"
];
foreach (var candidate in candidates)
{
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Background,
candidate);
Assert.False(result.IsValid);
Assert.Null(result.Folder);
AssertPathFree(result.WarningMessage, missing);
}
}
[Fact]
public void Validation_RejectsAReparsePointInTheDirectoryAncestry_WhenSupported()
{
using var directory = new TemporaryDirectory();
var target = directory.CreateDirectory("target");
var link = Path.Combine(directory.Path, "link");
try
{
Directory.CreateSymbolicLink(link, target);
}
catch (Exception exception) when (
exception is IOException or UnauthorizedAccessException or
PlatformNotSupportedException or NotSupportedException)
{
return;
}
try
{
var result = LegacyOperatorFolderValidator.Validate(
LegacyOperatorSettingsFolderKind.Background,
link);
Assert.False(result.IsValid);
Assert.Equal(
LegacyOperatorFolderValidationFailure.ReparsePoint,
result.Failure);
}
finally
{
Directory.Delete(link);
}
}
[Fact]
public void Load_MissingFileUsesDefaultsWithoutWarning()
{
using var directory = new TemporaryDirectory();
var store = new LegacyOperatorSettingsStore(
Path.Combine(directory.Path, "Config", "runtime-folders.local.json"));
var result = store.Load();
Assert.Equal(LegacyOperatorSettings.Default, result.Settings);
Assert.False(result.LoadedFromDisk);
Assert.Equal(LegacyOperatorSettingsStoreFailure.None, result.Failure);
Assert.Null(result.WarningMessage);
}
[Fact]
public void SaveAndLoad_RoundTripsOnlyTheVersionedClosedSchemaAtomically()
{
using var directory = new TemporaryDirectory();
var sceneDirectory = directory.CreateDirectory("Cuts");
WriteValidSceneFiles(sceneDirectory);
var resourceDirectory = directory.CreateDirectory("Res");
WriteValidResourceFiles(resourceDirectory);
var backgroundDirectory = directory.CreateDirectory("Background");
var configurationPath = Path.Combine(
directory.Path,
"Config",
"runtime-folders.local.json");
var store = new LegacyOperatorSettingsStore(configurationPath);
var settings = new LegacyOperatorSettings(
sceneDirectory,
resourceDirectory,
backgroundDirectory,
NavigationExpanded: false,
ColorTheme: LegacyOperatorColorTheme.Dark,
ViewMode: LegacyOperatorViewMode.Cards,
StartWorkspace: LegacyOperatorStartWorkspace.LastWorkspace);
var firstSave = store.Save(settings);
var secondSave = store.Save(settings with { NavigationExpanded = true });
var loaded = store.Load();
Assert.True(firstSave.Succeeded);
Assert.True(secondSave.Succeeded);
Assert.NotNull(secondSave.SavedSettings);
Assert.Null(secondSave.WarningMessage);
Assert.True(loaded.LoadedFromDisk);
Assert.True(loaded.Settings.NavigationExpanded);
Assert.Equal(LegacyOperatorColorTheme.Dark, loaded.Settings.ColorTheme);
Assert.Equal(LegacyOperatorViewMode.Cards, loaded.Settings.ViewMode);
Assert.Equal(
LegacyOperatorStartWorkspace.LastWorkspace,
loaded.Settings.StartWorkspace);
Assert.Equal(Path.GetFullPath(sceneDirectory), loaded.Settings.SceneDirectory);
Assert.Equal(Path.GetFullPath(resourceDirectory), loaded.Settings.ResourceDirectory);
Assert.Equal(
Path.GetFullPath(backgroundDirectory),
loaded.Settings.BackgroundDirectory);
Assert.Empty(Directory.EnumerateFiles(
Path.GetDirectoryName(configurationPath)!,
"*.tmp"));
using var document = JsonDocument.Parse(File.ReadAllBytes(configurationPath));
var names = document.RootElement.EnumerateObject()
.Select(property => property.Name)
.ToArray();
Assert.Equal(
[
"schemaVersion",
"sceneDirectory",
"resourceDirectory",
"backgroundDirectory",
"navigationExpanded",
"colorTheme",
"viewMode",
"startWorkspace"
],
names);
Assert.Equal(
LegacyOperatorSettings.CurrentSchemaVersion,
document.RootElement.GetProperty("schemaVersion").GetInt32());
Assert.Equal("dark", document.RootElement.GetProperty("colorTheme").GetString());
Assert.Equal("cards", document.RootElement.GetProperty("viewMode").GetString());
Assert.Equal(
"lastWorkspace",
document.RootElement.GetProperty("startWorkspace").GetString());
Assert.DoesNotContain(
document.RootElement.EnumerateObject(),
property => property.Name.Contains("password", StringComparison.OrdinalIgnoreCase) ||
property.Name.Contains("target", StringComparison.OrdinalIgnoreCase) ||
property.Name.Contains("live", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void Load_LegacySchemaUsesClosedAppearanceDefaults()
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
File.WriteAllText(
configurationPath,
"{\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":false}",
new UTF8Encoding(false));
var result = new LegacyOperatorSettingsStore(configurationPath).Load();
Assert.True(result.LoadedFromDisk);
Assert.Equal(LegacyOperatorSettingsStoreFailure.None, result.Failure);
Assert.False(result.Settings.NavigationExpanded);
Assert.Equal(LegacyOperatorColorTheme.System, result.Settings.ColorTheme);
Assert.Equal(LegacyOperatorViewMode.Automatic, result.Settings.ViewMode);
Assert.Equal(
LegacyOperatorStartWorkspace.StockCut,
result.Settings.StartWorkspace);
}
[Theory]
[InlineData("{}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":3,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.UnsupportedSchema)]
[InlineData("{\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"unexpected\":1}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":1,\"schemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"SchemaVersion\":1,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":1,\"sceneDirectory\":\"relative\",\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true}", LegacyOperatorSettingsStoreFailure.InvalidSettings)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"mbn\",\"viewMode\":\"automatic\",\"startWorkspace\":\"stockCut\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"system\",\"viewMode\":\"grid\",\"startWorkspace\":\"stockCut\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
[InlineData("{\"schemaVersion\":2,\"sceneDirectory\":null,\"resourceDirectory\":null,\"backgroundDirectory\":null,\"navigationExpanded\":true,\"colorTheme\":\"system\",\"viewMode\":\"automatic\",\"startWorkspace\":\"other\"}", LegacyOperatorSettingsStoreFailure.InvalidJson)]
public void Load_StrictMalformedOrUnsafeJsonFailsClosed(
string json,
LegacyOperatorSettingsStoreFailure expectedFailure)
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
File.WriteAllText(configurationPath, json, new UTF8Encoding(false));
var store = new LegacyOperatorSettingsStore(configurationPath);
var result = store.Load();
Assert.Equal(LegacyOperatorSettings.Default, result.Settings);
Assert.False(result.LoadedFromDisk);
Assert.Equal(expectedFailure, result.Failure);
AssertPathFree(result.WarningMessage, directory.Path);
}
[Fact]
public void Load_OversizedJsonFailsBeforeParsing()
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
using (var stream = new FileStream(
configurationPath,
FileMode.CreateNew,
FileAccess.Write))
{
stream.SetLength(LegacyOperatorSettingsStore.MaximumFileBytes + 1L);
}
var result = new LegacyOperatorSettingsStore(configurationPath).Load();
Assert.Equal(LegacyOperatorSettingsStoreFailure.FileTooLarge, result.Failure);
Assert.Equal(LegacyOperatorSettings.Default, result.Settings);
AssertPathFree(result.WarningMessage, configurationPath);
}
[Fact]
public void Save_RejectsUnvalidatedPathsWithoutCreatingAConfigurationFile()
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
var store = new LegacyOperatorSettingsStore(configurationPath);
var settings = LegacyOperatorSettings.Default with
{
SceneDirectory = Path.Combine(directory.Path, "not-present-private-cuts")
};
var result = store.Save(settings);
Assert.False(result.Succeeded);
Assert.Equal(LegacyOperatorSettingsStoreFailure.InvalidSettings, result.Failure);
Assert.Null(result.SavedSettings);
Assert.False(File.Exists(configurationPath));
AssertPathFree(result.WarningMessage, settings.SceneDirectory!);
}
[Theory]
[InlineData(99, 0, 0)]
[InlineData(0, 99, 0)]
[InlineData(0, 0, 99)]
public void Save_RejectsUndefinedAppearanceValuesWithoutCreatingAConfigurationFile(
int colorTheme,
int viewMode,
int startWorkspace)
{
using var directory = new TemporaryDirectory();
var configurationPath = Path.Combine(directory.Path, "settings.json");
var settings = LegacyOperatorSettings.Default with
{
ColorTheme = (LegacyOperatorColorTheme)colorTheme,
ViewMode = (LegacyOperatorViewMode)viewMode,
StartWorkspace = (LegacyOperatorStartWorkspace)startWorkspace
};
var result = new LegacyOperatorSettingsStore(configurationPath).Save(settings);
Assert.False(result.Succeeded);
Assert.Equal(LegacyOperatorSettingsStoreFailure.InvalidSettings, result.Failure);
Assert.False(File.Exists(configurationPath));
}
[Fact]
public void AbbreviateForDisplay_IsBoundedAndNeverReturnsInvalidInput()
{
using var directory = new TemporaryDirectory();
var longLeaf = new string('a', 80);
var path = directory.CreateDirectory(longLeaf);
var abbreviated = LegacyOperatorFolderValidator.AbbreviateForDisplay(path, 20);
Assert.InRange(abbreviated.Length, 1, 20);
Assert.Contains('…', abbreviated);
Assert.Equal(
LegacyOperatorFolderValidator.UnconfiguredDisplay,
LegacyOperatorFolderValidator.AbbreviateForDisplay("relative\\path"));
}
private static void WriteValidResourceFiles(string resourceDirectory)
{
var stockLabel = LegacyStockCutCatalog.Rows
.First(row => !row.IsSeparator)
.RawLabel;
WriteCp949(
Path.Combine(resourceDirectory, "종목.ini"),
["[종목]", stockLabel]);
WriteFixedFile(
resourceDirectory,
"해외.ini",
LegacyFixedMarket.Overseas);
WriteFixedFile(
resourceDirectory,
"환율.ini",
LegacyFixedMarket.Exchange);
WriteFixedFile(
resourceDirectory,
"지수.ini",
LegacyFixedMarket.Index);
WriteIndustryFile(
resourceDirectory,
"업종_코스피.ini",
IndustryMarket.Kospi);
WriteIndustryFile(
resourceDirectory,
"업종_코스닥.ini",
IndustryMarket.Kosdaq);
WriteComparisonFile(resourceDirectory);
}
private static void WriteValidSceneFiles(string sceneDirectory)
{
foreach (var fileName in LegacyOperatorFolderValidator.RequiredSceneFileNames)
{
File.WriteAllText(Path.Combine(sceneDirectory, fileName), "fixture-scene");
}
foreach (var relativePath in
LegacyOperatorFolderValidator.RequiredBuiltInAssetRelativePaths)
{
var path = Path.Combine(sceneDirectory, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
File.WriteAllText(path, "fixture-asset");
}
}
private static void WriteFixedFile(
string resourceDirectory,
string fileName,
LegacyFixedMarket market)
{
var lines = new List<string>();
foreach (var section in LegacyFixedActionCatalog.Default
.GetActions(market)
.GroupBy(action => action.Section, StringComparer.Ordinal))
{
lines.Add($"[{section.Key}]");
lines.AddRange(section.Select(action => action.Label));
}
WriteCp949(Path.Combine(resourceDirectory, fileName), lines);
}
private static void WriteIndustryFile(
string resourceDirectory,
string fileName,
IndustryMarket market)
{
var actions = LegacyIndustryActionCatalog.GetActions(market);
var section = market == IndustryMarket.Kospi ? "코스피" : "코스닥";
WriteCp949(
Path.Combine(resourceDirectory, fileName),
[$"[{section}]", .. actions.Select(action => action.Label)]);
}
private static void WriteComparisonFile(string resourceDirectory)
{
var actions = LegacyComparisonWorkflowCatalog.Actions;
var directActions = actions
.Where(action => action.Kind != LegacyComparisonActionKind.Return)
.ToArray();
var returnActions = actions
.Where(action => action.Kind == LegacyComparisonActionKind.Return)
.ToArray();
var lines = new List<string> { "[지수]" };
lines.AddRange(LegacyComparisonWorkflowCatalog.FixedTargets
.Select(target => target.DisplayName));
lines.AddRange(directActions.Select(action => $"[{action.Label}]"));
lines.Add($"[{returnActions[0].Section}]");
lines.AddRange(returnActions.Select(action => action.Label));
WriteCp949(Path.Combine(resourceDirectory, "종목비교.ini"), lines);
}
private static void WriteCp949(string path, IEnumerable<string> lines) =>
File.WriteAllText(path, string.Join("\r\n", lines) + "\r\n", Cp949);
private static void AssertPathFree(string? warningMessage, string sensitivePath)
{
var warning = Assert.IsType<string>(warningMessage);
Assert.InRange(warning.Length, 1, 100);
Assert.DoesNotContain(sensitivePath, warning, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain('\\', warning);
Assert.DoesNotContain('/', warning);
}
private static Encoding CreateCp949Encoding()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
return Encoding.GetEncoding(
949,
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
}
private static string FindRepositoryRoot()
{
for (var current = new DirectoryInfo(AppContext.BaseDirectory);
current is not null;
current = current.Parent)
{
if (File.Exists(Path.Combine(current.FullName, "MBN_STOCK_WEBVIEW.sln")))
{
return current.FullName;
}
}
throw new DirectoryNotFoundException("Repository root was not found.");
}
private sealed class TemporaryDirectory : IDisposable
{
public TemporaryDirectory()
{
Path = System.IO.Path.Combine(
System.IO.Path.GetTempPath(),
$"MBN_STOCK_WEBVIEW_SETTINGS_{Guid.NewGuid():N}");
Directory.CreateDirectory(Path);
}
public string Path { get; }
public string CreateDirectory(string name)
{
var path = System.IO.Path.Combine(Path, name);
Directory.CreateDirectory(path);
return path;
}
public void Dispose()
{
if (Directory.Exists(Path))
{
Directory.Delete(Path, recursive: true);
}
}
}
}