diff --git a/.gitignore b/.gitignore index b33fc18..c9eb026 100644 --- a/.gitignore +++ b/.gitignore @@ -374,6 +374,9 @@ FodyWeavers.xsd #Specs und Plan datein /.superpowers/ + +#Test Datein +ChatTwo.Tests TestResults *.db-shm *.db-wal diff --git a/ChatTwo.Tests/AutoTellTabsHistoryTest.cs b/ChatTwo.Tests/AutoTellTabsHistoryTest.cs deleted file mode 100644 index 0c47836..0000000 --- a/ChatTwo.Tests/AutoTellTabsHistoryTest.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using ChatTwo.Code; -using ChatTwo.Util; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using JetBrains.Annotations; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace ChatTwo.Tests; - -// Hellion Chat — Auto-Tell-Tabs history-preload coverage. -// -// These tests exercise MessageStore.GetTellHistoryWithSender, the query the -// AutoTellTabsService uses to populate a freshly spawned temp tab with the -// last conversations with that player. -// -// NOTE: like the rest of ChatTwo.Tests today, these will fail at runtime -// until the project's Dalamud.dll runtime dependency is sorted out (see -// Phase-2 backlog item "Test-Projekt fixen"). Compile-time the suite builds -// fine via DALAMUD_HOME, so the tests guard against API drift even before -// they can be executed locally. -[TestClass] -[TestSubject(typeof(MessageStore))] -public class AutoTellTabsHistoryTest -{ - public TestContext TestContext { get; set; } - - [TestMethod] - [Timeout(5000)] - public void GetTellHistoryWithSender_FiltersByNameAndWorld() - { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - const ulong receiver = 99001; - var now = DateTimeOffset.UtcNow; - - // Two tells with the target sender, one with a different sender on - // the same world, one with the same name on a different world. Only - // the first two should make it into the result. - var asukaLichIn = TellMessage("Asuka", 76, receiver, now.AddMinutes(-30), ChatType.TellIncoming); - var asukaLichOut = TellMessage("Asuka", 76, receiver, now.AddMinutes(-20), ChatType.TellOutgoing); - var broboLich = TellMessage("Brobo", 76, receiver, now.AddMinutes(-10), ChatType.TellIncoming); - var asukaOmega = TellMessage("Asuka", 90, receiver, now.AddMinutes(-5), ChatType.TellIncoming); - - store.UpsertMessage(asukaLichIn); - store.UpsertMessage(asukaLichOut); - store.UpsertMessage(broboLich); - store.UpsertMessage(asukaOmega); - - var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 50); - - Assert.AreEqual(2, result.Count); - // Result is oldest-first so a tab can append messages chronologically. - Assert.AreEqual(asukaLichIn.Id, result[0].Id); - Assert.AreEqual(asukaLichOut.Id, result[1].Id); - } - - [TestMethod] - [Timeout(5000)] - public void GetTellHistoryWithSender_RespectsLimit() - { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - const ulong receiver = 99002; - var now = DateTimeOffset.UtcNow; - - for (var i = 0; i < 30; i++) - { - var msg = TellMessage("Asuka", 76, receiver, now.AddMinutes(-i - 1), ChatType.TellIncoming); - store.UpsertMessage(msg); - } - - var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 5); - - Assert.AreEqual(5, result.Count); - } - - [TestMethod] - [Timeout(5000)] - public void GetTellHistoryWithSender_ZeroLimitReturnsEmpty() - { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - const ulong receiver = 99003; - - var msg = TellMessage("Asuka", 76, receiver, DateTimeOffset.UtcNow, ChatType.TellIncoming); - store.UpsertMessage(msg); - - var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 0); - - Assert.AreEqual(0, result.Count); - } - - [TestMethod] - [Timeout(5000)] - public void GetTellHistoryWithSender_IgnoresOtherReceivers() - { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - const ulong ourReceiver = 99004; - const ulong otherReceiver = 99005; - var now = DateTimeOffset.UtcNow; - - // Tell on the local player's account. - var ours = TellMessage("Asuka", 76, ourReceiver, now.AddMinutes(-1), ChatType.TellIncoming); - // Same sender, but logged against a different local character — - // common when the user has alts. Must not surface. - var foreign = TellMessage("Asuka", 76, otherReceiver, now, ChatType.TellIncoming); - - store.UpsertMessage(ours); - store.UpsertMessage(foreign); - - var result = store.GetTellHistoryWithSender(ourReceiver, "Asuka", 76, limit: 50); - - Assert.AreEqual(1, result.Count); - Assert.AreEqual(ours.Id, result[0].Id); - } - - private static Message TellMessage( - string senderName, - uint senderWorld, - ulong receiver, - DateTimeOffset dateTime, - ChatType chatType) - { - var senderSeString = new SeStringBuilder() - .Add(new PlayerPayload(senderName, senderWorld)) - .AddText(senderName) - .Add(RawPayload.LinkTerminator) - .Build(); - - var contentSeString = new SeStringBuilder() - .AddText("test message") - .Build(); - - var senderChunks = ChunkUtil.ToChunks(senderSeString, ChunkSource.Sender, chatType).ToList(); - var contentChunks = ChunkUtil.ToChunks(contentSeString, ChunkSource.Content, chatType).ToList(); - - var chatCode = new ChatCode((XivChatType)chatType, XivChatRelationKind.LocalPlayer, XivChatRelationKind.LocalPlayer); - return new Message( - Guid.NewGuid(), - receiver, - 0, - dateTime, - chatCode, - senderChunks, - contentChunks, - senderSeString, - contentSeString, - Guid.Empty); - } -} diff --git a/ChatTwo.Tests/ChatTwo.Tests.csproj b/ChatTwo.Tests/ChatTwo.Tests.csproj deleted file mode 100644 index 8ea66bc..0000000 --- a/ChatTwo.Tests/ChatTwo.Tests.csproj +++ /dev/null @@ -1,53 +0,0 @@ - - - - net10.0-windows - - false - - - - - - - - - - - - - - - - $(AppData)\XIVLauncher\addon\Hooks\dev - - - - $(DALAMUD_HOME) - - - - $(HOME)/dalamud - - - - - $(DalamudLibPath)\Dalamud.dll - false - - - $(DalamudLibPath)\FFXIVClientStructs.dll - false - - - $(DalamudLibPath)\Lumina.dll - false - - - $(DalamudLibPath)\Lumina.Excel.dll - false - - - - - diff --git a/ChatTwo.Tests/MessageStoreTest.cs b/ChatTwo.Tests/MessageStoreTest.cs deleted file mode 100644 index 6e517b0..0000000 --- a/ChatTwo.Tests/MessageStoreTest.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using ChatTwo.Code; -using ChatTwo.Util; -using Dalamud.Game.Text; -using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Game.Text.SeStringHandling.Payloads; -using JetBrains.Annotations; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Chat2PartyFinderPayload = ChatTwo.Util.PartyFinderPayload; - -namespace ChatTwo.Tests; - -[TestClass] -[TestSubject(typeof(MessageStore))] -public class MessageStoreTest { - // From Message.cs - private static readonly byte[] ExtraChatChannelPayloadBytes = [0, 0x27, 18, 0x20]; - - public TestContext TestContext { get; set; } - - public static string GetImportPath() { - string[] importPaths = [ - @".\TestData", - @"..\TestData", - @"..\..\TestData", - @"..\..\..\TestData", - ]; - var importPath = importPaths.FirstOrDefault(Directory.Exists); - if (string.IsNullOrEmpty(importPath)) { - throw new DirectoryNotFoundException("Could not find the import path"); - } - return importPath; - } - - [TestMethod] - [Timeout(5000)] - public void StoreAndRetrieve() { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - // Write the message. - var input = BigMessage(); - store.UpsertMessage(input); - - // Read the message back. - using var messageEnumerator = store.GetMostRecentMessages(); - var messages = messageEnumerator.ToList(); - Assert.AreEqual(1, messages.Count); - AssertMessagesEqual(input, messages.First()); - } - - [TestMethod] - [Timeout(5000)] - public void RetrieveMultiple() { - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - // Insert 10 messages in the wrong order of date. - var messages = new List(); - const uint receiver = 12345; - var now = DateTimeOffset.UtcNow; - for (var i = 0; i < 10; i++) { - var message = BigMessage(true, receiver, now.AddSeconds(-i)); - TestContext.WriteLine($"Inserting message {i}: {message.Id}"); - store.UpsertMessage(message); - messages.Add(message); - } - - // Insert a message for a different receiver. This shouldn't be returned - // because of the receiver filtering. - var otherReceiverMsg = BigMessage(receiver: receiver + 1, dateTime: now.AddSeconds(1)); - TestContext.WriteLine($"Inserting other receiver message: {otherReceiverMsg.Id}"); - store.UpsertMessage(otherReceiverMsg); - - // Query the most recent 5 messages. Should return the 4 newest messages - // from the list, as well as the different receiver message because we - // aren't filtering. - using var unfilteredMessageEnumerator = store.GetMostRecentMessages(count: 5); - var outputMessages = unfilteredMessageEnumerator.ToList(); - var gotIds = outputMessages.Select(m => m.Id).ToList(); - TestContext.WriteLine($"Query 1 got IDs: {string.Join(", ", gotIds)}"); - AssertGuidsEqual(new List { - messages[3].Id, - messages[2].Id, - messages[1].Id, - messages[0].Id, - otherReceiverMsg.Id - }, gotIds); - - // Query the most recent 5 messages but filter by receiver ID. - using var filteredByReceiverMessageEnumerator = store.GetMostRecentMessages(receiver: receiver, count: 5); - outputMessages = filteredByReceiverMessageEnumerator.ToList(); - gotIds = outputMessages.Select(m => m.Id).ToList(); - TestContext.WriteLine($"Query 2 got IDs: {string.Join(", ", gotIds)}"); - AssertGuidsEqual(new List { - messages[4].Id, - messages[3].Id, - messages[2].Id, - messages[1].Id, - messages[0].Id, - }, gotIds); - - // Query the most recent 5 messages but only since a specific date. - using var filteredByReceiverAndDateMessageEnumerator = store.GetMostRecentMessages(receiver, since: messages[1].Date, count: 5); - outputMessages = filteredByReceiverAndDateMessageEnumerator.ToList(); - gotIds = outputMessages.Select(m => m.Id).ToList(); - TestContext.WriteLine($"Query 3 got IDs: {string.Join(", ", gotIds)}"); - AssertGuidsEqual(new List { - messages[1].Id, - messages[0].Id, - }, gotIds); - } - - [TestMethod] - [Timeout(5000)] - // This test guards against the data format changing in an incompatible way. - public void RetrieveExisting() { - var input = BigMessage(uniqId: false); - - var dbPath = Path.Join(GetImportPath(), "existing.db"); - TestContext.WriteLine($"Using existing database: {dbPath}"); - Assert.IsTrue(File.Exists(dbPath)); - - // Uncomment this section to regenerate the existing database. - /* - File.Delete(dbPath); - using (var newStore = new MessageStore(dbPath)) { - newStore.UpsertMessage(input); - } - */ - - using var store = new MessageStore(dbPath); - using var existingMessageEnumerator = store.GetMostRecentMessages(); - var output = existingMessageEnumerator.ToList(); - Assert.AreEqual(1, output.Count); - AssertMessagesEqual(input, output[0]); - } - - [TestMethod] - [Timeout(30_000)] - public void ProfileMany() { - const int count = 20_000; - - var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_"); - var dbPath = Path.Join(tempDir.FullName, "test.db"); - TestContext.WriteLine("Using database path: " + dbPath); - using var store = new MessageStore(dbPath); - - for (var i = 0; i < count; i++) { - var message = BigMessage(uniqId: true); - store.UpsertMessage(message); - } - - using var messageEnumerator = store.GetMostRecentMessages(count: count); - var messages = messageEnumerator.ToList(); - Assert.AreEqual(count, messages.Count); - foreach (var message in messages) { - // Load the message because they are lazily parsed. - Assert.IsTrue(message.Id != Guid.Empty); - } - } - - internal static Message BigMessage(bool uniqId = true, uint receiver = 12345, DateTimeOffset? dateTime = null) { - // NOTE: These values aren't valid in the game. - // NOTE: we can't test UiForeground, UiGlow, or AutoTranslatePayload - // because they load data from the game. - var senderSeString = new SeStringBuilder() - .AddText("<") - .Add(new PlayerPayload("Player Name", 12345)) - .AddItalics("Player Name") - .Add(RawPayload.LinkTerminator) - .AddText(">: ") - .Build(); - var extraChatId = Guid.Parse("03d9e6d4-dc1a-4005-bbe7-66b8c3529277"); - var contentSeString = new SeStringBuilder() - .Add(new RawPayload(ExtraChatChannelPayloadBytes.Concat(extraChatId.ToByteArray()).ToArray())) - .AddIcon(BitmapFontIcon.IslandSanctuary) - .AddMapLink(1, 2, 3, 4) - .AddText("map") - .Add(RawPayload.LinkTerminator) - .AddQuestLink(12345) - .AddText("quest") - .Add(RawPayload.LinkTerminator) - .Add(new DalamudLinkPayload()) - .AddText("dalamud") - .Add(RawPayload.LinkTerminator) - .AddStatusLink(12345) - .AddText("status") - .Add(RawPayload.LinkTerminator) - .AddPartyFinderLink(12345) - .AddText("party finder") - .Add(RawPayload.LinkTerminator) - .Build(); - - // Add Chat 2 specific payloads (that can't be serialized into the - // SeString). - var contentChunks = ChunkUtil.ToChunks(contentSeString, ChunkSource.Content, ChatType.Say).ToList(); - contentChunks = contentChunks.Concat([ - new TextChunk(ChunkSource.Content, new Chat2PartyFinderPayload(12345), "chat 2 party finder"), - new TextChunk(ChunkSource.Content, new AchievementPayload(12345), "chat 2 achievement"), - new TextChunk(ChunkSource.Content, new UriPayload(new Uri("https://dalamud.dev")), "chat 2 uri"), - ]).ToList(); - - var chatCode = new ChatCode((XivChatType)46, XivChatRelationKind.LocalPlayer, XivChatRelationKind.EngagedEnemy); - return new Message( - uniqId ? Guid.NewGuid() : Guid.Parse("f011343e-6a21-49e5-a6f9-238f0f1f8c2c"), - receiver, - 54321, - dateTime ?? DateTimeOffset.FromUnixTimeMilliseconds(1713520182440), - chatCode, - ChunkUtil.ToChunks(senderSeString, ChunkSource.Sender, ChatType.Debug).ToList(), - contentChunks, - senderSeString, - contentSeString, - extraChatId - ); - } - - internal static void AssertMessagesEqual(Message input, Message output) { - // Check basic fields. - Assert.AreEqual(input.Id, output.Id); - Assert.AreEqual(input.Receiver, output.Receiver); - Assert.AreEqual(input.ContentId, output.ContentId); - // Assert time is within 1 second - var timeDifference = Math.Abs(input.Date.ToUniversalTime().Subtract(output.Date.ToUniversalTime()).TotalSeconds); - Assert.IsTrue(timeDifference < 1); - Assert.AreEqual(input.Code, output.Code); - Assert.AreEqual($"{input.SenderSource.Encode():X}", $"{output.SenderSource.Encode():X}"); - Assert.AreEqual($"{input.ContentSource.Encode():X}", $"{output.ContentSource.Encode():X}"); - Assert.AreEqual(input.SortCodeV2, output.SortCodeV2); - Assert.AreEqual(input.ExtraChatChannel, output.ExtraChatChannel); - - // Check chunks. - AssertChunksEqual(input.Sender, output.Sender); - AssertChunksEqual(input.Content, output.Content); - } - - private static void AssertChunksEqual(IReadOnlyList inputChunks, IReadOnlyList outputChunks) { - Assert.AreEqual(inputChunks.Count, outputChunks.Count); - for (var i = 0; i < inputChunks.Count; i++) { - var inputChunk = inputChunks[i]; - var outputChunk = outputChunks[i]; - Assert.AreEqual(inputChunk.Source, outputChunk.Source); - switch (inputChunk.Link) { - case AchievementPayload inputAchievementPayload: - Assert.AreEqual(inputAchievementPayload.Id, ((AchievementPayload) outputChunk.Link)!.Id); - break; - case Chat2PartyFinderPayload inputPartyFinderPayload: - Assert.AreEqual(inputPartyFinderPayload.Id, ((Chat2PartyFinderPayload) outputChunk.Link)!.Id); - break; - case UriPayload inputUriPayload: - Assert.AreEqual(inputUriPayload.Uri, ((UriPayload) outputChunk.Link)!.Uri); - break; - case null: - Assert.IsTrue(outputChunk.Link == null); - break; - default: - Assert.AreEqual($"{inputChunk.Link.Encode():X}", $"{outputChunk.Link!.Encode():X}"); - break; - } - - switch (inputChunk) { - case TextChunk inputTextChunk: - var outputTextChunk = (TextChunk)outputChunk; - Assert.AreEqual(inputTextChunk.FallbackColour, outputTextChunk.FallbackColour); - Assert.AreEqual(inputTextChunk.Foreground, outputTextChunk.Foreground); - Assert.AreEqual(inputTextChunk.Glow, outputTextChunk.Glow); - Assert.AreEqual(inputTextChunk.Italic, outputTextChunk.Italic); - Assert.AreEqual(inputTextChunk.Content, outputTextChunk.Content); - break; - case IconChunk inputIconChunk: - Assert.AreEqual(inputIconChunk.Icon, ((IconChunk) outputChunk).Icon); - break; - default: - throw new Exception("Unknown chunk type"); - } - } - } - - private static void AssertGuidsEqual(IReadOnlyList expected, IReadOnlyList got) { - Assert.AreEqual(expected.Count, got.Count); - for (var i = 0; i < expected.Count; i++) { - Assert.AreEqual(expected[i].ToString(), got[i].ToString()); - } - } -} diff --git a/ChatTwo.Tests/testdata/existing.db b/ChatTwo.Tests/testdata/existing.db deleted file mode 100644 index b67eb8c..0000000 Binary files a/ChatTwo.Tests/testdata/existing.db and /dev/null differ