using System.Text; using w4c_workflows.Services.Nodes.Binary; using Xunit; namespace w4c_workflows.Tests; /// /// Unit tests for the content-addressed filesystem binary store. /// public class FileSystemBinaryStoreTests { private static async Task ReadAllAsync(Stream stream) { using var buffer = new MemoryStream(); await stream.CopyToAsync(buffer); return buffer.ToArray(); } [Fact] public async Task Save_then_open_round_trips_bytes_and_metadata() { var store = BinaryTestData.Store(); var bytes = Encoding.UTF8.GetBytes("hello binary"); var attachment = await store.SaveAsync( new MemoryStream(bytes), "greeting.txt", "text/plain"); Assert.StartsWith("sha256:", attachment.AssetId); Assert.Equal("greeting.txt", attachment.FileName); Assert.Equal("text/plain", attachment.MimeType); Assert.Equal(bytes.Length, attachment.SizeBytes); await using var opened = await store.OpenAsync(attachment.AssetId); Assert.NotNull(opened); Assert.Equal(bytes, await ReadAllAsync(opened!)); var described = await store.DescribeAsync(attachment.AssetId); Assert.Equal(attachment, described); } [Fact] public async Task Identical_content_is_content_addressed_to_one_asset() { var store = BinaryTestData.Store(); var bytes = Encoding.UTF8.GetBytes("same bytes"); var first = await store.SaveAsync(new MemoryStream(bytes), "a.bin", "application/octet-stream"); var second = await store.SaveAsync(new MemoryStream(bytes), "b.bin", "application/octet-stream"); Assert.Equal(first.AssetId, second.AssetId); } [Fact] public async Task Different_content_yields_different_assets() { var store = BinaryTestData.Store(); var first = await store.SaveAsync(new MemoryStream([1, 2, 3]), "a.bin", null); var second = await store.SaveAsync(new MemoryStream([4, 5, 6]), "b.bin", null); Assert.NotEqual(first.AssetId, second.AssetId); } [Fact] public async Task Open_can_be_called_more_than_once() { var store = BinaryTestData.Store(); var bytes = Encoding.UTF8.GetBytes("repeatable"); var attachment = await store.SaveAsync(new MemoryStream(bytes), null, null); await using var first = await store.OpenAsync(attachment.AssetId); await using var second = await store.OpenAsync(attachment.AssetId); Assert.Equal(bytes, await ReadAllAsync(first!)); Assert.Equal(bytes, await ReadAllAsync(second!)); } [Theory] [InlineData("")] [InlineData("not-a-sha")] [InlineData("sha256:../../etc/passwd")] [InlineData("sha256:zzzz")] public async Task Malformed_ids_are_rejected(string assetId) { var store = BinaryTestData.Store(); Assert.Null(await store.OpenAsync(assetId)); Assert.Null(await store.DescribeAsync(assetId)); } [Fact] public async Task Valid_but_unknown_id_returns_null() { var store = BinaryTestData.Store(); var id = FileSystemBinaryStore.BuildAssetId(new string('a', 64)); Assert.Null(await store.OpenAsync(id)); Assert.Null(await store.DescribeAsync(id)); } [Fact] public async Task Save_rejects_payload_over_the_cap() { var store = BinaryTestData.Store(o => o.MaxBytes = 3); await Assert.ThrowsAsync(() => store.SaveAsync(new MemoryStream(Encoding.UTF8.GetBytes("four")), "x", null)); } [Fact] public async Task Blank_name_and_mime_are_normalised_to_null() { var store = BinaryTestData.Store(); var attachment = await store.SaveAsync(new MemoryStream([9]), " ", " "); Assert.Null(attachment.FileName); Assert.Null(attachment.MimeType); } }