using System.Text; using MBN_STOCK_WEBVIEW.Infrastructure; using MMoneyCoderSharp.Data; namespace MBN_STOCK_WEBVIEW.Infrastructure.Tests; public sealed class LegacyIniDatabaseOptionsLoaderTests { private const string IntegrationPathEnvironmentVariable = "MBN_STOCK_LEGACY_INI_INTEGRATION_PATH"; [Fact] public void Load_Utf8LegacyFile_MapsBothProvidersAndKeepsLastValue() { using var file = TemporaryLegacyIni.Create( Encoding.UTF8.GetBytes( """ [Oracle] //ConnectionName=ignored.invalid:1/IGNORED ConnectionName=oracle.dev.invalid:1522/DEVORA ID=old-user ID=oracle-user Pass=oracle=password [Maria] ConnectionName=maria.dev.invalid:3307/dev_stock ID=maria-user Pass=maria=password """)); var loader = new LegacyIniDatabaseOptionsLoader(_ => null); var options = loader.Load(file.Path); Assert.True(options.IsConfigured(DataSourceKind.Oracle)); Assert.True(options.IsConfigured(DataSourceKind.MariaDb)); Assert.Equal("oracle.dev.invalid", options.Oracle.Host); Assert.Equal(1522, options.Oracle.Port); Assert.Equal("DEVORA", options.Oracle.Sid); Assert.Null(options.Oracle.ServiceName); Assert.Equal("oracle-user", options.Oracle.UserName); Assert.Equal("oracle=password", options.Oracle.Password); Assert.Equal("maria.dev.invalid", options.MariaDb.Host); Assert.Equal(3307, options.MariaDb.Port); Assert.Equal("dev_stock", options.MariaDb.Database); Assert.Equal("maria-user", options.MariaDb.UserName); Assert.Equal("maria=password", options.MariaDb.Password); Assert.Equal(MariaDbTlsMode.Disabled, options.MariaDb.TlsMode); } [Fact] public void Load_Cp949LegacyFile_PreservesKoreanValues() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); var cp949 = Encoding.GetEncoding( 949, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); using var file = TemporaryLegacyIni.Create(cp949.GetBytes( """ [Oracle] ConnectionName=oracle.dev.invalid:1521/개발 ID=오라클사용자 Pass=암호 [Maria] ConnectionName=maria.dev.invalid:3306/주식 ID=마리아사용자 Pass=암호2 """)); var options = new LegacyIniDatabaseOptionsLoader(_ => null).Load(file.Path); Assert.Equal("개발", options.Oracle.Sid); Assert.Equal("오라클사용자", options.Oracle.UserName); Assert.Equal("주식", options.MariaDb.Database); Assert.Equal("마리아사용자", options.MariaDb.UserName); } [Fact] public void Load_EnvironmentStillOverridesLegacyValues() { using var file = TemporaryLegacyIni.CreateValidUtf8(); var environment = new Dictionary(StringComparer.Ordinal) { ["MBN_STOCK_ORACLE_HOST"] = "override-oracle.invalid", ["MBN_STOCK_MARIADB_TLS_MODE"] = "Required" }; var loader = new LegacyIniDatabaseOptionsLoader( name => environment.TryGetValue(name, out var value) ? value : null); var options = loader.Load(file.Path); Assert.Equal("override-oracle.invalid", options.Oracle.Host); Assert.Equal(MariaDbTlsMode.Required, options.MariaDb.TlsMode); } [Fact] public void Load_InvalidFile_DoesNotExposeFileValues() { const string secret = "sensitive-password-sentinel"; using var file = TemporaryLegacyIni.Create(Encoding.UTF8.GetBytes( $"[Oracle]\nConnectionName=invalid\nID=user\nPass={secret}")); var exception = Assert.Throws( () => new LegacyIniDatabaseOptionsLoader(_ => null).Load(file.Path)); Assert.DoesNotContain(secret, exception.ToString(), StringComparison.Ordinal); Assert.DoesNotContain(file.Path, exception.ToString(), StringComparison.Ordinal); } [Fact] public void Runtime_PrefersExecutableResIni_AndFallsBackWhenAbsent() { using var directory = TemporaryDirectory.Create(); var res = Path.Combine(directory.Path, "Res"); Directory.CreateDirectory(res); File.WriteAllBytes( Path.Combine(res, "MmoneyCoder.ini"), TemporaryLegacyIni.ValidUtf8Bytes()); var runtime = DatabaseRuntime.CreateLegacyExecutableDefault( directory.Path, Path.Combine(directory.Path, "missing.json")); Assert.Equal("oracle.dev.invalid", runtime.Options.Oracle.Host); File.Delete(Path.Combine(res, "MmoneyCoder.ini")); var fallback = DatabaseRuntime.CreateLegacyExecutableDefault( directory.Path, Path.Combine(directory.Path, "missing.json"), Path.Combine(directory.Path, "missing-legacy.ini")); Assert.False(fallback.Options.IsConfigured(DataSourceKind.Oracle)); Assert.False(fallback.Options.IsConfigured(DataSourceKind.MariaDb)); } [Fact] public void Runtime_UsesLocalLegacyOverrideWhenPackageDoesNotContainCredentials() { using var directory = TemporaryDirectory.Create(); using var overrideFile = TemporaryLegacyIni.CreateValidUtf8(); var runtime = DatabaseRuntime.CreateLegacyExecutableDefault( directory.Path, Path.Combine(directory.Path, "missing.json"), overrideFile.Path); Assert.Equal("oracle.dev.invalid", runtime.Options.Oracle.Host); Assert.Equal("maria.dev.invalid", runtime.Options.MariaDb.Host); } [Fact] public void Runtime_UserSelectedResCanExplicitlyPrecedeExecutableCredentials() { using var directory = TemporaryDirectory.Create(); var executableRes = Path.Combine(directory.Path, "Res"); Directory.CreateDirectory(executableRes); File.WriteAllBytes( Path.Combine(executableRes, "MmoneyCoder.ini"), TemporaryLegacyIni.ValidUtf8Bytes()); using var selected = TemporaryLegacyIni.Create(Encoding.UTF8.GetBytes( """ [Oracle] ConnectionName=selected.oracle.invalid:1521/DEVORA ID=oracle-user Pass=oracle-password [Maria] ConnectionName=selected.maria.invalid:3306/dev_stock ID=maria-user Pass=maria-password """)); var runtime = DatabaseRuntime.CreateLegacyExecutableDefault( directory.Path, Path.Combine(directory.Path, "missing.json"), selected.Path, preferLegacyOverride: true); Assert.Equal("selected.oracle.invalid", runtime.Options.Oracle.Host); Assert.Equal("selected.maria.invalid", runtime.Options.MariaDb.Host); } [Fact] public void Runtime_DevelopmentLiveUsesOnlyExactLocalAppDataIni() { using var localApplicationData = TemporaryDirectory.Create(); var resourceDirectory = Path.Combine( localApplicationData.Path, "MBN_STOCK_WEBVIEW", "Res"); Directory.CreateDirectory(resourceDirectory); File.WriteAllBytes( Path.Combine(resourceDirectory, "MmoneyCoder.ini"), TemporaryLegacyIni.ValidUtf8Bytes()); const string environmentName = "MBN_STOCK_ORACLE_HOST"; var originalEnvironment = Environment.GetEnvironmentVariable(environmentName); DatabaseRuntime runtime; try { Environment.SetEnvironmentVariable( environmentName, "stale-environment.invalid"); runtime = DatabaseRuntime.CreateDevelopmentLiveLocalOverride( localApplicationData.Path); } finally { Environment.SetEnvironmentVariable( environmentName, originalEnvironment); } Assert.Equal("oracle.dev.invalid", runtime.Options.Oracle.Host); Assert.Equal("maria.dev.invalid", runtime.Options.MariaDb.Host); } [Fact] public void Runtime_DevelopmentLiveFailsClosedWhenBothProtectedProfilesAreMissing() { using var localApplicationData = TemporaryDirectory.Create(); Directory.CreateDirectory(Path.Combine( localApplicationData.Path, "MBN_STOCK_WEBVIEW", "Config")); Assert.Throws( () => DatabaseRuntime.CreateDevelopmentLiveLocalOverride( localApplicationData.Path)); } [Fact] public void Runtime_DevelopmentLiveFallsBackToProtectedLocalAppDataJson() { using var localApplicationData = TemporaryDirectory.Create(); var configurationDirectory = Path.Combine( localApplicationData.Path, "MBN_STOCK_WEBVIEW", "Config"); Directory.CreateDirectory(configurationDirectory); File.WriteAllText( Path.Combine(configurationDirectory, "database.local.json"), """ { "oracle": { "host": "json.oracle.invalid", "port": 1521, "sid": "DEVORA", "serviceName": null, "userName": "oracle-user", "password": "oracle-password" }, "mariaDb": { "host": "json.maria.invalid", "port": 3306, "database": "dev_stock", "userName": "maria-user", "password": "maria-password", "tlsMode": "Required" } } """); var runtime = DatabaseRuntime.CreateDevelopmentLiveLocalOverride( localApplicationData.Path); Assert.Equal("json.oracle.invalid", runtime.Options.Oracle.Host); Assert.Equal("json.maria.invalid", runtime.Options.MariaDb.Host); Assert.Equal(MariaDbTlsMode.Required, runtime.Options.MariaDb.TlsMode); } [Fact] public void Runtime_DevelopmentLiveRejectsNonOrdinaryIniTarget() { using var localApplicationData = TemporaryDirectory.Create(); var iniDirectory = Path.Combine( localApplicationData.Path, "MBN_STOCK_WEBVIEW", "Res", "MmoneyCoder.ini"); Directory.CreateDirectory(iniDirectory); Assert.Throws( () => DatabaseRuntime.CreateDevelopmentLiveLocalOverride( localApplicationData.Path)); } [Fact] public void Runtime_DevelopmentLiveRejectsNonOrdinaryJsonTarget() { using var localApplicationData = TemporaryDirectory.Create(); var jsonDirectory = Path.Combine( localApplicationData.Path, "MBN_STOCK_WEBVIEW", "Config", "database.local.json"); Directory.CreateDirectory(jsonDirectory); Assert.Throws( () => DatabaseRuntime.CreateDevelopmentLiveLocalOverride( localApplicationData.Path)); } [Fact] public void Load_OptionalRealLegacyIni_IsConfiguredWithoutPrintingValues() { var path = Environment.GetEnvironmentVariable( IntegrationPathEnvironmentVariable); if (string.IsNullOrWhiteSpace(path)) { return; } var options = new LegacyIniDatabaseOptionsLoader().Load(path); Assert.True(options.IsConfigured(DataSourceKind.Oracle)); Assert.True(options.IsConfigured(DataSourceKind.MariaDb)); } private sealed class TemporaryLegacyIni : IDisposable { private TemporaryLegacyIni(string directory, string path) { Directory = directory; Path = path; } public string Directory { get; } public string Path { get; } public static TemporaryLegacyIni CreateValidUtf8() => Create(ValidUtf8Bytes()); public static byte[] ValidUtf8Bytes() => Encoding.UTF8.GetBytes( """ [Oracle] ConnectionName=oracle.dev.invalid:1521/DEVORA ID=oracle-user Pass=oracle-password [Maria] ConnectionName=maria.dev.invalid:3306/dev_stock ID=maria-user Pass=maria-password """); public static TemporaryLegacyIni Create(byte[] bytes) { var directory = System.IO.Path.Combine( System.IO.Path.GetTempPath(), Guid.NewGuid().ToString("N")); System.IO.Directory.CreateDirectory(directory); var path = System.IO.Path.Combine(directory, "MmoneyCoder.ini"); File.WriteAllBytes(path, bytes); return new TemporaryLegacyIni(directory, path); } public void Dispose() => System.IO.Directory.Delete(Directory, recursive: true); } private sealed class TemporaryDirectory : IDisposable { private TemporaryDirectory(string path) => Path = path; public string Path { get; } public static TemporaryDirectory Create() { var path = System.IO.Path.Combine( System.IO.Path.GetTempPath(), Guid.NewGuid().ToString("N")); System.IO.Directory.CreateDirectory(path); return new TemporaryDirectory(path); } public void Dispose() => System.IO.Directory.Delete(Path, recursive: true); } }