File: Contents\DataContentTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.AI.Abstractions.Tests\Microsoft.Extensions.AI.Abstractions.Tests.csproj (Microsoft.Extensions.AI.Abstractions.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
 
namespace Microsoft.Extensions.AI;
 
public sealed class DataContentTests
{
    [Theory]
 
    // Invalid URI
    [InlineData("", typeof(ArgumentException))]
    [InlineData("invalid", typeof(ArgumentException))]
    [InlineData("data", typeof(ArgumentException))]
 
    // Not a data URI
    [InlineData("http://localhost/blah.png", typeof(ArgumentException))]
    [InlineData("https://localhost/blah.png", typeof(ArgumentException))]
    [InlineData("ftp://localhost/blah.png", typeof(ArgumentException))]
    [InlineData("a://localhost/blah.png", typeof(ArgumentException))]
 
    // Format errors
    [InlineData("data:", typeof(UriFormatException))] // data missing comma
    [InlineData("data:something,", typeof(UriFormatException))] // mime type without subtype
    [InlineData("data:something;else,data", typeof(UriFormatException))] // mime type without subtype
    [InlineData("data:type/subtype;;parameter=value;else,", typeof(UriFormatException))] // parameter without value
    [InlineData("data:type/subtype;parameter=va=lue;else,", typeof(UriFormatException))] // parameter with multiple =
    [InlineData("data:type/subtype;=value;else,", typeof(UriFormatException))] // empty parameter name
    [InlineData("data:image/j/peg;base64,/9j/4AAQSkZJRgABAgAAZABkAAD", typeof(UriFormatException))] // multiple slashes in media type
 
    // Base64 Validation Errors
    [InlineData("data:text;base64,something!", typeof(UriFormatException))]  // Invalid base64 due to invalid character '!'
    [InlineData("data:text/plain;base64,U29tZQ==\t", typeof(UriFormatException))] // Invalid base64 due to tab character
    [InlineData("data:text/plain;base64,U29tZQ==\r", typeof(UriFormatException))] // Invalid base64 due to carriage return character
    [InlineData("data:text/plain;base64,U29tZQ==\n", typeof(UriFormatException))] // Invalid base64 due to line feed character
    [InlineData("data:text/plain;base64,U29t\r\nZQ==", typeof(UriFormatException))] // Invalid base64 due to carriage return and line feed characters
    [InlineData("data:text/plain;base64,U29", typeof(UriFormatException))] // Invalid base64 due to missing padding
    [InlineData("data:text/plain;base64,U29tZQ", typeof(UriFormatException))] // Invalid base64 due to missing padding
    [InlineData("data:text/plain;base64,U29tZQ=", typeof(UriFormatException))] // Invalid base64 due to missing padding
    public void Ctor_InvalidUri_Throws(string path, Type exception)
    {
        Assert.Throws(exception, () => new DataContent(path));
    }
 
    [Theory]
    [InlineData("type")]
    [InlineData("type//subtype")]
    [InlineData("type/subtype/")]
    [InlineData("type/subtype;key=")]
    [InlineData("type/subtype;=value")]
    [InlineData("type/subtype;key=value;another=")]
    public void Ctor_InvalidMediaType_Throws(string type)
    {
        Assert.Throws<ArgumentException>("mediaType", () => new DataContent("data:image/png;base64,aGVsbG8=", type));
    }
 
    [Theory]
    [InlineData("type/subtype")]
    [InlineData("type/subtype;key=value")]
    [InlineData("type/subtype;key=value;another=value")]
    [InlineData("type/subtype;key=value;another=value;yet_another=value")]
    public void Ctor_ValidMediaType_Roundtrips(string mediaType)
    {
        var content = new DataContent("data:image/png;base64,aGVsbG8=", mediaType);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("aGVsbG8=", content.Base64Data.ToString());
 
        content = new DataContent("data:,", mediaType);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("", content.Base64Data.ToString());
 
        content = new DataContent("data:text/plain,", mediaType);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("", content.Base64Data.ToString());
 
        content = new DataContent(new Uri("data:text/plain,"), mediaType);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("", content.Base64Data.ToString());
 
        content = new DataContent(new byte[] { 0, 1, 2 }, mediaType);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("AAEC", content.Base64Data.ToString());
 
        content = new DataContent(content.Uri);
        Assert.Equal(mediaType, content.MediaType);
        Assert.Equal("AAEC", content.Base64Data.ToString());
    }
 
    [Fact]
    public void Ctor_NoMediaType_Roundtrips()
    {
        DataContent content;
 
        content = new DataContent("data:image/png;base64,aGVsbG8=");
        Assert.Equal("data:image/png;base64,aGVsbG8=", content.Uri);
        Assert.Equal("image/png", content.MediaType);
        Assert.Equal("aGVsbG8=", content.Base64Data.ToString());
 
        content = new DataContent(new Uri("data:image/png;base64,aGVsbG8="));
        Assert.Equal("data:image/png;base64,aGVsbG8=", content.Uri);
        Assert.Equal("image/png", content.MediaType);
        Assert.Equal("aGVsbG8=", content.Base64Data.ToString());
    }
 
    [Fact]
    public void Serialize_MatchesExpectedJson()
    {
        Assert.Equal(
            """{"uri":"data:application/octet-stream;base64,AQIDBA=="}""",
            JsonSerializer.Serialize(
                new DataContent(uri: "data:application/octet-stream;base64,AQIDBA=="),
                TestJsonSerializerContext.Default.Options));
 
        Assert.Equal(
            """{"uri":"data:application/octet-stream;base64,AQIDBA=="}""",
            JsonSerializer.Serialize(
                new DataContent(new ReadOnlyMemory<byte>([0x01, 0x02, 0x03, 0x04]), "application/octet-stream"),
                TestJsonSerializerContext.Default.Options));
 
        Assert.Equal(
            """{"uri":"data:application/octet-stream;base64,AQIDBA==","name":"test.bin"}""",
            JsonSerializer.Serialize(
                new DataContent(new ReadOnlyMemory<byte>([0x01, 0x02, 0x03, 0x04]), "application/octet-stream") { Name = "test.bin" },
                TestJsonSerializerContext.Default.Options));
    }
 
    [Theory]
    [InlineData("{}")]
    [InlineData("""{ "mediaType":"text/plain" }""")]
    public void Deserialize_MissingUriString_Throws(string json)
    {
        Assert.Throws<ArgumentNullException>("uri", () => JsonSerializer.Deserialize<DataContent>(json, TestJsonSerializerContext.Default.Options)!);
    }
 
    [Fact]
    public void Deserialize_MatchesExpectedData()
    {
        // Data + MimeType only
        var content = JsonSerializer.Deserialize<DataContent>("""{"uri":"data:application/octet-stream;base64,AQIDBA=="}""", TestJsonSerializerContext.Default.Options)!;
 
        Assert.Equal("data:application/octet-stream;base64,AQIDBA==", content.Uri);
        Assert.Equal([0x01, 0x02, 0x03, 0x04], content.Data.ToArray());
        Assert.Equal("AQIDBA==", content.Base64Data.ToString());
        Assert.Equal("application/octet-stream", content.MediaType);
 
        // Uri referenced content-only
        content = JsonSerializer.Deserialize<DataContent>("""{"uri":"data:application/octet-stream;base64,AQIDBA=="}""", TestJsonSerializerContext.Default.Options)!;
 
        Assert.Equal("data:application/octet-stream;base64,AQIDBA==", content.Uri);
        Assert.Equal("application/octet-stream", content.MediaType);
 
        // Using extra metadata
        content = JsonSerializer.Deserialize<DataContent>("""
            {
                "uri": "data:audio/wav;base64,AQIDBA==",
                "modelId": "gpt-4",
                "additionalProperties":
                {
                    "key": "value"
                }
            }
        """, TestJsonSerializerContext.Default.Options)!;
 
        Assert.Equal("data:audio/wav;base64,AQIDBA==", content.Uri);
        Assert.Equal([0x01, 0x02, 0x03, 0x04], content.Data.ToArray());
        Assert.Equal("AQIDBA==", content.Base64Data.ToString());
        Assert.Equal("audio/wav", content.MediaType);
        Assert.Equal("value", content.AdditionalProperties!["key"]!.ToString());
    }
 
    [Theory]
    [InlineData(
        """{"uri": "data:text/plain;base64,AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="}""",
        """{"uri":"data:text/plain;base64,AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="}""")]
    [InlineData( // Does not support non-readable content
        """{"uri": "data:text/plain;base64,AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", "unexpected": true}""",
        """{"uri":"data:text/plain;base64,AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="}""")]
    public void Serialize_Deserialize_Roundtrips(string serialized, string expectedToString)
    {
        var content = JsonSerializer.Deserialize<DataContent>(serialized, TestJsonSerializerContext.Default.Options)!;
        var reSerialization = JsonSerializer.Serialize(content, TestJsonSerializerContext.Default.Options);
        Assert.Equal(expectedToString, reSerialization);
    }
 
    [Theory]
    [InlineData("application/json")]
    [InlineData("application/octet-stream")]
    [InlineData("application/pdf")]
    [InlineData("application/xml")]
    [InlineData("audio/mpeg")]
    [InlineData("audio/ogg")]
    [InlineData("audio/wav")]
    [InlineData("image/apng")]
    [InlineData("image/avif")]
    [InlineData("image/bmp")]
    [InlineData("image/gif")]
    [InlineData("image/jpeg")]
    [InlineData("image/png")]
    [InlineData("image/svg+xml")]
    [InlineData("image/tiff")]
    [InlineData("image/webp")]
    [InlineData("text/css")]
    [InlineData("text/csv")]
    [InlineData("text/html")]
    [InlineData("text/javascript")]
    [InlineData("text/plain")]
    [InlineData("text/plain;charset=UTF-8")]
    [InlineData("text/xml")]
    [InlineData("custom/mediatypethatdoesntexists")]
    public void MediaType_Roundtrips(string mediaType)
    {
        DataContent c = new("data:,", mediaType);
        Assert.Equal(mediaType, c.MediaType);
    }
 
    [Theory]
    [InlineData("image/gif", "image")]
    [InlineData("IMAGE/JPEG", "image")]
    [InlineData("image/vnd.microsoft.icon", "imAge")]
    [InlineData("image/svg+xml", "IMAGE")]
    [InlineData("image/nonexistentimagemimetype", "IMAGE")]
    [InlineData("audio/mpeg", "aUdIo")]
    public void HasMediaTypePrefix_ReturnsTrue(string mediaType, string prefix)
    {
        var content = new DataContent("data:application/octet-stream;base64,AQIDBA==", mediaType);
        Assert.True(content.HasTopLevelMediaType(prefix));
    }
 
    [Theory]
    [InlineData("audio/mpeg", "audio/")]
    [InlineData("audio/mpeg", "image")]
    [InlineData("audio/mpeg", "audio/mpeg")]
    [InlineData("text/css", "text/csv")]
    [InlineData("text/css", "/csv")]
    [InlineData("application/json", "application/json!")]
    public void HasMediaTypePrefix_ReturnsFalse(string mediaType, string prefix)
    {
        var content = new DataContent("data:application/octet-stream;base64,AQIDBA==", mediaType);
        Assert.False(content.HasTopLevelMediaType(prefix));
    }
 
    [Fact]
    public void Data_Roundtrips()
    {
        Random rand = new(42);
        for (int length = 0; length < 100; length++)
        {
            byte[] data = new byte[length];
            rand.NextBytes(data);
 
            var content = new DataContent(data, "application/octet-stream");
            Assert.Equal(data, content.Data.ToArray());
            Assert.Equal(Convert.ToBase64String(data), content.Base64Data.ToString());
            Assert.Equal($"data:application/octet-stream;base64,{Convert.ToBase64String(data)}", content.Uri);
        }
    }
 
    [Fact]
    public void NonBase64Data_Normalized()
    {
        var content = new DataContent("data:text/plain,hello world");
        Assert.Equal("data:text/plain;base64,aGVsbG8gd29ybGQ=", content.Uri);
        Assert.Equal("aGVsbG8gd29ybGQ=", content.Base64Data.ToString());
        Assert.Equal("hello world", Encoding.ASCII.GetString(content.Data.ToArray()));
    }
 
    [Fact]
    public void FileName_Roundtrips()
    {
        DataContent content = new(new byte[] { 1, 2, 3 }, "application/octet-stream");
        Assert.Null(content.Name);
        content.Name = "test.bin";
        Assert.Equal("test.bin", content.Name);
    }
 
    [Fact]
    public async Task LoadFromAsync_Path_InfersMediaTypeAndName()
    {
        // Create a temporary file with known content
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.json");
        try
        {
            byte[] testData = Encoding.UTF8.GetBytes("{\"key\": \"value\"}");
            await File.WriteAllBytesAsync(tempPath, testData);
 
            // Load from path
            DataContent content = await DataContent.LoadFromAsync(tempPath);
 
            // Verify the content
            Assert.Equal("application/json", content.MediaType);
            Assert.Equal(Path.GetFileName(tempPath), content.Name);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Path_UsesProvidedMediaType()
    {
        // Create a temporary file with known content
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.bin");
        try
        {
            byte[] testData = new byte[] { 1, 2, 3, 4, 5 };
            await File.WriteAllBytesAsync(tempPath, testData);
 
            // Load from path with specified media type
            DataContent content = await DataContent.LoadFromAsync(tempPath, "custom/type");
 
            // Verify the content uses the provided media type, not inferred
            Assert.Equal("custom/type", content.MediaType);
            Assert.Equal(Path.GetFileName(tempPath), content.Name);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Path_FallsBackToOctetStream()
    {
        // Create a temporary file with unknown extension
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.unknownextension");
        try
        {
            byte[] testData = new byte[] { 1, 2, 3 };
            await File.WriteAllBytesAsync(tempPath, testData);
 
            // Load from path
            DataContent content = await DataContent.LoadFromAsync(tempPath);
 
            // Verify the content falls back to octet-stream
            Assert.Equal("application/octet-stream", content.MediaType);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Stream_InfersFromFileStream()
    {
        // Create a temporary file
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.png");
        try
        {
            byte[] testData = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; // PNG signature
            await File.WriteAllBytesAsync(tempPath, testData);
 
            // Load from FileStream
            using FileStream fs = new(tempPath, FileMode.Open, FileAccess.Read);
            DataContent content = await DataContent.LoadFromAsync(fs);
 
            // Verify inference from FileStream path
            Assert.Equal("image/png", content.MediaType);
            Assert.Equal(Path.GetFileName(tempPath), content.Name);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Stream_UsesProvidedMediaType()
    {
        // Create a MemoryStream with test data
        byte[] testData = [1, 2, 3, 4];
        using MemoryStream ms = new(testData);
 
        // Load from stream with explicit media type
        DataContent content = await DataContent.LoadFromAsync(ms, "video/mp4");
 
        // Verify the explicit media type is used
        Assert.Equal("video/mp4", content.MediaType);
        Assert.Null(content.Name); // Name can be assigned after the call if needed
        Assert.Equal(testData, content.Data.ToArray());
    }
 
    [Fact]
    public async Task LoadFromAsync_Stream_FallsBackToOctetStream()
    {
        // Create a MemoryStream with test data (non-FileStream, no inference possible)
        byte[] testData = new byte[] { 1, 2, 3 };
        using MemoryStream ms = new(testData);
 
        // Load from stream without media type or name
        DataContent content = await DataContent.LoadFromAsync(ms);
 
        // Verify fallback to octet-stream
        Assert.Equal("application/octet-stream", content.MediaType);
        Assert.Null(content.Name);
        Assert.Equal(testData, content.Data.ToArray());
    }
 
    [Fact]
    public async Task SaveToAsync_WritesDataToFile()
    {
        // Create DataContent with known data
        byte[] testData = new byte[] { 1, 2, 3, 4, 5 };
        DataContent content = new(testData, "application/octet-stream");
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.bin");
        try
        {
            // Save to path
            string actualPath = await content.SaveToAsync(tempPath);
 
            // Verify data was written
            Assert.Equal(tempPath, actualPath);
            Assert.True(File.Exists(actualPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(actualPath));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_InfersExtension_WhenPathHasNoExtension()
    {
        // Create DataContent with JSON media type
        byte[] testData = Encoding.UTF8.GetBytes("{}");
        DataContent content = new(testData, "application/json");
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}");
        try
        {
            // Save to path without extension
            string actualPath = await content.SaveToAsync(tempPath);
 
            // Verify extension was inferred
            Assert.Equal(tempPath, actualPath);
            Assert.True(File.Exists(actualPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(actualPath));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_UsesDirectoryAndInfersNameFromNameProperty()
    {
        // Create DataContent with JSON media type and a name
        byte[] testData = Encoding.UTF8.GetBytes("{}");
        DataContent content = new(testData, "application/json")
        {
            Name = "myfile.json"
        };
 
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}");
        Directory.CreateDirectory(tempDir);
        string expectedPath = Path.Combine(tempDir, "myfile.json");
        try
        {
            // Save to directory path
            string actualPath = await content.SaveToAsync(tempDir);
 
            // Verify the name was used
            Assert.Equal(expectedPath, actualPath);
            Assert.True(File.Exists(actualPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(actualPath));
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_UsesRandomNameWhenDirectoryProvidedAndNoName()
    {
        // Create DataContent with JSON media type but no name
        byte[] testData = Encoding.UTF8.GetBytes("{}");
        DataContent content = new(testData, "application/json");
 
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}");
        Directory.CreateDirectory(tempDir);
        try
        {
            // Save to directory path
            string actualPath = await content.SaveToAsync(tempDir);
 
            // Verify a file was created with .json extension
            Assert.StartsWith(tempDir, actualPath);
            Assert.EndsWith(".json", actualPath);
            Assert.True(File.Exists(actualPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(actualPath));
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_DoesNotInferExtension_WhenPathAlreadyHasExtension()
    {
        // Create DataContent with JSON media type
        byte[] testData = Encoding.UTF8.GetBytes("{}");
        DataContent content = new(testData, "application/json");
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_{Guid.NewGuid()}.txt");
        try
        {
            // Save to path that already has an extension
            string actualPath = await content.SaveToAsync(tempPath);
 
            // Verify the original extension was preserved, not replaced
            Assert.Equal(tempPath, actualPath);
            Assert.True(File.Exists(actualPath));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_WithJustFilename_SavesInCurrentDirectory()
    {
        // Test that providing just a filename (no directory) saves to current directory
        byte[] testData = [1, 2, 3];
        DataContent content = new(testData, "application/json");
 
        string filename = $"test_{Guid.NewGuid()}.json";
        string? savedPath = null;
 
        try
        {
            savedPath = await content.SaveToAsync(filename);
 
            // The returned path should be in the current directory
            Assert.Equal(filename, Path.GetFileName(savedPath));
            Assert.True(File.Exists(savedPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(savedPath));
        }
        finally
        {
            if (savedPath is not null && File.Exists(savedPath))
            {
                File.Delete(savedPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_WithRelativeDirectory_UsesNameFromContent()
    {
        // Test that providing a relative directory path uses name from content
        byte[] testData = [4, 5, 6];
        DataContent content = new(testData, "image/png") { Name = "myimage.png" };
 
        string relativeDir = "subdir_" + Guid.NewGuid().ToString("N");
        Directory.CreateDirectory(relativeDir);
 
        try
        {
            string savedPath = await content.SaveToAsync(relativeDir);
 
            // The returned path should be in the relative directory using content's name
            Assert.Equal(content.Name, Path.GetFileName(savedPath));
            Assert.Contains(relativeDir, savedPath);
            Assert.True(File.Exists(savedPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(savedPath));
        }
        finally
        {
            if (Directory.Exists(relativeDir))
            {
                Directory.Delete(relativeDir, recursive: true);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_WithEmptyPath_UsesCurrentDirectoryAndContentName()
    {
        // Test that providing an empty string path uses current directory with name from content
        byte[] testData = [7, 8, 9];
        DataContent content = new(testData, "text/plain") { Name = $"testfile_{Guid.NewGuid()}.txt" };
 
        string? savedPath = null;
 
        try
        {
            savedPath = await content.SaveToAsync(string.Empty);
 
            // The returned path should be in the current directory using content's name
            Assert.Equal(content.Name, Path.GetFileName(savedPath));
            Assert.True(File.Exists(savedPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(savedPath));
        }
        finally
        {
            if (savedPath is not null && File.Exists(savedPath))
            {
                File.Delete(savedPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Path_ThrowsOnNull()
    {
        await Assert.ThrowsAsync<ArgumentNullException>("path", async () => await DataContent.LoadFromAsync((string)null!));
    }
 
    [Fact]
    public async Task LoadFromAsync_Path_ThrowsOnEmpty()
    {
        await Assert.ThrowsAsync<ArgumentException>("path", async () => await DataContent.LoadFromAsync(string.Empty));
    }
 
    [Fact]
    public async Task LoadFromAsync_Stream_ThrowsOnNull()
    {
        await Assert.ThrowsAsync<ArgumentNullException>("stream", async () => await DataContent.LoadFromAsync((Stream)null!));
    }
 
    [Fact]
    public async Task SaveToAsync_ThrowsOnNull()
    {
        DataContent content = new(new byte[] { 1 }, "application/octet-stream");
        await Assert.ThrowsAsync<ArgumentNullException>("path", async () => await content.SaveToAsync(null!));
    }
 
    [Fact]
    public async Task LoadFromAsync_ExtractsFilenameFromPath()
    {
        // Test that LoadFromAsync properly extracts the filename from a nested path
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_subdir_{Guid.NewGuid()}");
        string subDir = Path.Combine(tempDir, "subdir");
        Directory.CreateDirectory(subDir);
 
        try
        {
            string filePath = Path.Combine(subDir, "nested.html");
            byte[] testData = Encoding.UTF8.GetBytes("<html></html>");
            await File.WriteAllBytesAsync(filePath, testData);
 
            DataContent content = await DataContent.LoadFromAsync(filePath);
 
            Assert.Equal("text/html", content.MediaType);
            Assert.Equal("nested.html", content.Name);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_DirectoryPath_WithoutName_GeneratesRandomName()
    {
        byte[] testData = [5, 6, 7];
        DataContent content = new(testData, "text/css");
 
        // Note: Name is NOT set
 
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_dir_noname_{Guid.NewGuid()}");
        Directory.CreateDirectory(tempDir);
 
        try
        {
            string savedPath = await content.SaveToAsync(tempDir);
 
            // Should generate a random GUID-based name with .css extension
            Assert.StartsWith(tempDir, savedPath);
            Assert.EndsWith(".css", savedPath);
            Assert.True(File.Exists(savedPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(savedPath));
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_WithCancellationToken_PropagatesToken()
    {
        // This test verifies that the cancellation token is properly propagated.
        // With already-cancelled tokens, TaskCanceledException (a subclass of OperationCanceledException) may be thrown.
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_cancel_{Guid.NewGuid()}.txt");
 
        try
        {
            await File.WriteAllBytesAsync(tempPath, new byte[] { 1, 2, 3 });
 
            using CancellationTokenSource cts = new();
            cts.Cancel();
 
            await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
                await DataContent.LoadFromAsync(tempPath, cancellationToken: cts.Token));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_WithCancellationToken_PropagatesToken()
    {
        // This test verifies that the cancellation token is properly propagated.
        // With already-cancelled tokens, TaskCanceledException (a subclass of OperationCanceledException) may be thrown.
        DataContent content = new(new byte[] { 1, 2, 3 }, "application/octet-stream");
 
        using CancellationTokenSource cts = new();
        cts.Cancel();
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_save_cancel_{Guid.NewGuid()}.bin");
 
        try
        {
            await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
                await content.SaveToAsync(tempPath, cts.Token));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_Stream_WithCancellationToken_PropagatesToken()
    {
        // This test verifies that the cancellation token is properly propagated.
        // With already-cancelled tokens, TaskCanceledException (a subclass of OperationCanceledException) may be thrown.
        byte[] testData = [1, 2, 3];
        using MemoryStream ms = new(testData);
 
        using CancellationTokenSource cts = new();
        cts.Cancel();
 
        await Assert.ThrowsAnyAsync<OperationCanceledException>(async () =>
            await DataContent.LoadFromAsync(ms, cancellationToken: cts.Token));
    }
 
    [Fact]
    public async Task SaveToAsync_ExistingDirectory_WithName_UsesNameAsFilename()
    {
        byte[] testData = [1, 2, 3];
        DataContent content = new(testData, "application/json")
        {
            Name = "specific-output.json"
        };
 
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_dir_name_{Guid.NewGuid()}");
        Directory.CreateDirectory(tempDir);
        string expectedPath = Path.Combine(tempDir, content.Name);
 
        try
        {
            string savedPath = await content.SaveToAsync(tempDir);
 
            Assert.Equal(expectedPath, savedPath);
            Assert.True(File.Exists(expectedPath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(expectedPath));
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_NonExistentPath_TreatedAsFilePath()
    {
        // When the path doesn't exist, it's treated as a file path, not a directory
        byte[] testData = [1, 2, 3];
        DataContent content = new(testData, "application/json");
 
        string tempDir = Path.Combine(Path.GetTempPath(), $"test_nonexist_{Guid.NewGuid()}");
        Directory.CreateDirectory(tempDir);
        string filePath = Path.Combine(tempDir, "newfile");
 
        try
        {
            string savedPath = await content.SaveToAsync(filePath);
 
            // Since the path doesn't exist as a directory, it's used as the file path directly
            Assert.Equal(filePath, savedPath);
            Assert.True(File.Exists(filePath));
            Assert.Equal(testData, await File.ReadAllBytesAsync(filePath));
        }
        finally
        {
            if (Directory.Exists(tempDir))
            {
                Directory.Delete(tempDir, true);
            }
        }
    }
 
    [Fact]
    public async Task LoadFromAsync_LargeFile_LoadsCorrectly()
    {
        // Test with a larger file to ensure streaming works properly
        byte[] testData = new byte[100_000];
        new Random(42).NextBytes(testData);
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_large_{Guid.NewGuid()}.bin");
 
        try
        {
            await File.WriteAllBytesAsync(tempPath, testData);
 
            DataContent content = await DataContent.LoadFromAsync(tempPath);
 
            Assert.Equal("application/octet-stream", content.MediaType);
            Assert.Equal(testData, content.Data.ToArray());
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_LargeContent_SavesCorrectly()
    {
        // Test with larger content to ensure streaming works properly
        byte[] testData = new byte[100_000];
        new Random(42).NextBytes(testData);
 
        DataContent content = new(testData, "application/octet-stream");
 
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_save_large_{Guid.NewGuid()}.bin");
 
        try
        {
            string savedPath = await content.SaveToAsync(tempPath);
 
            Assert.Equal(tempPath, savedPath);
            Assert.Equal(testData, await File.ReadAllBytesAsync(savedPath));
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
 
    [Fact]
    public async Task SaveToAsync_ExistingFile_ThrowsAndPreservesOriginalContent()
    {
        // Create a file with original content
        byte[] originalContent = [10, 20, 30, 40, 50];
        string tempPath = Path.Combine(Path.GetTempPath(), $"test_existing_{Guid.NewGuid()}.bin");
 
        try
        {
            await File.WriteAllBytesAsync(tempPath, originalContent);
 
            // Try to save different content to the same path
            byte[] newContent = [1, 2, 3];
            DataContent content = new(newContent, "application/octet-stream");
 
            // SaveToAsync should throw because the file already exists (using FileMode.CreateNew)
            await Assert.ThrowsAsync<IOException>(async () => await content.SaveToAsync(tempPath));
 
            // Verify the original file was not corrupted
            Assert.True(File.Exists(tempPath));
            byte[] actualContent = await File.ReadAllBytesAsync(tempPath);
            Assert.Equal(originalContent, actualContent);
        }
        finally
        {
            if (File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }
}