Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:37:37 +02:00
commit ad8ad252a9
825 files changed
+124216

No files matched your search

@@ -0,0 +1,556 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using ItWorx.KnowledgeHub.Application.Migration;
using ItWorx.KnowledgeHub.Contracts.Attachments;
using ItWorx.KnowledgeHub.Contracts.Automation;
using ItWorx.KnowledgeHub.Contracts.Knowledge;
using ItWorx.KnowledgeHub.Contracts.Migration;
using ItWorx.KnowledgeHub.Domain.Automation;
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.DependencyInjection;
namespace ItWorx.KnowledgeHub.IntegrationTests.Migration;
/// <summary>
/// The LeafWiki filesystem-Markdown importer end to end (docs/17-leafwiki-migration.md,
/// docs/27-acceptance-matrix.md M4-A04..A08): discovery/fingerprint, preflight without
/// target mutation, hierarchy/link/attachment-preserving execution, rerun idempotency,
/// rollback that preserves user edits, and source immutability. Every fixture here is
/// synthetic, written fresh per test into an isolated temp directory — never a real
/// LeafWiki snapshot (docs/09-data-safety, docs/22-testing-strategy.md).
/// </summary>
[Collection(PostgresTestGroup.Name)]
public sealed class LeafWikiImportTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
{
private string CreateSourceDirectory()
{
var path = Path.Combine(Factory.ImportsDirectory, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
private string RelativeToImportsRoot(string absolutePath) =>
Path.GetRelativePath(Factory.ImportsDirectory, absolutePath).Replace('\\', '/');
private static void WriteFile(string root, string relativePath, string content)
{
var full = Path.Combine(root, relativePath.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(full)!);
File.WriteAllText(full, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
}
[Fact]
public async Task Preflight_discovers_nested_pages_and_never_mutates_the_target()
{
var source = CreateSourceDirectory();
WriteFile(source, "index.md", "---\ntitle: Startpagina\n---\nWelkom.");
WriteFile(source, "school/index.md", "# School\nOverzicht van schoolinhoud.");
WriteFile(source, "school/wiskunde.md", "# Wiskunde\nFormules en oefeningen.");
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var beforeRootsResponse = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(beforeRootsResponse);
var beforeCount = beforeRootsResponse.Items.Count;
var preflightResponse = await SetupHelper.PostWithCsrfAsync(
client,
"/api/v1/admin/imports/preflight",
new PreflightImportRequest(RelativeToImportsRoot(source), $"test-{Guid.NewGuid():N}"));
Assert.Equal(HttpStatusCode.OK, preflightResponse.StatusCode);
var plan = await preflightResponse.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
Assert.Equal("Success", plan.Status);
Assert.Equal("filesystem-markdown", plan.AdapterName);
Assert.Equal(3, plan.PlannedCount);
Assert.Contains(plan.Items, item => item.ProposedTitle == "Startpagina" && item.ProposedParentSourceIdentity == null);
Assert.Contains(plan.Items, item => item.ProposedTitle == "School" && item.ProposedParentSourceIdentity == null);
Assert.Contains(plan.Items, item => item.ProposedTitle == "Wiskunde" && item.ProposedParentSourceIdentity == "school/index.md");
// No target mutation during preflight.
var afterRootsResponse = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(afterRootsResponse);
Assert.Equal(beforeCount, afterRootsResponse.Items.Count);
}
[Fact]
public async Task A_source_root_absolute_asset_link_is_recognized_and_imported_not_reported_missing()
{
// A real LeafWiki export commonly writes asset links as source-root-absolute
// ("/assets/<id>/file.png"), unlike every other fixture in this file (and
// CutoverRehearsalTests' own comment on this exact point), which only ever
// uses an already-relative reference. Found against a real export, not a
// hypothetical — this is the real regression test for that fix.
byte[] pngBytes = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x01, 0x02, 0x03];
var source = CreateSourceDirectory();
WriteFile(source, "index.md", "# Netwerkdiagram\n![Diagram](/assets/abc123/diagram.png)\n");
var assetPath = Path.Combine(source, "assets", "abc123", "diagram.png");
Directory.CreateDirectory(Path.GetDirectoryName(assetPath)!);
File.WriteAllBytes(assetPath, pngBytes);
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflightResponse = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight",
new PreflightImportRequest(RelativeToImportsRoot(source), $"asset-{Guid.NewGuid():N}"));
var plan = await preflightResponse.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
Assert.DoesNotContain(plan.Warnings, warning => warning.Contains("niet gevonden", StringComparison.Ordinal));
var executeResponse = await SetupHelper.PostWithCsrfAsync(
client,
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
(object?)null);
Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode);
var executed = await executeResponse.Content.ReadFromJsonAsync<ExecuteImportResponse>();
Assert.NotNull(executed);
Assert.Empty(executed.Diagnostics);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
var page = Assert.Single(roots!.Items, item => item.Title == "Netwerkdiagram");
var attachments = await client.GetFromJsonAsync<AttachmentListResponse>($"/api/v1/pages/{page.Id}/attachments");
Assert.NotNull(attachments);
Assert.Single(attachments.Items);
}
[Fact]
public async Task Execute_creates_the_hierarchy_resolves_wiki_links_and_leaves_unresolved_links_as_reported()
{
var source = CreateSourceDirectory();
WriteFile(source, "school/index.md", "# School\nZie [[Wiskunde]] en [[Onbestaande pagina]].");
WriteFile(source, "school/wiskunde.md", "# Wiskunde\nFormules.");
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflightResponse = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight",
new PreflightImportRequest(RelativeToImportsRoot(source), $"test-{Guid.NewGuid():N}"));
var plan = await preflightResponse.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
Assert.NotNull(plan.JobId);
var executeResponse = await SetupHelper.PostWithCsrfAsync(
client,
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
(object?)null);
Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode);
var executeResult = await executeResponse.Content.ReadFromJsonAsync<ExecuteImportResponse>();
Assert.NotNull(executeResult);
Assert.Equal(2, executeResult.Imported);
Assert.Equal(0, executeResult.Failed);
Assert.True(executeResult.SourceUnchangedAfterExecution);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(roots);
var schoolPage = Assert.Single(roots.Items, item => item.Title == "School");
var schoolDetail = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{schoolPage.Id}");
Assert.NotNull(schoolDetail);
// A resolved internal link renders as a real anchor; the unresolved one stays
// literal bracket text (docs/11-markdown-and-linking.md, proven again here on
// imported content).
Assert.Contains("<a href=", schoolDetail.RenderedHtml, StringComparison.Ordinal);
Assert.Contains("[[Onbestaande pagina]]", schoolDetail.RenderedHtml, StringComparison.Ordinal);
var children = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{schoolPage.Id}/children");
Assert.NotNull(children);
Assert.Contains(children.Items, item => item.Title == "Wiskunde");
}
[Fact]
public async Task Rerunning_the_same_source_is_idempotent_and_skips_unchanged_items()
{
var source = CreateSourceDirectory();
WriteFile(source, "pagina.md", "# Enkele pagina\nInhoud.");
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
async Task<ExecuteImportResponse> RunOnceAsync(string idempotencyKey)
{
var preflight = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight", new PreflightImportRequest(RelativeToImportsRoot(source), idempotencyKey));
var plan = await preflight.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
var execute = await SetupHelper.PostWithCsrfAsync(
client,
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
(object?)null);
var result = await execute.Content.ReadFromJsonAsync<ExecuteImportResponse>();
Assert.NotNull(result);
return result;
}
var first = await RunOnceAsync($"run1-{Guid.NewGuid():N}");
Assert.Equal(1, first.Imported);
Assert.Equal(0, first.Skipped);
var second = await RunOnceAsync($"run2-{Guid.NewGuid():N}");
Assert.Equal(0, second.Imported);
Assert.Equal(1, second.Skipped);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(roots);
Assert.Single(roots.Items, item => item.Title == "Enkele pagina");
}
[Fact]
public async Task Rollback_removes_an_unedited_imported_page_but_preserves_one_the_user_edited_afterward()
{
var source = CreateSourceDirectory();
WriteFile(source, "ongewijzigd.md", "# Ongewijzigd\nBlijft zoals geïmporteerd.");
WriteFile(source, "bewerkt.md", "# Bewerkt\nWordt na import aangepast.");
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflight = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight", new PreflightImportRequest(RelativeToImportsRoot(source), $"rb-{Guid.NewGuid():N}"));
var plan = await preflight.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
await SetupHelper.PostWithCsrfAsync(
client,
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
(object?)null);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(roots);
var editedPage = Assert.Single(roots.Items, item => item.Title == "Bewerkt");
var editedDetail = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{editedPage.Id}");
Assert.NotNull(editedDetail);
// The user edits the imported page before the rollback happens.
await SetupHelper.PutWithCsrfAsync(
client,
$"/api/v1/pages/{editedPage.Id}",
new UpdatePageRequest("Bewerkt door gebruiker", editedDetail.Markdown, "WikiPage", "Active", null),
editedDetail.ETag);
var rollbackResponse = await SetupHelper.PostWithCsrfAsync(client, $"/api/v1/admin/imports/{plan.JobId}/rollback", (object?)null);
Assert.Equal(HttpStatusCode.OK, rollbackResponse.StatusCode);
var rollback = await rollbackResponse.Content.ReadFromJsonAsync<RollbackImportResponse>();
Assert.NotNull(rollback);
Assert.Equal(1, rollback.RemovedPages);
Assert.Equal(1, rollback.PreservedPages);
var rootsAfterRollback = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(rootsAfterRollback);
Assert.DoesNotContain(rootsAfterRollback.Items, item => item.Title == "Ongewijzigd");
Assert.Contains(rootsAfterRollback.Items, item => item.Title == "Bewerkt door gebruiker");
}
[Fact]
public async Task A_symlink_escaping_the_source_root_is_recorded_but_never_followed()
{
var source = CreateSourceDirectory();
var outsideTarget = Path.Combine(Path.GetTempPath(), "ikh-outside-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(outsideTarget);
File.WriteAllText(Path.Combine(outsideTarget, "geheim.md"), "# Geheim\nMag niet gelezen worden.");
WriteFile(source, "normale-pagina.md", "# Normale pagina\nInhoud.");
try
{
File.CreateSymbolicLink(Path.Combine(source, "ontsnapping.md"), Path.Combine(outsideTarget, "geheim.md"));
}
catch (IOException)
{
// Symlink creation can require elevated privileges on some Windows
// configurations; skip this specific assertion rather than fail the whole
// suite for an environment limitation unrelated to the importer's logic.
return;
}
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflight = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight", new PreflightImportRequest(RelativeToImportsRoot(source), $"sym-{Guid.NewGuid():N}"));
var plan = await preflight.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
// The symlink is not itself a Markdown source item, so it's excluded from the
// plan entirely — the reader never opens a symlink target at all (recorded,
// never followed), so the outside file's content cannot appear anywhere here.
Assert.DoesNotContain(plan.Items, item => item.SourceIdentity.Contains("ontsnapping", StringComparison.Ordinal));
Assert.Single(plan.Items, item => item.SourceIdentity == "normale-pagina.md");
}
[Fact]
public async Task Preflight_refuses_a_linked_source_root_that_points_outside_imports()
{
Directory.CreateDirectory(Factory.ImportsDirectory);
var outsideTarget = Path.Combine(Path.GetTempPath(), "ikh-outside-root-" + Guid.NewGuid().ToString("N"));
var linkedSource = Path.Combine(Factory.ImportsDirectory, "linked-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(outsideTarget);
WriteFile(outsideTarget, "outside-secret.md", "# Buiten de imports-map\nDeze bron mag niet worden ontdekt.");
var outsideFile = Path.Combine(outsideTarget, "outside-secret.md");
var originalBytes = File.ReadAllBytes(outsideFile);
try
{
Assert.True(
TryCreateDirectoryLink(linkedSource, outsideTarget),
"The test host must support a directory symlink or junction to exercise the source-root boundary.");
Assert.NotNull(new DirectoryInfo(linkedSource).LinkTarget);
Assert.True((File.GetAttributes(linkedSource) & FileAttributes.ReparsePoint) != 0);
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflight = await SetupHelper.PostWithCsrfAsync(
client,
"/api/v1/admin/imports/preflight",
new PreflightImportRequest(RelativeToImportsRoot(linkedSource), $"linked-root-{Guid.NewGuid():N}"));
Assert.Equal(HttpStatusCode.UnprocessableEntity, preflight.StatusCode);
var responseBody = await preflight.Content.ReadAsStringAsync();
Assert.Contains("import-source-invalid", responseBody, StringComparison.Ordinal);
Assert.DoesNotContain("Deze bron mag niet worden ontdekt", responseBody, StringComparison.Ordinal);
Assert.Equal(originalBytes, File.ReadAllBytes(outsideFile));
var reader = Factory.Services.GetRequiredService<ILeafWikiSourceReader>();
await Assert.ThrowsAsync<InvalidOperationException>(
() => reader.OpenFileAsync(linkedSource, "outside-secret.md", CancellationToken.None));
Assert.Equal(originalBytes, File.ReadAllBytes(outsideFile));
}
finally
{
if (Directory.Exists(linkedSource))
{
Directory.Delete(linkedSource);
}
if (Directory.Exists(outsideTarget))
{
File.Delete(outsideFile);
Directory.Delete(outsideTarget);
}
}
}
private static bool TryCreateDirectoryLink(string linkPath, string targetPath)
{
if (!OperatingSystem.IsWindows())
{
try
{
Directory.CreateSymbolicLink(linkPath, targetPath);
return true;
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or PlatformNotSupportedException)
{
return false;
}
}
try
{
var startInfo = new ProcessStartInfo("cmd.exe")
{
UseShellExecute = false,
CreateNoWindow = true
};
startInfo.ArgumentList.Add("/d");
startInfo.ArgumentList.Add("/c");
startInfo.ArgumentList.Add("mklink");
startInfo.ArgumentList.Add("/J");
startInfo.ArgumentList.Add(linkPath);
startInfo.ArgumentList.Add(targetPath);
using var process = Process.Start(startInfo);
if (process is null)
{
return false;
}
process.WaitForExit();
return process.ExitCode == 0;
}
catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or System.ComponentModel.Win32Exception)
{
return false;
}
}
[Fact]
public async Task A_synthetic_sqlite_source_with_the_recognized_schema_is_imported_via_the_sqlite_adapter()
{
var source = CreateSourceDirectory();
var databasePath = Path.Combine(source, "leafwiki.sqlite");
CreateSyntheticSqliteFixture(databasePath);
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflight = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight", new PreflightImportRequest(RelativeToImportsRoot(source), $"sqlite-{Guid.NewGuid():N}"));
var plan = await preflight.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
Assert.Equal("synthetic-sqlite-v1", plan.AdapterName);
Assert.Equal(2, plan.PlannedCount);
var execute = await SetupHelper.PostWithCsrfAsync(
client,
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
(object?)null);
var result = await execute.Content.ReadFromJsonAsync<ExecuteImportResponse>();
Assert.NotNull(result);
Assert.Equal(2, result.Imported);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(roots);
Assert.Contains(roots.Items, item => item.Title == "SQLite-hoofdpagina");
}
[Fact]
public async Task An_unrecognized_sqlite_schema_is_a_preflight_blocker_not_a_guessed_mapping()
{
var source = CreateSourceDirectory();
var databasePath = Path.Combine(source, "unknown.sqlite");
CreateUnknownSchemaSqliteFixture(databasePath);
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var preflight = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/admin/imports/preflight", new PreflightImportRequest(RelativeToImportsRoot(source), $"unknown-{Guid.NewGuid():N}"));
Assert.Equal(HttpStatusCode.UnprocessableEntity, preflight.StatusCode);
}
[Fact]
public async Task Only_an_administrator_can_run_a_preflight()
{
using var ownerClient = Factory.CreateClient();
await SetupHelper.LoginAsync(ownerClient, OwnerEmail, OwnerPassword);
var editorEmail = $"redacteur-import-{Guid.NewGuid():N}@itworx.test";
const string editorPassword = "correct-horse-battery-staple";
await SetupHelper.PostWithCsrfAsync(
ownerClient,
"/api/v1/admin/users",
new ItWorx.KnowledgeHub.Contracts.Admin.CreateUserRequest("Redacteur", editorEmail, editorPassword, "Editor"));
using var editorClient = Factory.CreateClient();
await SetupHelper.LoginAsync(editorClient, editorEmail, editorPassword);
var response = await SetupHelper.PostWithCsrfAsync(
editorClient, "/api/v1/admin/imports/preflight", new PreflightImportRequest(".", $"forbidden-{Guid.NewGuid():N}"));
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task A_bearer_token_with_imports_scopes_can_preflight_and_execute_a_real_import()
{
var source = CreateSourceDirectory();
WriteFile(source, "index.md", "# Startpagina\nWelkom.");
using var adminClient = Factory.CreateClient();
await SetupHelper.LoginAsync(adminClient, OwnerEmail, OwnerPassword);
var createResponse = await SetupHelper.PostWithCsrfAsync(
adminClient, "/api/v1/service-accounts",
new CreateServiceAccountRequest("Migratie-bot", "Testaccount.", [Scopes.ImportsExecute, Scopes.ImportsRead]));
Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
var created = await createResponse.Content.ReadFromJsonAsync<CreatedServiceAccountToken>();
Assert.NotNull(created);
using var bearerClient = Factory.CreateClient();
bearerClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", created.RawToken);
var preflightResponse = await bearerClient.PostAsJsonAsync(
"/api/v1/admin/imports/preflight",
new PreflightImportRequest(RelativeToImportsRoot(source), $"bearer-import-{Guid.NewGuid():N}"));
Assert.Equal(HttpStatusCode.OK, preflightResponse.StatusCode);
var plan = await preflightResponse.Content.ReadFromJsonAsync<ImportPreflightResponse>();
Assert.NotNull(plan);
Assert.NotNull(plan.JobId);
var jobResponse = await bearerClient.GetAsync($"/api/v1/admin/imports/{plan.JobId}");
Assert.Equal(HttpStatusCode.OK, jobResponse.StatusCode);
var executeResponse = await bearerClient.PostAsync(
$"/api/v1/admin/imports/{plan.JobId}/execute?sourceRelativePath={Uri.EscapeDataString(RelativeToImportsRoot(source))}",
content: null);
Assert.Equal(HttpStatusCode.OK, executeResponse.StatusCode);
var executed = await executeResponse.Content.ReadFromJsonAsync<ExecuteImportResponse>();
Assert.NotNull(executed);
Assert.True(executed.Imported > 0);
// imports:execute/imports:read never implicitly grant pages:read — scopes are
// narrow and additive, never bundled (M6-A02), same property already proven
// for pages:write/projects:write in ServiceAccountTests.cs.
var pagesResponse = await bearerClient.GetAsync("/api/v1/pages/roots");
Assert.Equal(HttpStatusCode.Forbidden, pagesResponse.StatusCode);
}
[Fact]
public async Task A_bearer_token_without_imports_scopes_cannot_run_a_preflight_vertical_privilege_escalation()
{
using var adminClient = Factory.CreateClient();
await SetupHelper.LoginAsync(adminClient, OwnerEmail, OwnerPassword);
var createResponse = await SetupHelper.PostWithCsrfAsync(
adminClient, "/api/v1/service-accounts",
new CreateServiceAccountRequest("Read-only-bot", "Testaccount.", [Scopes.PagesRead]));
var created = await createResponse.Content.ReadFromJsonAsync<CreatedServiceAccountToken>();
Assert.NotNull(created);
using var bearerClient = Factory.CreateClient();
bearerClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", created.RawToken);
var response = await bearerClient.PostAsJsonAsync(
"/api/v1/admin/imports/preflight", new PreflightImportRequest(".", $"escalation-{Guid.NewGuid():N}"));
Assert.True(
response.StatusCode is HttpStatusCode.Forbidden or HttpStatusCode.Unauthorized,
$"Expected the escalation attempt to be denied, but got {response.StatusCode}.");
}
private static void CreateSyntheticSqliteFixture(string path)
{
using var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ConnectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText =
"""
CREATE TABLE leafwiki_meta (key TEXT PRIMARY KEY, value TEXT);
INSERT INTO leafwiki_meta (key, value) VALUES ('schema', 'itworx-synthetic-leafwiki-v1');
CREATE TABLE pages (
id INTEGER PRIMARY KEY,
parent_id INTEGER NULL REFERENCES pages(id),
title TEXT NOT NULL,
body_markdown TEXT NOT NULL,
created_at TEXT NOT NULL
);
INSERT INTO pages (id, parent_id, title, body_markdown, created_at)
VALUES (1, NULL, 'SQLite-hoofdpagina', '# Hoofdpagina', '2020-01-01');
INSERT INTO pages (id, parent_id, title, body_markdown, created_at)
VALUES (2, 1, 'SQLite-subpagina', '# Subpagina', '2020-01-02');
""";
command.ExecuteNonQuery();
}
private static void CreateUnknownSchemaSqliteFixture(string path)
{
using var connection = new SqliteConnection(new SqliteConnectionStringBuilder { DataSource = path, Pooling = false }.ConnectionString);
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE articles (id INTEGER PRIMARY KEY, headline TEXT);";
command.ExecuteNonQuery();
}
}