diff --git a/BotSharp.Core.UnitTests/BotSharp.Core.UnitTests.csproj b/BotSharp.Core.UnitTests/BotSharp.Core.UnitTests.csproj new file mode 100644 index 000000000..b050e4daa --- /dev/null +++ b/BotSharp.Core.UnitTests/BotSharp.Core.UnitTests.csproj @@ -0,0 +1,28 @@ + + + + $(TargetFramework) + enable + enable + false + BotSharp.Core.UnitTests + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/BotSharp.Core.UnitTests/Infrastructures/SettingServiceTests.cs b/BotSharp.Core.UnitTests/Infrastructures/SettingServiceTests.cs new file mode 100644 index 000000000..822e8e1e3 --- /dev/null +++ b/BotSharp.Core.UnitTests/Infrastructures/SettingServiceTests.cs @@ -0,0 +1,87 @@ +using BotSharp.Core.Infrastructures; +using Xunit; + +namespace BotSharp.Core.UnitTests.Infrastructures; + +public class SettingServiceTests +{ + [Fact] + public void Mask_null_or_empty_returns_empty() + { + Assert.Equal(string.Empty, SettingService.Mask(null!)); + Assert.Equal(string.Empty, SettingService.Mask(string.Empty)); + } + + [Theory] + [InlineData("a", "*")] + [InlineData("ab", "**")] + [InlineData("abc", "a**")] + [InlineData("abcd", "a***")] + [InlineData("0123456789", "0123******")] + public void Mask_short_and_medium_inputs_matches_expected_masked_form(string input, string expected) + { + var actual = SettingService.Mask(input); + Assert.Equal(expected, actual); + } + + [Fact] + public void Mask_long_value_preserves_length_and_replaces_suffix_with_stars() + { + var input = new string('x', 64); + var masked = SettingService.Mask(input); + + Assert.Equal(64, masked.Length); + Assert.NotEqual(input, masked); + Assert.Contains('*', masked); + Assert.StartsWith("x", masked, StringComparison.Ordinal); + Assert.EndsWith("*", masked); + } + + [Theory] + [InlineData("e", 1)] + [InlineData("ef", 2)] + [InlineData("efg", 3)] + [InlineData("efgh", 4)] + [InlineData("123456789012345", 15)] + [InlineData("abcdefghijklmnopqrstuvwxyz", 26)] + public void Mask_preserves_original_string_length(string input, int expectedLength) + { + var masked = SettingService.Mask(input); + Assert.Equal(expectedLength, masked.Length); + } + + [Theory] + [InlineData("a")] + [InlineData("ab")] + [InlineData("abc")] + [InlineData("password123")] + public void Mask_contains_at_least_one_asterisk(string input) + { + var masked = SettingService.Mask(input); + Assert.Contains('*', masked); + } + + [Fact] + public void Mask_very_long_string() + { + var input = new string('a', 1000); + var masked = SettingService.Mask(input); + + Assert.Equal(1000, masked.Length); + Assert.Contains('*', masked); + var keepLength = (1000 - 1) / 2; + var asteriskCount = 1000 - keepLength; + Assert.Equal(asteriskCount, masked.Count(c => c == '*')); + } + + [Fact] + public void Mask_api_key_like_string() + { + var input = "sk_live_1234567890abcdef"; + var masked = SettingService.Mask(input); + + Assert.Equal(input.Length, masked.Length); + Assert.Contains('*', masked); + Assert.StartsWith("sk_li", masked); + } +} diff --git a/BotSharp.sln b/BotSharp.sln index 9136e96c1..e37eca962 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -155,6 +155,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.A2A", "src\In EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MultiTenancy", "src\Plugins\BotSharp.Plugin.MultiTenancy\BotSharp.Plugin.MultiTenancy.csproj", "{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.UnitTests", "BotSharp.Core.UnitTests\BotSharp.Core.UnitTests.csproj", "{53E53E98-3C14-4AED-AC40-BD88170C5FE5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -659,6 +661,14 @@ Global {562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|Any CPU.Build.0 = Release|Any CPU {562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|x64.ActiveCfg = Release|Any CPU {562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|x64.Build.0 = Release|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Debug|x64.ActiveCfg = Debug|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Debug|x64.Build.0 = Debug|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Release|Any CPU.Build.0 = Release|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Release|x64.ActiveCfg = Release|Any CPU + {53E53E98-3C14-4AED-AC40-BD88170C5FE5}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -734,6 +744,7 @@ Global {13223C71-9EAC-9835-28ED-5A4833E6F915} = {53E7CD86-0D19-40D9-A0FA-AB4613837E89} {E8D01281-D52A-BFF4-33DB-E35D91754272} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {562DD0C6-DAC8-02CC-C1DD-D43DF186CE76} = {51AFE054-AE99-497D-A593-69BAEFB5106F} + {53E53E98-3C14-4AED-AC40-BD88170C5FE5} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs index 8a78b5a57..a868b5498 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/SettingService.cs @@ -46,9 +46,9 @@ public static string Mask(string value) { return string.Empty; } - value = value.Substring(0, value.Length / 2 - 1) - + string.Join("", Enumerable.Repeat("*", value.Length / 2)); - return value; + int keepLength = (value.Length - 1) / 2; + return value.Substring(0, keepLength) + + string.Join("", Enumerable.Repeat("*", value.Length - keepLength)); } public string GetUpgradeModel(string oldModelName)