diff --git a/src/Interfaces/IRegistryKey.cs b/src/Interfaces/IRegistryKey.cs index 78c8e6ed..f9016e37 100644 --- a/src/Interfaces/IRegistryKey.cs +++ b/src/Interfaces/IRegistryKey.cs @@ -8,6 +8,6 @@ public interface IRegistryKey : IDisposable object? GetValue(string name); void DeleteValue(string name); IRegistryKey? OpenSubKey(string name, bool writable); - IRegistryKey CreateSubKey(string name, bool writable); + IRegistryKey? CreateSubKey(string name, bool writable); } } diff --git a/src/Interfaces/ISystemSettingsService.cs b/src/Interfaces/ISystemSettingsService.cs index d37ce53f..acbef319 100644 --- a/src/Interfaces/ISystemSettingsService.cs +++ b/src/Interfaces/ISystemSettingsService.cs @@ -6,8 +6,8 @@ namespace WinHome.Interfaces { public interface ISystemSettingsService { - Task> GetTweaksAsync(Dictionary settings); - Task ApplyNonRegistrySettingsAsync(Dictionary settings, bool dryRun); + Task> GetTweaksAsync(Dictionary? settings); + Task ApplyNonRegistrySettingsAsync(Dictionary? settings, bool dryRun); Task> GetCapturedSettingsAsync(); string? GetFriendlyName(string registryPath, string registryName); } diff --git a/src/Services/System/RegistryKeyWrapper.cs b/src/Services/System/RegistryKeyWrapper.cs index 82460e88..190572c0 100644 --- a/src/Services/System/RegistryKeyWrapper.cs +++ b/src/Services/System/RegistryKeyWrapper.cs @@ -33,10 +33,10 @@ public void DeleteValue(string name) return subKey == null ? null : new RegistryKeyWrapper(subKey); } - public IRegistryKey CreateSubKey(string name, bool writable) + public IRegistryKey? CreateSubKey(string name, bool writable) { var subKey = _registryKey.CreateSubKey(name, writable); - return new RegistryKeyWrapper(subKey); + return subKey == null ? null : new RegistryKeyWrapper(subKey); } public void Dispose() diff --git a/src/Services/System/RegistryService.cs b/src/Services/System/RegistryService.cs index 241a243c..f31f3e23 100644 --- a/src/Services/System/RegistryService.cs +++ b/src/Services/System/RegistryService.cs @@ -29,7 +29,7 @@ public void Apply(RegistryTweak tweak, bool dryRun) { object? currentValue = key?.GetValue(tweak.Name); - if (currentValue != null && currentValue.ToString() == tweak.Value.ToString()) + if (currentValue != null && currentValue.ToString() == tweak.Value?.ToString()) { Console.WriteLine($"[Registry] Skipped: {tweak.Name} (Already set)"); return; @@ -44,8 +44,14 @@ public void Apply(RegistryTweak tweak, bool dryRun) } } - using (IRegistryKey key = root.CreateSubKey(subKeyPath, writable: true)) + using (IRegistryKey? key = root.CreateSubKey(subKeyPath, writable: true)) { + if (key == null) + { + Console.WriteLine($"[Error] Could not create registry subkey: {tweak.Path}"); + return; + } + RegistryValueKind kind = tweak.Type.ToLower() switch { "dword" => RegistryValueKind.DWord, @@ -54,11 +60,20 @@ public void Apply(RegistryTweak tweak, bool dryRun) _ => RegistryValueKind.String }; - object valueToWrite = tweak.Value; - if (kind == RegistryValueKind.DWord) valueToWrite = Convert.ToInt32(tweak.Value); - if (kind == RegistryValueKind.QWord) valueToWrite = Convert.ToInt64(tweak.Value); + object? valueToWrite = tweak.Value; + if (valueToWrite is global::System.Text.Json.JsonElement jsonElement) + { + if (kind == RegistryValueKind.DWord) valueToWrite = jsonElement.GetInt32(); + else if (kind == RegistryValueKind.QWord) valueToWrite = jsonElement.GetInt64(); + else valueToWrite = jsonElement.ToString() ?? string.Empty; + } + else + { + if (kind == RegistryValueKind.DWord) valueToWrite = Convert.ToInt32(tweak.Value); + else if (kind == RegistryValueKind.QWord) valueToWrite = Convert.ToInt64(tweak.Value); + } - key.SetValue(tweak.Name, valueToWrite, kind); + key.SetValue(tweak.Name, valueToWrite ?? string.Empty, kind); Console.WriteLine($"[Registry] Set {tweak.Name} = {tweak.Value}"); } } diff --git a/src/Services/System/SystemSettingsService.cs b/src/Services/System/SystemSettingsService.cs index 33ce1d87..369b292c 100644 --- a/src/Services/System/SystemSettingsService.cs +++ b/src/Services/System/SystemSettingsService.cs @@ -35,6 +35,25 @@ public class SystemSettingsService : ISystemSettingsService new RegistryTweak { Path = @"HKLM\System\CurrentControlSet\Control\Remote Assistance", Name = "fAllowToGetHelp", Value = 0, Type = "dword" }, // Disable NetBIOS over TCP/IP (prevent LLMNR/NBT-NS poisoning) new RegistryTweak { Path = @"HKLM\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces", Name = "NetbiosOptions", Value = 2, Type = "dword" } + }, + ["privacy"] = new() + { + // Disable Windows Telemetry data collection + new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\DataCollection", Name = "AllowTelemetry", Value = 0, Type = "dword" }, + // Disable Advertising ID for personalized ads + new RegistryTweak { Path = @"HKCU\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo", Name = "Enabled", Value = 0, Type = "dword" }, + // Disable Activity History feed + new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System", Name = "EnableActivityFeed", Value = 0, Type = "dword" }, + // Disable Activity History cloud upload + new RegistryTweak { Path = @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System", Name = "UploadUserActivities", Value = 0, Type = "dword" }, + // Disable Tailored Experiences based on diagnostic data + new RegistryTweak { Path = @"HKCU\Software\Microsoft\Windows\CurrentVersion\Privacy", Name = "TailoredExperiencesWithDiagnosticDataEnabled", Value = 0, Type = "dword" }, + // Disable Feedback Notifications + new RegistryTweak { Path = @"HKCU\Software\Microsoft\Siuf\Rules", Name = "NumberOfSIUFInPeriod", Value = 0, Type = "dword" }, + // Disable implicit text/ink collection for input personalization + new RegistryTweak { Path = @"HKCU\Software\Microsoft\InputPersonalization", Name = "RestrictImplicitTextCollection", Value = 1, Type = "dword" }, + // Disable contact harvesting for handwriting recognition + new RegistryTweak { Path = @"HKCU\Software\Microsoft\InputPersonalization\TrainedDataStore", Name = "HarvestContacts", Value = 0, Type = "dword" } } }; @@ -123,7 +142,7 @@ Dictionary ValueMap new() { { "true", 1 }, { "false", 0 } }), }; - public async Task> GetTweaksAsync(Dictionary settings) + public async Task> GetTweaksAsync(Dictionary? settings) { return await Task.Run(() => { @@ -223,7 +242,7 @@ public async Task> GetCapturedSettingsAsync() return match?.SettingKey; } - public Task ApplyNonRegistrySettingsAsync(Dictionary settings, bool dryRun) + public Task ApplyNonRegistrySettingsAsync(Dictionary? settings, bool dryRun) { if (settings == null) return Task.CompletedTask; diff --git a/tests/WinHome.Tests/RegistryServiceTests.cs b/tests/WinHome.Tests/RegistryServiceTests.cs index 46fa4fd4..f84b3e2c 100644 --- a/tests/WinHome.Tests/RegistryServiceTests.cs +++ b/tests/WinHome.Tests/RegistryServiceTests.cs @@ -43,6 +43,28 @@ public void Apply_Should_Set_Registry_Value() _mockRegistryKey.Verify(x => x.SetValue(tweak.Name, tweak.Value, RegistryValueKind.String), Times.Once); } + [Fact] + public void Apply_Should_Create_SubKey_When_Key_Is_Missing() + { + // Arrange + var tweak = new RegistryTweak { Path = "HKCU\\Software\\TestMissing", Name = "TestValue", Value = "Test", Type = "string" }; + var subKeyPath = "Software\\TestMissing"; + _mockRegistryWrapper.Setup(x => x.GetRootKey(tweak.Path, out subKeyPath)) + .Callback(new GetRootKeyCallback((string fullPath, out string s) => s = subKeyPath)) + .Returns(_mockRegistryKey.Object); + + // Simulate missing key: OpenSubKey returns null + _mockRegistryKey.Setup(x => x.OpenSubKey(It.IsAny(), false)).Returns((IRegistryKey?)null); + _mockRegistryKey.Setup(x => x.CreateSubKey(It.IsAny(), true)).Returns(_mockRegistryKey.Object); + + // Act + _registryService.Apply(tweak, false); + + // Assert + _mockRegistryKey.Verify(x => x.CreateSubKey(subKeyPath, true), Times.Once); + _mockRegistryKey.Verify(x => x.SetValue(tweak.Name, tweak.Value, RegistryValueKind.String), Times.Once); + } + [Fact] public void Revert_Should_Delete_Registry_Value() { diff --git a/tests/WinHome.Tests/SystemSettingsServiceTests.cs b/tests/WinHome.Tests/SystemSettingsServiceTests.cs index cf674d79..0acb5516 100644 --- a/tests/WinHome.Tests/SystemSettingsServiceTests.cs +++ b/tests/WinHome.Tests/SystemSettingsServiceTests.cs @@ -76,59 +76,215 @@ public async Task GetTweaksAsync_Should_Return_Security_Presets() } [Fact] - public async Task GetTweaksAsync_Should_Return_Transparency_Tweaks() + public async Task GetTweaksAsync_Should_Return_Strict_Security_Preset_Tweaks() { var settings = new Dictionary { - { "transparency", "true" } + { "security_preset", "strict" } }; var tweaks = await _service.GetTweaksAsync(settings); var tweaksList = new List(tweaks); - Assert.Single(tweaksList); - Assert.Equal("EnableTransparency", tweaksList[0].Name); - Assert.Equal(1, tweaksList[0].Value); - Assert.Equal("dword", tweaksList[0].Type); + // Baseline tweaks + Assert.Contains(tweaksList, t => t.Name == "EnableWebContentEvaluation" && t.Value.Equals(1)); + Assert.Contains(tweaksList, t => t.Name == "NoDriveTypeAutoRun" && t.Value.Equals(255)); + Assert.Contains(tweaksList, t => t.Name == "EnableMulticast" && t.Value.Equals(0)); + + // Strict tweaks + Assert.Contains(tweaksList, t => t.Name == "Enabled" && t.Path == @"HKLM\Software\Microsoft\Windows Script Host\Settings" && t.Value.Equals(0)); + Assert.Contains(tweaksList, t => t.Name == "fAllowToGetHelp" && t.Path == @"HKLM\System\CurrentControlSet\Control\Remote Assistance" && t.Value.Equals(0)); + Assert.Contains(tweaksList, t => t.Name == "NetbiosOptions" && t.Path == @"HKLM\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces" && t.Value.Equals(2)); } [Fact] - public async Task GetCapturedSettingsAsync_Should_Capture_Transparency() + public async Task GetTweaksAsync_Should_Return_Privacy_Preset_Tweaks() { - _mockRegistryService - .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "EnableTransparency")) - .Returns(1); + var settings = new Dictionary + { + { "security_preset", "privacy" } + }; - var captured = await _service.GetCapturedSettingsAsync(); + var tweaks = await _service.GetTweaksAsync(settings); + var tweaksList = new List(tweaks); - Assert.True(captured.ContainsKey("transparency")); - Assert.Equal(true, captured["transparency"]); + Assert.Equal(8, tweaksList.Count); } [Fact] - public async Task GetTweaksAsync_Should_Return_TaskbarAutoHide_Tweaks() + public async Task GetTweaksAsync_Privacy_Preset_Should_Contain_Expected_Registry_Keys() { var settings = new Dictionary { - { "taskbar_autohide", "true" } + { "security_preset", "privacy" } }; var tweaks = await _service.GetTweaksAsync(settings); var tweaksList = new List(tweaks); + Assert.Contains(tweaksList, t => t.Path == @"HKLM\SOFTWARE\Policies\Microsoft\Windows\DataCollection" && t.Name == "AllowTelemetry" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKCU\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo" && t.Name == "Enabled" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System" && t.Name == "EnableActivityFeed" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKLM\SOFTWARE\Policies\Microsoft\Windows\System" && t.Name == "UploadUserActivities" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKCU\Software\Microsoft\Windows\CurrentVersion\Privacy" && t.Name == "TailoredExperiencesWithDiagnosticDataEnabled" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKCU\Software\Microsoft\Siuf\Rules" && t.Name == "NumberOfSIUFInPeriod" && t.Value.Equals(0) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKCU\Software\Microsoft\InputPersonalization" && t.Name == "RestrictImplicitTextCollection" && t.Value.Equals(1) && t.Type == "dword"); + Assert.Contains(tweaksList, t => t.Path == @"HKCU\Software\Microsoft\InputPersonalization\TrainedDataStore" && t.Name == "HarvestContacts" && t.Value.Equals(0) && t.Type == "dword"); + } + + [Fact] + public async Task GetTweaksAsync_Should_Return_Empty_For_Unknown_Preset() + { + var settings = new Dictionary + { + { "security_preset", "unknown_preset" } + }; + + var tweaks = await _service.GetTweaksAsync(settings); + Assert.NotNull(tweaks); + Assert.Empty(tweaks); + } + + [Theory] + // dark_mode + [InlineData("dark_mode", "true", "AppsUseLightTheme", 0)] + [InlineData("dark_mode", "true", "SystemUsesLightTheme", 0)] + [InlineData("dark_mode", "false", "AppsUseLightTheme", 1)] + [InlineData("dark_mode", "false", "SystemUsesLightTheme", 1)] + // taskbar_alignment + [InlineData("taskbar_alignment", "left", "TaskbarAl", 0)] + [InlineData("taskbar_alignment", "center", "TaskbarAl", 1)] + // taskbar_widgets + [InlineData("taskbar_widgets", "hide", "TaskbarDa", 0)] + [InlineData("taskbar_widgets", "show", "TaskbarDa", 1)] + // show_file_extensions + [InlineData("show_file_extensions", "true", "HideFileExt", 0)] + [InlineData("show_file_extensions", "false", "HideFileExt", 1)] + // show_hidden_files + [InlineData("show_hidden_files", "true", "Hidden", 1)] + [InlineData("show_hidden_files", "false", "Hidden", 2)] + // seconds_in_clock + [InlineData("seconds_in_clock", "true", "ShowSecondsInSystemClock", 1)] + [InlineData("seconds_in_clock", "false", "ShowSecondsInSystemClock", 0)] + // explorer_launch_to + [InlineData("explorer_launch_to", "this_pc", "LaunchTo", 1)] + [InlineData("explorer_launch_to", "quick_access", "LaunchTo", 2)] + // bing_search_enabled + [InlineData("bing_search_enabled", "true", "BingSearchEnabled", 1)] + [InlineData("bing_search_enabled", "false", "BingSearchEnabled", 0)] + // transparency + [InlineData("transparency", "true", "EnableTransparency", 1)] + [InlineData("transparency", "false", "EnableTransparency", 0)] + // taskbar_task_view + [InlineData("taskbar_task_view", "true", "ShowTaskViewButton", 1)] + [InlineData("taskbar_task_view", "false", "ShowTaskViewButton", 0)] + // taskbar_end_task + [InlineData("taskbar_end_task", "true", "TaskbarEndTask", 1)] + [InlineData("taskbar_end_task", "false", "TaskbarEndTask", 0)] + // start_show_recent + [InlineData("start_show_recent", "true", "Start_TrackDocs", 1)] + [InlineData("start_show_recent", "false", "Start_TrackDocs", 0)] + // snap_assist_flyout + [InlineData("snap_assist_flyout", "true", "EnableSnapAssistFlyout", 1)] + [InlineData("snap_assist_flyout", "false", "EnableSnapAssistFlyout", 0)] + public async Task GetTweaksAsync_Should_Return_Expected_Tweak(string key, string value, string expectedTweakName, object expectedValue) + { + var settings = new Dictionary { { key, value } }; + var tweaks = await _service.GetTweaksAsync(settings); + var tweaksList = new List(tweaks); + + Assert.Contains(tweaksList, t => t.Name == expectedTweakName && t.Value.Equals(expectedValue)); + } + + [Theory] + [InlineData("true", 0x03)] + [InlineData("false", 0x02)] + public async Task GetTweaksAsync_Should_Return_TaskbarAutoHide_Tweaks(string value, byte expectedByte) + { + var settings = new Dictionary { { "taskbar_autohide", value } }; + var tweaks = await _service.GetTweaksAsync(settings); + var tweaksList = new List(tweaks); + Assert.Single(tweaksList); Assert.Equal("Settings", tweaksList[0].Name); Assert.Equal("binary", tweaksList[0].Type); Assert.IsType(tweaksList[0].Value); var byteVal = (byte[])tweaksList[0].Value; - Assert.Equal(0x03, byteVal[8]); // 9th byte is 0x03 for auto-hide enable + Assert.Equal(expectedByte, byteVal[8]); } - [Fact] - public async Task GetCapturedSettingsAsync_Should_Capture_TaskbarAutoHide() + [Theory] + // taskbar_alignment + [InlineData("taskbar_alignment", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarAl", 0, "left")] + [InlineData("taskbar_alignment", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarAl", 1, "center")] + // taskbar_widgets + [InlineData("taskbar_widgets", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarDa", 0, "hide")] + [InlineData("taskbar_widgets", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarDa", 1, "show")] + // show_file_extensions + [InlineData("show_file_extensions", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "HideFileExt", 0, true)] + [InlineData("show_file_extensions", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "HideFileExt", 1, false)] + // show_hidden_files + [InlineData("show_hidden_files", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "Hidden", 1, true)] + [InlineData("show_hidden_files", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "Hidden", 2, false)] + // seconds_in_clock + [InlineData("seconds_in_clock", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "ShowSecondsInSystemClock", 1, true)] + [InlineData("seconds_in_clock", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "ShowSecondsInSystemClock", 0, false)] + // explorer_launch_to + [InlineData("explorer_launch_to", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "LaunchTo", 1, "this_pc")] + [InlineData("explorer_launch_to", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "LaunchTo", 2, "quick_access")] + // bing_search_enabled + [InlineData("bing_search_enabled", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Search", "BingSearchEnabled", 1, true)] + [InlineData("bing_search_enabled", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Search", "BingSearchEnabled", 0, false)] + // transparency + [InlineData("transparency", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "EnableTransparency", 1, true)] + [InlineData("transparency", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "EnableTransparency", 0, false)] + // taskbar_task_view + [InlineData("taskbar_task_view", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "ShowTaskViewButton", 1, true)] + [InlineData("taskbar_task_view", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "ShowTaskViewButton", 0, false)] + // taskbar_end_task + [InlineData("taskbar_end_task", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarEndTask", 1, true)] + [InlineData("taskbar_end_task", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarEndTask", 0, false)] + // start_show_recent + [InlineData("start_show_recent", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "Start_TrackDocs", 1, true)] + [InlineData("start_show_recent", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "Start_TrackDocs", 0, false)] + // snap_assist_flyout + [InlineData("snap_assist_flyout", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "EnableSnapAssistFlyout", 1, true)] + [InlineData("snap_assist_flyout", @"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "EnableSnapAssistFlyout", 0, false)] + public async Task GetCapturedSettingsAsync_Should_Capture_Setting(string key, string path, string name, object registryValue, object expectedCapturedValue) { - var mockBytes = new byte[] { 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; + _mockRegistryService.Setup(r => r.Read(path, name)).Returns(registryValue); + + var captured = await _service.GetCapturedSettingsAsync(); + + Assert.True(captured.ContainsKey(key)); + Assert.Equal(expectedCapturedValue, captured[key]); + } + + [Theory] + [InlineData(0, 0, true)] + [InlineData(1, 1, false)] + public async Task GetCapturedSettingsAsync_Should_Capture_DarkMode(int appsValue, int systemValue, bool expected) + { + _mockRegistryService + .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme")) + .Returns(appsValue); + _mockRegistryService + .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "SystemUsesLightTheme")) + .Returns(systemValue); + + var captured = await _service.GetCapturedSettingsAsync(); + + Assert.True(captured.ContainsKey("dark_mode")); + Assert.Equal(expected, captured["dark_mode"]); + } + + [Theory] + [InlineData(0x03, true)] + [InlineData(0x02, false)] + public async Task GetCapturedSettingsAsync_Should_Capture_TaskbarAutoHide(byte settingByte, bool expected) + { + var mockBytes = new byte[] { 0x30, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, settingByte, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 }; _mockRegistryService .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects3", "Settings")) @@ -137,53 +293,64 @@ public async Task GetCapturedSettingsAsync_Should_Capture_TaskbarAutoHide() var captured = await _service.GetCapturedSettingsAsync(); Assert.True(captured.ContainsKey("taskbar_autohide")); - Assert.Equal(true, captured["taskbar_autohide"]); + Assert.Equal(expected, captured["taskbar_autohide"]); + } + + [Fact] + public async Task GetTweaksAsync_Should_Return_Empty_On_Null_Settings() + { + var tweaks = await _service.GetTweaksAsync(null); + Assert.NotNull(tweaks); + Assert.Empty(tweaks); + } + + [Fact] + public async Task GetTweaksAsync_Should_Return_Empty_On_Empty_Settings() + { + var tweaks = await _service.GetTweaksAsync(new Dictionary()); + Assert.NotNull(tweaks); + Assert.Empty(tweaks); } [Fact] - public async Task GetTweaksAsync_Should_Return_Remaining_Custom_Tweaks() + public async Task GetTweaksAsync_Should_Ignore_Unknown_Settings() { var settings = new Dictionary { - { "taskbar_task_view", "true" }, - { "taskbar_end_task", "true" }, - { "start_show_recent", "true" }, - { "snap_assist_flyout", "true" } + { "some_unknown_setting_xyz", "true" } }; - var tweaks = await _service.GetTweaksAsync(settings); - var tweaksList = new List(tweaks); - - Assert.Equal(4, tweaksList.Count); + Assert.NotNull(tweaks); + Assert.Empty(tweaks); + } - Assert.Contains(tweaksList, t => t.Name == "ShowTaskViewButton" && t.Value.Equals(1)); - Assert.Contains(tweaksList, t => t.Name == "TaskbarEndTask" && t.Value.Equals(1)); - Assert.Contains(tweaksList, t => t.Name == "Start_TrackDocs" && t.Value.Equals(1)); - Assert.Contains(tweaksList, t => t.Name == "EnableSnapAssistFlyout" && t.Value.Equals(1)); + [Fact] + public async Task GetTweaksAsync_Should_Ignore_Invalid_Values() + { + var settings = new Dictionary + { + { "taskbar_alignment", "invalid_value" } + }; + var tweaks = await _service.GetTweaksAsync(settings); + Assert.NotNull(tweaks); + Assert.Empty(tweaks); } [Fact] - public async Task GetCapturedSettingsAsync_Should_Capture_Remaining_Custom_Settings() + public void GetFriendlyName_Should_Return_Correct_Key_For_Known_Registry_Tweak() { - _mockRegistryService - .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "ShowTaskViewButton")) - .Returns(1); - _mockRegistryService - .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarEndTask")) - .Returns(1); - _mockRegistryService - .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "Start_TrackDocs")) - .Returns(1); - _mockRegistryService - .Setup(r => r.Read(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "EnableSnapAssistFlyout")) - .Returns(1); + var key1 = _service.GetFriendlyName(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme"); + Assert.Equal("dark_mode", key1); - var captured = await _service.GetCapturedSettingsAsync(); + var key2 = _service.GetFriendlyName(@"HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", "TaskbarAl"); + Assert.Equal("taskbar_alignment", key2); + } - Assert.True((bool)captured["taskbar_task_view"]); - Assert.True((bool)captured["taskbar_end_task"]); - Assert.True((bool)captured["start_show_recent"]); - Assert.True((bool)captured["snap_assist_flyout"]); + [Fact] + public void GetFriendlyName_Should_Return_Null_For_Unknown_Registry_Tweak() + { + var key = _service.GetFriendlyName(@"HKCU\Unknown\Path", "UnknownName"); + Assert.Null(key); } } }