using System.Text;
using Xunit;
using Xunit.Abstractions;
namespace XFESystem.Documents.Tests;
public sealed class WebsiteAssetCacheTests(ITestOutputHelper output)
{
[Fact]
public void EntireTextCatalogPreservesOriginalDecodedTextAndObjectIdentity()
{
const string prefix = "Documents/Website/";
var names = typeof(WebsiteAssets).Assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(prefix, StringComparison.Ordinal))
.Where(name => Path.GetExtension(name).ToLowerInvariant() is ".js" or ".css" or ".json" or ".md" or ".txt" or ".html" or ".htm")
.Select(name => name[prefix.Length..]).ToArray();
Assert.Equal(names.Length, WebsiteAssets.PrewarmTextResources());
long characters = 0;
foreach (string name in names)
{
using var original = new StreamReader(WebsiteAssets.Open(name));
string cached = WebsiteAssets.ReadText(name);
Assert.Equal(original.ReadToEnd(), cached);
Assert.Same(cached, WebsiteAssets.ReadText(name));
characters += cached.Length;
}
Assert.InRange(names.Length, 1, 512); Assert.InRange(characters, 1, 16_777_216);
output.WriteLine("Website text cache: {0} entries, {1} UTF-16 characters; embedded bytes remain unchanged.", names.Length, characters);
}
[Fact]
public void MermaidHitsReuseOneStringAcrossThreadsAndAvoidLargeTransientAllocations()
{
string expected = WebsiteAssets.ReadText("Exact/Mermaid.min.js");
Assert.True(expected.Length > 3_000_000);
Parallel.For(0, 32, _ => Assert.Same(expected, WebsiteAssets.ReadText("Exact/Mermaid.min.js")));
long before = GC.GetAllocatedBytesForCurrentThread();
for (int read = 0; read < 64; read++) Assert.Same(expected, WebsiteAssets.ReadText("Exact/Mermaid.min.js"));
Assert.InRange(GC.GetAllocatedBytesForCurrentThread() - before, 0, 1_048_576);
Assert.True(WebsiteAssets.TryReadWebText("/js/docs/mermaid.min.js", out string routed, out string mime));
Assert.Same(expected, routed); Assert.Equal("application/javascript", mime);
}
[Fact]
public void WebTextRoutesKeepOriginalBinaryFallbackAndRejectInvalidPaths()
{
foreach (var resource in WebsiteAssets.WebResources)
{
Assert.True(WebsiteAssets.TryReadWebText(resource.UrlPath, out string text, out string mime));
using var original = new StreamReader(WebsiteAssets.Open(resource.AssetName), Encoding.UTF8, true);
Assert.Equal(original.ReadToEnd(), text); Assert.Equal(resource.ContentType, mime);
}
Assert.False(WebsiteAssets.TryReadWebText("/js/docs/../mermaid.min.js", out _, out _));
Assert.False(WebsiteAssets.TryReadWebText("/js/docs/mermaid.min.js?changed=1", out _, out _));
Assert.Throws<ArgumentException>(() => WebsiteAssets.ReadText("../Mermaid.min.js"));
Assert.Throws<FileNotFoundException>(() => WebsiteAssets.ReadText("Missing.min.js"));
}
}
using System.Text;
using Xunit;
using Xunit.Abstractions;
namespace XFESystem.Documents.Tests;
public sealed class WebsiteAssetCacheTests(ITestOutputHelper output)
{
[Fact]
public void EntireTextCatalogPreservesOriginalDecodedTextAndObjectIdentity()
{
const string prefix = "Documents/Website/";
var names = typeof(WebsiteAssets).Assembly.GetManifestResourceNames()
.Where(name => name.StartsWith(prefix, StringComparison.Ordinal))
.Where(name => Path.GetExtension(name).ToLowerInvariant() is ".js" or ".css" or ".json" or ".md" or ".txt" or ".html" or ".htm")
.Select(name => name[prefix.Length..]).ToArray();
Assert.Equal(names.Length, WebsiteAssets.PrewarmTextResources());
long characters = 0;
foreach (string name in names)
{
using var original = new StreamReader(WebsiteAssets.Open(name));
string cached = WebsiteAssets.ReadText(name);
Assert.Equal(original.ReadToEnd(), cached);
Assert.Same(cached, WebsiteAssets.ReadText(name));
characters += cached.Length;
}
Assert.InRange(names.Length, 1, 512); Assert.InRange(characters, 1, 16_777_216);
output.WriteLine("Website text cache: {0} entries, {1} UTF-16 characters; embedded bytes remain unchanged.", names.Length, characters);
}
[Fact]
public void MermaidHitsReuseOneStringAcrossThreadsAndAvoidLargeTransientAllocations()
{
string expected = WebsiteAssets.ReadText("Exact/Mermaid.min.js");
Assert.True(expected.Length > 3_000_000);
Parallel.For(0, 32, _ => Assert.Same(expected, WebsiteAssets.ReadText("Exact/Mermaid.min.js")));
long before = GC.GetAllocatedBytesForCurrentThread();
for (int read = 0; read < 64; read++) Assert.Same(expected, WebsiteAssets.ReadText("Exact/Mermaid.min.js"));
Assert.InRange(GC.GetAllocatedBytesForCurrentThread() - before, 0, 1_048_576);
Assert.True(WebsiteAssets.TryReadWebText("/js/docs/mermaid.min.js", out string routed, out string mime));
Assert.Same(expected, routed); Assert.Equal("application/javascript", mime);
}
[Fact]
public void WebTextRoutesKeepOriginalBinaryFallbackAndRejectInvalidPaths()
{
foreach (var resource in WebsiteAssets.WebResources)
{
Assert.True(WebsiteAssets.TryReadWebText(resource.UrlPath, out string text, out string mime));
using var original = new StreamReader(WebsiteAssets.Open(resource.AssetName), Encoding.UTF8, true);
Assert.Equal(original.ReadToEnd(), text); Assert.Equal(resource.ContentType, mime);
}
Assert.False(WebsiteAssets.TryReadWebText("/js/docs/../mermaid.min.js", out _, out _));
Assert.False(WebsiteAssets.TryReadWebText("/js/docs/mermaid.min.js?changed=1", out _, out _));
Assert.Throws<ArgumentException>(() => WebsiteAssets.ReadText("../Mermaid.min.js"));
Assert.Throws<FileNotFoundException>(() => WebsiteAssets.ReadText("Missing.min.js"));
}
}