using System.Text.Json.Nodes; using decePubClient.E2E.Support; using Microsoft.Playwright; namespace decePubClient.E2E.Actions { // The persona's notification policy from Settings: people it does not follow are filtered, a like from one waits in a // request the Notifications tab leads to, and letting the request in brings the notification back; the policy is put back to accepting everything, and // the stranger's permission is taken away again public sealed class NotificationPolicyTests(Browser browser) { [Fact] public async Task A_strangers_like_waits_in_a_request_the_persona_lets_in() { var personas = Pasture.Personas(); var author = personas[0]; // (a persona of the town the author does not follow) var stranger = default(Pasture.Persona); foreach (var candidate in personas.Skip(1)) if ((await PersonaApi.Call(author, $"/api/v1/accounts/relationships?id[]={candidate.LocalId}"))![0]!["following"]!.GetValue() == false) { stranger = candidate; break; } Assert.SkipWhen(stranger is null, "the town's first persona follows every other"); var strangerAcct = (await PersonaApi.Call(stranger!, "/api/v1/accounts/verify_credentials"))!["acct"]!.GetValue(); var post = (await PersonaApi.Call(author, "/api/v1/statuses", HttpMethod.Post, new JsonObject { ["status"] = $"like me, stranger {Guid.NewGuid():N}"[..30], ["visibility"] = "public" }))!; var id = post["id"]!.GetValue(); await using var session = await SignIn.As(browser, author); var page = session.Page; try { await Results.Cell("client.action.notification-policy", "privapub", author.UserName, async () => { await page.GotoAsync($"{Pasture.Site}/settings"); var header = page.Locator(".collapse-title", new() { HasText = "Notifications" }).First; await header.ClickAsync(); var panel = page.Locator("[data-notification-policy]"); await panel.WaitForAsync(new() { Timeout = 30000 }); await panel.Locator("[data-policy='for_not_following']").SelectOptionAsync("filter"); await panel.Locator("[data-policy-save]").ClickAsync(); Assert.True(await Pasture.Eventually(async () => (await PersonaApi.Call(author, "/api/v2/notifications/policy"))!["for_not_following"]!.GetValue() == "filter", 15), "the policy was never saved"); await PersonaApi.Call(stranger!, $"/api/v1/statuses/{id}/favourite", HttpMethod.Post); Assert.DoesNotContain((await PersonaApi.Call(author, "/api/v1/notifications?types[]=favourite"))!.AsArray(), n => n!["account"]!["acct"]!.GetValue() == strangerAcct && n["status"]?["id"]?.GetValue() == id); // the Notifications tab says requests are waiting, and leads to them await page.GotoAsync($"{Pasture.Site}/"); await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')"); await page.GotoAsync($"{Pasture.Site}/"); await page.GetByRole(AriaRole.Tab).Filter(new() { Has = page.Locator(".ion-md-notifications") }).ClickAsync(); await page.Locator("[data-filtered-requests]").ClickAsync(new() { Timeout = 30000 }); var request = page.Locator($"[data-notification-requests] [data-request='{strangerAcct}']"); await request.WaitForAsync(new() { Timeout = 30000 }); await request.Locator("[data-request-accept]").ClickAsync(); await Assertions.Expect(request).ToHaveCountAsync(0, new() { Timeout = 15000 }); Assert.Contains((await PersonaApi.Call(author, "/api/v1/notifications?types[]=favourite"))!.AsArray(), n => n!["account"]!["acct"]!.GetValue() == strangerAcct && n["status"]?["id"]?.GetValue() == id); await session.AssertGuards(); }, page); } finally { await PersonaApi.Call(author, "/api/v2/notifications/policy", HttpMethod.Patch, new JsonObject { ["for_not_following"] = "accept" }); // (letting a request in lets its account in for good, which no API takes back) await Pasture.Mongo($"db.NotificationPermission.deleteMany({{AvatarId: '{author.LocalId}', FromActorURI: '{stranger!.ActorUri}'}})"); await PersonaApi.Call(author, $"/api/v1/statuses/{id}", HttpMethod.Delete); } } } }