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(); 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(); async Task Participation() => (await PersonaApi.Call(reader, $"/api/v1/statuses/{id}"))?["privapub"]?["event"]?["participation"]?.GetValue(); // 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()}']").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 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(); } } }