One delivery a server; BookWyrm 0.9.3 in the pasture

An activity now goes once to each server, to its shared inbox when it has one, for the accounts a post names, answers
or quotes as for its followers, as Mastodon delivers (a circle post excepted: each member's copy names that member).
BookWyrm took the same post twice when one copy reached its shared inbox and another the named account's inbox at once.
SharedInboxTests checks a reply to a follower's post goes once.

BookWyrm joins the pasture (peers/bookwyrm.sh: its image on the shared Postgres and Redis, gunicorn and a Celery worker,
its user, book and statuses made in its Django shell). scenarios/bookwyrm.sh: follows both ways, a review, a comment and
a quotation reaching alice as BookWyrm's pure posts, her like, boost and reply landing there, her post naming bwuser and
bwuser's like and reply, a deletion, the unfollow and statistics: 20 checks. GoToSocial (64), Mastodon (57) and Misskey
(35) still pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-06 01:56:06 +02:00
1 parent 12b51a211a
commit 53158b4cf4
9 files changed
+243 -21

No files matched your search

@@ -0,0 +1,50 @@
using PrivaPub.Domain.Statuses;
using PrivaPub.Tests.Support;
using System.Text.Json.Nodes;
namespace PrivaPub.Tests.Federation
{
// one delivery a server: a reply to a follower's post goes to its server's shared inbox once, not also to the
// follower's own inbox (BookWyrm kept two copies of a post that reached both at once)
[Trait("Category", "Integration")]
public sealed class SharedInboxTests : 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();
}
[Fact]
public async Task A_reply_to_a_followers_post_reaches_its_server_once_through_the_shared_inbox()
{
var token = TestContext.Current.CancellationToken;
var (_, alice) = await _harness.Persona("alice");
var bob = new RemoteActor(_harness.Peer, "bob", sharedInbox: true);
await _harness.FollowedBy(alice, bob);
var path = $"/notes/{Guid.NewGuid():N}";
var noteId = new Uri(bob.Id).GetLeftPart(UriPartial.Authority) + path;
_harness.Peer.Serve(path, new JsonObject
{
["id"] = noteId, ["type"] = "Note", ["attributedTo"] = bob.Id, ["content"] = "<p>a question</p>",
["to"] = new JsonArray("https://www.w3.org/ns/activitystreams#Public"), ["published"] = DateTime.UtcNow.ToString("O")
}.ToJsonString());
var parent = await _harness.RemotePosts.StoreContext(noteId, 0, token);
var reply = await _harness.Statuses.Publish(alice, new StatusDraft { Text = "an answer", InReplyTo = parent.ID }, token);
Assert.True(reply.Ok, reply.Error);
Assert.Single(await _harness.Outgoing(bob.SharedInbox), a => a["type"]!.GetValue<string>() == "Create");
Assert.Empty(await _harness.Outgoing(bob.Id + "/inbox"));
}
}
}