A thread owner's Add of a reply to its context is the reply passed on
FEP-171b, received (Hubzilla, Streams, Forte): an Add whose target is a collection of the actor's own server, and whose object is someone's Create, Update or Delete of their own object, is queued as that activity forwarded by the owner, so it is believed on its FEP-8b32 proof or as its origin has it, and shares its job with the plain forward Hubzilla also sends. Checked live against Hubzilla (unchanged, 20 checks). 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
b39eadffeb
commit
d06f684f0d
7 files changed
+171
-7
No files matched your search
@@ -0,0 +1,109 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Infrastructure.Jobs;
|
||||
using PrivaPub.Models.Jobs;
|
||||
using PrivaPub.Models.Social;
|
||||
using PrivaPub.Tests.Support;
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using PostEntity = PrivaPub.Models.Post.Post;
|
||||
|
||||
namespace PrivaPub.Tests.Federation
|
||||
{
|
||||
// FEP-171b: a thread's owner adds a reply someone else wrote to the thread's context (Hubzilla's Add{Create}), and the
|
||||
// reply is taken as passed on by the owner, on its own proof; an Add aimed at a collection elsewhere is not a thread's
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class ConversationContainerTests : IAsyncLifetime
|
||||
{
|
||||
const string Public = "https://www.w3.org/ns/activitystreams#Public";
|
||||
|
||||
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 CancellationToken Token => TestContext.Current.CancellationToken;
|
||||
|
||||
static string Origin(RemoteActor actor) => new Uri(actor.Id).GetLeftPart(UriPartial.Authority);
|
||||
|
||||
static JsonObject Create(RemoteActor author, string text, string context, string inReplyTo = default)
|
||||
{
|
||||
var noteId = $"{Origin(author)}/item/{Guid.NewGuid():N}";
|
||||
var note = new JsonObject
|
||||
{
|
||||
["id"] = noteId, ["type"] = "Note", ["attributedTo"] = author.Id, ["content"] = $"<p>{text}</p>", ["context"] = context,
|
||||
["to"] = new JsonArray(Public), ["published"] = DateTime.UtcNow.ToString("O")
|
||||
};
|
||||
if (inReplyTo != default)
|
||||
note["inReplyTo"] = inReplyTo;
|
||||
return new JsonObject
|
||||
{
|
||||
["id"] = $"{Origin(author)}/activity/{Guid.NewGuid():N}", ["type"] = "Create", ["actor"] = author.Id, ["to"] = new JsonArray(Public),
|
||||
["object"] = note
|
||||
};
|
||||
}
|
||||
|
||||
async Task<bool> Held(string objectUri) => await DB.Default.Find<PostEntity>().Match(p => p.ObjectURI == objectUri).ExecuteAnyAsync(Token);
|
||||
|
||||
async Task RunQueued(string innerId)
|
||||
{
|
||||
var job = await DB.Default.Find<Job>().Match(j => j.DedupeKey == "inbox|forwarded|" + innerId && j.State == JobState.Pending).ExecuteFirstAsync(Token);
|
||||
Assert.NotNull(job);
|
||||
Assert.Equal(JobResult.Done, (await _harness.Processor.Handle(job, Token)).Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_reply_the_threads_owner_adds_to_its_context_is_taken_on_its_proof()
|
||||
{
|
||||
var (_, alice) = await _harness.Persona("alice");
|
||||
var owner = new RemoteActor(_harness.Peer, "owner", ed25519: true);
|
||||
var commenter = new RemoteActor(_harness.Peer, "commenter", ed25519: true);
|
||||
await DB.Default.SaveAsync(new Following { AvatarId = alice.Id, TargetActorURI = owner.Id, TargetInboxURL = owner.Id + "/inbox", State = FollowState.Accepted }, Token);
|
||||
var context = $"{Origin(owner)}/conversation/{Guid.NewGuid():N}";
|
||||
var root = Create(owner, "a thread", context);
|
||||
await _harness.Deliver(owner, "/human-centipede", root);
|
||||
var rootId = root["object"]!["id"]!.GetValue<string>();
|
||||
Assert.True(await Held(rootId));
|
||||
|
||||
var reply = commenter.Prove(Create(commenter, "a reply in it", context, rootId));
|
||||
var replyId = reply["object"]!["id"]!.GetValue<string>();
|
||||
await _harness.Deliver(owner, "/human-centipede", owner.Prove(new JsonObject
|
||||
{
|
||||
["id"] = $"{Origin(owner)}/activity/{Guid.NewGuid():N}", ["type"] = "Add", ["actor"] = owner.Id, ["object"] = reply,
|
||||
["target"] = new JsonObject { ["id"] = context, ["type"] = "Collection", ["attributedTo"] = owner.Id }, ["to"] = new JsonArray(Public)
|
||||
}));
|
||||
await RunQueued(reply["id"]!.GetValue<string>());
|
||||
|
||||
var stored = await DB.Default.Find<PostEntity>().Match(p => p.ObjectURI == replyId).ExecuteFirstAsync(Token);
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal(commenter.Id, stored.ActorURI);
|
||||
Assert.Equal((await DB.Default.Find<PostEntity>().Match(p => p.ObjectURI == rootId).ExecuteFirstAsync(Token)).ID, stored.AnsweringToPostId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task An_add_to_a_collection_on_another_server_is_no_threads()
|
||||
{
|
||||
var owner = new RemoteActor(_harness.Peer, "owner", ed25519: true);
|
||||
var commenter = new RemoteActor(_harness.Peer, "commenter", _harness.Peer.B, ed25519: true);
|
||||
var reply = commenter.Prove(Create(commenter, "elsewhere", $"{Origin(commenter)}/conversation/1"));
|
||||
|
||||
await _harness.Deliver(owner, "/human-centipede", new JsonObject
|
||||
{
|
||||
["id"] = $"{Origin(owner)}/activity/{Guid.NewGuid():N}", ["type"] = "Add", ["actor"] = owner.Id, ["object"] = reply,
|
||||
["target"] = $"{Origin(commenter)}/conversation/1"
|
||||
});
|
||||
|
||||
Assert.False(await DB.Default.Find<Job>().Match(j => j.DedupeKey == "inbox|forwarded|" + reply["id"]!.GetValue<string>()).ExecuteAnyAsync(Token));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,8 +79,8 @@ namespace PrivaPub.Tests.Support
|
||||
new BlockHandler(Db, Local),
|
||||
new LockHandler(Db),
|
||||
new MoveHandler(Remote, Db, Local, Follows, Relationships),
|
||||
new FeaturedHandler(Featured, add: true, Walls, Remote),
|
||||
new FeaturedHandler(Featured, add: false, Walls, Remote)
|
||||
new FeaturedHandler(Featured, add: true, Walls, Remote, Queue),
|
||||
new FeaturedHandler(Featured, add: false, Walls, Remote, Queue)
|
||||
};
|
||||
((AnnounceHandler)Handlers.First(h => h is AnnounceHandler)).Relays = Handlers;
|
||||
Processor = new InboxProcessor(Remote, Handlers, NullLogger<InboxProcessor>.Instance, Ledger, Local);
|
||||
|
||||
Reference in new issue
Block a user