From d85657d64b2eef9e13668bf04cea573887fc3ae8 Mon Sep 17 00:00:00 2001 From: Doug Slater Date: Wed, 18 Jun 2025 05:59:32 -0400 Subject: [PATCH] Various chores: - Add BrowserServiceTests - Extract some services and interfaces out of Program.cs - Ship comments in json config - Use `unescape` macro in MS Teams replacement template - Fix spurious EditorConfig warning - Switch GH runner --- .editorconfig | 8 ++ .github/workflows/windows.yml | 2 +- .../Config/{ => Loaders}/IConfigLoader.cs | 2 +- .../Config/{ => Loaders}/IniConfigLoader.cs | 2 +- .../Config/{ => Loaders}/JsonConfigLoader.cs | 2 +- .../ConfigServiceFactory.cs | 6 +- BrowseRouter/Interop/Win32/Kernel32.cs | 3 - BrowseRouter/Program.cs | 40 +++++---- BrowseRouter/Services/BrowserService.cs | 18 ++-- .../{Config => Services}/ConfigService.cs | 5 +- BrowseRouter/Services/LaunchService.cs | 23 ++++++ BrowseRouter/Services/NotifyService.cs | 13 +-- BrowseRouter/Services/ProcessService.cs | 82 ++++++++++--------- BrowseRouter/Services/WindowTitleService.cs | 13 +++ BrowseRouter/config.json | 3 +- BrowseRouter/filters.json | 3 +- Directory.Build.props | 2 +- .../UrlPreferenceExtensionsTests.cs | 2 +- .../Services/BrowserServiceTests.cs | 37 +++++++++ .../{ => Services}/ConfigServiceTests.cs | 9 +- .../TestDoubles/SpyProcessService.cs | 21 +++++ Tests/BrowseRouter.Tests/Usings.cs | 2 +- 22 files changed, 204 insertions(+), 94 deletions(-) rename BrowseRouter/Config/{ => Loaders}/IConfigLoader.cs (61%) rename BrowseRouter/Config/{ => Loaders}/IniConfigLoader.cs (97%) rename BrowseRouter/Config/{ => Loaders}/JsonConfigLoader.cs (87%) rename BrowseRouter/{Config => Infrastructure}/ConfigServiceFactory.cs (78%) rename BrowseRouter/{Config => Services}/ConfigService.cs (94%) create mode 100644 BrowseRouter/Services/LaunchService.cs create mode 100644 BrowseRouter/Services/WindowTitleService.cs rename Tests/BrowseRouter.Tests/{ => Config}/UrlPreferenceExtensionsTests.cs (98%) create mode 100644 Tests/BrowseRouter.Tests/Services/BrowserServiceTests.cs rename Tests/BrowseRouter.Tests/{ => Services}/ConfigServiceTests.cs (88%) create mode 100644 Tests/BrowseRouter.Tests/TestDoubles/SpyProcessService.cs diff --git a/.editorconfig b/.editorconfig index 938ad15..ea31bbe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -60,6 +60,10 @@ dotnet_naming_rule.interface_names_must_begin_with_I.symbols = interfaces dotnet_naming_rule.interface_names_must_begin_with_I.style = pascal_begin_with_I_style dotnet_naming_rule.interface_names_must_begin_with_I.severity = warning +dotnet_naming_rule.private_const_fields_none.symbols = private_const_fields +dotnet_naming_rule.private_const_fields_none.style = camel_begin_with_underscore_style +dotnet_naming_rule.private_const_fields_none.severity = none + dotnet_naming_rule.private_and_protected_fields_must_begin_with_underscore.symbols = private_fields dotnet_naming_rule.private_and_protected_fields_must_begin_with_underscore.style = camel_begin_with_underscore_style dotnet_naming_rule.private_and_protected_fields_must_begin_with_underscore.severity = warning @@ -79,6 +83,10 @@ dotnet_naming_symbols.interfaces.applicable_accessibilities = * dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private,protected,protected_internal +dotnet_naming_symbols.private_const_fields.applicable_kinds = field +dotnet_naming_symbols.private_const_fields.applicable_accessibilities = private, protected, protected_internal +dotnet_naming_symbols.private_const_fields.required_modifiers = const + dotnet_naming_symbols.public_and_internal_members.applicable_kinds = class,struct,enum,property,method,event,delegate,field dotnet_naming_symbols.public_and_internal_members.applicable_accessibilities = public,internal diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index be4ae53..07c521f 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -13,7 +13,7 @@ concurrency: jobs: Build: name: Build on self-hosted - runs-on: m2mini-win11 + runs-on: M4700 permissions: contents: write defaults: diff --git a/BrowseRouter/Config/IConfigLoader.cs b/BrowseRouter/Config/Loaders/IConfigLoader.cs similarity index 61% rename from BrowseRouter/Config/IConfigLoader.cs rename to BrowseRouter/Config/Loaders/IConfigLoader.cs index 82ae51b..4f45877 100644 --- a/BrowseRouter/Config/IConfigLoader.cs +++ b/BrowseRouter/Config/Loaders/IConfigLoader.cs @@ -1,4 +1,4 @@ -namespace BrowseRouter.Config; +namespace BrowseRouter.Config.Loaders; internal interface IConfigLoader { diff --git a/BrowseRouter/Config/IniConfigLoader.cs b/BrowseRouter/Config/Loaders/IniConfigLoader.cs similarity index 97% rename from BrowseRouter/Config/IniConfigLoader.cs rename to BrowseRouter/Config/Loaders/IniConfigLoader.cs index fc680ef..a39c469 100644 --- a/BrowseRouter/Config/IniConfigLoader.cs +++ b/BrowseRouter/Config/Loaders/IniConfigLoader.cs @@ -1,4 +1,4 @@ -namespace BrowseRouter.Config; +namespace BrowseRouter.Config.Loaders; internal class IniConfigLoader(string path) : IConfigLoader { diff --git a/BrowseRouter/Config/JsonConfigLoader.cs b/BrowseRouter/Config/Loaders/JsonConfigLoader.cs similarity index 87% rename from BrowseRouter/Config/JsonConfigLoader.cs rename to BrowseRouter/Config/Loaders/JsonConfigLoader.cs index ce07568..80dc996 100644 --- a/BrowseRouter/Config/JsonConfigLoader.cs +++ b/BrowseRouter/Config/Loaders/JsonConfigLoader.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace BrowseRouter.Config; +namespace BrowseRouter.Config.Loaders; internal class JsonConfigLoader(string path) : IConfigLoader { diff --git a/BrowseRouter/Config/ConfigServiceFactory.cs b/BrowseRouter/Infrastructure/ConfigServiceFactory.cs similarity index 78% rename from BrowseRouter/Config/ConfigServiceFactory.cs rename to BrowseRouter/Infrastructure/ConfigServiceFactory.cs index 4015f5e..09e2363 100644 --- a/BrowseRouter/Config/ConfigServiceFactory.cs +++ b/BrowseRouter/Infrastructure/ConfigServiceFactory.cs @@ -1,6 +1,8 @@ -using BrowseRouter.Infrastructure; +using BrowseRouter.Config; +using BrowseRouter.Config.Loaders; +using BrowseRouter.Services; -namespace BrowseRouter.Config; +namespace BrowseRouter.Infrastructure; public static class ConfigServiceFactory { diff --git a/BrowseRouter/Interop/Win32/Kernel32.cs b/BrowseRouter/Interop/Win32/Kernel32.cs index ba0a087..255eb64 100644 --- a/BrowseRouter/Interop/Win32/Kernel32.cs +++ b/BrowseRouter/Interop/Win32/Kernel32.cs @@ -12,8 +12,5 @@ public static class Kernel32 [DllImport("kernel32.dll")] private static extern bool AttachConsole(uint dwProcessId); - /// - /// This enables e.g. showing --help in Terminal / cmd text even though this is a WinExe app. - /// public static void AttachToParentConsole() => AttachConsole(ATTACH_PARENT_PROCESS); } \ No newline at end of file diff --git a/BrowseRouter/Program.cs b/BrowseRouter/Program.cs index b483016..fb5c583 100644 --- a/BrowseRouter/Program.cs +++ b/BrowseRouter/Program.cs @@ -1,5 +1,4 @@ -using System.Runtime.InteropServices; -using BrowseRouter.Config; +using BrowseRouter.Config; using BrowseRouter.Infrastructure; using BrowseRouter.Interop.Win32; using BrowseRouter.Services; @@ -8,14 +7,14 @@ namespace BrowseRouter; public static class Program { - private static async Task Main(string[] args) { + // This enables e.g. showing --help in Terminal / cmd text even though this is a WinExe app. Kernel32.AttachToParentConsole(); if (args.Length == 0) { - await new DefaultBrowserService(new NotifyService(false)).RegisterOrUnregisterAsync(); + await new DefaultBrowserService(new NotifyService()).RegisterOrUnregisterAsync(); return; } @@ -28,24 +27,25 @@ private static async Task Main(string[] args) private static async Task RunAsync(string arg) { - Func getIsOption = () => arg.StartsWith('-') || arg.StartsWith('/'); - - bool isOption = getIsOption(); - while (getIsOption()) + bool isOption = GetIsOption(arg); + while (GetIsOption(arg)) { arg = arg[1..]; } if (isOption) { - await RunOption(arg); + await RunOptionAsync(arg); return; } await LaunchUrlAsyc(arg); + } - private static async Task RunOption(string arg) + private static bool GetIsOption(string arg) => arg.StartsWith('-') || arg.StartsWith('/'); + + private static async Task RunOptionAsync(string arg) { if (string.Equals(arg, "h") || string.Equals(arg, "help")) { @@ -55,13 +55,13 @@ private static async Task RunOption(string arg) if (string.Equals(arg, "r") || string.Equals(arg, "register")) { - await new DefaultBrowserService(new NotifyService(false)).RegisterAsync(); + await new DefaultBrowserService(new NotifyService()).RegisterAsync(); return true; } if (string.Equals(arg, "u") || string.Equals(arg, "unregister")) { - await new DefaultBrowserService(new NotifyService(false)).UnregisterAsync(); + await new DefaultBrowserService(new NotifyService()).UnregisterAsync(); return true; } @@ -70,22 +70,20 @@ private static async Task RunOption(string arg) private static async Task LaunchUrlAsyc(string url) { - // Get the window title for whichever application is opening the URL. - ProcessService processService = new(); - if (!processService.TryGetParentProcessTitle(out string windowTitle)) - windowTitle = User32.GetActiveWindowTitle(); //if it didn't work we get the current foreground window name instead - - IConfigService configService = await ConfigServiceFactory.CreateAsync(); - Log.Preference = configService.GetLogPreference(); + IConfigService config = await ConfigServiceFactory.CreateAsync(); + Log.Preference = config.GetLogPreference(); - NotifyPreference notifyPref = configService.GetNotifyPreference(); + NotifyPreference notifyPref = config.GetNotifyPreference(); INotifyService notifier = notifyPref.IsEnabled switch { true => new NotifyService(notifyPref.IsSilent), false => new EmptyNotifyService() }; - await new BrowserService(configService, notifier).LaunchAsync(url, windowTitle); + IBrowserService browsers = new BrowserService(config, notifier, new ProcessService()); + ILaunchService launchService = new LaunchService(browsers, new ProcessService(), new WindowTitleService()); + + await launchService.LaunchUrlAsyc(url); } private static void ShowHelp() diff --git a/BrowseRouter/Services/BrowserService.cs b/BrowseRouter/Services/BrowserService.cs index 2aa9858..9fdcc2a 100644 --- a/BrowseRouter/Services/BrowserService.cs +++ b/BrowseRouter/Services/BrowserService.cs @@ -1,11 +1,15 @@ -using System.Diagnostics; -using BrowseRouter.Config; +using BrowseRouter.Config; using BrowseRouter.Infrastructure; using BrowseRouter.Model; namespace BrowseRouter.Services; -public class BrowserService(IConfigService config, INotifyService notifier) +public interface IBrowserService +{ + Task LaunchAsync(string rawUrl, string windowTitle); +} + +public class BrowserService(IConfigService config, INotifyService notifier, IProcessService process) : IBrowserService { public async Task LaunchAsync(string rawUrl, string windowTitle) { @@ -16,7 +20,7 @@ public async Task LaunchAsync(string rawUrl, string windowTitle) List filters = await config.GetFiltersAsync(); bool didFilter = FilterPreference.TryApply(filters, rawUrl, out string url); - + if (didFilter) Log.Write($"Filtered URL: {rawUrl} -> {url}"); @@ -50,16 +54,16 @@ public async Task LaunchAsync(string rawUrl, string windowTitle) Log.Write($"Launching {path} with args \"{args}\""); string name = GetAppName(path); - + path = Environment.ExpandEnvironmentVariables(path); - if (!Actions.TryRun(() => Process.Start(path, args))) + if (!Actions.TryRun(() => process.Start(path, args))) { await notifier.NotifyAsync($"Error", $"Could not open {name}. Please check the log for more details."); return; } - await notifier.NotifyAsync($"Opening {name}", $"{(didFilter ? "Filtered ":"")}URL: {url}"); + await notifier.NotifyAsync($"Opening {name}", $"{(didFilter ? "Filtered " : "")}URL: {url}"); } catch (Exception e) { diff --git a/BrowseRouter/Config/ConfigService.cs b/BrowseRouter/Services/ConfigService.cs similarity index 94% rename from BrowseRouter/Config/ConfigService.cs rename to BrowseRouter/Services/ConfigService.cs index a75dc5d..bf7668b 100644 --- a/BrowseRouter/Config/ConfigService.cs +++ b/BrowseRouter/Services/ConfigService.cs @@ -1,10 +1,11 @@ using System.Text.Json; +using BrowseRouter.Config; using BrowseRouter.Infrastructure; using BrowseRouter.Model; -namespace BrowseRouter.Config; +namespace BrowseRouter.Services; -internal class ConfigService(Config config) : IConfigService +internal class ConfigService(Config.Config config) : IConfigService { public NotifyPreference GetNotifyPreference() => new() { diff --git a/BrowseRouter/Services/LaunchService.cs b/BrowseRouter/Services/LaunchService.cs new file mode 100644 index 0000000..dd62ebb --- /dev/null +++ b/BrowseRouter/Services/LaunchService.cs @@ -0,0 +1,23 @@ +namespace BrowseRouter.Services; + +public interface ILaunchService +{ + Task LaunchUrlAsyc(string url); +} + +public class LaunchService( + IBrowserService browsers, + IProcessService processes, + IWindowTitleService titles +) : ILaunchService +{ + public async Task LaunchUrlAsyc(string url) + { + // Get the window title for whichever application is opening the URL. + if (!processes.TryGetParentProcessTitle(out string windowTitle)) + // If it didn't work, fallback to the current foreground window name + windowTitle = titles.GetWindowTitle(); + + await browsers.LaunchAsync(url, windowTitle); + } +} diff --git a/BrowseRouter/Services/NotifyService.cs b/BrowseRouter/Services/NotifyService.cs index 3520f5f..7fbbb5e 100644 --- a/BrowseRouter/Services/NotifyService.cs +++ b/BrowseRouter/Services/NotifyService.cs @@ -23,13 +23,14 @@ public class NotifyService : INotifyService private static nint _hIcon; private static nint _hInstance = Kernel32.GetModuleHandle(App.ExePath); - private readonly bool _isSilent; + private readonly bool _noSound; - public NotifyService(bool isSilent) + public NotifyService(bool noSound = false) { - _isSilent = isSilent; + _noSound = noSound; LoadIcon(); } + private static bool LoadIcon(int size = 512) => Comctl32.LoadIconWithScaleDown(_hInstance, Icon.Application, size, size, out _hIcon) != 0; @@ -38,7 +39,7 @@ public async Task NotifyAsync(string title, string message) // Create a dummy window handle nint hWnd = CreateDummyWindow(); - NotifyIconData nid = GetNid(hWnd, title, message, _isSilent); + NotifyIconData nid = GetNid(hWnd, title, message, _noSound); // Add the icon. This also adds it to the system tray. Shell32.Shell_NotifyIcon(Shell32.NIM_ADD, ref nid); @@ -76,7 +77,7 @@ public void Remove(NotifyIconData nid) Shell32.Shell_NotifyIcon(Shell32.NIM_DELETE, ref nid); } - private static NotifyIconData GetNid(nint hWnd, string title, string message, bool isSilent) => new NotifyIconData + private static NotifyIconData GetNid(nint hWnd, string title, string message, bool noSound) => new NotifyIconData { cbSize = Marshal.SizeOf(typeof(NotifyIconData)), hWnd = hWnd, @@ -88,7 +89,7 @@ public void Remove(NotifyIconData nid) szTip = "BrowseRouter", szInfo = message, szInfoTitle = title, - dwInfoFlags = Shell32.NIIF_USER | Shell32.NIIF_LARGE_ICON | (isSilent ? Shell32.NIIF_NOSOUND : 0x00000000), + dwInfoFlags = Shell32.NIIF_USER | Shell32.NIIF_LARGE_ICON | (noSound ? Shell32.NIIF_NOSOUND : 0x00000000), dwState = 0, // For the popup to be shown, the system tray icon must not be hidden, but we can hide it immediately after dwStateMask = Shell32.NIS_HIDDEN, uVersion = Shell32.NOTIFYICON_VERSION_4 diff --git a/BrowseRouter/Services/ProcessService.cs b/BrowseRouter/Services/ProcessService.cs index 0aa97c6..c66bc5d 100644 --- a/BrowseRouter/Services/ProcessService.cs +++ b/BrowseRouter/Services/ProcessService.cs @@ -1,55 +1,57 @@ -using System.ComponentModel; -using System.Diagnostics; -using System.Runtime.InteropServices; +using System.Diagnostics; using BrowseRouter.Interop.Win32; -namespace BrowseRouter.Services +namespace BrowseRouter.Services; + +public interface IProcessService { + void Start(string path, string args); + + /// + /// Try to get the name of the parent process of the current process. + /// + /// The name of the parent process main window title (may be empty) and the specific process name. + /// True if the name was succesfully found, False otherwise. + bool TryGetParentProcessTitle(out string parentProcessTitle); +} - public interface IProcessService +public class ProcessService : IProcessService +{ + public void Start(string path, string args) { - /// - /// Try to get the name of the parent process of the current process. - /// - /// The name of the parent process main window title (may be empty) and the specific process name. - /// True if the name was succesfully found, False otherwise. - bool TryGetParentProcessTitle(out string parentProcessTitle); + Process.Start(path, args); } - public class ProcessService : IProcessService + public bool TryGetParentProcessTitle(out string parentProcessTitle) { - public bool TryGetParentProcessTitle(out string parentProcessTitle) + Process? parentProcess = GetParentProcess(); + if (parentProcess is null || parentProcess.MainWindowTitle == string.Empty && parentProcess.ProcessName == string.Empty) { - Process? parentProcess = GetParentProcess(); - if (parentProcess is null || parentProcess.MainWindowTitle == string.Empty && parentProcess.ProcessName == string.Empty) - { - parentProcessTitle = string.Empty; - return false; - } - - parentProcessTitle = parentProcess.MainWindowTitle + " -> " + parentProcess.ProcessName; - return true; + parentProcessTitle = string.Empty; + return false; } - /// - /// Gets the parent process of the current process. - /// - /// An instance of the Process class. - public static Process? GetParentProcess() - { - return BasicProcessInfo.GetParentProcess(Process.GetCurrentProcess().Handle); - } + parentProcessTitle = parentProcess.MainWindowTitle + " -> " + parentProcess.ProcessName; + return true; + } - /// - /// Gets the parent process of specified process. - /// - /// The process id. - /// An instance of the Process class. - public static Process? GetParentProcess(int id) - { - Process process = Process.GetProcessById(id); - return BasicProcessInfo.GetParentProcess(process.Handle); - } + /// + /// Gets the parent process of the current process. + /// + /// An instance of the Process class. + public static Process? GetParentProcess() + { + return BasicProcessInfo.GetParentProcess(Process.GetCurrentProcess().Handle); + } + /// + /// Gets the parent process of specified process. + /// + /// The process id. + /// An instance of the Process class. + public static Process? GetParentProcess(int id) + { + Process process = Process.GetProcessById(id); + return BasicProcessInfo.GetParentProcess(process.Handle); } } diff --git a/BrowseRouter/Services/WindowTitleService.cs b/BrowseRouter/Services/WindowTitleService.cs new file mode 100644 index 0000000..ec15fc5 --- /dev/null +++ b/BrowseRouter/Services/WindowTitleService.cs @@ -0,0 +1,13 @@ +using BrowseRouter.Interop.Win32; + +namespace BrowseRouter.Services; + +public interface IWindowTitleService +{ + string GetWindowTitle(); +} + +public class WindowTitleService : IWindowTitleService +{ + public string GetWindowTitle() => User32.GetActiveWindowTitle(); +} diff --git a/BrowseRouter/config.json b/BrowseRouter/config.json index 528a4de..f47132f 100644 --- a/BrowseRouter/config.json +++ b/BrowseRouter/config.json @@ -1,7 +1,8 @@ { + // Comments are supported with // syntax "notify": { "enabled": true, - "silent": false + "silent": false // Suppress sound }, "log": { "enabled": true diff --git a/BrowseRouter/filters.json b/BrowseRouter/filters.json index c6b7f72..1ccb46f 100644 --- a/BrowseRouter/filters.json +++ b/BrowseRouter/filters.json @@ -1,4 +1,5 @@ [ + // Comments are supported with // syntax { "name": "Remove urchin tracking", "find": "(.*)[&?]utm_source=[^&]+.*", @@ -8,7 +9,7 @@ { "name": "Bypass Teams Safelinks", "find": ".*teams\\.cdn\\.office\\.net.*url=([^&]+).*", - "replace": "$1", + "replace": "unescape($1)", // urldecode the URL. Cleans up the notification "priority": 2 }, { diff --git a/Directory.Build.props b/Directory.Build.props index e4e24c4..d1e0e08 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,7 +5,7 @@ enable enable latest - 0.15.4 + 0.15.5 BrowseRouter EnduraByte LLC 2025 diff --git a/Tests/BrowseRouter.Tests/UrlPreferenceExtensionsTests.cs b/Tests/BrowseRouter.Tests/Config/UrlPreferenceExtensionsTests.cs similarity index 98% rename from Tests/BrowseRouter.Tests/UrlPreferenceExtensionsTests.cs rename to Tests/BrowseRouter.Tests/Config/UrlPreferenceExtensionsTests.cs index ba8f70d..95ec54e 100644 --- a/Tests/BrowseRouter.Tests/UrlPreferenceExtensionsTests.cs +++ b/Tests/BrowseRouter.Tests/Config/UrlPreferenceExtensionsTests.cs @@ -1,7 +1,7 @@ using BrowseRouter.Config; using BrowseRouter.Model; -namespace BrowserRouter.Tests; +namespace BrowseRouter.Tests.Config; public class UrlPreferenceExtensionsTests { diff --git a/Tests/BrowseRouter.Tests/Services/BrowserServiceTests.cs b/Tests/BrowseRouter.Tests/Services/BrowserServiceTests.cs new file mode 100644 index 0000000..e650e44 --- /dev/null +++ b/Tests/BrowseRouter.Tests/Services/BrowserServiceTests.cs @@ -0,0 +1,37 @@ +using BrowseRouter.Config; +using BrowseRouter.Services; + +namespace BrowseRouter.Tests.Services; + +public class BrowserServiceTests : IAsyncLifetime +{ + private BrowseRouter.Config.Config _config = null!; // Set in InitializeAsync + + public Task InitializeAsync() + { + _config = BrowseRouter.Config.Config.Empty with + { + Browsers = new Dictionary + { + ["fake"] = "fake-browser.exe", + }, + }; + + CatchAllConfig.AddTo(_config); + return Task.CompletedTask; + } + + public Task DisposeAsync() => Task.CompletedTask; + + [Theory] + [InlineData("https://example.com")] + [InlineData("https://statics.teams.cdn.office.net/evergreen-assets/safelinks/1/atp-safelinks.html?url=https%3A%2F%2Fwww.example.org%2Fpath%2F")] + public async Task HandlesUrl(string url) + { + var spy = new SpyProcessService(); + await new BrowserService(new ConfigService(_config), new EmptyNotifyService(), spy) + .LaunchAsync(url, "Fake Window"); + + spy.LastPath.Should().Be("fake-browser.exe"); + } +} \ No newline at end of file diff --git a/Tests/BrowseRouter.Tests/ConfigServiceTests.cs b/Tests/BrowseRouter.Tests/Services/ConfigServiceTests.cs similarity index 88% rename from Tests/BrowseRouter.Tests/ConfigServiceTests.cs rename to Tests/BrowseRouter.Tests/Services/ConfigServiceTests.cs index b8a6765..5e7da0b 100644 --- a/Tests/BrowseRouter.Tests/ConfigServiceTests.cs +++ b/Tests/BrowseRouter.Tests/Services/ConfigServiceTests.cs @@ -1,13 +1,14 @@ using BrowseRouter.Config; +using BrowseRouter.Services; -namespace BrowserRouter.Tests; +namespace BrowseRouter.Tests.Services; public class ConfigServiceTests { [Fact] public void GetUrlPreferences_NoBrowsers_ReturnsEmpty() { - var config = Config.Empty with + var config = BrowseRouter.Config.Config.Empty with { Browsers = [] }; @@ -20,7 +21,7 @@ public void GetUrlPreferences_NoBrowsers_ReturnsEmpty() [Fact] public void GetUrlPreferences_LoadsUrls() { - var config = Config.Empty with + var config = BrowseRouter.Config.Config.Empty with { Browsers = new Dictionary { @@ -54,7 +55,7 @@ public void GetUrlPreferences_LoadsUrls() [Fact] public void GetUrlPreferences_ConfigTypeUrls_AppendsCatchAll() { - var config = Config.Empty with + var config = BrowseRouter.Config.Config.Empty with { Browsers = new Dictionary { diff --git a/Tests/BrowseRouter.Tests/TestDoubles/SpyProcessService.cs b/Tests/BrowseRouter.Tests/TestDoubles/SpyProcessService.cs new file mode 100644 index 0000000..f002bab --- /dev/null +++ b/Tests/BrowseRouter.Tests/TestDoubles/SpyProcessService.cs @@ -0,0 +1,21 @@ +using BrowseRouter.Services; + +namespace BrowseRouter.Tests.TestDoubles; + +internal class SpyProcessService : IProcessService +{ + public string? LastPath { get; private set; } + public string? LastArgs { get; private set; } + + public void Start(string path, string args) + { + LastPath = path; + LastArgs = args; + } + + public bool TryGetParentProcessTitle(out string parentProcessTitle) + { + parentProcessTitle = string.Empty; + return false; + } +} diff --git a/Tests/BrowseRouter.Tests/Usings.cs b/Tests/BrowseRouter.Tests/Usings.cs index 04106bd..b36ecd1 100644 --- a/Tests/BrowseRouter.Tests/Usings.cs +++ b/Tests/BrowseRouter.Tests/Usings.cs @@ -1,3 +1,3 @@ -global using BrowseRouter; +global using BrowseRouter.Tests.TestDoubles; global using Xunit; global using FluentAssertions;