An event card joins and leaves the event
PrivaPub lets a persona join another server's event (owner decision 2026-10-05). The card of a remote event shows the persona's participation (pending, accepted, rejected) with Join or Leave, except for events by invitation or taken on another site. Joining is public on the event's server, so the first join asks the persona once, as reactions and votes do, remembered apart from them (PublicAct). The e2e joins the event SocialPub's Mobilizon scenario keeps, checks the participant on Mobilizon and PrivaPub's answer, and leaves it. 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
6f8472bc9f
commit
5e8fdc43af
12 files changed
+216
-15
No files matched your search
@@ -0,0 +1,70 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using decePubClient.E2E.Support;
|
||||
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace decePubClient.E2E.Actions
|
||||
{
|
||||
// Joining another server's event from its card (an owner decision): the persona is asked once that taking part is
|
||||
// public, Mobilizon makes it a participant and accepts, and leaving takes it off there. The event is the one
|
||||
// SocialPub's tools/pasture/scenarios/mobilizon.sh keeps for these tests.
|
||||
public sealed class EventTests(Browser browser)
|
||||
{
|
||||
[Fact]
|
||||
public async Task A_persona_joins_a_mobilizon_event_from_its_card_and_leaves_it()
|
||||
{
|
||||
var kept = Path.Combine(Pasture.Root, ".state", "mobilizon-kept-event.json");
|
||||
Assert.SkipUnless(File.Exists(kept), "no Mobilizon event kept for the client: run tools/pasture/interop.sh mobilizon");
|
||||
var url = JsonNode.Parse(File.ReadAllText(kept))!["url"]!.GetValue<string>();
|
||||
var personas = Pasture.Personas();
|
||||
Assert.SkipWhen(personas.Count == 0, "the town has no personas");
|
||||
var reader = personas[0];
|
||||
var status = (await PersonaApi.Call(reader, $"/api/v2/search?q={Uri.EscapeDataString(url)}&resolve=true&type=statuses"))?["statuses"]?.AsArray().FirstOrDefault();
|
||||
Assert.SkipWhen(status is null, "PrivaPub cannot resolve the kept Mobilizon event");
|
||||
var id = status!["id"]!.GetValue<string>();
|
||||
async Task<string> Participation() =>
|
||||
(await PersonaApi.Call(reader, $"/api/v1/statuses/{id}"))?["privapub"]?["event"]?["participation"]?.GetValue<string>();
|
||||
// a run cut short may have left the persona in: start from outside
|
||||
if (await Participation() is not null)
|
||||
await PersonaApi.Call(reader, $"/api/privapub/v1/statuses/{id}/leave", HttpMethod.Post);
|
||||
await using var session = await SignIn.As(browser, reader);
|
||||
var page = session.Page;
|
||||
await Results.Cell("client.action.join", "mobilizon", "kept-event", async () =>
|
||||
{
|
||||
await page.GotoAsync($"{Pasture.Site}/expand/{id}");
|
||||
var card = page.Locator($"article[data-uri='{status["uri"]!.GetValue<string>()}']").First;
|
||||
await card.Locator("[data-event-join]").ClickAsync(new() { Timeout = 30000 });
|
||||
await VoteAndReactTests.AcceptNoticeIfAsked(page);
|
||||
await card.Locator("[data-event-leave]").WaitForAsync(new() { Timeout = 15000 });
|
||||
Assert.True(await Pasture.Eventually(async () => await Participation() == "accepted", 45), "Mobilizon never accepted the Join");
|
||||
Assert.Equal("participant", await MobilizonRole(reader.ActorUri, url));
|
||||
|
||||
await page.ReloadAsync();
|
||||
await Assertions.Expect(card.Locator("[data-participation]")).ToHaveAttributeAsync("data-participation", "accepted", new() { Timeout = 30000 });
|
||||
await card.Locator("[data-event-leave]").ClickAsync();
|
||||
await Assertions.Expect(card.Locator("[data-participation]")).ToHaveAttributeAsync("data-participation", "none", new() { Timeout = 15000 });
|
||||
Assert.Null(await Participation());
|
||||
Assert.True(await Pasture.Eventually(async () => await MobilizonRole(reader.ActorUri, url) == "", 45),
|
||||
"the persona is still a participant on Mobilizon");
|
||||
await session.AssertGuards();
|
||||
}, page);
|
||||
}
|
||||
|
||||
// the persona's role in the event, from Mobilizon's database; empty when it takes no part
|
||||
static async Task<string> MobilizonRole(string actorUri, string eventUrl)
|
||||
{
|
||||
static string Quoted(string value) => "'" + value.Replace("'", "''") + "'";
|
||||
var start = new ProcessStartInfo("podman") { RedirectStandardOutput = true, RedirectStandardError = true };
|
||||
foreach (var arg in new[] { "exec", "pasture-postgis", "psql", "-U", "pasture", "-d", "mobilizon", "-tAc",
|
||||
"select p.role from participants p join actors a on a.id = p.actor_id join events e on e.id = p.event_id " +
|
||||
$"where a.url = {Quoted(actorUri)} and e.url = {Quoted(eventUrl)}" })
|
||||
start.ArgumentList.Add(arg);
|
||||
using var process = Process.Start(start)!;
|
||||
var output = await process.StandardOutput.ReadToEndAsync();
|
||||
await process.WaitForExitAsync();
|
||||
return output.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace decePubClient.E2E.Actions
|
||||
}
|
||||
|
||||
// the one-time notice, when this persona has not seen it yet
|
||||
static async Task AcceptNoticeIfAsked(IPage page)
|
||||
internal static async Task AcceptNoticeIfAsked(IPage page)
|
||||
{
|
||||
var proceed = page.GetByRole(AriaRole.Button, new() { Name = "Continue" });
|
||||
try
|
||||
|
||||
Reference in new issue
Block a user