using MongoDB.Entities; using PrivaPub.Models.Post; using PrivaPub.Models.Social; using PrivaPub.Tests.Support; using System.Text.Json.Nodes; using static PrivaPub.Tests.Support.FederatedSeeds; namespace PrivaPub.Tests.Federation { // a persona joins a remote event (owner decision 2026-10-05): Join, answered Accept or Reject; Leave [Trait("Category", "Integration")] public sealed class ParticipationTests : 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(); } async Task<(PrivaPub.Federation.Actors.LocalActor Alice, RemoteActor Group, Post Event)> Announced(string joinMode = "free") { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var group = new RemoteActor(_harness.Peer, "group", type: "Group"); await Follows(alice.Id, group); var happening = new JsonObject { ["id"] = NewId(group, "events"), ["type"] = "Event", ["name"] = "A picnic", ["content"] = "

bring bread

", ["attributedTo"] = group.Id, ["startTime"] = DateTime.UtcNow.AddDays(3).ToString("O"), ["joinMode"] = joinMode, ["to"] = new JsonArray(PrivaPub.Federation.Objects.Addressing.Public), ["cc"] = new JsonArray(group.Id + "/followers") }; await _harness.Deliver(group, "/human-centipede", Create(group, happening)); return (alice, group, await DB.Default.Find().Match(p => p.ObjectURI == IdOf(happening)).ExecuteSingleAsync(token)); } Task Of(PrivaPub.Federation.Actors.LocalActor alice, Post happening) => DB.Default.Find().Match(p => p.AvatarId == alice.Id && p.PostId == happening.ID).ExecuteFirstAsync(TestContext.Current.CancellationToken); [Fact] public async Task A_persona_joins_an_event_its_server_accepts_and_leaves_it() { var token = TestContext.Current.CancellationToken; var (alice, group, happening) = await Announced(); Assert.True((await _harness.Participations.Join(alice, happening.ID, token)).Ok); var join = Assert.Single(await _harness.Outgoing(group.Id + "/inbox"), a => a["type"]!.GetValue() == "Join"); Assert.Equal((alice.Uri, happening.ObjectURI), (join["actor"]!.GetValue(), join["object"]!.GetValue())); Assert.Equal(ParticipationState.Pending, (await Of(alice, happening)).State); await _harness.Deliver(group, "/human-centipede", new JsonObject { ["id"] = NewId(group, "accepts"), ["type"] = "Accept", ["actor"] = group.Id, ["object"] = join["id"]!.GetValue() }); Assert.Equal(ParticipationState.Accepted, (await Of(alice, happening)).State); Assert.True((await _harness.Participations.Leave(alice, happening.ID, token)).Ok); Assert.Null(await Of(alice, happening)); var leave = Assert.Single(await _harness.Outgoing(group.Id + "/inbox"), a => a["type"]!.GetValue() == "Leave"); Assert.Equal(happening.ObjectURI, leave["object"]!.GetValue()); } [Fact] public async Task A_refusal_counts_an_answer_from_another_server_does_not_and_an_invitation_only_event_is_not_joined() { var token = TestContext.Current.CancellationToken; var (alice, group, happening) = await Announced(); await _harness.Participations.Join(alice, happening.ID, token); var join = Assert.Single(await _harness.Outgoing(group.Id + "/inbox"), a => a["type"]!.GetValue() == "Join"); var stranger = new RemoteActor(_harness.Peer, "stranger", _harness.Peer.B); await _harness.Deliver(stranger, "/human-centipede", new JsonObject { ["id"] = NewId(stranger, "accepts"), ["type"] = "Accept", ["actor"] = stranger.Id, ["object"] = join["id"]!.GetValue() }); Assert.Equal(ParticipationState.Pending, (await Of(alice, happening)).State); await _harness.Deliver(group, "/human-centipede", new JsonObject { ["id"] = NewId(group, "rejects"), ["type"] = "Reject", ["actor"] = group.Id, ["object"] = new JsonObject { ["id"] = join["id"]!.GetValue(), ["type"] = "Join", ["actor"] = alice.Uri, ["object"] = happening.ObjectURI } }); Assert.Equal(ParticipationState.Rejected, (await Of(alice, happening)).State); var (carol, _, invitationOnly) = await Announced("invite"); Assert.Equal(422, (await _harness.Participations.Join(carol, invitationOnly.ID, token)).Status); } } }