From 6352fa9809eb63c8a56ec7c53f8f0521f28751a4 Mon Sep 17 00:00:00 2001 From: Michael Jolley Date: Thu, 19 Feb 2026 22:16:10 -0600 Subject: [PATCH] Fixing slnx and slnf --- PowerToys.slnx | 8 + src/modules/cmdpal/CommandPalette.slnf | 1 + .../GlobalUsings.cs | 9 - .../Microsoft.CmdPal.Common.UnitTests.csproj | 25 -- .../ConnectionStringRuleProviderTests.cs | 107 ------- .../ErrorReportSanitizerTests.TestData.cs | 131 --------- .../Sanitizer/ErrorReportSanitizerTests.cs | 39 --- .../FilenameMaskRuleProviderTests.cs | 62 ---- .../Sanitizer/PiiRuleProviderTests.cs | 126 --------- .../SecretKeyValueRulesProviderTests.cs | 266 ------------------ .../TestUtils/SanitizerTestHelper.cs | 110 -------- .../Text/PrecomputedFuzzyMatcherEmojiTests.cs | 78 ----- .../PrecomputedFuzzyMatcherOptionsTests.cs | 84 ------ ...computedFuzzyMatcherSecondaryInputTests.cs | 227 --------------- .../Text/PrecomputedFuzzyMatcherTests.cs | 209 -------------- .../PrecomputedFuzzyMatcherUnicodeTests.cs | 124 -------- .../PrecomputedFuzzyMatcherWithPinyinTests.cs | 117 -------- .../Text/StringFolderTests.cs | 55 ---- 18 files changed, 9 insertions(+), 1769 deletions(-) delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/GlobalUsings.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Common.UnitTests.csproj delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ConnectionStringRuleProviderTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.TestData.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/FilenameMaskRuleProviderTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/PiiRuleProviderTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/SecretKeyValueRulesProviderTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/TestUtils/SanitizerTestHelper.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherEmojiTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherOptionsTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherSecondaryInputTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherUnicodeTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherWithPinyinTests.cs delete mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/StringFolderTests.cs diff --git a/PowerToys.slnx b/PowerToys.slnx index f6ceadcb65..185d0fac24 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -196,6 +196,10 @@ + + + + @@ -295,6 +299,10 @@ + + + + diff --git a/src/modules/cmdpal/CommandPalette.slnf b/src/modules/cmdpal/CommandPalette.slnf index fc0288cc73..6ac089f0b4 100644 --- a/src/modules/cmdpal/CommandPalette.slnf +++ b/src/modules/cmdpal/CommandPalette.slnf @@ -15,6 +15,7 @@ "src\\modules\\cmdpal\\Microsoft.CmdPal.UI.ViewModels\\Microsoft.CmdPal.UI.ViewModels.csproj", "src\\modules\\cmdpal\\Microsoft.CmdPal.UI\\Microsoft.CmdPal.UI.csproj", "src\\modules\\cmdpal\\Microsoft.Terminal.UI\\Microsoft.Terminal.UI.vcxproj", + "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Common.UnitTests\\Microsoft.CmdPal.Common.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Apps.UnitTests\\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Calc.UnitTests\\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/GlobalUsings.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/GlobalUsings.cs deleted file mode 100644 index 022cf98f31..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/GlobalUsings.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -global using System; -global using System.Collections.Generic; -global using System.Diagnostics.CodeAnalysis; -global using System.Linq; -global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Common.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Common.UnitTests.csproj deleted file mode 100644 index b85df53da4..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Microsoft.CmdPal.Common.UnitTests.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - false - true - Microsoft.CmdPal.Common.UnitTests - $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\ - false - false - enable - preview - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ConnectionStringRuleProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ConnectionStringRuleProviderTests.cs deleted file mode 100644 index 1c9615e950..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ConnectionStringRuleProviderTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services.Sanitizer; -using Microsoft.CmdPal.Common.Services.Sanitizer.Abstraction; -using Microsoft.CmdPal.Common.UnitTests.TestUtils; - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -[TestClass] -public class ConnectionStringRuleProviderTests -{ - [TestMethod] - public void GetRules_ShouldReturnExpectedRules() - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var rules = provider.GetRules(); - - // Assert - var ruleList = new List(rules); - Assert.AreEqual(1, ruleList.Count); - Assert.AreEqual("Connection string parameters", ruleList[0].Description); - } - - [DataTestMethod] - [DataRow("Server=localhost;Database=mydb;User ID=admin;Password=secret123", "Server=[REDACTED];Database=[REDACTED];User ID=[REDACTED];Password=[REDACTED]")] - [DataRow("Data Source=server.example.com;Initial Catalog=testdb;Uid=user;Pwd=pass", "Data Source=[REDACTED];Initial Catalog=[REDACTED];Uid=[REDACTED];Pwd=[REDACTED]")] - [DataRow("Server=localhost;Password=my_secret", "Server=[REDACTED];Password=[REDACTED]")] - [DataRow("No connection string here", "No connection string here")] - public void ConnectionStringRules_ShouldMaskConnectionStringParameters(string input, string expected) - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("Password=\"complexPassword123!\"", "Password=[REDACTED]")] - [DataRow("Password='myPassword'", "Password=[REDACTED]")] - [DataRow("Password=unquotedSecret", "Password=[REDACTED]")] - public void ConnectionStringRules_ShouldHandleQuotedAndUnquotedValues(string input, string expected) - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("SERVER=server1;PASSWORD=pass1", "SERVER=[REDACTED];PASSWORD=[REDACTED]")] - [DataRow("server=server1;password=pass1", "server=[REDACTED];password=[REDACTED]")] - [DataRow("Server=server1;Password=pass1", "Server=[REDACTED];Password=[REDACTED]")] - public void ConnectionStringRules_ShouldBeCaseInsensitive(string input, string expected) - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("User ID=admin;Username=john;Password=secret", "User ID=[REDACTED];Username=[REDACTED];Password=[REDACTED]")] - [DataRow("Database=mydb;Uid=user1;Pwd=pass1;Server=localhost", "Database=[REDACTED];Uid=[REDACTED];Pwd=[REDACTED];Server=[REDACTED]")] - public void ConnectionStringRules_ShouldHandleMultipleParameters(string input, string expected) - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("Server = localhost ; Password = secret123", "Server=[REDACTED] ; Password=[REDACTED]")] - [DataRow("Initial Catalog=db; User ID=admin; Password=pass", "Initial Catalog=[REDACTED]; User ID=[REDACTED]; Password=[REDACTED]")] - public void ConnectionStringRules_ShouldHandleWhitespace(string input, string expected) - { - // Arrange - var provider = new ConnectionStringRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.TestData.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.TestData.cs deleted file mode 100644 index 54c7ba92d7..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.TestData.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -public partial class ErrorReportSanitizerTests -{ - internal static class TestData - { - internal static string Input => - $""" - HRESULT: 0x80004005 - HRESULT: -2147467259 - - Here is e-mail address - IPv4 address: 192.168.100.1 - IPv4 loopback address: 127.0.0.1 - MAC address: 00-14-22-01-23-45 - IPv6 address: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 - IPv6 loopback address: ::1 - Password: P@ssw0rd123! - Password=secret - Api key: 1234567890abcdef - PostgreSQL connection string: Host=localhost;Username=postgres;Password=secret;Database=mydb - InstrumentationKey=00000000-0000-0000-0000-000000000000;EndpointSuffix=ai.contoso.com; - X-API-key: 1234567890abcdef - Pet-Shop-Subscription-Key: 1234567890abcdef - Here is a user name {Environment.UserName} - And here is a profile path {Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}\RandomFolder - Here is a local app data path {Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Microsoft\PowerToys\CmdPal - Here is machine name {Environment.MachineName} - JWT token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30 - User email john.doe@company.com failed validation - File not found: {Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)}\\secret.txt - Connection string: Server=localhost;User ID=admin;Password=secret123;Database=test - Phone number 555-123-4567 is invalid - API key abc123def456ghi789jkl012mno345pqr678 expired - Failed to connect to https://api.internal-company.com/users/12345?token=secret_abc123 - Error accessing file://C:/Users/john.doe/Documents/confidential.pdf - JDBC connection failed: jdbc://database-server:5432/userdb?user=admin&password=secret - FTP upload error: ftp://internal-server.company.com/uploads/user_data.csv - Email service error: mailto:admin@internal-company.com?subject=Alert - """; - - public const string Expected = - $""" - HRESULT: 0x80004005 - HRESULT: -2147467259 - - Here is e-mail address <[EMAIL_REDACTED]> - IPv4 address: 192.168.100.1 - IPv4 loopback address: 127.0.0.1 - MAC address: [MAC_ADDRESS_REDACTED] - IPv6 address: [IP6_REDACTED] - IPv6 loopback address: [IP6_REDACTED] - Password: [REDACTED] - Password= [REDACTED] - Api key: [REDACTED] - PostgreSQL connection string: [REDACTED] - InstrumentationKey= [REDACTED] - X-API-key: [REDACTED] - Pet-Shop-Subscription-Key: [REDACTED] - Here is a user name [USERNAME_REDACTED] - And here is a profile path [USER_PROFILE_DIR]RandomFolder - Here is a local app data path [LOCALAPPLICATIONDATA_DIR]Microsoft\PowerToys\CmdPal - Here is machine name [MACHINE_NAME_REDACTED] - JWT token: [REDACTED] - User email [EMAIL_REDACTED] failed validation - File not found: [MYDOCUMENTS_DIR]se****.txt - Connection string: [REDACTED] ID=[REDACTED];Password= [REDACTED] - Phone number [PHONE_REDACTED] is invalid - API key [TOKEN_REDACTED] expired - Failed to connect to [URL_REDACTED] - Error accessing [URL_REDACTED] - JDBC connection failed: [URL_REDACTED] - FTP upload error: [URL_REDACTED] - Email service error: mailto:[EMAIL_REDACTED]?subject=Alert - """; - - internal static string Input2 => - $""" - ============================================================ - Hello World! Command Palette is starting. - - Application: - App version: 0.0.1.0 - Packaging flavor: Packaged - Is elevated: no - - Environment: - OS version: Microsoft Windows 10.0.26220 - OS architecture: X64 - Runtime identifier: win-x64 - Framework: .NET 9.0.13 - Process architecture: X64 - Culture: cs-CZ - UI culture: en-US - - Paths: - Log directory: {Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Microsoft\PowerToys\CmdPal\Logs\0.0.1.0 - Config directory: {Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\Packages\Microsoft.CommandPalette.Dev_8wekyb3d8bbwe\LocalState - ============================================================ - """; - - public const string Expected2 = - """ - ============================================================ - Hello World! Command Palette is starting. - - Application: - App version: 0.0.1.0 - Packaging flavor: Packaged - Is elevated: no - - Environment: - OS version: Microsoft Windows 10.0.26220 - OS architecture: X64 - Runtime identifier: win-x64 - Framework: .NET 9.0.13 - Process architecture: X64 - Culture: cs-CZ - UI culture: en-US - - Paths: - Log directory: [LOCALAPPLICATIONDATA_DIR]Microsoft\PowerToys\CmdPal\Logs\0.0.1.0 - Config directory: [LOCALAPPLICATIONDATA_DIR]Packages\Microsoft.CommandPalette.Dev_8wekyb3d8bbwe\LocalState - ============================================================ - """; - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.cs deleted file mode 100644 index 4df8220965..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/ErrorReportSanitizerTests.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services.Sanitizer; - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -[TestClass] -public partial class ErrorReportSanitizerTests -{ - [TestMethod] - public void Sanitize_ShouldMaskPiiInErrorReport() - { - // Arrange - var reportSanitizer = new ErrorReportSanitizer(); - var input = TestData.Input; - - // Act - var result = reportSanitizer.Sanitize(input); - - // Assert - Assert.AreEqual(TestData.Expected, result); - } - - [TestMethod] - public void Sanitize_ShouldNotMaskTooMuchPiiInErrorReport() - { - // Arrange - var reportSanitizer = new ErrorReportSanitizer(); - var input = TestData.Input2; - - // Act - var result = reportSanitizer.Sanitize(input); - - // Assert - Assert.AreEqual(TestData.Expected2, result); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/FilenameMaskRuleProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/FilenameMaskRuleProviderTests.cs deleted file mode 100644 index 9d5abcb444..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/FilenameMaskRuleProviderTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services.Sanitizer; -using Microsoft.CmdPal.Common.Services.Sanitizer.Abstraction; -using Microsoft.CmdPal.Common.UnitTests.TestUtils; - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -[TestClass] -public class FilenameMaskRuleProviderTests -{ - [TestMethod] - public void GetRules_ShouldReturnExpectedRules() - { - // Arrange - var provider = new FilenameMaskRuleProvider(); - - // Act - var rules = provider.GetRules(); - - // Assert - var ruleList = new List(rules); - Assert.AreEqual(1, ruleList.Count); - Assert.AreEqual("Mask filename in any path", ruleList[0].Description); - } - - [DataTestMethod] - [DataRow(@"C:\Users\Alice\Documents\secret.txt", @"C:\Users\Alice\Documents\se****.txt")] - [DataRow(@"logs\error-report.log", @"logs\er**********.log")] - [DataRow(@"/var/logs/trace.json", @"/var/logs/tr***.json")] - public void FilenameRules_ShouldMaskFileNamesInPaths(string input, string expected) - { - // Arrange - var provider = new FilenameMaskRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("C:\\Users\\Alice\\Documents\\", "C:\\Users\\Alice\\Documents\\")] - [DataRow(@"C:\Users\Alice\PowerToys\CmdPal\Logs\1.2.3.4", @"C:\Users\Alice\PowerToys\CmdPal\Logs\1.2.3.4")] - [DataRow(@"C:\Users\Alice\appsettings.json", @"C:\Users\Alice\appsettings.json")] - [DataRow(@"C:\Users\Alice\.env", @"C:\Users\Alice\.env")] - [DataRow(@"logs\readme", @"logs\readme")] - public void FilenameRules_ShouldNotMaskNonSensitivePatterns(string input, string expected) - { - // Arrange - var provider = new FilenameMaskRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/PiiRuleProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/PiiRuleProviderTests.cs deleted file mode 100644 index 89ce30f881..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/PiiRuleProviderTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services.Sanitizer; -using Microsoft.CmdPal.Common.Services.Sanitizer.Abstraction; -using Microsoft.CmdPal.Common.UnitTests.TestUtils; - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -[TestClass] -public class PiiRuleProviderTests -{ - [TestMethod] - public void GetRules_ShouldReturnExpectedRules() - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var rules = provider.GetRules(); - - // Assert - var ruleList = new List(rules); - Assert.AreEqual(4, ruleList.Count); - Assert.AreEqual("Email addresses", ruleList[0].Description); - Assert.AreEqual("Social Security Numbers", ruleList[1].Description); - Assert.AreEqual("Credit card numbers", ruleList[2].Description); - Assert.AreEqual("Phone numbers", ruleList[3].Description); - } - - [DataTestMethod] - [DataRow("Contact me at john.doe@contoso.com", "Contact me at [EMAIL_REDACTED]")] - [DataRow("Contact me at a_b-c%2@foo-bar.example.co.uk", "Contact me at [EMAIL_REDACTED]")] - [DataRow("My email is john@sub-domain.contoso.com.", "My email is [EMAIL_REDACTED].")] - [DataRow("Two: a@b.com and c@d.org", "Two: [EMAIL_REDACTED] and [EMAIL_REDACTED]")] - [DataRow("No email here", "No email here")] - public void EmailRules_ShouldMaskEmailAddresses(string input, string expected) - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("Call me at 123-456-7890", "Call me at [PHONE_REDACTED]")] - [DataRow("My number is (123) 456-7890.", "My number is [PHONE_REDACTED].")] - [DataRow("Office: +1 123 456 7890", "Office: [PHONE_REDACTED]")] - [DataRow("Two numbers: 123-456-7890 and +420 777123456", "Two numbers: [PHONE_REDACTED] and [PHONE_REDACTED]")] - [DataRow("Czech phone +420 777 123 456", "Czech phone [PHONE_REDACTED]")] - [DataRow("Slovak phone +421 777 12 34 56", "Slovak phone [PHONE_REDACTED]")] - [DataRow("Version 1.2.3.4", "Version 1.2.3.4")] - [DataRow("OS version: Microsoft Windows 10.0.26220", "OS version: Microsoft Windows 10.0.26220")] - [DataRow("No phone number here", "No phone number here")] - public void PhoneRules_ShouldMaskPhoneNumbers(string input, string expected) - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("My SSN is 123-45-6789", "My SSN is [SSN_REDACTED]")] - [DataRow("No SSN here", "No SSN here")] - public void SsnRules_ShouldMaskSsn(string input, string expected) - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("My credit card number is 1234-5678-9012-3456", "My credit card number is [CARD_REDACTED]")] - [DataRow("My credit card number is 1234567890123456", "My credit card number is [CARD_REDACTED]")] - [DataRow("No credit card here", "No credit card here")] - public void CreditCardRules_ShouldMaskCreditCardNumbers(string input, string expected) - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("Error code: 0x80070005", "Error code: 0x80070005")] - [DataRow("Error code: -2147467262", "Error code: -2147467262")] - [DataRow("GUID: 123e4567-e89b-12d3-a456-426614174000", "GUID: 123e4567-e89b-12d3-a456-426614174000")] - [DataRow("Timestamp: 2023-10-05T14:32:10Z", "Timestamp: 2023-10-05T14:32:10Z")] - [DataRow("Version: 1.2.3", "Version: 1.2.3")] - [DataRow("Version: 1.2.3.4", "Version: 1.2.3.4")] - [DataRow("Version: 0.2.3.4", "Version: 0.2.3.4")] - [DataRow("Version: 10.0.22631.3448", "Version: 10.0.22631.3448")] - [DataRow("MAC: 00:1A:2B:3C:4D:5E", "MAC: 00:1A:2B:3C:4D:5E")] - [DataRow("Date: 2023-10-05", "Date: 2023-10-05")] - [DataRow("Date: 05/10/2023", "Date: 05/10/2023")] - public void PiiRuleProvider_ShouldNotOverRedact(string input, string expected) - { - // Arrange - var provider = new PiiRuleProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/SecretKeyValueRulesProviderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/SecretKeyValueRulesProviderTests.cs deleted file mode 100644 index 33ee54a32b..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Services/Sanitizer/SecretKeyValueRulesProviderTests.cs +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Services.Sanitizer; -using Microsoft.CmdPal.Common.Services.Sanitizer.Abstraction; -using Microsoft.CmdPal.Common.UnitTests.TestUtils; - -namespace Microsoft.CmdPal.Common.UnitTests.Services.Sanitizer; - -[TestClass] -public class SecretKeyValueRulesProviderTests -{ - [TestMethod] - public void GetRules_ShouldReturnExpectedRules() - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var rules = provider.GetRules(); - - // Assert - var ruleList = new List(rules); - Assert.AreEqual(1, ruleList.Count); - Assert.AreEqual("Sensitive key/value pairs", ruleList[0].Description); - } - - [DataTestMethod] - [DataRow("password=secret123", "password= [REDACTED]")] - [DataRow("passphrase=myPassphrase", "passphrase= [REDACTED]")] - [DataRow("pwd=test", "pwd= [REDACTED]")] - [DataRow("passwd=pass1234", "passwd= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskPasswordSecrets(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("token=abc123def456", "token= [REDACTED]")] - [DataRow("access_token=token_value", "access_token= [REDACTED]")] - [DataRow("refresh-token=refresh_value", "refresh-token= [REDACTED]")] - [DataRow("id token=id_token_value", "id token= [REDACTED]")] - [DataRow("bearer token=bearer_value", "bearer token= [REDACTED]")] - [DataRow("session token=session_value", "session token= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskTokens(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("api key=my_api_key", "api key= [REDACTED]")] - [DataRow("api-key=key123", "api-key= [REDACTED]")] - [DataRow("api_key=secret_key", "api_key= [REDACTED]")] - [DataRow("x-api-key=api123", "x-api-key= [REDACTED]")] - [DataRow("x api key=key456", "x api key= [REDACTED]")] - [DataRow("client id=client123", "client id= [REDACTED]")] - [DataRow("client-secret=secret123", "client-secret= [REDACTED]")] - [DataRow("consumer secret=secret456", "consumer secret= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskApiCredentials(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("subscription key=sub_key_123", "subscription key= [REDACTED]")] - [DataRow("instrumentation key=instr_key", "instrumentation key= [REDACTED]")] - [DataRow("account key=account123", "account key= [REDACTED]")] - [DataRow("storage account key=storage_key", "storage account key= [REDACTED]")] - [DataRow("shared access key=sak123", "shared access key= [REDACTED]")] - [DataRow("SAS token=sas123", "SAS token= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskCloudPlatformKeys(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("connection string=Server=localhost;Pwd=pass", "connection string= [REDACTED]")] - [DataRow("conn string=conn_value", "conn string= [REDACTED]")] - [DataRow("storage connection string=connection_value", "storage connection string= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskConnectionStrings(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("private key=pk123", "private key= [REDACTED]")] - [DataRow("certificate password=cert_pass", "certificate password= [REDACTED]")] - [DataRow("client certificate password=cert123", "client certificate password= [REDACTED]")] - [DataRow("pfx password=pfx_pass", "pfx password= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskCertificateSecrets(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("aws access key id=AKIAIOSFODNN7EXAMPLE", "aws access key id= [REDACTED]")] - [DataRow("aws secret access key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "aws secret access key= [REDACTED]")] - [DataRow("aws session token=session_token_value", "aws session token= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskAwsKeys(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("password=\"complexPassword123!\"", "password= \"[REDACTED]\"")] - [DataRow("api-key='secret-key'", "api-key= '[REDACTED]'")] - [DataRow("token=\"bearer_token_value\"", "token= \"[REDACTED]\"")] - public void SecretKeyValueRules_ShouldPreserveQuotesAroundRedactedValue(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("PASSWORD=secret", "PASSWORD= [REDACTED]")] - [DataRow("Api-Key=key123", "Api-Key= [REDACTED]")] - [DataRow("CLIENT_ID=client123", "CLIENT_ID= [REDACTED]")] - [DataRow("Pwd=pass123", "Pwd= [REDACTED]")] - public void SecretKeyValueRules_ShouldBeCaseInsensitive(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("regularKey=regularValue", "regularKey=regularValue")] - [DataRow("config=myConfig", "config=myConfig")] - [DataRow("hostname=server.example.com", "hostname=server.example.com")] - [DataRow("port=8080", "port=8080")] - public void SecretKeyValueRules_ShouldNotRedactNonSecretKeyValuePairs(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("password:secret123", "password: [REDACTED]")] - [DataRow("api key:api_key_value", "api key: [REDACTED]")] - [DataRow("client_secret:secret_value", "client_secret: [REDACTED]")] - public void SecretKeyValueRules_ShouldSupportColonSeparator(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("password = secret123", "password= [REDACTED]")] - [DataRow("api key = api_key_value", "api key= [REDACTED]")] - [DataRow("token : token_value", "token: [REDACTED]")] - public void SecretKeyValueRules_ShouldHandleWhitespace(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("password=secret API_KEY=key config=myConfig", "password= [REDACTED] API_KEY= [REDACTED] config=myConfig")] - [DataRow("client_id=id123 name=admin pwd=pass123", "client_id= [REDACTED] name=admin pwd= [REDACTED]")] - public void SecretKeyValueRules_ShouldHandleMultipleKeyValuePairsInSingleString(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } - - [DataTestMethod] - [DataRow("cosmos db key=cosmos_key", "cosmos db key= [REDACTED]")] - [DataRow("service principal secret=sp_secret", "service principal secret= [REDACTED]")] - [DataRow("shared access signature=sas_signature", "shared access signature= [REDACTED]")] - public void SecretKeyValueRules_ShouldMaskServiceSpecificSecrets(string input, string expected) - { - // Arrange - var provider = new SecretKeyValueRulesProvider(); - - // Act - var result = SanitizerTestHelper.ApplyRules(input, provider.GetRules()); - - // Assert - Assert.AreEqual(expected, result); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/TestUtils/SanitizerTestHelper.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/TestUtils/SanitizerTestHelper.cs deleted file mode 100644 index 00de3fdad8..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/TestUtils/SanitizerTestHelper.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Text.RegularExpressions; -using Microsoft.CmdPal.Common.Services.Sanitizer.Abstraction; - -namespace Microsoft.CmdPal.Common.UnitTests.TestUtils; - -/// -/// Test-only helpers for applying SanitizationRule sets without relying on production ITextSanitizer implementation. -/// -public static class SanitizerTestHelper -{ - /// - /// Applies the provided rules to the input, in order, mimicking the production sanitizer behavior closely - /// but without any external dependencies. - /// - public static string ApplyRules(string? input, IEnumerable rules) - { - if (string.IsNullOrEmpty(input)) - { - return input ?? string.Empty; - } - - var result = input; - foreach (var rule in rules ?? []) - { - try - { - var previous = result; - result = rule.Evaluator is null - ? rule.Regex.Replace(previous, rule.Replacement ?? string.Empty) - : rule.Regex.Replace(previous, rule.Evaluator); - - // Guardrail to avoid accidental mass-redaction from a faulty rule - if (result.Length < previous.Length * 0.3) - { - result = previous; - } - } - catch (RegexMatchTimeoutException) - { - // Ignore timeouts in tests - } - } - - return result; - } - - /// - /// Creates a lightweight sanitizer instance backed by the given rules. - /// Useful when a component expects an ITextSanitizer, but you want deterministic behavior in tests. - /// - public static ITextSanitizer CreateSanitizer(IEnumerable rules) - => new InlineSanitizer(rules); - - private sealed class InlineSanitizer : ITextSanitizer - { - private readonly List _rules; - - public InlineSanitizer(IEnumerable rules) - { - _rules = rules?.ToList() ?? []; - } - - public string Sanitize(string? input) => ApplyRules(input, _rules); - - public void AddRule(string pattern, string replacement, string description = "") - { - var rx = new Regex(pattern, RegexOptions.Compiled | RegexOptions.CultureInvariant); - _rules.Add(new SanitizationRule(rx, replacement, description)); - } - - public void RemoveRule(string description) - { - _rules.RemoveAll(r => r.Description.Equals(description, StringComparison.OrdinalIgnoreCase)); - } - - public IReadOnlyList GetRules() => _rules.AsReadOnly(); - - public string TestRule(string input, string ruleDescription) - { - var rule = _rules.FirstOrDefault(r => r.Description.Contains(ruleDescription, StringComparison.OrdinalIgnoreCase)); - if (rule.Regex is null) - { - return input; - } - - try - { - if (rule.Evaluator is not null) - { - return rule.Regex.Replace(input, rule.Evaluator); - } - - if (rule.Replacement is not null) - { - return rule.Regex.Replace(input, rule.Replacement); - } - } - catch - { - // Ignore exceptions for test determinism - } - - return input; - } - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherEmojiTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherEmojiTests.cs deleted file mode 100644 index 0ba18634dc..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherEmojiTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public sealed class PrecomputedFuzzyMatcherEmojiTests -{ - private readonly PrecomputedFuzzyMatcher _matcher = new(); - - [TestMethod] - public void ExactMatch_SimpleEmoji_ReturnsScore() - { - const string needle = "🚀"; - const string haystack = "Launch 🚀 sequence"; - - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for simple emoji"); - } - - [TestMethod] - public void ExactMatch_SkinTone_ReturnsScore() - { - const string needle = "👍🏽"; // Medium skin tone - const string haystack = "Thumbs up 👍🏽 here"; - - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for emoji with skin tone"); - } - - [TestMethod] - public void ZWJSequence_Family_Match() - { - const string needle = "👨‍👩‍👧‍👦"; // Family: Man, Woman, Girl, Boy - const string haystack = "Emoji 👨‍👩‍👧‍👦 Test"; - - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for ZWJ sequence"); - } - - [TestMethod] - public void Flags_Match() - { - const string needle = "🇺🇸"; // US Flag (Regional Indicator U + Regional Indicator S) - const string haystack = "USA 🇺🇸"; - - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for flag emoji"); - } - - [TestMethod] - public void Emoji_MixedWithText_Search() - { - const string needle = "t🌮o"; // "t" + taco + "o" - const string haystack = "taco 🌮 on tuesday"; - - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for emoji mixed with text"); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherOptionsTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherOptionsTests.cs deleted file mode 100644 index 997c3d2c89..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherOptionsTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public sealed class PrecomputedFuzzyMatcherOptionsTests -{ - [TestMethod] - public void Score_RemoveDiacriticsOption_AffectsMatching() - { - var withDiacriticsRemoved = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { RemoveDiacritics = true }); - var withoutDiacriticsRemoved = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { RemoveDiacritics = false }); - - const string needle = "cafe"; - const string haystack = "CAFÉ"; - - var scoreWithRemoval = withDiacriticsRemoved.Score( - withDiacriticsRemoved.PrecomputeQuery(needle), - withDiacriticsRemoved.PrecomputeTarget(haystack)); - var scoreWithoutRemoval = withoutDiacriticsRemoved.Score( - withoutDiacriticsRemoved.PrecomputeQuery(needle), - withoutDiacriticsRemoved.PrecomputeTarget(haystack)); - - Assert.IsTrue(scoreWithRemoval > 0, "Expected match when diacritics are removed."); - Assert.AreEqual(0, scoreWithoutRemoval, "Expected no match when diacritics are preserved."); - } - - [TestMethod] - public void Score_SkipWordSeparatorsOption_AffectsMatching() - { - var skipSeparators = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { SkipWordSeparators = true }); - var keepSeparators = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { SkipWordSeparators = false }); - - const string needle = "a b"; - const string haystack = "ab"; - - var scoreSkip = skipSeparators.Score( - skipSeparators.PrecomputeQuery(needle), - skipSeparators.PrecomputeTarget(haystack)); - var scoreKeep = keepSeparators.Score( - keepSeparators.PrecomputeQuery(needle), - keepSeparators.PrecomputeTarget(haystack)); - - Assert.IsTrue(scoreSkip > 0, "Expected match when word separators are skipped."); - Assert.AreEqual(0, scoreKeep, "Expected no match when word separators are preserved."); - } - - [TestMethod] - public void Score_IgnoreSameCaseBonusOption_AffectsLowercaseQuery() - { - var ignoreSameCase = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions - { - IgnoreSameCaseBonusIfQueryIsAllLowercase = true, - SameCaseBonus = 10, - }); - var applySameCase = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions - { - IgnoreSameCaseBonusIfQueryIsAllLowercase = false, - SameCaseBonus = 10, - }); - - const string needle = "test"; - const string haystack = "test"; - - var scoreIgnore = ignoreSameCase.Score( - ignoreSameCase.PrecomputeQuery(needle), - ignoreSameCase.PrecomputeTarget(haystack)); - var scoreApply = applySameCase.Score( - applySameCase.PrecomputeQuery(needle), - applySameCase.PrecomputeTarget(haystack)); - - Assert.IsTrue(scoreApply > scoreIgnore, "Expected same-case bonus to apply when not ignored."); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherSecondaryInputTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherSecondaryInputTests.cs deleted file mode 100644 index 5403453b4b..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherSecondaryInputTests.cs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public sealed class PrecomputedFuzzyMatcherSecondaryInputTests -{ - private readonly PrecomputedFuzzyMatcher _matcher = new(); - private readonly StringFolder _folder = new(); - private readonly BloomFilter _bloom = new(); - - [TestMethod] - public void Score_PrimaryQueryMatchesSecondaryTarget_ShouldMatch() - { - // Scenario: Searching for "calc" should match a file "calculator.exe" where primary is filename, secondary is path - var query = CreateQuery("calc"); - var target = CreateTarget(primary: "important.txt", secondary: "C:\\Programs\\Calculator\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected primary query to match secondary target"); - } - - [TestMethod] - public void Score_SecondaryQueryMatchesPrimaryTarget_ShouldMatch() - { - // Scenario: User types "documents\\report" and we want to match against filename - var query = CreateQuery(primary: "documents", secondary: "report"); - var target = CreateTarget(primary: "report.docx"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected secondary query to match primary target"); - } - - [TestMethod] - public void Score_SecondaryQueryMatchesSecondaryTarget_ShouldMatch() - { - // Scenario: Both query and target have secondary info that matches - var query = CreateQuery(primary: "test", secondary: "documents"); - var target = CreateTarget(primary: "something.txt", secondary: "C:\\Users\\Documents\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected secondary query to match secondary target"); - } - - [TestMethod] - public void Score_PrimaryQueryMatchesBothTargets_ShouldReturnBestScore() - { - // The same query matches both primary and secondary of target - var query = CreateQuery("test"); - var target = CreateTarget(primary: "test.txt", secondary: "test_folder\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected query to match when it appears in both primary and secondary"); - } - - [TestMethod] - public void Score_NoSecondaryInQuery_MatchesSecondaryTarget() - { - // Query without secondary can still match target's secondary - var query = CreateQuery("downloads"); - var target = CreateTarget(primary: "file.txt", secondary: "C:\\Downloads\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected primary query to match secondary target"); - } - - [TestMethod] - public void Score_NoSecondaryInTarget_SecondaryQueryShouldNotMatch() - { - // Query with secondary but target without secondary - secondary query shouldn't interfere - var query = CreateQuery(primary: "test", secondary: "extra"); - var target = CreateTarget(primary: "test.txt"); - - var score = _matcher.Score(query, target); - - // Primary should still match, secondary query just doesn't contribute - Assert.IsTrue(score > 0, "Expected primary query to match primary target"); - } - - [TestMethod] - public void Score_SecondaryQueryNoMatch_PrimaryCanStillMatch() - { - // Secondary doesn't match anything, but primary does - var query = CreateQuery(primary: "file", secondary: "nomatch"); - var target = CreateTarget(primary: "myfile.txt", secondary: "C:\\Documents\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected primary query to match even when secondary doesn't"); - } - - [TestMethod] - public void Score_OnlySecondaryMatches_ShouldReturnScore() - { - // Only the secondary parts match, primary doesn't - var query = CreateQuery(primary: "xyz", secondary: "documents"); - var target = CreateTarget(primary: "abc.txt", secondary: "C:\\Users\\Documents\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match when only secondary parts match"); - } - - [TestMethod] - public void Score_BothQueriesMatchDifferentTargets_ShouldReturnBestScore() - { - // Primary query matches secondary target, secondary query matches primary target - var query = CreateQuery(primary: "docs", secondary: "report"); - var target = CreateTarget(primary: "report.pdf", secondary: "C:\\Documents\\"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match when queries cross-match with targets"); - } - - [TestMethod] - public void Score_CompletelyDifferent_ShouldNotMatch() - { - var query = CreateQuery(primary: "xyz", secondary: "abc"); - var target = CreateTarget(primary: "hello", secondary: "world"); - - var score = _matcher.Score(query, target); - - Assert.AreEqual(0, score, "Expected no match when nothing matches"); - } - - [TestMethod] - public void Score_EmptySecondaryInputs_ShouldMatchOnPrimary() - { - var query = CreateQuery(primary: "test", secondary: string.Empty); - var target = CreateTarget(primary: "test.txt", secondary: string.Empty); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match on primary when secondaries are empty"); - } - - [TestMethod] - public void Score_WordSeparatorMatching_AcrossSecondary() - { - // Test that "Power Point" matches "PowerPoint" using secondary - var query = CreateQuery(primary: "power", secondary: "point"); - var target = CreateTarget(primary: "PowerPoint.exe"); - - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected 'power' + 'point' to match 'PowerPoint'"); - } - - private FuzzyQuery CreateQuery(string primary, string? secondary = null) - { - var primaryFolded = _folder.Fold(primary, removeDiacritics: true); - var primaryBloom = _bloom.Compute(primaryFolded); - var primaryEffectiveLength = primaryFolded.Length; - var primaryIsAllLowercase = IsAllLowercaseAsciiOrNonLetter(primary); - - string? secondaryFolded = null; - ulong secondaryBloom = 0; - var secondaryEffectiveLength = 0; - var secondaryIsAllLowercase = true; - - if (!string.IsNullOrEmpty(secondary)) - { - secondaryFolded = _folder.Fold(secondary, removeDiacritics: true); - secondaryBloom = _bloom.Compute(secondaryFolded); - secondaryEffectiveLength = secondaryFolded.Length; - secondaryIsAllLowercase = IsAllLowercaseAsciiOrNonLetter(secondary); - } - - return new FuzzyQuery( - original: primary, - folded: primaryFolded, - bloom: primaryBloom, - effectiveLength: primaryEffectiveLength, - isAllLowercaseAsciiOrNonLetter: primaryIsAllLowercase, - secondaryOriginal: secondary, - secondaryFolded: secondaryFolded, - secondaryBloom: secondaryBloom, - secondaryEffectiveLength: secondaryEffectiveLength, - secondaryIsAllLowercaseAsciiOrNonLetter: secondaryIsAllLowercase); - } - - private FuzzyTarget CreateTarget(string primary, string? secondary = null) - { - var primaryFolded = _folder.Fold(primary, removeDiacritics: true); - var primaryBloom = _bloom.Compute(primaryFolded); - - string? secondaryFolded = null; - ulong secondaryBloom = 0; - - if (!string.IsNullOrEmpty(secondary)) - { - secondaryFolded = _folder.Fold(secondary, removeDiacritics: true); - secondaryBloom = _bloom.Compute(secondaryFolded); - } - - return new FuzzyTarget( - original: primary, - folded: primaryFolded, - bloom: primaryBloom, - secondaryOriginal: secondary, - secondaryFolded: secondaryFolded, - secondaryBloom: secondaryBloom); - } - - private static bool IsAllLowercaseAsciiOrNonLetter(string s) - { - foreach (var c in s) - { - if ((uint)(c - 'A') <= ('Z' - 'A')) - { - return false; - } - } - - return true; - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherTests.cs deleted file mode 100644 index 9cf3d5d38a..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public class PrecomputedFuzzyMatcherTests -{ - private readonly PrecomputedFuzzyMatcher _matcher = new(); - - public static IEnumerable MatchData => - [ - ["a", "a"], - ["abc", "abc"], - ["a", "ab"], - ["b", "ab"], - ["abc", "axbycz"], - ["pt", "PowerToys"], - ["calc", "Calculator"], - ["vs", "Visual Studio"], - ["code", "Visual Studio Code"], - - // Diacritics - ["abc", "ÁBC"], - - // Separators - ["p/t", "power\\toys"], - ]; - - public static IEnumerable NonMatchData => - [ - ["z", "abc"], - ["verylongstring", "short"], - ]; - - [TestMethod] - [DynamicData(nameof(MatchData))] - public void Score_Matches_ShouldHavePositiveScore(string needle, string haystack) - { - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, $"Expected positive score for needle='{needle}', haystack='{haystack}'"); - } - - [TestMethod] - [DynamicData(nameof(NonMatchData))] - public void Score_NonMatches_ShouldHaveZeroScore(string needle, string haystack) - { - var query = _matcher.PrecomputeQuery(needle); - var target = _matcher.PrecomputeTarget(haystack); - var score = _matcher.Score(query, target); - - Assert.AreEqual(0, score, $"Expected 0 score for needle='{needle}', haystack='{haystack}'"); - } - - [TestMethod] - public void Score_EmptyQuery_ReturnsZero() - { - var query = _matcher.PrecomputeQuery(string.Empty); - var target = _matcher.PrecomputeTarget("something"); - Assert.AreEqual(0, _matcher.Score(query, target)); - } - - [TestMethod] - public void Score_EmptyTarget_ReturnsZero() - { - var query = _matcher.PrecomputeQuery("something"); - var target = _matcher.PrecomputeTarget(string.Empty); - Assert.AreEqual(0, _matcher.Score(query, target)); - } - - [TestMethod] - public void SchemaId_DefaultMatcher_IsConsistent() - { - var matcher1 = new PrecomputedFuzzyMatcher(); - var matcher2 = new PrecomputedFuzzyMatcher(); - - Assert.AreEqual(matcher1.SchemaId, matcher2.SchemaId, "Default matchers should have the same SchemaId"); - } - - [TestMethod] - public void SchemaId_SameOptions_ProducesSameId() - { - var options = new PrecomputedFuzzyMatcherOptions { RemoveDiacritics = true }; - var matcher1 = new PrecomputedFuzzyMatcher(options); - var matcher2 = new PrecomputedFuzzyMatcher(options); - - Assert.AreEqual(matcher1.SchemaId, matcher2.SchemaId, "Matchers with same options should have the same SchemaId"); - } - - [TestMethod] - public void SchemaId_DifferentRemoveDiacriticsOption_ProducesDifferentId() - { - var matcherWithDiacriticsRemoval = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { RemoveDiacritics = true }); - var matcherWithoutDiacriticsRemoval = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { RemoveDiacritics = false }); - - Assert.AreNotEqual( - matcherWithDiacriticsRemoval.SchemaId, - matcherWithoutDiacriticsRemoval.SchemaId, - "Different RemoveDiacritics option should produce different SchemaId"); - } - - [TestMethod] - public void SchemaId_ScoringOptionsDoNotAffectId() - { - // SchemaId should only be affected by options that affect folding/bloom, not scoring - var matcher1 = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { CharMatchBonus = 1, CamelCaseBonus = 2 }); - var matcher2 = new PrecomputedFuzzyMatcher( - new PrecomputedFuzzyMatcherOptions { CharMatchBonus = 100, CamelCaseBonus = 200 }); - - Assert.AreEqual(matcher1.SchemaId, matcher2.SchemaId, "Scoring options should not affect SchemaId"); - } - - [TestMethod] - public void Score_WordSeparatorMatching_PowerPoint() - { - // Test that "Power Point" can match "PowerPoint" when word separators are skipped - var query = _matcher.PrecomputeQuery("Power Point"); - var target = _matcher.PrecomputeTarget("PowerPoint"); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected 'Power Point' to match 'PowerPoint'"); - } - - [TestMethod] - public void Score_WordSeparatorMatching_UnderscoreDash() - { - // Test that different word separators match each other - var query = _matcher.PrecomputeQuery("hello_world"); - var target = _matcher.PrecomputeTarget("hello-world"); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected 'hello_world' to match 'hello-world'"); - } - - [TestMethod] - public void Score_WordSeparatorMatching_MixedSeparators() - { - // Test multiple different separators - var query = _matcher.PrecomputeQuery("my.file_name"); - var target = _matcher.PrecomputeTarget("my-file.name"); - var score = _matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected mixed separators to match"); - } - - [TestMethod] - public void Score_PrecomputedQueryReuse_ShouldWorkConsistently() - { - // Test that precomputed query can be reused across multiple targets - var query = _matcher.PrecomputeQuery("test"); - var target1 = _matcher.PrecomputeTarget("test123"); - var target2 = _matcher.PrecomputeTarget("mytest"); - var target3 = _matcher.PrecomputeTarget("unrelated"); - - var score1 = _matcher.Score(query, target1); - var score2 = _matcher.Score(query, target2); - var score3 = _matcher.Score(query, target3); - - Assert.IsTrue(score1 > 0, "Expected query to match first target"); - Assert.IsTrue(score2 > 0, "Expected query to match second target"); - Assert.AreEqual(0, score3, "Expected query not to match third target"); - } - - [TestMethod] - public void Score_PrecomputedTargetReuse_ShouldWorkConsistently() - { - // Test that precomputed target can be reused across multiple queries - var target = _matcher.PrecomputeTarget("calculator"); - var query1 = _matcher.PrecomputeQuery("calc"); - var query2 = _matcher.PrecomputeQuery("lator"); - var query3 = _matcher.PrecomputeQuery("xyz"); - - var score1 = _matcher.Score(query1, target); - var score2 = _matcher.Score(query2, target); - var score3 = _matcher.Score(query3, target); - - Assert.IsTrue(score1 > 0, "Expected first query to match target"); - Assert.IsTrue(score2 > 0, "Expected second query to match target"); - Assert.AreEqual(0, score3, "Expected third query not to match target"); - } - - [TestMethod] - public void Score_CaseInsensitiveMatching_Works() - { - // Test various case combinations - var query1 = _matcher.PrecomputeQuery("test"); - var query2 = _matcher.PrecomputeQuery("TEST"); - var query3 = _matcher.PrecomputeQuery("TeSt"); - - var target = _matcher.PrecomputeTarget("TestFile"); - - var score1 = _matcher.Score(query1, target); - var score2 = _matcher.Score(query2, target); - var score3 = _matcher.Score(query3, target); - - Assert.IsTrue(score1 > 0, "Expected lowercase query to match"); - Assert.IsTrue(score2 > 0, "Expected uppercase query to match"); - Assert.IsTrue(score3 > 0, "Expected mixed case query to match"); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherUnicodeTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherUnicodeTests.cs deleted file mode 100644 index 66d2805242..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherUnicodeTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public sealed class PrecomputedFuzzyMatcherUnicodeTests -{ - private readonly PrecomputedFuzzyMatcher _defaultMatcher = new(); - - [TestMethod] - public void UnpairedHighSurrogateInNeedle_ShouldNotThrow() - { - const string needle = "\uD83D"; // high surrogate (unpaired) - const string haystack = "abc"; - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void UnpairedLowSurrogateInNeedle_ShouldNotThrow() - { - const string needle = "\uDC00"; // low surrogate (unpaired) - const string haystack = "abc"; - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void UnpairedHighSurrogateInHaystack_ShouldNotThrow() - { - const string needle = "a"; - const string haystack = "a\uD83D" + "bc"; // inject unpaired high surrogate - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void MixedSurrogatesAndMarks_ShouldNotThrow() - { - // "Garbage smoothie": unpaired surrogate + combining mark + emoji surrogate pair - const string needle = "a\uD83D\u0301"; // 'a' + unpaired high surrogate + combining acute - const string haystack = "a\u0301 \U0001F600"; // 'a' + combining acute + space + 😀 (valid pair) - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void ValidEmojiSurrogatePair_ShouldNotThrow_AndCanMatch() - { - // 😀 U+1F600 encoded as surrogate pair in UTF-16 - const string needle = "\U0001F600"; - const string haystack = "x \U0001F600 y"; - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - var score = _defaultMatcher.Score(q, t); - - Assert.IsTrue(score > 0, "Expected emoji to produce a match score > 0."); - } - - [TestMethod] - public void RandomUtf16Garbage_ShouldNotThrow() - { - // Deterministic pseudo-random "UTF-16 garbage", including surrogates. - var s1 = MakeDeterministicGarbage(seed: 1234, length: 512); - var s2 = MakeDeterministicGarbage(seed: 5678, length: 1024); - - var q = _defaultMatcher.PrecomputeQuery(s1); - var t = _defaultMatcher.PrecomputeTarget(s2); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void HighSurrogateAtEndOfHaystack_ShouldNotThrow() - { - const string needle = "a"; - const string haystack = "abc\uD83D"; // Ends with high surrogate - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - [TestMethod] - public void VeryLongStrings_ShouldNotThrow() - { - var needle = new string('a', 100); - var haystack = new string('b', 10000) + needle + new string('c', 10000); - - var q = _defaultMatcher.PrecomputeQuery(needle); - var t = _defaultMatcher.PrecomputeTarget(haystack); - _ = _defaultMatcher.Score(q, t); - } - - private static string MakeDeterministicGarbage(int seed, int length) - { - // LCG for deterministic generation without Random’s platform/version surprises. - var x = (uint)seed; - var chars = length <= 2048 ? stackalloc char[length] : new char[length]; - - for (var i = 0; i < chars.Length; i++) - { - // LCG: x = (a*x + c) mod 2^32 - x = unchecked((1664525u * x) + 1013904223u); - - // Take top 16 bits as UTF-16 code unit (includes surrogates). - chars[i] = (char)(x >> 16); - } - - return new string(chars); - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherWithPinyinTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherWithPinyinTests.cs deleted file mode 100644 index a6edfe6f6c..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/PrecomputedFuzzyMatcherWithPinyinTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Globalization; -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public class PrecomputedFuzzyMatcherWithPinyinTests -{ - private PrecomputedFuzzyMatcherWithPinyin CreateMatcher(PinyinMode mode = PinyinMode.On, bool removeApostrophes = true) - { - return new PrecomputedFuzzyMatcherWithPinyin( - new PrecomputedFuzzyMatcherOptions(), - new PinyinFuzzyMatcherOptions { Mode = mode, RemoveApostrophesForQuery = removeApostrophes }, - new StringFolder(), - new BloomFilter()); - } - - [TestMethod] - [DataRow("bj", "北京")] - [DataRow("sh", "上海")] - [DataRow("nihao", "你好")] - [DataRow("beijing", "北京")] - [DataRow("ce", "测试")] - public void Score_PinyinMatches_ShouldHavePositiveScore(string needle, string haystack) - { - var matcher = CreateMatcher(PinyinMode.On); - var query = matcher.PrecomputeQuery(needle); - var target = matcher.PrecomputeTarget(haystack); - var score = matcher.Score(query, target); - - Assert.IsTrue(score > 0, $"Expected positive score for needle='{needle}', haystack='{haystack}'"); - } - - [TestMethod] - public void Score_PinyinOff_ShouldNotMatchPinyin() - { - var matcher = CreateMatcher(PinyinMode.Off); - var needle = "bj"; - var haystack = "北京"; - - var query = matcher.PrecomputeQuery(needle); - var target = matcher.PrecomputeTarget(haystack); - var score = matcher.Score(query, target); - - Assert.AreEqual(0, score, "Pinyin match should be disabled."); - } - - [TestMethod] - public void Score_StandardMatch_WorksWithPinyinMatcher() - { - var matcher = CreateMatcher(PinyinMode.On); - var needle = "abc"; - var haystack = "abc"; - - var query = matcher.PrecomputeQuery(needle); - var target = matcher.PrecomputeTarget(haystack); - var score = matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Standard match should still work."); - } - - [TestMethod] - public void Score_ApostropheRemoval_Works() - { - var matcher = CreateMatcher(PinyinMode.On, removeApostrophes: true); - var needle = "xi'an"; - - // "xi'an" -> "xian" -> matches "西安" (Xi An) - var haystack = "西安"; - - var query = matcher.PrecomputeQuery(needle); - var target = matcher.PrecomputeTarget(haystack); - var score = matcher.Score(query, target); - - Assert.IsTrue(score > 0, "Expected match for 'xi'an' -> '西安' with apostrophe removal."); - } - - [TestMethod] - public void AutoMode_EnablesForChineseCulture() - { - var originalCulture = CultureInfo.CurrentUICulture; - try - { - CultureInfo.CurrentUICulture = new CultureInfo("zh-CN"); - var matcher = CreateMatcher(PinyinMode.AutoSimplifiedChineseUi); - - var score = matcher.Score(matcher.PrecomputeQuery("bj"), matcher.PrecomputeTarget("北京")); - Assert.IsTrue(score > 0, "Should match when UI culture is zh-CN"); - } - finally - { - CultureInfo.CurrentUICulture = originalCulture; - } - } - - [TestMethod] - public void AutoMode_DisablesForNonChineseCulture() - { - var originalCulture = CultureInfo.CurrentUICulture; - try - { - CultureInfo.CurrentUICulture = new CultureInfo("en-US"); - var matcher = CreateMatcher(PinyinMode.AutoSimplifiedChineseUi); - - var score = matcher.Score(matcher.PrecomputeQuery("bj"), matcher.PrecomputeTarget("北京")); - Assert.AreEqual(0, score, "Should NOT match when UI culture is en-US"); - } - finally - { - CultureInfo.CurrentUICulture = originalCulture; - } - } -} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/StringFolderTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/StringFolderTests.cs deleted file mode 100644 index 3ebdfdbc3e..0000000000 --- a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Core.Common.UnitTests/Text/StringFolderTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft Corporation -// The Microsoft Corporation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CmdPal.Common.Text; - -namespace Microsoft.CmdPal.Common.UnitTests.Text; - -[TestClass] -public class StringFolderTests -{ - private readonly StringFolder _folder = new(); - - [TestMethod] - [DataRow(null, "")] - [DataRow("", "")] - [DataRow("abc", "ABC")] - [DataRow("ABC", "ABC")] - [DataRow("a\\b", "A/B")] - [DataRow("a/b", "A/B")] - [DataRow("ÁBC", "ABC")] // Diacritic removal - [DataRow("ñ", "N")] - [DataRow("hello world", "HELLO WORLD")] - public void Fold_RemoveDiacritics_Works(string input, string expected) - { - Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: true)); - } - - [TestMethod] - [DataRow("abc", "ABC")] - [DataRow("ÁBC", "ÁBC")] // No diacritic removal - [DataRow("a\\b", "A/B")] - public void Fold_KeepDiacritics_Works(string input, string expected) - { - Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: false)); - } - - [TestMethod] - public void Fold_IsAlreadyFolded_ReturnsSameInstance() - { - var input = "ALREADY/FOLDED"; - var result = _folder.Fold(input, removeDiacritics: true); - Assert.AreSame(input, result); - } - - [TestMethod] - public void Fold_WithNonAsciiButNoDiacritics_ReturnsFolded() - { - // E.g. Cyrillic or other scripts that might not decompose in a simple way or just upper case - // "привет" -> "ПРИВЕТ" - var input = "привет"; - var expected = "ПРИВЕТ"; - Assert.AreEqual(expected, _folder.Fold(input, removeDiacritics: true)); - } -}