using MongoDB.Entities; using PrivaPub.ClientModels.Post; using PrivaPub.ClientModels; using PrivaPub.Models.Post; using PrivaPub.Models.User; using PrivaPub.Tests.Support; namespace PrivaPub.Tests.Domain { [Trait("Category", "Integration")] public sealed class OutboxTests : 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(); } async Task Known(RemoteActor actor) => await _harness.Remote.GetActor(actor.Id, refresh: false, TestContext.Current.CancellationToken); [Fact] public async Task The_audience_follows_mastodons_table() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var follower = new RemoteActor(_harness.Peer, "follower"); var mentioned = await Known(new RemoteActor(_harness.Peer, "mentioned")); var parentAuthor = await Known(new RemoteActor(_harness.Peer, "parent")); await _harness.FollowedBy(alice, follower); var mention = new PostMention { ActorURI = mentioned.ActorURI }; async Task> Audience(PostVisibility visibility, string replyTo = default) => await _harness.Outbox.Audience(alice, new Post { Visibility = visibility, Mentions = new() { mention }, To = new() { mentioned.ActorURI }, InReplyToAccountId = replyTo }, token); Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL }, await Audience(PostVisibility.Public)); Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL, parentAuthor.InboxURL }, await Audience(PostVisibility.Unlisted, parentAuthor.ID)); Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL }, await Audience(PostVisibility.FollowersOnly)); Assert.Equal(new[] { mentioned.InboxURL }, await Audience(PostVisibility.Direct, parentAuthor.ID)); Assert.Empty(await Audience(PostVisibility.Circle)); } [Fact] public async Task A_post_is_created_edited_and_deleted_for_its_audience() { var token = TestContext.Current.CancellationToken; var (root, alice) = await _harness.Persona("alice"); var follower = new RemoteActor(_harness.Peer, "follower"); await _harness.FollowedBy(alice, follower); var created = await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "first", Visibility = "followersonly" }, token); var postId = ((ViewPost)created.Data).Id; await _harness.Posts.UpdatePost(root, new UpdatePostForm { AvatarId = alice.Id, PostId = postId, Text = "second", SpoilerText = "cw" }, token); await _harness.Posts.DeletePost(root, new DeletePostForm { AvatarId = alice.Id, PostId = postId }, token); var sent = await _harness.Outgoing(follower.SharedInbox); Assert.Equal(new[] { "Create", "Update", "Delete" }, sent.Select(a => a["type"]!.GetValue())); Assert.DoesNotContain(sent[0]["to"]!.AsArray(), n => n!.GetValue().EndsWith("#Public")); // Pleroma reads an empty cc without a "/followers" address as a direct message Assert.Contains(sent[0]["object"]!["cc"]!.AsArray(), n => n!.GetValue() == alice.Followers); Assert.Equal("cw", sent[1]["object"]!["summary"]!.GetValue()); Assert.NotNull(sent[1]["object"]!["updated"]); Assert.Equal(sent[0]["to"]!.ToJsonString(), sent[2]["to"]!.ToJsonString()); var stored = await DB.Default.Find().OneAsync(postId, token); Assert.NotNull(stored.DeletedAt); Assert.Null(stored.ContentHtml); Assert.Empty(stored.Revisions); } [Fact] public async Task Someone_elses_post_cannot_be_edited() { var token = TestContext.Current.CancellationToken; var (root, alice) = await _harness.Persona("alice"); var (otherRoot, bob) = await _harness.Persona("bob"); var created = await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "mine" }, token); var result = await _harness.Posts.UpdatePost(otherRoot, new UpdatePostForm { AvatarId = bob.Id, PostId = ((ViewPost)created.Data).Id, Text = "yours" }, token); Assert.False(result.IsValid); Assert.Equal(404, result.StatusCode); } } }