using System.Security.Cryptography;
using System.Text.Json;
using Xunit;
namespace XFESystem.Documents.Tests;
public sealed class WebsiteWebPreviewTests
{
[Fact]
public void NativeWebsiteSnapshotKeepsEveryImportedSourceByteAndLicenseReference()
{
using var manifest = JsonDocument.Parse(WebsiteAssets.ReadText("Exact/SourceManifest.json"));
Assert.Equal(2, manifest.RootElement.GetProperty("SchemaVersion").GetInt32());
Assert.Equal("XFEStudioWebSite", manifest.RootElement.GetProperty("SourceRepository").GetString());
foreach (var entry in manifest.RootElement.GetProperty("Entries").EnumerateArray())
{
var expected = entry.GetProperty("SourceSha256").GetString();
Assert.Equal(expected, entry.GetProperty("SnapshotSha256").GetString());
using var asset = WebsiteAssets.Open("Exact/" + entry.GetProperty("SnapshotPath").GetString());
Assert.Equal(expected, Convert.ToHexStringLower(SHA256.HashData(asset)));
Assert.False(string.IsNullOrWhiteSpace(entry.GetProperty("License").GetString()));
}
}
[Fact]
public void NativeWebsitePreviewUsesTheOriginalPartialsOrderWithoutInlineStyleOrScriptOverrides()
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb("# XFE\n\n```csharp\npublic class Sample {}\n```", "Sample.md");
Assert.Contains("class=\"markdown-body docs-article-body\"", preview.Html);
Assert.Contains("data-bs-theme=\"dark\"", preview.Html);
Assert.DoesNotContain("<style", preview.Html);
Assert.DoesNotContain("data-manual-doc-init", preview.Html);
Assert.DoesNotContain("data-tool-label", preview.Html);
Assert.DoesNotContain("Content-Security-Policy", preview.Html);
Assert.Contains("script-src 'self'", preview.ContentSecurityPolicy);
Assert.True(preview.Html.IndexOf("/css/bootstrap.min.css", StringComparison.Ordinal) < preview.Html.IndexOf("/css/theme-variables.css", StringComparison.Ordinal));
Assert.True(preview.Html.IndexOf("/js/docs/doc-utils.js", StringComparison.Ordinal) < preview.Html.IndexOf("/js/docs/code-block-tools.js", StringComparison.Ordinal));
Assert.Contains("language-csharp", preview.Html);
Assert.True(preview.RequiresJavaScript);
}
[Theory]
[InlineData(true, "dark", "class=\"dark\"")]
[InlineData(false, "light", "class=\"\"")]
public void WebsiteThemeSelectsTheOriginalThemeVariableRules(bool dark, string theme, string cssClass)
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb("Hello", dark: dark);
Assert.Contains(cssClass, preview.Html);
Assert.Contains("data-bs-theme=\"" + theme + "\"", preview.Html);
Assert.Contains("--main-color: #ac99ea", WebsiteAssets.ReadText("Exact/ThemeVariables.css"));
Assert.Contains("--main-color: #512db4", WebsiteAssets.ReadText("Exact/ThemeVariables.css"));
}
[Fact]
public void MarkdownContentCannotInjectExecutableScriptIntoTheTrustedDocumentWrapper()
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb(
"# Safe\n<script>globalThis.Evil=true;</script><a href=\"javascript:alert(1)\">Bad</a>" +
"<img src=\"x\" onerror=\"globalThis.Evil=true\">", "</title><script>evil()</script>");
Assert.DoesNotContain("globalThis.Evil", preview.Html);
Assert.DoesNotContain("javascript:", preview.Html);
Assert.DoesNotContain("<script>evil()", preview.Html);
Assert.Contains("</title>", preview.Html);
}
[Fact]
public void CanonicalWebsiteUrlsMapToEmbeddedBytesWithoutRewritingTheirContents()
{
Assert.True(WebsiteAssets.TryOpenWebPath("/js/docs/doc-utils.js", out var script, out var scriptType));
using (script)
using (var reader = new StreamReader(script!))
{
Assert.Equal("application/javascript", scriptType);
var content = reader.ReadToEnd();
Assert.Contains("/js/docs/mermaid.min.js?v=11.16.0", content);
Assert.DoesNotContain("xfe-resources.invalid", content);
}
Assert.True(WebsiteAssets.TryOpenWebPath("/css/docs/katex.min.css", out var style, out var styleType));
using (style)
using (var reader = new StreamReader(style!))
{
Assert.Equal("text/css", styleType);
Assert.Contains("fonts/KaTeX_", reader.ReadToEnd());
}
foreach (var resource in WebsiteAssets.WebResources)
{
Assert.True(WebsiteAssets.TryOpenWebPath(resource.UrlPath, out var stream, out var type));
using (stream) Assert.True(stream!.Length > 0);
Assert.Equal(resource.ContentType, type);
}
}
[Theory]
[InlineData("/css/docs/../Secret")]
[InlineData("/css/docs\\Secret")]
[InlineData("/js/docs/doc-utils.js?outside=true")]
[InlineData("https://outside.invalid/js/docs/doc-utils.js")]
[InlineData("/js/docs//doc-utils.js")]
public void ResourceRoutingDoesNotAcceptFilesystemPathsOrArbitraryUrls(string path)
{
Assert.False(WebsiteAssets.TryOpenWebPath(path, out var stream, out var type));
Assert.Null(stream);
Assert.Empty(type);
}
[Fact]
public void CodePreviewUsesTheWebsitesCSharpGrammarAndEscapesMarkup()
{
var preview = new DocumentPreviewBuilder(new()).CodeWeb("public string Text = \"</script>\";", "Sample.cs");
Assert.Contains("language-csharp", preview.Html);
Assert.Contains("</script>", preview.Html);
Assert.Contains("/js/docs/csharp.js", preview.Html);
Assert.DoesNotContain("monarch", preview.Html, StringComparison.OrdinalIgnoreCase);
}
}
using System.Security.Cryptography;
using System.Text.Json;
using Xunit;
namespace XFESystem.Documents.Tests;
public sealed class WebsiteWebPreviewTests
{
[Fact]
public void NativeWebsiteSnapshotKeepsEveryImportedSourceByteAndLicenseReference()
{
using var manifest = JsonDocument.Parse(WebsiteAssets.ReadText("Exact/SourceManifest.json"));
Assert.Equal(2, manifest.RootElement.GetProperty("SchemaVersion").GetInt32());
Assert.Equal("XFEStudioWebSite", manifest.RootElement.GetProperty("SourceRepository").GetString());
foreach (var entry in manifest.RootElement.GetProperty("Entries").EnumerateArray())
{
var expected = entry.GetProperty("SourceSha256").GetString();
Assert.Equal(expected, entry.GetProperty("SnapshotSha256").GetString());
using var asset = WebsiteAssets.Open("Exact/" + entry.GetProperty("SnapshotPath").GetString());
Assert.Equal(expected, Convert.ToHexStringLower(SHA256.HashData(asset)));
Assert.False(string.IsNullOrWhiteSpace(entry.GetProperty("License").GetString()));
}
}
[Fact]
public void NativeWebsitePreviewUsesTheOriginalPartialsOrderWithoutInlineStyleOrScriptOverrides()
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb("# XFE\n\n```csharp\npublic class Sample {}\n```", "Sample.md");
Assert.Contains("class=\"markdown-body docs-article-body\"", preview.Html);
Assert.Contains("data-bs-theme=\"dark\"", preview.Html);
Assert.DoesNotContain("<style", preview.Html);
Assert.DoesNotContain("data-manual-doc-init", preview.Html);
Assert.DoesNotContain("data-tool-label", preview.Html);
Assert.DoesNotContain("Content-Security-Policy", preview.Html);
Assert.Contains("script-src 'self'", preview.ContentSecurityPolicy);
Assert.True(preview.Html.IndexOf("/css/bootstrap.min.css", StringComparison.Ordinal) < preview.Html.IndexOf("/css/theme-variables.css", StringComparison.Ordinal));
Assert.True(preview.Html.IndexOf("/js/docs/doc-utils.js", StringComparison.Ordinal) < preview.Html.IndexOf("/js/docs/code-block-tools.js", StringComparison.Ordinal));
Assert.Contains("language-csharp", preview.Html);
Assert.True(preview.RequiresJavaScript);
}
[Theory]
[InlineData(true, "dark", "class=\"dark\"")]
[InlineData(false, "light", "class=\"\"")]
public void WebsiteThemeSelectsTheOriginalThemeVariableRules(bool dark, string theme, string cssClass)
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb("Hello", dark: dark);
Assert.Contains(cssClass, preview.Html);
Assert.Contains("data-bs-theme=\"" + theme + "\"", preview.Html);
Assert.Contains("--main-color: #ac99ea", WebsiteAssets.ReadText("Exact/ThemeVariables.css"));
Assert.Contains("--main-color: #512db4", WebsiteAssets.ReadText("Exact/ThemeVariables.css"));
}
[Fact]
public void MarkdownContentCannotInjectExecutableScriptIntoTheTrustedDocumentWrapper()
{
var preview = new DocumentPreviewBuilder(new()).MarkdownWeb(
"# Safe\n<script>globalThis.Evil=true;</script><a href=\"javascript:alert(1)\">Bad</a>" +
"<img src=\"x\" onerror=\"globalThis.Evil=true\">", "</title><script>evil()</script>");
Assert.DoesNotContain("globalThis.Evil", preview.Html);
Assert.DoesNotContain("javascript:", preview.Html);
Assert.DoesNotContain("<script>evil()", preview.Html);
Assert.Contains("</title>", preview.Html);
}
[Fact]
public void CanonicalWebsiteUrlsMapToEmbeddedBytesWithoutRewritingTheirContents()
{
Assert.True(WebsiteAssets.TryOpenWebPath("/js/docs/doc-utils.js", out var script, out var scriptType));
using (script)
using (var reader = new StreamReader(script!))
{
Assert.Equal("application/javascript", scriptType);
var content = reader.ReadToEnd();
Assert.Contains("/js/docs/mermaid.min.js?v=11.16.0", content);
Assert.DoesNotContain("xfe-resources.invalid", content);
}
Assert.True(WebsiteAssets.TryOpenWebPath("/css/docs/katex.min.css", out var style, out var styleType));
using (style)
using (var reader = new StreamReader(style!))
{
Assert.Equal("text/css", styleType);
Assert.Contains("fonts/KaTeX_", reader.ReadToEnd());
}
foreach (var resource in WebsiteAssets.WebResources)
{
Assert.True(WebsiteAssets.TryOpenWebPath(resource.UrlPath, out var stream, out var type));
using (stream) Assert.True(stream!.Length > 0);
Assert.Equal(resource.ContentType, type);
}
}
[Theory]
[InlineData("/css/docs/../Secret")]
[InlineData("/css/docs\\Secret")]
[InlineData("/js/docs/doc-utils.js?outside=true")]
[InlineData("https://outside.invalid/js/docs/doc-utils.js")]
[InlineData("/js/docs//doc-utils.js")]
public void ResourceRoutingDoesNotAcceptFilesystemPathsOrArbitraryUrls(string path)
{
Assert.False(WebsiteAssets.TryOpenWebPath(path, out var stream, out var type));
Assert.Null(stream);
Assert.Empty(type);
}
[Fact]
public void CodePreviewUsesTheWebsitesCSharpGrammarAndEscapesMarkup()
{
var preview = new DocumentPreviewBuilder(new()).CodeWeb("public string Text = \"</script>\";", "Sample.cs");
Assert.Contains("language-csharp", preview.Html);
Assert.Contains("</script>", preview.Html);
Assert.Contains("/js/docs/csharp.js", preview.Html);
Assert.DoesNotContain("monarch", preview.Html, StringComparison.OrdinalIgnoreCase);
}
}