using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using Microsoft.Extensions.Logging; using StellaOps.Cli.Configuration; using StellaOps.Cli.Services; using Xunit; namespace StellaOps.Cli.Tests.Services; public sealed class AuthorityDiagnosticsReporterTests : IDisposable { private readonly string _originalDirectory = Directory.GetCurrentDirectory(); private readonly string _tempDirectory = Path.Combine(Path.GetTempPath(), $"stellaops-cli-tests-{Guid.NewGuid():N}"); public AuthorityDiagnosticsReporterTests() { Directory.CreateDirectory(_tempDirectory); Directory.SetCurrentDirectory(_tempDirectory); } [Fact] public void Emit_LogsWarning_WhenPasswordPolicyWeakened() { WriteAuthorityConfiguration(minimumLength: 8); var (_, configuration) = CliBootstrapper.Build(Array.Empty()); var logger = new ListLogger(); AuthorityDiagnosticsReporter.Emit(configuration, logger); var warning = Assert.Single(logger.Entries, entry => entry.Level == LogLevel.Warning); Assert.Contains("minimum length 8 < 12", warning.Message, StringComparison.OrdinalIgnoreCase); Assert.Contains("standard.yaml", warning.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void Emit_EmitsNoWarnings_WhenPasswordPolicyMeetsBaseline() { WriteAuthorityConfiguration(minimumLength: 12); var (_, configuration) = CliBootstrapper.Build(Array.Empty()); var logger = new ListLogger(); AuthorityDiagnosticsReporter.Emit(configuration, logger); Assert.DoesNotContain(logger.Entries, entry => entry.Level >= LogLevel.Warning); } public void Dispose() { Directory.SetCurrentDirectory(_originalDirectory); try { if (Directory.Exists(_tempDirectory)) { Directory.Delete(_tempDirectory, recursive: true); } } catch { // Ignored. } } private static void WriteAuthorityConfiguration(int minimumLength) { var payload = new { Authority = new { Plugins = new { ConfigurationDirectory = "plugins", Descriptors = new { standard = new { AssemblyName = "StellaOps.Authority.Plugin.Standard", Enabled = true, ConfigFile = "standard.yaml" } } } } }; var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText("appsettings.json", json); var pluginDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins"); Directory.CreateDirectory(pluginDirectory); var pluginConfig = $""" bootstrapUser: username: "admin" password: "changeme" passwordPolicy: minimumLength: {minimumLength} requireUppercase: true requireLowercase: true requireDigit: true requireSymbol: true """; File.WriteAllText(Path.Combine(pluginDirectory, "standard.yaml"), pluginConfig); } private sealed class ListLogger : ILogger { public readonly record struct LogEntry(LogLevel Level, string Message); public List Entries { get; } = new(); 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) { var message = formatter(state, exception); Entries.Add(new LogEntry(logLevel, message)); } } private sealed class NullScope : IDisposable { public static NullScope Instance { get; } = new(); public void Dispose() { } } }