A vote turned the other way replaces the first
Lemmy turns an upvote into a downvote with a Dislike alone (and back with a Like), so the like stayed counted next to the downvote. Now an account has one vote on a post: a Dislike takes its like away, a Like its downvote. The Lemmy scenario's relayed votes were never failing for that reason only: Lemmy 1.0 sends what it queued every 30 seconds, and the check gave up after 30. It waits a minute now, and both votes are plain checks: 22 pass, the moderator's removal stays an expected failure (P7). 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
325f7605f2
commit
bbeeda7268
5 files changed
+43
-13
No files matched your search
@@ -490,7 +490,8 @@ tools/pasture/run.sh down # removes e
|
|||||||
activity at `warn` (`LEMMY_LOG` sets `RUST_LOG`); the reason is in the 400's body. It answers our community's echo of
|
activity at `warn` (`LEMMY_LOG` sets `RUST_LOG`); the reason is in the 400's body. It answers our community's echo of
|
||||||
its own activity and every bare `Announce{object}` 400 by design, and the echo is still needed (see
|
its own activity and every bare `Announce{object}` 400 by design, and the echo is still needed (see
|
||||||
`docs/INTEROP.md`, Lemmy). A new Lemmy never sends what it queued for a server before its send worker for that
|
`docs/INTEROP.md`, Lemmy). A new Lemmy never sends what it queued for a server before its send worker for that
|
||||||
server started, so the scenario waits for that worker (`lm_worker`) before its first follow. 20 checks; relayed votes and a moderator's removal are expected failures (P7).
|
server started, so the scenario waits for that worker (`lm_worker`) before its first follow, and sends what it queued every 30 seconds, so a vote takes up to a minute.
|
||||||
|
22 checks; a moderator's removal is an expected failure (P7).
|
||||||
- **Hollo (0.9.19):** Fedify's microblog server on the shared Postgres, set up through its web form (which
|
- **Hollo (0.9.19):** Fedify's microblog server on the shared Postgres, set up through its web form (which
|
||||||
checks `Origin` against `Host`, so the request names `hollo.test` without the port). It needs a 44-character
|
checks `Origin` against `Host`, so the request names `hollo.test` without the port). It needs a 44-character
|
||||||
`SECRET_KEY`, a media directory and a `themeColor`; statuses and votes go as JSON. Town only, no scenario.
|
`SECRET_KEY`, a media directory and a `themeColor`; statuses and votes go as JSON. Town only, no scenario.
|
||||||
|
|||||||
@@ -95,17 +95,43 @@ namespace PrivaPub.Tests.Federation
|
|||||||
["id"] = $"{Origin(carol)}/reactions/{Guid.NewGuid():N}", ["type"] = "EmojiReact", ["actor"] = carol.Id, ["object"] = post.ObjectURI,
|
["id"] = $"{Origin(carol)}/reactions/{Guid.NewGuid():N}", ["type"] = "EmojiReact", ["actor"] = carol.Id, ["object"] = post.ObjectURI,
|
||||||
["content"] = "🔥"
|
["content"] = "🔥"
|
||||||
});
|
});
|
||||||
|
Assert.Equal(1, (await DB.Default.Find<Post>().OneAsync(post.ID, token)).FavouritesCount);
|
||||||
await _harness.Deliver(carol, "/human-centipede", new JsonObject
|
await _harness.Deliver(carol, "/human-centipede", new JsonObject
|
||||||
{
|
{
|
||||||
["id"] = $"{Origin(carol)}/dislikes/{Guid.NewGuid():N}", ["type"] = "Dislike", ["actor"] = carol.Id, ["object"] = post.ObjectURI
|
["id"] = $"{Origin(carol)}/dislikes/{Guid.NewGuid():N}", ["type"] = "Dislike", ["actor"] = carol.Id, ["object"] = post.ObjectURI
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// the downvote counts, and takes the place of carol's like (one vote each, as Lemmy votes)
|
||||||
var stored = await DB.Default.Find<Post>().OneAsync(post.ID, token);
|
var stored = await DB.Default.Find<Post>().OneAsync(post.ID, token);
|
||||||
Assert.Equal(1, stored.FavouritesCount);
|
Assert.Equal(0, stored.FavouritesCount);
|
||||||
Assert.Equal(1, stored.DownvotesCount);
|
Assert.Equal(1, stored.DownvotesCount);
|
||||||
Assert.True(await DB.Default.Find<Reaction>().Match(r => r.PostId == post.ID && r.ActorURI == carol.Id && r.Emoji == "🔥").ExecuteAnyAsync(token));
|
Assert.True(await DB.Default.Find<Reaction>().Match(r => r.PostId == post.ID && r.ActorURI == carol.Id && r.Emoji == "🔥").ExecuteAnyAsync(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task A_vote_turned_the_other_way_replaces_the_first_as_lemmy_sends_it()
|
||||||
|
{
|
||||||
|
var token = TestContext.Current.CancellationToken;
|
||||||
|
var (_, post) = await LocalPost();
|
||||||
|
var voter = new RemoteActor(_harness.Peer, "voter");
|
||||||
|
var other = new RemoteActor(_harness.Peer, "other");
|
||||||
|
JsonObject Vote(RemoteActor actor, string type) => new()
|
||||||
|
{
|
||||||
|
["id"] = $"{Origin(actor)}/{type.ToLowerInvariant()}s/{Guid.NewGuid():N}", ["type"] = type, ["actor"] = actor.Id, ["object"] = post.ObjectURI
|
||||||
|
};
|
||||||
|
|
||||||
|
await _harness.Deliver(other, "/human-centipede", Vote(other, "Like"));
|
||||||
|
await _harness.Deliver(voter, "/human-centipede", Vote(voter, "Dislike"));
|
||||||
|
await _harness.Deliver(voter, "/human-centipede", Vote(voter, "Like"));
|
||||||
|
var up = await DB.Default.Find<Post>().OneAsync(post.ID, token);
|
||||||
|
Assert.Equal((2, 0), (up.FavouritesCount, up.DownvotesCount));
|
||||||
|
|
||||||
|
await _harness.Deliver(voter, "/human-centipede", Vote(voter, "Dislike"));
|
||||||
|
var down = await DB.Default.Find<Post>().OneAsync(post.ID, token);
|
||||||
|
Assert.Equal((1, 1), (down.FavouritesCount, down.DownvotesCount));
|
||||||
|
Assert.False(await DB.Default.Find<Favourite>().Match(f => f.PostId == post.ID && f.ActorURI == voter.Id).ExecuteAnyAsync(token));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task A_circle_member_likes_a_circle_post_and_an_outsider_cannot()
|
public async Task A_circle_member_likes_a_circle_post_and_an_outsider_cannot()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
|||||||
}
|
}
|
||||||
Arrival.Accept("stored");
|
Arrival.Accept("stored");
|
||||||
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.DownvotesCount, 1)).ExecuteAsync(token);
|
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.DownvotesCount, 1)).ExecuteAsync(token);
|
||||||
|
// a vote replaces the other one: Lemmy turns an upvote into a downvote with this Dislike alone
|
||||||
|
if (await DB.Default.Find<Favourite>().Match(f => f.PostId == post.ID && f.ActorURI == actor.ActorURI).ExecuteFirstAsync(token) is { } liked
|
||||||
|
&& (await DB.Default.DeleteAsync<Favourite>(liked.ID)).DeletedCount > 0)
|
||||||
|
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.FavouritesCount, -1)).ExecuteAsync(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,10 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
|||||||
}
|
}
|
||||||
Arrival.Accept("stored");
|
Arrival.Accept("stored");
|
||||||
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.FavouritesCount, 1)).ExecuteAsync(token);
|
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.FavouritesCount, 1)).ExecuteAsync(token);
|
||||||
|
// a vote replaces the other one: Lemmy turns a downvote into an upvote with this Like alone
|
||||||
|
if (await DB.Default.Find<Downvote>().Match(d => d.PostId == post.ID && d.ActorURI == actor.ActorURI).ExecuteFirstAsync(token) is { } downvoted
|
||||||
|
&& (await DB.Default.DeleteAsync<Downvote>(downvoted.ID)).DeletedCount > 0)
|
||||||
|
await DB.Default.Update<PostEntity>().MatchID(post.ID).Modify(b => b.Inc(p => p.DownvotesCount, -1)).ExecuteAsync(token);
|
||||||
if (!post.IsFederatedCopy)
|
if (!post.IsFederatedCopy)
|
||||||
await Notifications.Add(post.GroupUserId, NotificationType.Favourite, actor.ID, actor.ActorURI, post.ID, token);
|
await Notifications.Add(post.GroupUserId, NotificationType.Favourite, actor.ID, actor.ActorURI, post.ID, token);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,19 +78,14 @@ until_true 30 '[ "$(lm GET "comment/list?post_id=$lm_cats_id" | j "print(any(\"a
|
|||||||
&& ok "alice's reply becomes a comment on Lemmy" || ko "PrivaPub reply missing as a Lemmy comment"
|
&& ok "alice's reply becomes a comment on Lemmy" || ko "PrivaPub reply missing as a Lemmy comment"
|
||||||
|
|
||||||
echo " votes"
|
echo " votes"
|
||||||
# Lemmy sends a vote only to the community, which relays it inside an Announce: P7 trusts those for the community's own posts.
|
# Lemmy sends a vote only to the community, which relays it inside an Announce (trusted since ed08f80); Lemmy 1.0 sends
|
||||||
|
# what it queued every 30 seconds, so a vote takes up to a minute
|
||||||
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":true}" >/dev/null
|
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":true}" >/dev/null
|
||||||
if until_true 15 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"])")" = "1" ]'; then
|
until_true 45 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"])")" = "1" ]' \
|
||||||
ok "Lemmy's upvote counts as a like on PrivaPub"
|
&& ok "Lemmy's upvote counts as a like on PrivaPub" || ko "Lemmy's relayed upvote not counted"
|
||||||
else
|
|
||||||
xf "Lemmy's upvote counts as a like on PrivaPub (votes relayed by a community are P7)"
|
|
||||||
fi
|
|
||||||
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":false}" >/dev/null
|
lm POST post/like "{\"post_id\":$p_thread_on_lm,\"is_upvote\":false}" >/dev/null
|
||||||
if until_true 15 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"], ((d.get(\"privapub\") or {}).get(\"votes\") or {}).get(\"down\"))")" = "0 1" ]'; then
|
until_true 45 '[ "$(curl -s -H "$LAH" "$P/api/v1/statuses/$p_thread_id" | j "print(d[\"favourites_count\"], ((d.get(\"privapub\") or {}).get(\"votes\") or {}).get(\"down\"))")" = "0 1" ]' \
|
||||||
ok "Lemmy's change to a downvote reaches PrivaPub"
|
&& ok "Lemmy's change to a downvote reaches PrivaPub" || ko "Lemmy's change to a downvote not applied"
|
||||||
else
|
|
||||||
xf "Lemmy's change to a downvote reaches PrivaPub (votes relayed by a community are P7)"
|
|
||||||
fi
|
|
||||||
curl -s -o /dev/null -X POST -H "$LAH" "$P/api/v1/statuses/$lm_cats_on_p/favourite"
|
curl -s -o /dev/null -X POST -H "$LAH" "$P/api/v1/statuses/$lm_cats_on_p/favourite"
|
||||||
until_true 30 '[ "$(lm GET "post?id=$lm_cats_id" | j "print(d[\"post_view\"][\"post\"].get(\"upvotes\"))")" = "2" ]' && ok "alice's like is an upvote on Lemmy" || ko "like not counted as an upvote on Lemmy"
|
until_true 30 '[ "$(lm GET "post?id=$lm_cats_id" | j "print(d[\"post_view\"][\"post\"].get(\"upvotes\"))")" = "2" ]' && ok "alice's like is an upvote on Lemmy" || ko "like not counted as an upvote on Lemmy"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user