using Admin.NET.Plugin.AiDOP.DataPlatform; using Admin.NET.Plugin.AiDOP.Job; using Microsoft.Extensions.Logging; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform; public class MdpSourcePasswordResolverTests { private const string SourceCode = "UNIT_TEST_SRC"; private const string Secret = "unit-test-secret-value"; [Fact] public void Env_wins_over_stored_secret() { var env = MdpSourcePasswordResolver.EnvironmentVariableName(SourceCode); var previous = Environment.GetEnvironmentVariable(env); try { Environment.SetEnvironmentVariable(env, Secret); var logger = new ListLogger(); var value = new MdpSourcePasswordResolver(logger).Resolve(SourceCode, "stored-cipher"); Assert.Equal(Secret, value); Assert.DoesNotContain(Secret, logger.Text); } finally { Environment.SetEnvironmentVariable(env, previous); } } [Fact] public void Missing_env_uses_successful_decrypt_without_logging_it() { var env = MdpSourcePasswordResolver.EnvironmentVariableName(SourceCode); var previous = Environment.GetEnvironmentVariable(env); try { Environment.SetEnvironmentVariable(env, null); var logger = new ListLogger(); var value = MdpSourcePasswordResolver.ResolveCore( SourceCode, "cipher-blob", _ => "opened-secret", logger); Assert.Equal("opened-secret", value); Assert.DoesNotContain("cipher-blob", logger.Text); Assert.DoesNotContain("opened-secret", logger.Text); Assert.DoesNotContain(Secret, logger.Text); } finally { Environment.SetEnvironmentVariable(env, previous); } } [Fact] public void Missing_env_uses_legacy_plaintext_without_logging_it() { var env = MdpSourcePasswordResolver.EnvironmentVariableName(SourceCode); var previous = Environment.GetEnvironmentVariable(env); try { Environment.SetEnvironmentVariable(env, null); var logger = new ListLogger(); var value = new MdpSourcePasswordResolver(logger).Resolve(SourceCode, Secret); Assert.Equal(Secret, value); Assert.Contains("legacy plaintext", logger.Text); Assert.DoesNotContain(Secret, logger.Text); } finally { Environment.SetEnvironmentVariable(env, previous); } } [Fact] public void Missing_env_and_stored_secret_throws_SECRET_MISSING() { var env = MdpSourcePasswordResolver.EnvironmentVariableName(SourceCode); var previous = Environment.GetEnvironmentVariable(env); try { Environment.SetEnvironmentVariable(env, null); var ex = Assert.Throws(() => new MdpSourcePasswordResolver().Resolve(SourceCode, null)); Assert.Equal("SECRET_MISSING", ex.Code); Assert.Contains(env, ex.Message); Assert.DoesNotContain(Secret, ex.Message); } finally { Environment.SetEnvironmentVariable(env, previous); } } [Fact] public void Health_classifier_does_not_keep_connection_text() { Assert.Equal("SECRET_MISSING", MdpSourceHealthCheckJob.ClassifyHealthFailure( new MdpSourceSecretException("SECRET_MISSING", "set the env"))); Assert.Equal("AUTH_FAILED", MdpSourceHealthCheckJob.ClassifyHealthFailure( new InvalidOperationException("Login failed for user 'dopsa'"))); Assert.Equal("NETWORK_FAILED", MdpSourceHealthCheckJob.ClassifyHealthFailure( new TimeoutException("Unable to connect to the server"))); Assert.Equal("连接失败,详情见运行日志", MdpSourceHealthCheckJob.SafeHealthMessage( "Password=unit-test-secret-value;Server=example")); Assert.DoesNotContain(Secret, MdpSourceHealthCheckJob.SafeHealthMessage( "Password=unit-test-secret-value;Server=example")); } [Fact] public void Database_json_no_longer_contains_t8_v5() { var dir = new DirectoryInfo(AppContext.BaseDirectory); string? path = null; while (dir != null) { var candidate = Path.Combine(dir.FullName, "server", "Admin.NET.Application", "Configuration", "Database.json"); if (File.Exists(candidate)) { path = candidate; break; } dir = dir.Parent; } Assert.False(string.IsNullOrEmpty(path), "Database.json not found from " + AppContext.BaseDirectory); var text = File.ReadAllText(path); Assert.DoesNotContain("t8_v5", text, StringComparison.OrdinalIgnoreCase); } private sealed class ListLogger : ILogger { public string Text { get; private set; } = ""; public IDisposable BeginScope(TState state) where TState : notnull => NullScope.Instance; public bool IsEnabled(LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { Text += formatter(state, exception); } private sealed class NullScope : IDisposable { public static readonly NullScope Instance = new(); public void Dispose() { } } } }