using MongoDB.Entities; using PrivaPub.ClientModels.Post; using PrivaPub.ClientModels.Social; using PrivaPub.Federation.Objects; using PrivaPub.Models.Federation; using PrivaPub.Models.Social; using PrivaPub.Tests.Support; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Domain { [Trait("Category", "Integration")] public sealed class RelationshipTests : 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); [Fact] public async Task A_block_cuts_follows_both_ways_and_hides_the_account() { var token = TestContext.Current.CancellationToken; var (aliceRoot, alice) = await _harness.Persona("alice"); var (bobRoot, bob) = await _harness.Persona("bob"); await _harness.Follows.Follow(aliceRoot, new FollowForm { AvatarId = alice.Id, Target = bob.UserName }, token); await _harness.Follows.Follow(bobRoot, new FollowForm { AvatarId = bob.Id, Target = alice.UserName }, token); await _harness.Relationships.Block(alice, bob.Uri, bob.Id, token); await _harness.Posts.InsertPost(bobRoot, new InsertPostForm { AvatarId = bob.Id, Text = $"hey @{alice.UserName}" }, token); Assert.False(await DB.Default.Find().Match(f => f.AvatarId == alice.Id || f.AvatarId == bob.Id).ExecuteAnyAsync(token)); Assert.False(await DB.Default.Find().Match(f => f.LocalActorId == alice.Id || f.LocalActorId == bob.Id).ExecuteAnyAsync(token)); Assert.False(await DB.Default.Find().Match(e => e.AvatarId == alice.Id && e.AuthorAccountId == bob.Id).ExecuteAnyAsync(token)); Assert.False(await DB.Default.Find().Match(n => n.AvatarId == alice.Id).ExecuteAnyAsync(token)); } [Fact] public async Task A_blocked_remote_account_is_refused_when_it_follows() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var mallory = new RemoteActor(_harness.Peer, "mallory"); await _harness.Relationships.Block(alice, mallory.Id, default, token); await _harness.Deliver(mallory, "/human-centipede", new JsonObject { ["id"] = $"{Origin(mallory)}/follows/{Guid.NewGuid():N}", ["type"] = "Follow", ["actor"] = mallory.Id, ["object"] = alice.Uri }); Assert.False(await DB.Default.Find().Match(f => f.LocalActorId == alice.Id).ExecuteAnyAsync(token)); Assert.Equal("Reject", Assert.Single(await _harness.Outgoing(mallory.Id + "/inbox"))["type"]!.GetValue()); } [Fact] public async Task A_muted_account_stays_out_of_home_and_notifications() { var token = TestContext.Current.CancellationToken; var (aliceRoot, alice) = await _harness.Persona("alice"); var (bobRoot, bob) = await _harness.Persona("bob"); await _harness.Follows.Follow(aliceRoot, new FollowForm { AvatarId = alice.Id, Target = bob.UserName }, token); await _harness.Relationships.Mute(alice, bob.Uri, bob.Id, hideNotifications: true, default, token); await _harness.Posts.InsertPost(bobRoot, new InsertPostForm { AvatarId = bob.Id, Text = $"hey @{alice.UserName}" }, token); Assert.False(await DB.Default.Find().Match(e => e.AvatarId == alice.Id).ExecuteAnyAsync(token)); Assert.False(await DB.Default.Find().Match(n => n.AvatarId == alice.Id && n.Type == NotificationType.Mention).ExecuteAnyAsync(token)); } [Fact] public async Task A_report_is_forwarded_by_the_instance_not_the_persona() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var mallory = new RemoteActor(_harness.Peer, "mallory"); var foreign = await _harness.Remote.GetActor(mallory.Id, refresh: false, token); var report = await _harness.Reports.File(alice, foreign.ID, Array.Empty(), "spam everywhere", "spam", forward: true, token); Assert.True(report.Forwarded); var flag = Assert.Single(await _harness.Outgoing(mallory.Id + "/inbox")); Assert.Equal("Flag", flag["type"]!.GetValue()); Assert.EndsWith("/peasants/privapub", flag["actor"]!.GetValue()); Assert.DoesNotContain(alice.UserName, flag.ToJsonString()); Assert.DoesNotContain(alice.Id, flag.ToJsonString()); } [Fact] public async Task An_inbound_flag_becomes_a_report_for_moderators() { var token = TestContext.Current.CancellationToken; var (root, alice) = await _harness.Persona("alice"); var reporter = new RemoteActor(_harness.Peer, "reporter"); await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "bad post" }, token); var post = await DB.Default.Find().Match(p => p.GroupUserId == alice.Id).ExecuteSingleAsync(token); await _harness.Deliver(reporter, "/human-centipede", new JsonObject { ["id"] = $"{Origin(reporter)}/flags/{Guid.NewGuid():N}", ["type"] = "Flag", ["actor"] = reporter.Id, ["content"] = "please look", ["object"] = new JsonArray(alice.Uri, post.ObjectURI) }); var report = await DB.Default.Find().Match(r => r.TargetAccountId == alice.Id).ExecuteSingleAsync(token); Assert.Equal(reporter.Id, report.ReporterActorURI); Assert.Equal(new[] { post.ID }, report.PostIds); Assert.Equal("please look", report.Comment); } } }