Files

138 lines
6.5 KiB
C#
Raw Permalink Normal View History

using System.Formats.Tar;
using System.Text.Json.Nodes;
using decePubClient.E2E.Support;
using Microsoft.Playwright;
namespace decePubClient.E2E.Actions
{
// The server's backups from Administration (owner decision 2026-10-07), as the pasture's administrator: one made now,
// downloaded through its link (a tar whose files are the backup's), deleted and uploaded again from the browser in
// pieces; each step checked on /clientapi
public sealed class BackupsTests(Browser browser)
{
const string Root = PastureAdmin.Root;
static readonly Pasture.Persona Admin = new("alice", "alice", Root, PastureAdmin.Password, null, null, null);
internal static async Task<ILocator> Open(IPage page)
{
await page.GotoAsync($"{Pasture.Site}/administration");
var header = page.Locator(".collapse-title", new() { HasText = "Backups" }).First;
await header.ClickAsync();
var panel = header.Locator("xpath=..").Locator("[data-backups]");
await panel.Locator("[data-backup-now]").WaitForAsync(new() { Timeout = 30000 });
return panel;
}
internal static async Task<string> BackUp(ILocator panel)
{
var before = (await PastureAdmin.Get("/clientapi/admin/backups"))!["backups"]!.AsArray().Select(b => b!["id"]!.GetValue<string>()).ToHashSet();
await panel.Locator("[data-backup-now]").ClickAsync();
var made = default(string);
Assert.True(await Pasture.Eventually(async () =>
{
var list = (await PastureAdmin.Get("/clientapi/admin/backups"))!;
made = list["backups"]!.AsArray().Select(b => b!["id"]!.GetValue<string>()).FirstOrDefault(id => !before.Contains(id));
return made is not null && list["running"] is null;
}, 300), "the backup was never made");
await Assertions.Expect(panel.Locator($"[data-backup='{made}']")).ToBeVisibleAsync(new() { Timeout = 30000 });
return made;
}
[Fact]
public async Task A_backup_is_made_downloaded_and_uploaded_again()
{
await using var session = await SignIn.As(browser, Admin);
var page = session.Page;
var file = Path.Combine(Path.GetTempPath(), $"decepub-backup-{Guid.NewGuid():N}.tar");
try
{
await Results.Cell("client.admin.backup", "privapub", Root, async () =>
{
var panel = await Open(page);
var id = await BackUp(panel);
var row = panel.Locator($"[data-backup='{id}']");
await Assertions.Expect(row).ToContainTextAsync("Made by hand");
// the link, for the password
await row.Locator("[data-backup-download]").ClickAsync();
var dialog = page.Locator("[data-backup-download-dialog]");
await dialog.Locator("[data-backup-password]").FillAsync(PastureAdmin.Password);
await dialog.Locator("[data-backup-confirm]").ClickAsync();
var link = dialog.Locator("[data-backup-link]");
await Assertions.Expect(link).ToBeVisibleAsync(new() { Timeout = 15000 });
var download = await page.RunAndWaitForDownloadAsync(() => link.ClickAsync(), new() { Timeout = 120000 });
await download.SaveAsAsync(file);
var names = new List<string>();
await using (var reader = new TarReader(File.OpenRead(file)))
while (await reader.GetNextEntryAsync() is { } entry)
names.Add(entry.Name);
Assert.Contains($"{id}/manifest.json", names);
Assert.Contains(names, n => n.StartsWith($"{id}/db/", StringComparison.Ordinal));
await dialog.Locator("[data-backup-dialog-close]").ClickAsync();
// deleted (asked first), then uploaded again from the file
await row.Locator("[data-backup-delete]").ClickAsync();
await page.Locator("[data-confirm-yes]").ClickAsync();
await Assertions.Expect(row).ToHaveCountAsync(0, new() { Timeout = 15000 });
Assert.DoesNotContain((await PastureAdmin.Get("/clientapi/admin/backups"))!["backups"]!.AsArray(), b => b!["id"]!.GetValue<string>() == id);
await panel.Locator("input[data-backup-upload]").SetInputFilesAsync(file);
await panel.Locator("[data-backup-upload-start]").ClickAsync();
await Assertions.Expect(row).ToContainTextAsync("Uploaded", new() { Timeout = 120000 });
var uploaded = (await PastureAdmin.Get("/clientapi/admin/backups"))!["backups"]!.AsArray().Single(b => b!["id"]!.GetValue<string>() == id)!;
Assert.Equal("uploaded", uploaded["kind"]!.GetValue<string>());
await session.AssertGuards();
}, page);
}
finally
{
File.Delete(file);
}
}
}
// A real restore of the pasture from Administration: PrivaPub stops and restores as it starts again, every session
// ends, and the page says so; signed in again, the last restore's report is there. It restores the whole server and
// ends the town's sessions, so it runs only when asked (--filter "Category=Restore"), alone, and signs the town in
// again (town.sh renew privapub).
[Trait("Category", "Restore")]
public sealed class RestoreTests(Browser browser)
{
static readonly Pasture.Persona Admin = new("alice", "alice", PastureAdmin.Root, PastureAdmin.Password, null, null, null);
[Fact]
public async Task A_backup_is_restored_and_everyone_signs_in_again()
{
await using var session = await SignIn.As(browser, Admin);
var page = session.Page;
await Results.Cell("client.admin.restore", "privapub", PastureAdmin.Root, async () =>
{
var panel = await BackupsTests.Open(page);
var id = await BackupsTests.BackUp(panel);
var host = (await PastureAdmin.Get("/clientapi/admin/backups"))!["host"]!.GetValue<string>();
await panel.Locator($"[data-backup='{id}'] [data-backup-restore]").ClickAsync();
var dialog = page.Locator("[data-backup-restore-dialog]");
await dialog.Locator("[data-backup-password]").FillAsync(PastureAdmin.Password);
var confirm = dialog.Locator("[data-backup-confirm]");
await dialog.Locator("[data-backup-host]").FillAsync("elsewhere.test");
await Assertions.Expect(confirm).ToBeDisabledAsync();
await dialog.Locator("[data-backup-host]").FillAsync(host);
await confirm.ClickAsync();
await Assertions.Expect(page.Locator("[data-backup-restarting]")).ToBeVisibleAsync(new() { Timeout = 15000 });
// once PrivaPub is back, the session it ended sends the page to sign in again
await page.WaitForURLAsync(url => url.Contains("/login"), new() { Timeout = 300000 });
await Pasture.RenewTown();
}, page);
await using var again = await SignIn.As(browser, Admin);
var panelAgain = await BackupsTests.Open(again.Page);
await Assertions.Expect(panelAgain.Locator("[data-last-restore]")).ToBeVisibleAsync(new() { Timeout = 30000 });
Assert.NotNull((await PastureAdmin.Get("/clientapi/admin/backups"))!["lastRestore"]);
await again.AssertGuards();
}
}
}