using System.Net.Http.Headers; using System.Text; using System.Text.Json.Nodes; using decePubClient.E2E.Support; using Microsoft.Playwright; namespace decePubClient.E2E.Actions { // An article in a feed shows its title, its excerpt and a Read button, and its whole text once asked: a WriteFreely // blog the persona follows publishes one (SocialPub's peers/writefreely.sh keeps the blog's token). public sealed class ArticleTests(Browser browser) { [Fact] public async Task An_article_is_folded_in_the_feed_and_read_on_demand() { var tokenFile = Path.Combine(Pasture.Root, ".state", "writefreely", "wfuser.token"); Assert.SkipUnless(File.Exists(tokenFile), "no WriteFreely blog: run tools/pasture/run.sh add writefreely"); var persona = Pasture.Personas().First(); var blog = (await PersonaApi.Call(persona, "/api/v2/search?q=wfuser%40writefreely.test&resolve=true&type=accounts"))?["accounts"]?.AsArray().FirstOrDefault(); Assert.SkipWhen(blog is null, "PrivaPub cannot resolve the WriteFreely blog"); var blogId = blog!["id"]!.GetValue(); await PersonaApi.Call(persona, $"/api/v1/accounts/{blogId}/follow", HttpMethod.Post); Assert.True(await Pasture.Eventually(async () => (await PersonaApi.Call(persona, $"/api/v1/accounts/relationships?id[]={blogId}"))![0]!["following"]!.GetValue(), 45), "the blog never accepted"); var title = $"An article to fold {Guid.NewGuid():N}"[..30]; var ending = $"the last words {Guid.NewGuid():N}"[..24]; var body = string.Join(" ", Enumerable.Repeat("A long paragraph that a feed should not show whole.", 12)) + " " + ending; using var http = Pasture.Http(); using var publish = new HttpRequestMessage(HttpMethod.Post, "https://writefreely.test/api/collections/wfuser/posts") { Content = new StringContent(new JsonObject { ["title"] = title, ["body"] = body }.ToJsonString(), Encoding.UTF8, "application/json") }; publish.Headers.Authorization = new AuthenticationHeaderValue("Token", File.ReadAllText(tokenFile).Trim()); (await http.SendAsync(publish)).EnsureSuccessStatusCode(); JsonNode article = null; Assert.True(await Pasture.Eventually(async () => (article = (await PersonaApi.Call(persona, "/api/v1/timelines/home?limit=40"))!.AsArray() .FirstOrDefault(s => s!["privapub"]?["title"]?.GetValue() == title)) is not null, 60), "the article never reached the persona"); await using var session = await SignIn.As(browser, persona); var page = session.Page; await Results.Cell("client.render.article", "writefreely", persona.UserName, async () => { await page.GotoAsync($"{Pasture.Site}/"); await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')"); await page.GotoAsync($"{Pasture.Site}/"); var card = page.Locator($"article[data-uri='{article!["uri"]!.GetValue()}']").First; await card.WaitForAsync(new() { Timeout = 30000 }); await Assertions.Expect(card).ToContainTextAsync(title); await Assertions.Expect(card.Locator("[data-article]")).ToBeVisibleAsync(); await Assertions.Expect(card).Not.ToContainTextAsync(ending); await card.Locator("[data-article-open]").ClickAsync(); await Assertions.Expect(card).ToContainTextAsync(ending, new() { Timeout = 5000 }); await session.AssertGuards(); }, page); await PersonaApi.Call(persona, $"/api/v1/accounts/{blogId}/unfollow", HttpMethod.Post); } } }