Votes a community relays count
Lemmy, PieFed and Mbin relay their members' votes, and the undoing of them, inside the community's Announce; PrivaPub dropped them all as unsupported (G-0003, the Lemmy scenario's expected failures). A relayed Like, Dislike or Undo from a community a persona follows is now handled as if its actor had sent it. The community vouches for what accounts on its own server do and for what is done to its own posts, as Lemmy trusts it (refetching every vote would not scale); a vote from elsewhere on anything else is believed only once fetched from its own origin. Moderation relayed the same way is still open. 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
2873344690
commit
ed08f80f05
4 files changed
+104
-6
No files matched your search
@@ -147,7 +147,7 @@ namespace PrivaPub.Tests.Federation
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_followed_community_announcing_a_vote_or_its_undo_is_recorded_and_changes_nothing_yet()
|
||||
public async Task A_vote_a_followed_community_relays_counts_and_its_undo_takes_it_back()
|
||||
{
|
||||
var token = TestContext.Current.CancellationToken;
|
||||
var (root, alice) = await _harness.Persona("alice");
|
||||
@@ -168,20 +168,64 @@ namespace PrivaPub.Tests.Federation
|
||||
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", like));
|
||||
var liked = Processed("Announce");
|
||||
Assert.Equal(1, (await DB.Default.Find<Post>().OneAsync(post.ID, token)).FavouritesCount);
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", Activity(voter, "Undo", like)));
|
||||
var unliked = Processed("Announce");
|
||||
await _harness.Deliver(stranger, "/human-centipede", Activity(stranger, "Announce", like));
|
||||
var unfollowed = Processed("Announce");
|
||||
|
||||
//votes relayed by a community are a documented gap (docs/INTEROP.md, Lemmy: "Announces of activities other than Create")
|
||||
Assert.Equal(("dropped", "unsupported", "Like"), (liked.Outcome, liked.Reason, liked.Object));
|
||||
Assert.Equal(("dropped", "unsupported", "Undo"), (unliked.Outcome, unliked.Reason, unliked.Object));
|
||||
//Lemmy relays its members' votes inside the community's Announce (G-0003): a voter on the community's own server
|
||||
//is vouched for by it, and the vote is handled as if the voter had sent it; a community nobody follows is ignored
|
||||
Assert.Equal("accepted", liked.Outcome);
|
||||
Assert.Equal("accepted", unliked.Outcome);
|
||||
Assert.Equal(("dropped", "not-followed"), (unfollowed.Outcome, unfollowed.Reason));
|
||||
var after = await DB.Default.Find<Post>().OneAsync(post.ID, token);
|
||||
Assert.Equal(0, after.FavouritesCount);
|
||||
Assert.False(await DB.Default.Find<Favourite>().Match(f => f.PostId == post.ID).ExecuteAnyAsync(token));
|
||||
}
|
||||
|
||||
// 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]
|
||||
public async Task A_relayed_vote_from_another_server_counts_on_the_communitys_posts_and_elsewhere_only_once_fetched()
|
||||
{
|
||||
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 voter = new RemoteActor(_harness.Peer, "voter", origin: _harness.Peer.B);
|
||||
var forger = new RemoteActor(_harness.Peer, "forger", origin: _harness.Peer.B);
|
||||
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);
|
||||
var page = PublicNote(poster, "<p>a post</p>", community.Id);
|
||||
page["type"] = "Page";
|
||||
page["name"] = "a title";
|
||||
_harness.Peer.Serve(new Uri(IdOf(page)).AbsolutePath, page.ToJsonString());
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", Create(poster, page)));
|
||||
var post = await DB.Default.Find<Post>().Match(p => p.ObjectURI == IdOf(page)).ExecuteSingleAsync(token);
|
||||
var other = await OwnPost(alice, token);
|
||||
var like = new JsonObject { ["id"] = NewId(voter, "likes"), ["type"] = "Like", ["actor"] = voter.Id, ["object"] = post.ObjectURI };
|
||||
var forged = new JsonObject { ["id"] = NewId(forger, "likes"), ["type"] = "Like", ["actor"] = forger.Id, ["object"] = other.ObjectURI };
|
||||
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", like));
|
||||
await _harness.Deliver(community, "/human-centipede", Activity(community, "Announce", forged));
|
||||
|
||||
Assert.Equal(1, (await DB.Default.Find<Post>().OneAsync(post.ID, token)).FavouritesCount);
|
||||
Assert.True(await DB.Default.Find<Favourite>().Match(f => f.PostId == post.ID && f.ActorURI == voter.Id).ExecuteAnyAsync(token));
|
||||
Assert.Equal(("dropped", "fetch-failed"), (Processed("Announce").Outcome, Processed("Announce").Reason));
|
||||
Assert.Equal(0, (await DB.Default.Find<Post>().OneAsync(other.ID, token)).FavouritesCount);
|
||||
}
|
||||
|
||||
// a public post of a persona's own, outside any community
|
||||
static async Task<Post> OwnPost(PrivaPub.Federation.Actors.LocalActor author, CancellationToken token)
|
||||
{
|
||||
var post = new Post { GroupUserId = author.Id, AuthorAccountId = author.Id, Text = "not the community's" };
|
||||
post.ID = (string)post.GenerateNewID();
|
||||
post.ObjectURI = author.PostUri(post.ID);
|
||||
await DB.Default.SaveAsync(post, token);
|
||||
return post;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_locked_persona_holds_a_follow_until_it_decides_and_answers_with_the_original_follow()
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user