diff --git a/src/EllieBot.Tests/BotStringsTests.cs b/src/EllieBot.Tests/BotStringsTests.cs new file mode 100644 index 0000000..3432ac3 --- /dev/null +++ b/src/EllieBot.Tests/BotStringsTests.cs @@ -0,0 +1,131 @@ +using NUnit.Framework; +using System.Globalization; +using System.Linq; +using System.Reflection; +using Discord.Commands; +using EllieBot.Common; +using EllieBot.Common.Attributes; +using EllieBot.Services; + +namespace EllieBot.Tests +{ + public class CommandStringsTests + { + private const string responsesPath = "../../../../EllieBot/data/strings/responses"; + private const string commandsPath = "../../../../EllieBot/data/strings/commands"; + private const string aliasesPath = "../../../../EllieBot/data/aliases.yml"; + + [Test] + public void AllCommandNamesHaveStrings() + { + var stringsSource = new LocalFileStringsSource( + responsesPath, + commandsPath); + var strings = new MemoryBotStringsProvider(stringsSource); + + var culture = new CultureInfo("en-US"); + + var isSuccess = true; + foreach (var (methodName, _) in CommandNameLoadHelper.LoadAliases(aliasesPath)) + { + var cmdStrings = strings.GetCommandStrings(culture.Name, methodName); + if (cmdStrings is null) + { + isSuccess = false; + TestContext.Out.WriteLine($"{methodName} doesn't exist in commands.en-US.yml"); + } + } + + Assert.IsTrue(isSuccess); + } + + private static string[] GetCommandMethodNames() + => typeof(Bot).Assembly + .GetExportedTypes() + .Where(type => type.IsClass && !type.IsAbstract) + .Where(type => typeof(EllieModule).IsAssignableFrom(type) // if its a top level module + || !(type.GetCustomAttribute(true) is null)) // or a submodule + .SelectMany(x => x.GetMethods() + .Where(mi => mi.CustomAttributes + .Any(ca => ca.AttributeType == typeof(CmdAttribute)))) + .Select(x => x.Name.ToLowerInvariant()) + .ToArray(); + + [Test] + public void AllCommandMethodsHaveNames() + { + var allAliases = CommandNameLoadHelper.LoadAliases( + aliasesPath); + + var methodNames = GetCommandMethodNames(); + + var isSuccess = true; + foreach (var methodName in methodNames) + { + if (!allAliases.TryGetValue(methodName, out _)) + { + TestContext.Error.WriteLine($"{methodName} is missing an alias."); + isSuccess = false; + } + } + + Assert.IsTrue(isSuccess); + } + + [Test] + public void NoObsoleteAliases() + { + var allAliases = CommandNameLoadHelper.LoadAliases(aliasesPath); + + var methodNames = GetCommandMethodNames() + .ToHashSet(); + + var isSuccess = true; + + foreach (var item in allAliases) + { + var methodName = item.Key; + + if (!methodNames.Contains(methodName)) + { + TestContext.WriteLine($"'{methodName}' from aliases.yml doesn't have a matching command method."); + isSuccess = false; + } + } + + if (isSuccess) + Assert.Pass(); + else + Assert.Warn("There are some unused entries in data/aliases.yml"); + } + + [Test] + public void NoObsoleteCommandStrings() + { + var stringsSource = new LocalFileStringsSource(responsesPath, commandsPath); + + var culture = new CultureInfo("en-US"); + + var methodNames = GetCommandMethodNames() + .ToHashSet(); + + var isSuccess = true; + // var allCommandNames = CommandNameLoadHelper.LoadCommandStrings(commandsPath)); + foreach (var entry in stringsSource.GetCommandStrings()[culture.Name]) + { + var cmdName = entry.Key; + + if (!methodNames.Contains(cmdName)) + { + TestContext.Out.WriteLine($"'{cmdName}' from commands.en-US.yml doesn't have a matching command method."); + isSuccess = false; + } + } + + if (isSuccess) + Assert.IsTrue(isSuccess); + else + Assert.Warn("There are some unused command strings in data/strings/commands.en-US.yml"); + } + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/ConcurrentHashSetTests.cs b/src/EllieBot.Tests/ConcurrentHashSetTests.cs new file mode 100644 index 0000000..a6fb963 --- /dev/null +++ b/src/EllieBot.Tests/ConcurrentHashSetTests.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using NUnit.Framework; + +namespace EllieBot.Tests; + +public class ConcurrentHashSetTests +{ + private ConcurrentHashSet<(int?, int?)> _set; + + [SetUp] + public void SetUp() + { + _set = new(); + } + + [Test] + public void AddTest() + { + var result = _set.Add((1, 2)); + + Assert.AreEqual(true, result); + + result = _set.Add((1, 2)); + + Assert.AreEqual(false, result); + } + + [Test] + public void TryRemoveTest() + { + _set.Add((1, 2)); + var result = _set.TryRemove((1, 2)); + + Assert.AreEqual(true, result); + + result = _set.TryRemove((1, 2)); + Assert.AreEqual(false, result); + } + + [Test] + public void CountTest() + { + _set.Add((1, 2)); // 1 + _set.Add((1, 2)); // 1 + + _set.Add((2, 2)); // 2 + + _set.Add((3, 2)); // 3 + _set.Add((3, 2)); // 3 + + Assert.AreEqual(3, _set.Count); + } + + [Test] + public void ClearTest() + { + _set.Add((1, 2)); + _set.Add((1, 3)); + _set.Add((1, 4)); + + _set.Clear(); + + Assert.AreEqual(0, _set.Count); + } + + [Test] + public void ContainsTest() + { + _set.Add((1, 2)); + _set.Add((3, 2)); + + Assert.AreEqual(true, _set.Contains((1, 2))); + Assert.AreEqual(true, _set.Contains((3, 2))); + Assert.AreEqual(false, _set.Contains((2, 1))); + Assert.AreEqual(false, _set.Contains((2, 3))); + } + + [Test] + public void RemoveWhereTest() + { + _set.Add((1, 2)); + _set.Add((1, 3)); + _set.Add((1, 4)); + _set.Add((2, 5)); + + // remove tuples which have even second item + _set.RemoveWhere(static x => x.Item2 % 2 == 0); + + Assert.AreEqual(2, _set.Count); + Assert.AreEqual(true, _set.Contains((1, 3))); + Assert.AreEqual(true, _set.Contains((2, 5))); + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/EllieBot.Tests.csproj b/src/EllieBot.Tests/EllieBot.Tests.csproj new file mode 100644 index 0000000..d1d7845 --- /dev/null +++ b/src/EllieBot.Tests/EllieBot.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + false + + + + + + + + + + + + + diff --git a/src/EllieBot.Tests/GroupGreetTests.cs b/src/EllieBot.Tests/GroupGreetTests.cs new file mode 100644 index 0000000..ba2bf37 --- /dev/null +++ b/src/EllieBot.Tests/GroupGreetTests.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Threading.Tasks; +using Ellie.Common; +using EllieBot.Services; +using NUnit.Framework; + +namespace EllieBot.Tests +{ + public class GroupGreetTests + { + private GreetGrouper _grouper; + + [SetUp] + public void Setup() + => _grouper = new GreetGrouper(); + + [Test] + public void CreateTest() + { + var created = _grouper.CreateOrAdd(0, 5); + + Assert.True(created); + } + + [Test] + public void CreateClearTest() + { + _grouper.CreateOrAdd(0, 5); + _grouper.ClearGroup(0, 5, out var items); + + Assert.AreEqual(0, items.Count()); + } + + [Test] + public void NotCreatedTest() + { + _grouper.CreateOrAdd(0, 5); + var created = _grouper.CreateOrAdd(0, 4); + + Assert.False(created); + } + + [Test] + public void ClearAddedTest() + { + _grouper.CreateOrAdd(0, 5); + _grouper.CreateOrAdd(0, 4); + _grouper.ClearGroup(0, 5, out var items); + + var list = items.ToList(); + + Assert.AreEqual(1, list.Count, $"Count was {list.Count}"); + Assert.AreEqual(4, list[0]); + } + + [Test] + public async Task ClearManyTest() + { + _grouper.CreateOrAdd(0, 5); + + // add 15 items + await Enumerable.Range(10, 15) + .Select(x => Task.Run(() => _grouper.CreateOrAdd(0, x))).WhenAll(); + + // get 5 at most + _grouper.ClearGroup(0, 5, out var items); + var list = items.ToList(); + Assert.AreEqual(5, list.Count, $"Count was {list.Count}"); + + // try to get 15, but there should be 10 left + _grouper.ClearGroup(0, 15, out items); + list = items.ToList(); + Assert.AreEqual(10, list.Count, $"Count was {list.Count}"); + } + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/IndexedCollectionTests.cs b/src/EllieBot.Tests/IndexedCollectionTests.cs new file mode 100644 index 0000000..587f2c4 --- /dev/null +++ b/src/EllieBot.Tests/IndexedCollectionTests.cs @@ -0,0 +1,188 @@ +using Ellie.Common; +using EllieBot.Db.Models; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace EllieBot.Tests +{ + public class IndexedCollectionTests + { + [Test] + public void AddTest() + { + var collection = GetCollectionSample(Enumerable.Empty()); + + // Add the items + for (var counter = 0; counter < 10; counter++) + collection.Add(new ShopEntry()); + + // Evaluate the items are ordered + CheckIndices(collection); + } + + [Test] + public void RemoveTest() + { + var collection = GetCollectionSample(); + + collection.Remove(collection[1]); + collection.Remove(collection[1]); + + // Evaluate the indices are ordered + CheckIndices(collection); + Assert.AreEqual(8, collection.Count); + } + + [Test] + public void RemoveAtTest() + { + var collection = GetCollectionSample(); + + // Remove items 5 and 7 + collection.RemoveAt(5); + collection.RemoveAt(6); + + // Evaluate if the items got removed + foreach (var item in collection) + Assert.IsFalse(item.Id == 5 || item.Id == 7, $"Item at index {item.Index} was not removed"); + + CheckIndices(collection); + + // RemoveAt out of range + Assert.Throws(() => collection.RemoveAt(999), $"No exception thrown when removing from index 999 in a collection of size {collection.Count}."); + Assert.Throws(() => collection.RemoveAt(-3), $"No exception thrown when removing from negative index -3."); + } + + [Test] + public void ClearTest() + { + var collection = GetCollectionSample(); + collection.Clear(); + + Assert.IsTrue(collection.Count == 0, "Collection has not been cleared."); + Assert.Throws(() => + { + _ = collection[0]; + }, "Collection has not been cleared."); + } + + [Test] + public void CopyToTest() + { + var collection = GetCollectionSample(); + var fullCopy = new ShopEntry[10]; + + collection.CopyTo(fullCopy, 0); + + // Evaluate copy + for (var index = 0; index < fullCopy.Length; index++) + Assert.AreEqual(index, fullCopy[index].Index); + + Assert.Throws(() => collection.CopyTo(new ShopEntry[10], 4)); + Assert.Throws(() => collection.CopyTo(new ShopEntry[6], 0)); + } + + [Test] + public void IndexOfTest() + { + var collection = GetCollectionSample(); + + Assert.AreEqual(4, collection.IndexOf(collection[4])); + Assert.AreEqual(0, collection.IndexOf(collection[0])); + Assert.AreEqual(7, collection.IndexOf(collection[7])); + Assert.AreEqual(9, collection.IndexOf(collection[9])); + } + + [Test] + public void InsertTest() + { + var collection = GetCollectionSample(); + + // Insert items at indices 5 and 7 + collection.Insert(5, new ShopEntry() { Id = 555 }); + collection.Insert(7, new ShopEntry() { Id = 777 }); + + Assert.AreEqual(12, collection.Count); + Assert.AreEqual(555, collection[5].Id); + Assert.AreEqual(777, collection[7].Id); + + CheckIndices(collection); + + // Insert out of range + Assert.Throws(() => collection.Insert(999, new ShopEntry() { Id = 999 }), $"No exception thrown when inserting at index 999 in a collection of size {collection.Count}."); + Assert.Throws(() => collection.Insert(-3, new ShopEntry() { Id = -3 }), $"No exception thrown when inserting at negative index -3."); + } + + [Test] + public void ContainsTest() + { + var subCol = new[] + { + new ShopEntry() { Id = 111 }, + new ShopEntry() { Id = 222 }, + new ShopEntry() { Id = 333 } + }; + + var collection = GetCollectionSample( + Enumerable.Range(0, 10) + .Select(x => new ShopEntry() { Id = x }) + .Concat(subCol) + ); + + collection.Remove(subCol[1]); + CheckIndices(collection); + + Assert.IsTrue(collection.Contains(subCol[0])); + Assert.IsFalse(collection.Contains(subCol[1])); + Assert.IsTrue(collection.Contains(subCol[2])); + } + + [Test] + public void EnumeratorTest() + { + var collection = GetCollectionSample(); + var enumerator = collection.GetEnumerator(); + + foreach (var item in collection) + { + enumerator.MoveNext(); + Assert.AreEqual(item, enumerator.Current); + } + } + + [Test] + public void IndexTest() + { + var collection = GetCollectionSample(); + + collection[4] = new ShopEntry() { Id = 444 }; + collection[7] = new ShopEntry() { Id = 777 }; + CheckIndices(collection); + + Assert.AreEqual(444, collection[4].Id); + Assert.AreEqual(777, collection[7].Id); + } + + /// + /// Checks whether all indices of the items are properly ordered. + /// + /// An indexed, reference type. + /// The indexed collection to be checked. + private void CheckIndices(IndexedCollection collection) where T : class, IIndexed + { + for (var index = 0; index < collection.Count; index++) + Assert.AreEqual(index, collection[index].Index); + } + + /// + /// Gets an from the specified or a collection with 10 shop entries if none is provided. + /// + /// An indexed, database entity type. + /// A sample collection to be added as an indexed collection. + /// An indexed collection of . + private IndexedCollection GetCollectionSample(IEnumerable sample = default) where T : DbEntity, IIndexed, new() + => new IndexedCollection(sample ?? Enumerable.Range(0, 10).Select(x => new T() { Id = x })); + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/KwumTests.cs b/src/EllieBot.Tests/KwumTests.cs new file mode 100644 index 0000000..21f0223 --- /dev/null +++ b/src/EllieBot.Tests/KwumTests.cs @@ -0,0 +1,124 @@ +using Ellie.Common; +using NUnit.Framework; + +namespace EllieBot.Tests +{ + public class KwumTests + { + [Test] + public void TestDefaultHashCode() + { + var num = default(kwum); + + Assert.AreEqual(0, num.GetHashCode()); + } + + [Test] + public void TestEqualGetHashCode() + { + var num1 = new kwum("234"); + var num2 = new kwum("234"); + + Assert.AreEqual(num1.GetHashCode(), num2.GetHashCode()); + } + + [Test] + public void TestNotEqualGetHashCode() + { + var num1 = new kwum("234"); + var num2 = new kwum("235"); + + Assert.AreNotEqual(num1.GetHashCode(), num2.GetHashCode()); + } + + [Test] + public void TestLongEqualGetHashCode() + { + var num1 = new kwum("hgbkhdbk"); + var num2 = new kwum("hgbkhdbk"); + + Assert.AreEqual(num1.GetHashCode(), num2.GetHashCode()); + } + + [Test] + public void TestEqual() + { + var num1 = new kwum("hgbkhd"); + var num2 = new kwum("hgbkhd"); + + Assert.AreEqual(num1, num2); + } + + [Test] + public void TestNotEqual() + { + var num1 = new kwum("hgbk5d"); + var num2 = new kwum("hgbk4d"); + + Assert.AreNotEqual(num1, num2); + } + + [Test] + public void TestParseValidValue() + { + var validValue = "234e"; + Assert.True(kwum.TryParse(validValue, out _)); + } + + [Test] + public void TestParseInvalidValue() + { + var invalidValue = "1234"; + Assert.False(kwum.TryParse(invalidValue, out _)); + } + + [Test] + public void TestCorrectParseValue() + { + var validValue = "qwerf4bm"; + kwum.TryParse(validValue, out var parsedValue); + + Assert.AreEqual(parsedValue, new kwum(validValue)); + } + + [Test] + public void TestToString() + { + var validValue = "46g5yh"; + kwum.TryParse(validValue, out var parsedValue); + + Assert.AreEqual(validValue, parsedValue.ToString()); + } + + [Test] + public void TestConversionsToFromInt() + { + var num = new kwum(10); + + Assert.AreEqual(10, (int)num); + Assert.AreEqual(num, (kwum)10); + } + + [Test] + public void TestConverstionsToString() + { + var num = new kwum(10); + Assert.AreEqual("c", num.ToString()); + num = new kwum(123); + Assert.AreEqual("5v", num.ToString()); + + // leading zeros have no meaning + Assert.AreEqual(new kwum("22225v"), num); + } + + [Test] + public void TestMaxValue() + { + var num = new kwum(int.MaxValue - 1); + Assert.AreEqual("3zzzzzy", num.ToString()); + + num = new kwum(int.MaxValue); + Assert.AreEqual("3zzzzzz", num.ToString()); + } + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/NewDeckTests.cs b/src/EllieBot.Tests/NewDeckTests.cs new file mode 100644 index 0000000..24d31d0 --- /dev/null +++ b/src/EllieBot.Tests/NewDeckTests.cs @@ -0,0 +1,84 @@ +using Ellie.Econ; +using NUnit.Framework; + +namespace EllieBot.Tests; + + +public class NewDeckTests +{ + private RegularDeck _deck; + + [SetUp] + public void Setup() + { + _deck = new RegularDeck(); + } + + [Test] + public void TestCount() + { + Assert.AreEqual(52, _deck.TotalCount); + Assert.AreEqual(52, _deck.CurrentCount); + } + + [Test] + public void TestDeckDraw() + { + var card = _deck.Draw(); + + Assert.IsNotNull(card); + Assert.AreEqual(card.Suit, RegularSuit.Hearts); + Assert.AreEqual(card.Value, RegularValue.Ace); + Assert.AreEqual(_deck.CurrentCount, _deck.TotalCount - 1); + } + + [Test] + public void TestDeckSpent() + { + for (var i = 0; i < _deck.TotalCount - 1; ++i) + { + _deck.Draw(); + } + + var lastCard = _deck.Draw(); + + Assert.IsNotNull(lastCard); + Assert.AreEqual(new RegularCard(RegularSuit.Spades, RegularValue.King), lastCard); + + var noCard = _deck.Draw(); + + Assert.IsNull(noCard); + } + + [Test] + public void TestCardGetName() + { + var ace = _deck.Draw()!; + var two = _deck.Draw()!; + + Assert.AreEqual("Ace of Hearts", ace.GetName()); + Assert.AreEqual("Two of Hearts", two.GetName()); + } + + [Test] + public void TestPeek() + { + var ace = _deck.Peek()!; + + var tenOfSpades = _deck.Peek(48); + Assert.AreEqual(new RegularCard(RegularSuit.Hearts, RegularValue.Ace), ace); + Assert.AreEqual(new RegularCard(RegularSuit.Spades, RegularValue.Ten), tenOfSpades); + } + + [Test] + public void TestMultipleDeck() + { + var quadDeck = new MultipleRegularDeck(4); + var count = quadDeck.TotalCount; + + Assert.AreEqual(52 * 4, count); + + var card = quadDeck.Peek(54); + Assert.AreEqual(new RegularCard(RegularSuit.Hearts, RegularValue.Three), card); + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/PubSubTests.cs b/src/EllieBot.Tests/PubSubTests.cs new file mode 100644 index 0000000..81b89bc --- /dev/null +++ b/src/EllieBot.Tests/PubSubTests.cs @@ -0,0 +1,136 @@ +using System.Threading.Tasks; +using Ellie.Common; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace EllieBot.Tests +{ + public class PubSubTests + { + [Test] + public async Task Test_EventPubSub_PubSub() + { + TypedKey key = "test_key"; + var expected = new Randomizer().Next(); + var pubsub = new EventPubSub(); + await pubsub.Sub(key, data => + { + Assert.AreEqual(expected, data); + Assert.Pass(); + return default; + }); + await pubsub.Pub(key, expected); + Assert.Fail("Event not registered"); + } + + [Test] + public async Task Test_EventPubSub_MeaninglessUnsub() + { + TypedKey key = "test_key"; + var expected = new Randomizer().Next(); + var pubsub = new EventPubSub(); + await pubsub.Sub(key, data => + { + Assert.AreEqual(expected, data); + Assert.Pass(); + return default; + }); + await pubsub.Unsub(key, _ => default); + await pubsub.Pub(key, expected); + Assert.Fail("Event not registered"); + } + + [Test] + public async Task Test_EventPubSub_MeaninglessUnsubThatLooksTheSame() + { + TypedKey key = "test_key"; + var expected = new Randomizer().Next(); + var pubsub = new EventPubSub(); + await pubsub.Sub(key, data => + { + Assert.AreEqual(expected, data); + Assert.Pass(); + return default; + }); + await pubsub.Unsub(key, data => + { + Assert.AreEqual(expected, data); + Assert.Pass(); + return default; + }); + await pubsub.Pub(key, expected); + Assert.Fail("Event not registered"); + } + + [Test] + public async Task Test_EventPubSub_MeaningfullUnsub() + { + TypedKey key = "test_key"; + var pubsub = new EventPubSub(); + + ValueTask Action(int data) + { + Assert.Fail("Event is raised when it shouldn't be"); + return default; + } + + await pubsub.Sub(key, Action); + await pubsub.Unsub(key, Action); + await pubsub.Pub(key, 0); + Assert.Pass(); + } + + [Test] + public async Task Test_EventPubSub_ObjectData() + { + TypedKey key = "test_key"; + var pubsub = new EventPubSub(); + + var localData = new byte[1]; + + ValueTask Action(byte[] data) + { + Assert.AreEqual(localData, data); + Assert.Pass(); + return default; + } + + await pubsub.Sub(key, Action); + await pubsub.Pub(key, localData); + + Assert.Fail("Event not raised"); + } + + [Test] + public async Task Test_EventPubSub_MultiSubUnsub() + { + TypedKey key = "test_key"; + var pubsub = new EventPubSub(); + + var localData = new object(); + int successCounter = 0; + + ValueTask Action1(object data) + { + Assert.AreEqual(localData, data); + successCounter += 10; + return default; + } + + ValueTask Action2(object data) + { + Assert.AreEqual(localData, data); + successCounter++; + return default; + } + + await pubsub.Sub(key, Action1); // + 10 \ + await pubsub.Sub(key, Action2); // + 1 - + = 12 + await pubsub.Sub(key, Action2); // + 1 / + await pubsub.Unsub(key, Action2); // - 1/ + await pubsub.Pub(key, localData); + + Assert.AreEqual(successCounter, 11, "Not all events are raised."); + } + } +} \ No newline at end of file diff --git a/src/EllieBot.Tests/README.md b/src/EllieBot.Tests/README.md new file mode 100644 index 0000000..738a879 --- /dev/null +++ b/src/EllieBot.Tests/README.md @@ -0,0 +1 @@ +Project which contains tests. Self explanatory \ No newline at end of file diff --git a/src/EllieBot.Tests/Random.cs b/src/EllieBot.Tests/Random.cs new file mode 100644 index 0000000..b124e0c --- /dev/null +++ b/src/EllieBot.Tests/Random.cs @@ -0,0 +1,23 @@ +using System; +using System.Text; +using EllieBot.Common.Yml; +using NUnit.Framework; + +namespace EllieBot.Tests +{ + public class RandomTests + { + [SetUp] + public void Setup() + => Console.OutputEncoding = Encoding.UTF8; + + [Test] + public void Utf8CodepointsToEmoji() + { + var point = @"0001F338"; + var hopefullyEmoji = YamlHelper.UnescapeUnicodeCodePoint(point); + + Assert.AreEqual("🌸", hopefullyEmoji, hopefullyEmoji); + } + } +} \ No newline at end of file