710 lines
29 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|