diff --git a/DisplayDetective.CommandLineApp/Program.cs b/DisplayDetective.CommandLineApp/Program.cs index 07ea47a..e7b5f7a 100644 --- a/DisplayDetective.CommandLineApp/Program.cs +++ b/DisplayDetective.CommandLineApp/Program.cs @@ -17,18 +17,11 @@ var services = context.GetHost().Services; var logger = services.GetRequiredService>(); var service = services.GetRequiredService(); - logger.LogInformation("🔍 Scanning displays"); - var displays = service.GetDisplays(); - logger.LogInformation("✨ Found {count} display(s)", displays.Count); - for (int i = 0; i < displays.Count; i++) - { - var display = displays[i]; - logger.LogInformation("\n📺 Display {i}\n{display}", i + 1, display.ToMultilineString()); - } + service.ListDisplays(); }); var monitorCmd = new Command("monitor"); -monitorCmd.SetHandler(async context => +monitorCmd.SetHandler(static async context => { var token = context.GetCancellationToken(); var services = context.GetHost().Services; @@ -59,9 +52,9 @@ { services.Configure(host.Configuration.GetSection("Logging:EventLog")); } - services.AddTransient((_) => + services.AddTransient((provider) => { - var factory = new DisplayListServiceFactory(OperatingSystem.IsWindows()); + var factory = new DisplayListServiceFactory(provider, OperatingSystem.IsWindows()); return factory.Create(); }); services.AddTransient((_) => diff --git a/DisplayDetective.Library.Tests/Common/DisplayDetectiveServiceTests.cs b/DisplayDetective.Library.Tests/Common/DisplayDetectiveServiceTests.cs index cc015aa..cc0ab67 100644 --- a/DisplayDetective.Library.Tests/Common/DisplayDetectiveServiceTests.cs +++ b/DisplayDetective.Library.Tests/Common/DisplayDetectiveServiceTests.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Reflection.Emit; using DisplayDetective.Library.Common; @@ -26,16 +25,6 @@ private static IEnumerable> GetConfigFiles(string subDirec private static IConfiguration GoodConfiguration => GetGoodConfig("both-commands-with-args"); - private static void VerifyLog(Mock> loggerMock, LogLevel level, Times times) where T : class - { - loggerMock.Verify(m => m.Log( - level, - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny>()), times); - } - private const string DeviceID = "TestDeviceID"; private static readonly IDisplay TestDisplay = Display.Create(DeviceID, "TestName", "TestManufacturer", "TestDescription"); @@ -102,8 +91,8 @@ public void RunAsync_Works_BothCommandAndArgs() runnerMock.Verify(m => m.Run("test2.exe", new string[] { "argZ" }, TestContext.Current.CancellationToken), Times.Once()); runnerMock.VerifyNoOtherCalls(); - VerifyLog(loggerMock, LogLevel.Error, Times.Never()); - VerifyLog(loggerMock, LogLevel.Warning, Times.Never()); + loggerMock.VerifyLog(LogLevel.Error, Times.Never()); + loggerMock.VerifyLog(LogLevel.Warning, Times.Never()); } [Fact] @@ -130,8 +119,8 @@ public void RunAsync_Works_OnlyCreateCommandWithArgs() runnerMock.Verify(m => m.Run("test1.exe", new string[] { "argX", "argY" }, TestContext.Current.CancellationToken), Times.Once()); runnerMock.VerifyNoOtherCalls(); - VerifyLog(loggerMock, LogLevel.Error, Times.Never()); - VerifyLog(loggerMock, LogLevel.Warning, Times.Never()); + loggerMock.VerifyLog(LogLevel.Error, Times.Never()); + loggerMock.VerifyLog(LogLevel.Warning, Times.Never()); } [Fact] @@ -162,8 +151,8 @@ public void RunAsync_AfterCancelling_NoMoreEventsFire() runnerMock.VerifyNoOtherCalls(); - VerifyLog(loggerMock, LogLevel.Error, Times.Never()); - VerifyLog(loggerMock, LogLevel.Warning, Times.Never()); + loggerMock.VerifyLog(LogLevel.Error, Times.Never()); + loggerMock.VerifyLog(LogLevel.Warning, Times.Never()); } [Fact] diff --git a/DisplayDetective.Library.Tests/Common/DisplayListServiceFactoryTests.cs b/DisplayDetective.Library.Tests/Common/DisplayListServiceFactoryTests.cs index 3d4a3bb..55f94fd 100644 --- a/DisplayDetective.Library.Tests/Common/DisplayListServiceFactoryTests.cs +++ b/DisplayDetective.Library.Tests/Common/DisplayListServiceFactoryTests.cs @@ -3,6 +3,8 @@ using DisplayDetective.Library.Common; using DisplayDetective.Library.Windows; +using Microsoft.Extensions.Logging; + namespace DisplayDetective.Library.Tests.Windows; [Trait("Category", "Unit")] @@ -12,19 +14,30 @@ public class DisplayListServiceFactoryTests [Fact] public void Create_OnWindows_Works() { + var provider = new Mock(); + provider.Setup(m => m.GetService(typeof(ILogger))) + .Returns(Mock.Of>()); + bool isWindows = true; - var factory = new DisplayListServiceFactory(isWindows); + + var factory = new DisplayListServiceFactory(provider.Object, isWindows); var service = factory.Create(); + Assert.NotNull(service); Assert.IsType(service); + + provider.Verify(m => m.GetService(typeof(ILogger)), Times.Once()); + provider.VerifyNoOtherCalls(); } [UnsupportedOSPlatform("windows")] [Fact] public void Create_NotOnWindows_DoesNotWork() { + var provider = new Mock(); bool isWindows = false; - var factory = new DisplayListServiceFactory(isWindows); - Assert.Throws(() => factory.Create()); + var factory = new DisplayListServiceFactory(provider.Object, isWindows); + Assert.Throws(factory.Create); + provider.VerifyNoOtherCalls(); } } \ No newline at end of file diff --git a/DisplayDetective.Library.Tests/MockLoggerExtensions.cs b/DisplayDetective.Library.Tests/MockLoggerExtensions.cs new file mode 100644 index 0000000..42ea5dd --- /dev/null +++ b/DisplayDetective.Library.Tests/MockLoggerExtensions.cs @@ -0,0 +1,30 @@ +using System.Text.RegularExpressions; + +using Microsoft.Extensions.Logging; + +public static class MockLoggerExtensions +{ + public static void VerifyLog(this Mock> loggerMock, LogLevel level, Times times) where T : class + { + loggerMock.Verify(m => m.Log( + level, + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>()), times); + } + + public static Mock> VerifyLogMatch(this Mock> logger, LogLevel level, Times times, string pattern) + { + Func test = (value, _) => value != null && Regex.IsMatch(value.ToString() ?? "", pattern); + + logger.Verify(m => m.Log( + level, + It.IsAny(), + It.Is((v, t) => test(v, t)), + It.IsAny(), + It.IsAny>()), times); + + return logger; + } +} \ No newline at end of file diff --git a/DisplayDetective.Library.Tests/Windows/WindowsDisplayListServiceTests.cs b/DisplayDetective.Library.Tests/Windows/WindowsDisplayListServiceTests.cs index 71e1446..49dfadf 100644 --- a/DisplayDetective.Library.Tests/Windows/WindowsDisplayListServiceTests.cs +++ b/DisplayDetective.Library.Tests/Windows/WindowsDisplayListServiceTests.cs @@ -2,6 +2,8 @@ using DisplayDetective.Library.Windows; +using Microsoft.Extensions.Logging; + namespace DisplayDetective.Library.Tests.Windows; [Trait("Category", "Acceptance")] @@ -11,16 +13,13 @@ public class WindowsDisplayListServiceTests [Fact] public void GetDisplays_ReturnsNonEmptyList() { - var service = new WindowsDisplayListService(); - var displays = service.GetDisplays(); - - Assert.NotNull(displays); - Assert.NotEmpty(displays); + var loggerMock = new Mock>(); + var service = new WindowsDisplayListService(loggerMock.Object); + service.ListDisplays(); - var display = displays.First(); - Assert.NotEmpty(display.DeviceID); - Assert.NotEmpty(display.Name); - Assert.NotEmpty(display.Manufacturer); - Assert.NotEmpty(display.Description); + loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+scanning"); + loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+found \d+ display"); + loggerMock.VerifyLogMatch(LogLevel.Information, Times.Once(), @"(?i).+display 1"); + loggerMock.VerifyLog(LogLevel.Error, Times.Never()); } } \ No newline at end of file diff --git a/DisplayDetective.Library/Common/DisplayListServiceFactory.cs b/DisplayDetective.Library/Common/DisplayListServiceFactory.cs index e82bb16..cb47b04 100644 --- a/DisplayDetective.Library/Common/DisplayListServiceFactory.cs +++ b/DisplayDetective.Library/Common/DisplayListServiceFactory.cs @@ -2,15 +2,21 @@ using DisplayDetective.Library.Windows; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + namespace DisplayDetective.Library.Common; public sealed class DisplayListServiceFactory { + private readonly IServiceProvider _provider; + [SupportedOSPlatformGuard("windows")] private readonly bool _isWindows; - public DisplayListServiceFactory(bool isWindows) + public DisplayListServiceFactory(IServiceProvider provider, bool isWindows) { + _provider = provider; _isWindows = isWindows; } @@ -18,7 +24,7 @@ public IDisplayListService Create() { if (_isWindows) { - return new WindowsDisplayListService(); + return new WindowsDisplayListService(_provider.GetRequiredService>()); } throw new PlatformNotSupportedException(); } diff --git a/DisplayDetective.Library/Common/IDisplayListService.cs b/DisplayDetective.Library/Common/IDisplayListService.cs index 403b60d..fbf244a 100644 --- a/DisplayDetective.Library/Common/IDisplayListService.cs +++ b/DisplayDetective.Library/Common/IDisplayListService.cs @@ -2,5 +2,5 @@ namespace DisplayDetective.Library.Common; public interface IDisplayListService { - IList GetDisplays(); + void ListDisplays(); } \ No newline at end of file diff --git a/DisplayDetective.Library/Windows/WindowsDisplayListService.cs b/DisplayDetective.Library/Windows/WindowsDisplayListService.cs index 56db279..2038244 100644 --- a/DisplayDetective.Library/Windows/WindowsDisplayListService.cs +++ b/DisplayDetective.Library/Windows/WindowsDisplayListService.cs @@ -3,18 +3,34 @@ using DisplayDetective.Library.Common; +using Microsoft.Extensions.Logging; + namespace DisplayDetective.Library.Windows; [SupportedOSPlatform("windows")] -internal sealed class WindowsDisplayListService : IDisplayListService +public sealed class WindowsDisplayListService : IDisplayListService { private static readonly ManagementScope Scope = new(ManagementPath.DefaultPath); private static readonly ObjectQuery Query = new("SELECT * FROM Win32_PnPEntity WHERE PNPClass = 'Monitor'"); - public IList GetDisplays() + private readonly ILogger _logger; + + internal WindowsDisplayListService(ILogger logger) + { + _logger = logger; + } + + public void ListDisplays() { + _logger.LogInformation("🔍 Scanning displays"); using var searcher = new ManagementObjectSearcher(Scope, Query); - return [.. searcher.Get().Cast().Select(Display.Create)]; + IList displays = [.. searcher.Get().Cast().Select(Display.Create)]; + _logger.LogInformation("✨ Found {count} display(s)", displays.Count); + for (int i = 0; i < displays.Count; i++) + { + var display = displays[i]; + _logger.LogInformation("\n📺 Display {i}\n{display}", i + 1, display.ToMultilineString()); + } } } \ No newline at end of file diff --git a/DisplayDetective.Library/Windows/WindowsDisplayMonitorService.cs b/DisplayDetective.Library/Windows/WindowsDisplayMonitorService.cs index 9baa7d0..eebc648 100644 --- a/DisplayDetective.Library/Windows/WindowsDisplayMonitorService.cs +++ b/DisplayDetective.Library/Windows/WindowsDisplayMonitorService.cs @@ -6,7 +6,7 @@ namespace DisplayDetective.Library.Windows; [SupportedOSPlatform("windows")] -internal sealed class WindowsDisplayMonitorService : IDisplayMonitorService +public sealed class WindowsDisplayMonitorService : IDisplayMonitorService { public event EventHandler OnDisplayCreated = delegate { };