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