using MongoDB.Entities; using PrivaPub.ClientModels.Post; using PrivaPub.Domain.Social; using PrivaPub.Federation.Objects; using PrivaPub.Models.Post; using PrivaPub.Models.Social; using PrivaPub.Tests.Support; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Federation { public class ReactionNamesTests { [Theory] [InlineData(":blobcat@k.example:", "blobcat")] [InlineData("👍🏽", "👍🏽")] [InlineData("❤️", "❤️")] [InlineData("nope", null)] [InlineData("👍👍", null)] [InlineData(":unknown:", null)] public void Normalises_what_peers_send(string content, string expected) => Assert.Equal(expected, Reactions.Normalise(content, new List { new() { Shortcode = "blobcat", URL = "https://k.example/e.png" } })?.Name); } [Trait("Category", "Integration")] public sealed class ReactionTests : 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 Origin(RemoteActor actor) => new Uri(actor.Id).GetLeftPart(UriPartial.Authority); static string NewId(RemoteActor actor, string kind) => $"{Origin(actor)}/{kind}/{Guid.NewGuid():N}"; async Task LocalPost() { var (root, alice) = await _harness.Persona("alice"); await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "react to me" }, TestContext.Current.CancellationToken); return await DB.Default.Find().Match(p => p.GroupUserId == alice.Id).ExecuteFirstAsync(TestContext.Current.CancellationToken); } [Fact] public async Task Misskey_and_pleroma_reactions_are_kept_apart_from_favourites() { var token = TestContext.Current.CancellationToken; var post = await LocalPost(); var misskey = new RemoteActor(_harness.Peer, "kitty"); var pleroma = new RemoteActor(_harness.Peer, "pleroma"); await _harness.Deliver(misskey, "/human-centipede", new JsonObject { ["id"] = NewId(misskey, "likes"), ["type"] = "Like", ["actor"] = misskey.Id, ["object"] = post.ObjectURI, ["content"] = ":blobcat:", ["_misskey_reaction"] = ":blobcat:", ["tag"] = new JsonArray(new JsonObject { ["type"] = "Emoji", ["name"] = ":blobcat:", ["icon"] = new JsonObject { ["type"] = "Image", ["url"] = $"{Origin(misskey)}/emoji/blobcat.png" } }) }); await _harness.Deliver(misskey, "/human-centipede", new JsonObject { ["id"] = NewId(misskey, "likes"), ["type"] = "Like", ["actor"] = misskey.Id, ["object"] = post.ObjectURI, ["content"] = "❤" }); var fire = new JsonObject { ["id"] = NewId(pleroma, "activities"), ["type"] = "EmojiReact", ["actor"] = pleroma.Id, ["object"] = post.ObjectURI, ["content"] = "🔥" }; await _harness.Deliver(pleroma, "/human-centipede", fire); var kept = await DB.Default.Find().Match(r => r.PostId == post.ID).ExecuteAsync(token); Assert.Equal(new HashSet { "blobcat", "🔥" }, kept.Select(r => r.Emoji).ToHashSet()); Assert.Equal($"{Origin(misskey)}/emoji/blobcat.png", kept.Single(r => r.Emoji == "blobcat").EmojiURL); Assert.Equal(1, (await DB.Default.Find().OneAsync(post.ID, token)).FavouritesCount); Assert.True(await DB.Default.Find().Match(n => n.Type == NotificationType.Reaction && n.Emoji == "🔥").ExecuteAnyAsync(token)); await _harness.Deliver(pleroma, "/human-centipede", new JsonObject { ["id"] = NewId(pleroma, "activities"), ["type"] = "Undo", ["actor"] = pleroma.Id, ["object"] = fire.DeepClone() }); Assert.Equal("blobcat", Assert.Single(await DB.Default.Find().Match(r => r.PostId == post.ID).ExecuteAsync(token)).Emoji); } [Fact] public async Task A_persona_reacts_to_a_remote_post_and_takes_it_back() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var mallory = new RemoteActor(_harness.Peer, "mallory"); var noteId = NewId(mallory, "notes"); await _harness.Deliver(mallory, "/human-centipede", new JsonObject { ["id"] = NewId(mallory, "activities"), ["type"] = "Create", ["actor"] = mallory.Id, ["object"] = new JsonObject { ["id"] = noteId, ["type"] = "Note", ["attributedTo"] = mallory.Id, ["content"] = "

hi

", ["to"] = new JsonArray(Addressing.Public), ["cc"] = new JsonArray(alice.Uri), ["tag"] = new JsonArray(new JsonObject { ["type"] = "Mention", ["href"] = alice.Uri, ["name"] = "@alice" }) } }); var post = await DB.Default.Find().Match(p => p.ObjectURI == noteId).ExecuteFirstAsync(token); Assert.True(await _harness.Reactions.React(alice, post, "🎉", token)); Assert.False(await _harness.Reactions.React(alice, post, "not an emoji", token)); Assert.True(await _harness.Reactions.Unreact(alice, post, "🎉", token)); var sent = await _harness.Outgoing(mallory.Id + "/inbox"); var react = Assert.Single(sent, a => a["type"]!.GetValue() == "EmojiReact"); Assert.Equal("🎉", react["content"]!.GetValue()); Assert.Equal(noteId, react["object"]!.GetValue()); Assert.Equal(react["id"]!.GetValue(), Assert.Single(sent, a => a["type"]!.GetValue() == "Undo")["object"]!["id"]!.GetValue()); } } }