From 062b0d01333cf08c04986e7161d8c9232798ba81 Mon Sep 17 00:00:00 2001 From: Andrei Tudor Date: Fri, 30 May 2025 20:55:41 +0300 Subject: [PATCH] Added Configuration tests --- .../ConfigurationBase.cs | 2 +- SethDiscordBot.sln | 15 ++ .../ConfigurationTests.cs | 246 ++++++++++++++++++ .../Tests.DiscordBotCore.Configuration.csproj | 28 ++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 Tests/Tests.DiscordBotCore.Configuration/ConfigurationTests.cs create mode 100644 Tests/Tests.DiscordBotCore.Configuration/Tests.DiscordBotCore.Configuration.csproj diff --git a/DiscordBotCore.Configuration/ConfigurationBase.cs b/DiscordBotCore.Configuration/ConfigurationBase.cs index a3a8514..e2d3a71 100644 --- a/DiscordBotCore.Configuration/ConfigurationBase.cs +++ b/DiscordBotCore.Configuration/ConfigurationBase.cs @@ -16,7 +16,7 @@ public abstract class ConfigurationBase : IConfiguration this._Logger = logger; } - public virtual void Add(string key, object value) + public virtual void Add(string key, object? value) { if (_InternalDictionary.ContainsKey(key)) return; diff --git a/SethDiscordBot.sln b/SethDiscordBot.sln index 00617de..ceff943 100644 --- a/SethDiscordBot.sln +++ b/SethDiscordBot.sln @@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8F27B3EA EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Logging", "Tests\Tests.DiscordBotCore.Logging\Tests.DiscordBotCore.Logging.csproj", "{94238D37-60C6-4E40-80EC-4B4D242F5914}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Configuration", "Tests\Tests.DiscordBotCore.Configuration\Tests.DiscordBotCore.Configuration.csproj", "{52C59C73-C23C-4608-8827-383577DEB8D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -231,6 +233,18 @@ Global {94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|ARM64.Build.0 = Release|Any CPU {94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|x64.ActiveCfg = Release|Any CPU {94238D37-60C6-4E40-80EC-4B4D242F5914}.Release|x64.Build.0 = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|ARM64.Build.0 = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Debug|x64.Build.0 = Debug|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|Any CPU.Build.0 = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|ARM64.ActiveCfg = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|ARM64.Build.0 = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.ActiveCfg = Release|Any CPU + {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,6 +255,7 @@ Global {C67908F9-4A55-4DD8-B993-C26C648226F1} = {EA4FA308-7B2C-458E-8485-8747D745DD59} {9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1} {94238D37-60C6-4E40-80EC-4B4D242F5914} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70} + {52C59C73-C23C-4608-8827-383577DEB8D8} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF} diff --git a/Tests/Tests.DiscordBotCore.Configuration/ConfigurationTests.cs b/Tests/Tests.DiscordBotCore.Configuration/ConfigurationTests.cs new file mode 100644 index 0000000..3cb6e1d --- /dev/null +++ b/Tests/Tests.DiscordBotCore.Configuration/ConfigurationTests.cs @@ -0,0 +1,246 @@ +using DiscordBotCore.Configuration; +using DiscordBotCore.Logging; +using Moq; + +namespace Tests.DiscordBotCore.Configuration; + +public class ConfigurationTests +{ + private readonly Mock _loggerMock; + private readonly string _testFilePath; + + public ConfigurationTests() + { + _loggerMock = new Mock(); + _testFilePath = Path.GetTempFileName(); + } + + #region Basic Operations + + [Fact] + public void Add_ShouldAddKeyValuePair() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 100); + Assert.True(config.ContainsKey("Key1")); + Assert.Equal(100, config.Get("Key1")); + } + + [Fact] + public void Add_ShouldIgnoreNullKey() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", null); + Assert.False(config.ContainsKey("Key1")); + } + + [Fact] + public void Set_ShouldOverrideValue() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 1); + config.Set("Key1", 2); + Assert.Equal(2, config.Get("Key1")); + } + + [Fact] + public void Remove_ShouldDeleteKey() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 1); + config.Remove("Key1"); + Assert.False(config.ContainsKey("Key1")); + } + + [Fact] + public void Clear_ShouldEmptyDictionary() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 1); + config.Clear(); + Assert.False(config.ContainsKey("Key1")); + } + + #endregion + + #region Retrieval + + [Fact] + public void Get_ShouldReturnStoredValue() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 123); + Assert.Equal(123, config.Get("Key1")); + } + + [Fact] + public void Get_WithDefault_ShouldReturnStoredValue() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 50); + Assert.Equal(50, config.Get("Key1", 100)); + } + + [Fact] + public void Get_WithDefault_ShouldReturnDefaultWithoutAutoAddIfDisabled() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + int result = config.Get("MissingKey", 300); + Assert.Equal(300, result); + Assert.False(config.ContainsKey("MissingKey")); + } + + [Fact] + public void TryGetValue_ShouldReturnTrueIfKeyExists() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 5); + bool result = config.TryGetValue("Key1", out object? val); + Assert.True(result); + Assert.Equal(5, val); + } + + [Fact] + public void TryGetValue_ShouldReturnFalseIfKeyDoesNotExist() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + bool result = config.TryGetValue("KeyX", out object? val); + Assert.False(result); + Assert.Null(val); + } + + #endregion + + #region Type Conversion and Complex Types + + [Fact] + public void Get_WithTypeConversion_ShouldReturnConvertedValue() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", "123"); + int result = config.Get("Key1"); + Assert.Equal(123, result); + } + + [Fact] + public void GetList_ShouldReturnStoredList() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + var expected = new List { 1, 2, 3 }; + config.Set("KeyList", expected); + var result = config.GetList("KeyList", new List()); + Assert.Equal(expected, result); + } + + [Fact] + public void GetList_ShouldReturnDefaultAndLogIfMissing() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + var defaultList = new List { 10 }; + var result = config.GetList("MissingList", defaultList); + Assert.Equal(defaultList, result); + _loggerMock.Verify(log => log.Log(It.Is(s => s.Contains("Key 'MissingList' not found")), LogType.Warning), Times.Once); + } + + [Fact] + public void GetDictionary_ShouldReturnStoredDictionary() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + var dict = new Dictionary { { "a", 1 }, { "b", 2 } }; + config.Set("DictKey", dict); + var result = config.GetDictionary("DictKey"); + Assert.Equal(2, result.Count); + Assert.Equal(1, result["a"]); + } + + [Fact] + public void GetDictionary_ShouldThrowIfNotDictionary() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Set("NotDict", 123); + Assert.Throws(() => config.GetDictionary("NotDict")); + } + + #endregion + + #region LINQ Operations + + [Fact] + public void ContainsKey_ShouldReturnTrueForExistingKey() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("Key1", 1); + Assert.True(config.ContainsKey("Key1")); + } + + [Fact] + public void ContainsKey_ShouldReturnFalseForMissingKey() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + Assert.False(config.ContainsKey("KeyX")); + } + + [Fact] + public void ContainsAllKeys_ShouldReturnTrueIfAllKeysExist() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + config.Add("B", 2); + Assert.True(config.ContainsAllKeys("A", "B")); + } + + [Fact] + public void ContainsAllKeys_ShouldReturnFalseIfAnyKeyMissing() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + Assert.False(config.ContainsAllKeys("A", "C")); + } + + [Fact] + public void Where_WithPredicate_ShouldFilterResults() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + config.Add("B", 2); + var result = config.Where(pair => (int)pair.Value > 1); + Assert.Single(result); + } + + [Fact] + public void Where_WithSelector_ShouldMapResults() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + config.Add("B", 2); + var result = config.Where(pair => pair.Key); + Assert.Contains("A", result); + } + + [Fact] + public void FirstOrDefault_WithPredicate_ShouldReturnMatchingElement() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + config.Add("B", 2); + var result = config.FirstOrDefault(pair => (int)pair.Value == 2); + Assert.Equal("B", result.Key); + } + + [Fact] + public void FirstOrDefault_ShouldReturnFirstElement() + { + var config = new TestableConfigurationBase(_loggerMock.Object, _testFilePath); + config.Add("A", 1); + var result = config.FirstOrDefault(); + Assert.Equal("A", result.Key); + } + + #endregion + + private class TestableConfigurationBase(ILogger logger, string path) : ConfigurationBase(logger, path) + { + public override Task SaveToFile() => Task.CompletedTask; + public override void LoadFromFile() { } + } +} \ No newline at end of file diff --git a/Tests/Tests.DiscordBotCore.Configuration/Tests.DiscordBotCore.Configuration.csproj b/Tests/Tests.DiscordBotCore.Configuration/Tests.DiscordBotCore.Configuration.csproj new file mode 100644 index 0000000..328675d --- /dev/null +++ b/Tests/Tests.DiscordBotCore.Configuration/Tests.DiscordBotCore.Configuration.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + +