Files
decePubClient/tests/e2e/Actions/SettingsTests.cs
T

208 lines
9.8 KiB
C#
Raw Normal View History

using System.Text.Json.Nodes;
using decePubClient.E2E.Support;
using Microsoft.Playwright;
namespace decePubClient.E2E.Actions
{
// Settings as a persona: its profile saved (update_credentials) and read back from PrivaPub, a server blocked and
// unblocked, the follows exported and servers imported in Mastodon's files, and a new persona made; what a test changes
// is put back through the API afterwards, but for the persona (PrivaPub keeps every persona, so the test makes it under
// the scenarios' own root, never a town one)
public sealed class SettingsTests(Browser browser)
{
static async Task<ILocator> Open(IPage page, string section)
{
await page.GotoAsync($"{Pasture.Site}/settings");
var header = page.Locator(".collapse-title", new() { HasText = section }).First;
await header.ClickAsync();
return header.Locator("xpath=..");
}
static JsonObject Restore(JsonNode account)
{
var fields = new JsonObject();
var source = account["source"]!;
var kept = source["fields"]!.AsArray();
for (var i = 0; i < 4; i++)
fields[i.ToString()] = new JsonObject
{
["name"] = i < kept.Count ? kept[i]!["name"]!.GetValue<string>() : "",
["value"] = i < kept.Count ? kept[i]!["value"]!.GetValue<string>() : ""
};
return new JsonObject
{
["display_name"] = account["display_name"]!.GetValue<string>(),
["note"] = source["note"]!.GetValue<string>(),
["locked"] = account["locked"]!.GetValue<bool>(),
["source"] = new JsonObject { ["privacy"] = source["privacy"]!.GetValue<string>() },
["fields_attributes"] = fields
};
}
[Fact]
public async Task The_personas_profile_is_saved_from_settings()
{
var persona = Pasture.Personas().Last();
var original = (await PersonaApi.Call(persona, "/api/v1/accounts/verify_credentials"))!;
var mark = Guid.NewGuid().ToString("N")[..6];
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
try
{
await Results.Cell("client.action.profile-edit", "privapub", persona.UserName, async () =>
{
var panel = await Open(page, "Profile");
var editor = panel.Locator("[data-profile-editor]");
await editor.WaitForAsync(new() { Timeout = 30000 });
await Assertions.Expect(editor.Locator("[data-profile-name]")).ToHaveValueAsync(original["display_name"]!.GetValue<string>(),
new() { Timeout = 10000 });
await editor.Locator("[data-profile-name]").FillAsync($"Edited {mark}");
await editor.Locator("[data-profile-note]").FillAsync($"A biography written in the e2e tests {mark}");
var row = editor.Locator("[data-profile-field='3'] input");
await row.Nth(0).FillAsync("e2e");
await row.Nth(1).FillAsync(mark);
await editor.Locator("[data-profile-privacy]").SelectOptionAsync("Unlisted");
await editor.Locator("[data-profile-save]").ClickAsync();
await Assertions.Expect(page.GetByText("Profile saved.")).ToBeVisibleAsync(new() { Timeout = 15000 });
var saved = (await PersonaApi.Call(persona, "/api/v1/accounts/verify_credentials"))!;
Assert.Equal($"Edited {mark}", saved["display_name"]!.GetValue<string>());
Assert.Equal($"A biography written in the e2e tests {mark}", saved["source"]!["note"]!.GetValue<string>());
Assert.Equal("unlisted", saved["source"]!["privacy"]!.GetValue<string>());
Assert.Contains(saved["source"]!["fields"]!.AsArray(), f => f!["name"]!.GetValue<string>() == "e2e" && f["value"]!.GetValue<string>() == mark);
// the persona chooser shows the new name at once
await Assertions.Expect(page.GetByText($"Edited {mark}", new() { Exact = true }).First).ToBeVisibleAsync(new() { Timeout = 5000 });
await session.AssertGuards();
}, page);
}
finally
{
await PersonaApi.Call(persona, "/api/v1/accounts/update_credentials", HttpMethod.Patch, Restore(original));
}
}
[Fact]
public async Task A_server_is_blocked_and_unblocked_from_settings()
{
var persona = Pasture.Personas().Last();
const string server = "blocked-by-e2e.example";
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
try
{
await Results.Cell("client.action.domain-block", "privapub", persona.UserName, async () =>
{
var panel = await Open(page, "Mutes/Blocks");
var servers = panel.Locator("[data-domain-blocks]");
await servers.WaitForAsync(new() { Timeout = 30000 });
await servers.Locator("[data-domain-new]").FillAsync($"https://{server.ToUpperInvariant()}/about");
await servers.GetByRole(AriaRole.Button, new() { Name = "Block server" }).ClickAsync();
await Assertions.Expect(servers.Locator($"[data-domain='{server}']")).ToBeVisibleAsync(new() { Timeout = 15000 });
Assert.Contains(server, (await PersonaApi.Call(persona, "/api/v1/domain_blocks"))!.AsArray().Select(d => d!.GetValue<string>()));
await servers.Locator($"[data-domain='{server}']").GetByRole(AriaRole.Button, new() { Name = "Unblock" }).ClickAsync();
await Assertions.Expect(servers.Locator($"[data-domain='{server}']")).ToHaveCountAsync(0, new() { Timeout = 15000 });
Assert.DoesNotContain(server, (await PersonaApi.Call(persona, "/api/v1/domain_blocks"))!.AsArray().Select(d => d!.GetValue<string>()));
await session.AssertGuards();
}, page);
}
finally
{
await PersonaApi.Call(persona, $"/api/v1/domain_blocks?domain={server}", HttpMethod.Delete);
}
}
// the follows export is Mastodon's following_accounts.csv, naming every account in full
[Fact]
public async Task The_follows_are_exported_in_mastodons_format()
{
var persona = Pasture.Personas().First();
var following = (await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/following?limit=80"))!.AsArray();
Assert.SkipWhen(following.Count == 0, $"{persona.UserName} follows nobody in this town");
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
await Results.Cell("client.action.export", "privapub", persona.UserName, async () =>
{
var panel = await Open(page, "Data import/export");
var button = panel.Locator("[data-export-kind='Follows']");
await button.WaitForAsync(new() { Timeout = 30000 });
var download = await page.RunAndWaitForDownloadAsync(() => button.ClickAsync(), new() { Timeout = 60000 });
Assert.Equal("following_accounts.csv", download.SuggestedFilename);
var lines = (await File.ReadAllLinesAsync((await download.PathAsync())!)).ToList();
Assert.Equal("Account address,Show boosts,Notify on new posts,Languages", lines[0]);
foreach (var account in following)
{
var acct = account!["acct"]!.GetValue<string>();
var full = acct.Contains('@') ? acct : $"{acct}@privapub.test";
Assert.Contains(lines, l => l.StartsWith(full + ",", StringComparison.Ordinal));
}
await session.AssertGuards();
}, page);
}
[Fact]
public async Task Blocked_servers_are_imported_from_a_mastodon_file()
{
var persona = Pasture.Personas().Last();
string[] servers = ["imported-one.example", "imported-two.example"];
var csv = Path.Combine(Path.GetTempPath(), $"blocked_domains-{Guid.NewGuid():N}.csv");
await File.WriteAllTextAsync(csv, string.Join("\n", servers) + "\n");
await using var session = await SignIn.As(browser, persona);
var page = session.Page;
try
{
await Results.Cell("client.action.import", "privapub", persona.UserName, async () =>
{
var panel = await Open(page, "Data import/export");
var import = panel.Locator("[data-import]");
await import.WaitForAsync(new() { Timeout = 30000 });
await import.Locator("[data-import-kind]").SelectOptionAsync("ServerBlocks");
await import.Locator("[data-import-file]").SetInputFilesAsync(csv);
await import.Locator("[data-import-start]").ClickAsync();
await Assertions.Expect(import.Locator("[data-import-report]")).ToContainTextAsync("2 of 2 done, 0 could not be.", new() { Timeout = 30000 });
var blocked = (await PersonaApi.Call(persona, "/api/v1/domain_blocks"))!.AsArray().Select(d => d!.GetValue<string>()).ToList();
Assert.All(servers, s => Assert.Contains(s, blocked));
await session.AssertGuards();
}, page);
}
finally
{
foreach (var server in servers)
await PersonaApi.Call(persona, $"/api/v1/domain_blocks?domain={server}", HttpMethod.Delete);
File.Delete(csv);
}
}
[Fact]
public async Task A_new_persona_is_made_and_used()
{
var name = $"e2e_{Guid.NewGuid():N}"[..12];
await using var session = await SignIn.As(browser, new Pasture.Persona("alice", "alice", "pastureroot", "Pasture-Pass-1!", null, null, null));
var page = session.Page;
await Results.Cell("client.action.persona-create", "privapub", "pastureroot", async () =>
{
var panel = await Open(page, "Personas");
var form = panel.Locator("[data-persona-new]");
await form.WaitForAsync(new() { Timeout = 30000 });
await form.Locator("[data-persona-username]").FillAsync(name);
await form.Locator("[data-persona-name]").FillAsync($"Made by e2e {name}");
await form.Locator("[data-persona-biography]").FillAsync("A persona the end-to-end tests made.");
await form.Locator("[data-persona-create]").ClickAsync();
var made = panel.Locator($"[data-persona='{name}']");
await made.WaitForAsync(new() { Timeout = 15000 });
await made.GetByRole(AriaRole.Button, new() { Name = "Use" }).ClickAsync();
await Assertions.Expect(made).ToContainTextAsync("in use", new() { Timeout = 15000 });
await Assertions.Expect(page.GetByText($"Made by e2e {name}", new() { Exact = true }).First).ToBeVisibleAsync();
using var http = Pasture.Http();
var webfinger = await http.GetAsync($"{Pasture.Api}/.well-known/webfinger?resource=acct:{name}@privapub.test");
Assert.True(webfinger.IsSuccessStatusCode, $"{name} is not found by WebFinger");
await session.AssertGuards();
}, page);
}
}
}