Reports, and who liked or boosted a post
Another's profile and posts can be reported from the client (/api/v1/reports): a category, words, and for a remote account a copy to its server, which PrivaPub sends from its own actor, never naming the persona. Who liked or boosted a post opens from its card's menu (favourited_by, reblogged_by). e2e: a report from a Mastodon account's profile reaches PrivaPub's moderators forwarded, and a post's likes and boosts list the persona who gave them; the admin helper the tests share moves to Support. 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
bb68ff8d5e
commit
a0f07abf8c
17 files changed
+410
-16
No files matched your search
@@ -1,4 +1,3 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using decePubClient.E2E.Support;
|
||||
@@ -11,22 +10,10 @@ namespace decePubClient.E2E.Actions
|
||||
// and unblocked, the federation statistics, and the reports moderators handle; each change checked on /clientapi
|
||||
public sealed class AdministrationTests(Browser browser)
|
||||
{
|
||||
const string Root = "pastureroot";
|
||||
const string Password = "Pasture-Pass-1!";
|
||||
static readonly Pasture.Persona Admin = new("alice", "alice", Root, Password, null, null, null);
|
||||
const string Root = PastureAdmin.Root;
|
||||
static readonly Pasture.Persona Admin = new("alice", "alice", Root, PastureAdmin.Password, null, null, null);
|
||||
|
||||
static async Task<JsonNode> AsAdmin(string path)
|
||||
{
|
||||
using var http = Pasture.Http();
|
||||
var login = await http.PostAsJsonAsync($"{Pasture.Api}/clientapi/user/login", new { userName = Root, password = Password });
|
||||
login.EnsureSuccessStatusCode();
|
||||
var jwt = (await login.Content.ReadFromJsonAsync<JsonObject>())!["token"]!.GetValue<string>();
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, Pasture.Api + path);
|
||||
request.Headers.Authorization = new("Bearer", jwt);
|
||||
using var response = await http.SendAsync(request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return JsonNode.Parse(await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
static Task<JsonNode> AsAdmin(string path) => PastureAdmin.Get(path);
|
||||
|
||||
static async Task<ILocator> Open(IPage page, string section)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using decePubClient.E2E.Support;
|
||||
|
||||
using Microsoft.Playwright;
|
||||
|
||||
namespace decePubClient.E2E.Actions
|
||||
{
|
||||
// A persona reports a remote account from its profile, with a category, its words and a copy to the account's server:
|
||||
// PrivaPub's moderators see it, forwarded (/clientapi/moderator/reports). And who liked or boosted a post is listed
|
||||
// from its card
|
||||
public sealed class ReportTests(Browser browser)
|
||||
{
|
||||
[Fact]
|
||||
public async Task A_remote_account_is_reported_from_its_profile()
|
||||
{
|
||||
var persona = Pasture.Personas().First();
|
||||
var target = (await PersonaApi.Call(persona, "/api/v2/search?q=mastouser%40mastodon.test&resolve=true&type=accounts"))?["accounts"]?.AsArray().FirstOrDefault();
|
||||
Assert.SkipWhen(target is null, "no Mastodon: run tools/pasture/run.sh add mastodon");
|
||||
var targetId = target!["id"]!.GetValue<string>();
|
||||
var words = $"reported by the e2e tests {Guid.NewGuid():N}"[..40];
|
||||
await using var session = await SignIn.As(browser, persona);
|
||||
var page = session.Page;
|
||||
await Results.Cell("client.action.report", "mastodon", persona.UserName, async () =>
|
||||
{
|
||||
await page.GotoAsync($"{Pasture.Site}/account/{Uri.EscapeDataString(targetId)}");
|
||||
// (the profile's own button: each of its posts' cards has one too)
|
||||
await page.Locator($"article[data-account-id='{targetId}'] button[data-report]").ClickAsync(new() { Timeout = 30000 });
|
||||
var dialog = page.Locator("[data-report-dialog]");
|
||||
await dialog.Locator("[data-report-category]").SelectOptionAsync("Spam");
|
||||
await dialog.Locator("[data-report-comment]").FillAsync(words);
|
||||
await Assertions.Expect(dialog.Locator("[data-report-forward]")).ToBeCheckedAsync();
|
||||
await dialog.Locator("[data-report-send]").ClickAsync();
|
||||
await Assertions.Expect(dialog).ToHaveCountAsync(0, new() { Timeout = 15000 });
|
||||
|
||||
var report = (await PastureAdmin.Get("/clientapi/moderator/reports?resolved=false"))!.AsArray()
|
||||
.FirstOrDefault(r => r!["comment"]?.GetValue<string>() == words);
|
||||
Assert.NotNull(report);
|
||||
Assert.Equal((targetId, "spam", true), (report!["targetAccountId"]!.GetValue<string>(), report["category"]!.GetValue<string>(),
|
||||
report["forwarded"]!.GetValue<bool>()));
|
||||
await session.AssertGuards();
|
||||
}, page);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Who_liked_and_boosted_a_post_is_listed_from_its_card()
|
||||
{
|
||||
var personas = Pasture.Personas();
|
||||
Assert.SkipWhen(personas.Count < 2, "the town has one persona");
|
||||
var (author, fan) = (personas[0], personas[1]);
|
||||
var post = (await PersonaApi.Call(author, "/api/v1/statuses", HttpMethod.Post,
|
||||
new JsonObject { ["status"] = $"like and boost me {Guid.NewGuid():N}"[..28], ["visibility"] = "public" }))!;
|
||||
var id = post["id"]!.GetValue<string>();
|
||||
var fanAcct = (await PersonaApi.Call(fan, "/api/v1/accounts/verify_credentials"))!["acct"]!.GetValue<string>();
|
||||
await PersonaApi.Call(fan, $"/api/v1/statuses/{id}/favourite", HttpMethod.Post);
|
||||
await PersonaApi.Call(fan, $"/api/v1/statuses/{id}/reblog", HttpMethod.Post);
|
||||
await using var session = await SignIn.As(browser, author);
|
||||
var page = session.Page;
|
||||
try
|
||||
{
|
||||
await Results.Cell("client.render.interacted-by", "privapub", author.UserName, async () =>
|
||||
{
|
||||
await page.GotoAsync($"{Pasture.Site}/expand/{Uri.EscapeDataString(id)}");
|
||||
var card = page.Locator($"article[data-uri='{post["uri"]!.GetValue<string>()}']").First;
|
||||
await card.WaitForAsync(new() { Timeout = 30000 });
|
||||
foreach (var (button, list) in new[] { ("data-liked-by", "likes"), ("data-boosted-by", "boosts") })
|
||||
{
|
||||
await card.Locator("footer [title='Other'] > [role=button]").ClickAsync();
|
||||
await card.Locator($"button[{button}]").ClickAsync();
|
||||
var dialog = card.Locator($"[data-interacted-by='{list}']");
|
||||
await Assertions.Expect(dialog.Locator($"[data-account='{fanAcct}']")).ToHaveCountAsync(1, new() { Timeout = 15000 });
|
||||
await page.Keyboard.PressAsync("Escape");
|
||||
await Assertions.Expect(dialog).ToHaveCountAsync(0);
|
||||
}
|
||||
await session.AssertGuards();
|
||||
}, page);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await PersonaApi.Call(author, $"/api/v1/statuses/{id}", HttpMethod.Delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user