2026-10-05 04:15:54 +02:00
|
|
|
using System.Text.Json.Nodes;
|
|
|
|
|
|
|
|
|
|
using decePubClient.E2E.Support;
|
|
|
|
|
|
|
|
|
|
using Microsoft.Playwright;
|
|
|
|
|
|
|
|
|
|
namespace decePubClient.E2E.Actions
|
|
|
|
|
{
|
|
|
|
|
// The home feed hears of a new post and of its deletion through PrivaPub's streaming API, in seconds, where polling
|
|
|
|
|
// would take up to 45
|
|
|
|
|
public sealed class StreamingTests(Browser browser)
|
|
|
|
|
{
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task A_post_made_elsewhere_waits_behind_the_new_button_and_its_deletion_takes_it_away()
|
|
|
|
|
{
|
|
|
|
|
var persona = Pasture.Personas().First();
|
|
|
|
|
var text = $"streamed {Guid.NewGuid():N}"[..21];
|
|
|
|
|
await using var session = await SignIn.As(browser, persona);
|
|
|
|
|
var page = session.Page;
|
2026-10-07 02:14:42 +02:00
|
|
|
// the stream counts once the app has subscribed to the home feed on it (it opens after the first page is read,
|
|
|
|
|
// later on a busy page)
|
|
|
|
|
var subscribed = new TaskCompletionSource();
|
|
|
|
|
page.WebSocket += (_, socket) =>
|
|
|
|
|
{
|
|
|
|
|
if (socket.Url.Contains("/api/v1/streaming"))
|
|
|
|
|
socket.FrameSent += (_, frame) =>
|
|
|
|
|
{
|
|
|
|
|
if (frame.Text?.Contains("\"subscribe\"") == true && frame.Text.Contains("\"user\""))
|
|
|
|
|
subscribed.TrySetResult();
|
|
|
|
|
};
|
|
|
|
|
};
|
2026-10-05 04:15:54 +02:00
|
|
|
JsonNode post = null;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await Results.Cell("client.render.stream", "privapub", persona.UserName, async () =>
|
|
|
|
|
{
|
|
|
|
|
await page.GotoAsync($"{Pasture.Site}/");
|
|
|
|
|
await page.EvaluateAsync("() => localStorage.removeItem('FeedFilter')");
|
2026-10-07 02:14:42 +02:00
|
|
|
subscribed = new();
|
2026-10-05 04:15:54 +02:00
|
|
|
await page.GotoAsync($"{Pasture.Site}/");
|
|
|
|
|
await page.Locator("article[data-uri], [data-feed-empty]").First.WaitForAsync(new() { Timeout = 30000 });
|
2026-10-07 02:14:42 +02:00
|
|
|
await subscribed.Task.WaitAsync(TimeSpan.FromSeconds(30));
|
|
|
|
|
// (and the server has taken the subscription in)
|
|
|
|
|
await Task.Delay(500);
|
2026-10-05 04:15:54 +02:00
|
|
|
|
|
|
|
|
post = await PersonaApi.Call(persona, "/api/v1/statuses", HttpMethod.Post, new JsonObject { ["status"] = text, ["visibility"] = "unlisted" });
|
|
|
|
|
var fresh = page.GetByRole(AriaRole.Button, new() { Name = "1 new" });
|
|
|
|
|
await fresh.WaitForAsync(new() { Timeout = 10000 });
|
|
|
|
|
await fresh.ClickAsync();
|
|
|
|
|
var card = page.Locator($"article[data-uri='{post!["uri"]!.GetValue<string>()}']");
|
|
|
|
|
await Assertions.Expect(card).ToContainTextAsync(text, new() { Timeout = 5000 });
|
|
|
|
|
|
|
|
|
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{post["id"]!.GetValue<string>()}", HttpMethod.Delete);
|
|
|
|
|
post = null;
|
|
|
|
|
await Assertions.Expect(card).ToHaveCountAsync(0, new() { Timeout = 10000 });
|
|
|
|
|
await session.AssertGuards();
|
|
|
|
|
}, page);
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (post is not null)
|
|
|
|
|
await PersonaApi.Call(persona, $"/api/v1/statuses/{post["id"]!.GetValue<string>()}", HttpMethod.Delete);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|