using MongoDB.Entities; using PrivaPub.ClientModels.Post; using PrivaPub.Models.Post; using PrivaPub.Tests.Support; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Federation { // alone in its collection: every test's peer is 127.0.0.1, and the server row it writes decides for whoever writes there [Trait("Category", "Integration")] [Xunit.Collection(nameof(Exclusive))] public sealed class ChatMessageSoftwareTests : IAsyncLifetime { Harness _harness; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _harness = await Harness.Start(); } public async ValueTask DisposeAsync() { if (_harness != default) await _harness.DisposeAsync(); } static string NewId(RemoteActor actor, string kind) => $"{new Uri(actor.Id).GetLeftPart(UriPartial.Authority)}/{kind}/{Guid.NewGuid():N}"; // the one decision taken by a server's software (owner decision 2026-10-05): Lemmy before 1.0 and Mbin get a // ChatMessage even from a persona answering a Note; Lemmy 1.0 a Note [Theory] [InlineData("lemmy", "0.19.20", "ChatMessage")] [InlineData("mbin", "1.10.1", "ChatMessage")] [InlineData("lemmy", "1.0.0-beta.2", "Note")] public async Task A_direct_message_to_lemmy_before_1_0_or_mbin_is_a_chat_message(string software, string version, string sent) { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var sender = new RemoteActor(_harness.Peer, "pm"); var host = new Uri(sender.Id).Host; await DB.Default.DeleteAsync(i => i.Host == host); await DB.Default.SaveAsync(new PrivaPub.Models.Jobs.RemoteInstance { Host = host, Software = software, SoftwareVersion = version }, token); try { var id = NewId(sender, "messages"); await _harness.Deliver(sender, "/human-centipede", new JsonObject { ["id"] = NewId(sender, "activities/create"), ["type"] = "Create", ["actor"] = sender.Id, ["to"] = new JsonArray(alice.Uri), ["object"] = new JsonObject { ["id"] = id, ["type"] = "Note", ["attributedTo"] = sender.Id, ["to"] = new JsonArray(alice.Uri), ["content"] = "

psst

", ["published"] = DateTime.UtcNow.ToString("O") } }); var message = await DB.Default.Find().Match(p => p.ObjectURI == id).ExecuteSingleAsync(token); var answer = await _harness.Statuses.Publish(alice, new PrivaPub.Domain.Statuses.StatusDraft { Text = "psst back", Visibility = PostVisibility.Direct, ConversationId = message.ConversationId }, token); Assert.True(answer.Ok, answer.Error); var create = (await _harness.Outgoing(sender.Id + "/inbox")).Last(a => a["type"]!.GetValue() == "Create"); Assert.Equal(sent, create["object"]!["type"]!.GetValue()); } finally { await DB.Default.DeleteAsync(i => i.Host == host); } } } }