A community's moderators lock threads and ban members
Lemmy's moderation reached PrivaPub only as removals. Now a remote
community's lock and ban, relayed in its Announce, apply too:
- a lock (Announce{Lock}, or commentsEnabled false on the post) refuses
replies to the thread, ours included, until Undo{Lock}; statuses say so
in privapub.locked;
- a ban of a persona (Announce{Block} with the community as target, or the
moderator's own Block sent straight to us, which is the community's ban
and never the moderator's block of the persona) shows as blocked_by on
the community and refuses the persona's posts and replies there until
the Undo.
The Lemmy scenario's removal was an expected failure only because it gave
up before Lemmy's 30-second batch; it now waits, and checks the lock and
the ban live (Lemmy refuses a lock or an unban without a reason): 29
checks, none expected to fail. G-0003 and G-0006 are closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
4a9a3d3235
commit
34c0f696af
16 files changed
+231
-20
No files matched your search
@@ -184,6 +184,71 @@ namespace PrivaPub.Tests.Federation
|
||||
Assert.False(await DB.Default.Find<Favourite>().Match(f => f.PostId == post.ID).ExecuteAnyAsync(token));
|
||||
}
|
||||
|
||||
// Lemmy's moderators lock a community's post and ban members, and the community relays both inside its Announce
|
||||
// (G-0006): a locked post takes no reply, a banned persona posts nothing there, until the Undo
|
||||
[Fact]
|
||||
public async Task A_community_locks_a_post_and_bans_a_persona_until_it_undoes_either()
|
||||
{
|
||||
var token = TestContext.Current.CancellationToken;
|
||||
var (root, alice) = await _harness.Persona("alice");
|
||||
var community = new RemoteActor(_harness.Peer, "cats", type: "Group");
|
||||
var poster = new RemoteActor(_harness.Peer, "poster");
|
||||
var moderator = new RemoteActor(_harness.Peer, "moderator");
|
||||
await _harness.Follows.Follow(root, new FollowForm { AvatarId = alice.Id, Target = community.Id }, token);
|
||||
await DB.Default.Update<Following>().Match(f => f.AvatarId == alice.Id).Modify(f => f.State, FollowState.Accepted).ExecuteAsync(token);
|
||||
JsonObject Page(string text, bool commentsEnabled = true)
|
||||
{
|
||||
var page = PublicNote(poster, $"<p>{text}</p>", community.Id);
|
||||
page["type"] = "Page";
|
||||
page["name"] = text;
|
||||
page["audience"] = community.Id;
|
||||
page["commentsEnabled"] = commentsEnabled;
|
||||
_harness.Peer.Serve(new Uri(IdOf(page)).AbsolutePath, page.ToJsonString());
|
||||
return page;
|
||||
}
|
||||
async Task<Post> Announced(JsonObject page)
|
||||
{
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", Create(poster, page)));
|
||||
return await DB.Default.Find<Post>().Match(p => p.ObjectURI == IdOf(page)).ExecuteSingleAsync(token);
|
||||
}
|
||||
Task<StatusOutcome> Reply(Post post) => _harness.Statuses.Publish(alice, new StatusDraft { Text = "a reply", InReplyTo = post.ID }, token);
|
||||
var post = await Announced(Page("a thread"));
|
||||
var lockIt = new JsonObject { ["id"] = NewId(moderator, "locks"), ["type"] = "Lock", ["actor"] = moderator.Id, ["object"] = post.ObjectURI };
|
||||
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", lockIt));
|
||||
Assert.Equal(("accepted", "locked"), (Processed("Announce").Outcome, Processed("Announce").Reason));
|
||||
var refused = await Reply(post);
|
||||
Assert.Equal((422, "Validation failed: This thread is locked"), (refused.Status, refused.Error));
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", Activity(moderator, "Undo", lockIt)));
|
||||
Assert.Null((await DB.Default.Find<Post>().OneAsync(post.ID, token)).LockedAt);
|
||||
Assert.True((await Reply(post)).Ok);
|
||||
// a post its community serves locked arrives locked
|
||||
Assert.NotNull((await Announced(Page("born locked", commentsEnabled: false))).LockedAt);
|
||||
|
||||
var ban = new JsonObject { ["id"] = NewId(moderator, "bans"), ["type"] = "Block", ["actor"] = moderator.Id, ["object"] = alice.Uri, ["target"] = community.Id };
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", ban));
|
||||
Assert.Equal(("accepted", "banned"), (Processed("Announce").Outcome, Processed("Announce").Reason));
|
||||
var banned = await Reply(post);
|
||||
Assert.Equal((422, "Validation failed: This community banned you"), (banned.Status, banned.Error));
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", Activity(moderator, "Undo", ban)));
|
||||
Assert.Equal(("accepted", "unbanned"), (Processed("Announce").Outcome, Processed("Announce").Reason));
|
||||
Assert.True((await Reply(post)).Ok);
|
||||
|
||||
// Lemmy also sends the ban straight to the persona's server, as its moderator's Block: still the community's ban,
|
||||
// never the moderator's own block of the persona
|
||||
var direct = new JsonObject { ["id"] = NewId(moderator, "bans"), ["type"] = "Block", ["actor"] = moderator.Id, ["object"] = alice.Uri, ["target"] = community.Id };
|
||||
await _harness.Deliver(moderator, "/human-centipede", direct);
|
||||
Assert.Equal(("accepted", "banned"), (Processed("Block").Outcome, Processed("Block").Reason));
|
||||
Assert.False(await DB.Default.Find<BlockedBy>().Match(b => b.AvatarId == alice.Id && b.ActorURI == moderator.Id).ExecuteAnyAsync(token));
|
||||
Assert.Equal(422, (await Reply(post)).Status);
|
||||
await _harness.Deliver(moderator, "/human-centipede", Activity(moderator, "Undo", direct));
|
||||
Assert.True((await Reply(post)).Ok);
|
||||
|
||||
var elsewhere = new JsonObject { ["id"] = NewId(moderator, "bans"), ["type"] = "Block", ["actor"] = moderator.Id, ["object"] = poster.Id, ["target"] = community.Id };
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", elsewhere));
|
||||
Assert.Equal(("dropped", "not-ours"), (Processed("Announce").Outcome, Processed("Announce").Reason));
|
||||
}
|
||||
|
||||
// a voter on another server than the community is believed for the community's own posts, as Lemmy trusts it; for
|
||||
// anything else only once its vote is fetched from its own origin
|
||||
[Fact]
|
||||
|
||||
Reference in new issue
Block a user