File: DocumentTests.StandardContent.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Documents.Abstractions.Tests\Microsoft.Extensions.Documents.Abstractions.Tests.csproj (Microsoft.Extensions.Documents.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.Linq;
using System.Runtime.InteropServices;
using System.Text.Json;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Documents;
using Xunit;
 
namespace Microsoft.Extensions.Documents.Tests;
 
public partial class DocumentTests
{
    [Fact]
    public void RootOnlyPayload_ReusesCurrentStringWithoutFormatting()
    {
        string markdown = new('x', 4096);
        TextContent payload = new(markdown);
        ExtractionDocument document = new("root-only", payload);
 
        Assert.Same(payload, document.Content);
        Assert.Same(markdown, Assert.IsType<TextContent>(document.Content).Text);
        payload.Text = "**changed**";
        Assert.Equal("**changed**", Assert.IsType<TextContent>(document.Content).Text);
        document.Content = null;
        Assert.Null(document.Content);
        Assert.Empty(document.EnumerateContent());
    }
 
    [Fact]
    public void LeafSectionPayload_RemainsLocalAndNullable()
    {
        string markdown = new('x', 4096);
        ExtractionDocumentSection section = new(new TextContent(markdown));
 
        Assert.Same(markdown, Assert.IsType<TextContent>(section.Content).Text);
        Assert.Empty(section.Elements);
        section.Content = null;
        Assert.Null(section.Content);
    }
 
    [Fact]
    public void TransparentContainers_PreserveSingleDescendantAndPayloadIdentity()
    {
        string markdown = new('x', 4096);
        TextContent payload = new(markdown);
        ExtractionDocumentTextElement text = new(payload);
        ExtractionDocumentSection inner = new();
        ExtractionDocumentSection outer = new();
        inner.Elements.Add(text);
        outer.Elements.Add(inner);
        ExtractionDocument document = new("transparent");
        document.Sections.Add(outer);
 
        Assert.Null(document.Content);
        Assert.Null(outer.Content);
        Assert.Null(inner.Content);
        Assert.Same(inner, Assert.Single(outer.Elements));
        Assert.Same(text, Assert.Single(inner.Elements));
        Assert.Same(text, Assert.Single(document.EnumerateContent()));
        Assert.Same(payload, text.Content);
        Assert.Same(markdown, payload.Text);
        payload.Text = "updated";
        Assert.Equal("updated", Assert.IsType<TextContent>(text.Content).Text);
    }
 
    [Fact]
    public void FlatTextPayloads_PreserveEmptyEntriesAndObserveEdits()
    {
        TextContent first = new("**first**");
        ExtractionDocumentSection section = new();
        section.Elements.Add(new ExtractionDocumentTextElement(first));
        section.Elements.Add(new ExtractionDocumentTextElement(new TextContent(string.Empty)));
        section.Elements.Add(new ExtractionDocumentTextElement(new TextContent("last|value")));
        ExtractionDocument document = new("flat");
        document.Sections.Add(section);
 
        Assert.Equal(new[] { "**first**", "", "last|value" },
            document.EnumerateContent().Select(element => Assert.IsType<TextContent>(element.Content).Text));
        first.Text = "changed";
        Assert.Equal(new[] { "changed", "", "last|value" },
            document.EnumerateContent().Select(element => Assert.IsType<TextContent>(element.Content).Text));
    }
 
    [Fact]
    public void EmptyLocalPayloads_AreDistinctFromAbsentPayloads()
    {
        ExtractionDocument document = new("empty-local", new TextContent(string.Empty));
        ExtractionDocumentSection section = new(new TextContent(string.Empty));
        section.Elements.Add(new ExtractionDocumentTextElement(new TextContent("child")));
        document.Sections.Add(section);
 
        Assert.Equal("", Assert.IsType<TextContent>(document.Content).Text);
        Assert.Equal("", Assert.IsType<TextContent>(section.Content).Text);
        document.Content = null;
        Assert.Null(document.Content);
        Assert.Equal("", Assert.IsType<TextContent>(section.Content).Text);
        section.Content = null;
        Assert.Null(section.Content);
        Assert.Equal("child", Assert.IsType<TextContent>(Assert.Single(document.EnumerateContent()).Content).Text);
    }
 
    [Fact]
    public void CurrentPayloadKind_RemainsVisibleWithoutImplicitConversion()
    {
        ExtractionDocumentSection section = new();
        section.Elements.Add(new ExtractionDocumentTextElement(new TextContent("first")));
        section.Elements.Add(new CustomElement(new AIContent()));
        ExtractionDocument document = new("unsupported-flat");
        document.Sections.Add(section);
 
        Assert.Equal(section.Elements, document.EnumerateContent());
        Assert.IsType<AIContent>(section.Elements[1].Content);
        Assert.Equal("first", Assert.IsType<TextContent>(section.Elements[0].Content).Text);
    }
 
    [Fact]
    public void CustomElements_PreserveLocalPayloadAndGridIdentity()
    {
        CustomElement custom = new(new TextContent("custom|value"));
        ExtractionDocumentSection section = new();
        section.Elements.Add(custom);
        ExtractionDocument document = new("custom-projection");
        document.Sections.Add(section);
 
        Assert.Same(custom, Assert.Single(document.EnumerateContent()));
        Assert.Equal("custom|value", Assert.IsType<TextContent>(custom.Content).Text);
        section.Elements.Insert(0, new ExtractionDocumentTextElement(new TextContent("prefix")));
        Assert.Equal(section.Elements, document.EnumerateContent());
 
        CustomElement cellContent = new(new TextContent("cell|value"));
        ExtractionDocumentTableElement table = new(null, 1, 1);
        table.Cells[0, 0] = new ExtractionDocumentTableCell(cellContent);
        section.Elements.Add(table);
        Assert.Equal(section.Elements, document.EnumerateContent());
        Assert.Same(cellContent, table.Cells[0, 0]!.Content);
        Assert.Equal("cell|value", Assert.IsType<TextContent>(cellContent.Content).Text);
    }
 
    [Fact]
    public void StandardTextContent_PreservesSharedAuthoritativeStringPayload()
    {
        string markdown = new(['*', '*', 'O', 'C', 'R', '*', '*']);
        TextContent payload = new(markdown);
        ExtractionDocument document = new("shared-text", payload);
        ExtractionDocumentSection section = new(payload);
        ExtractionDocumentTextElement text = new(payload, role: "heading") { HeadingLevel = 2 };
        section.Elements.Add(text);
        document.Sections.Add(section);
 
        Assert.Same(payload, document.Content);
        Assert.Same(payload, section.Content);
        Assert.Same(payload, text.Content);
        Assert.Same(markdown, payload.Text);
        Assert.Equal("**OCR**", Assert.IsType<TextContent>(text.Content).Text);
        Assert.Equal("heading", text.Role);
        Assert.Equal(2, text.HeadingLevel);
    }
 
    [Fact]
    public void NullableContainers_KeepLocalContentSeparateFromDescendants()
    {
        ExtractionDocument document = new("local", new TextContent("root introduction"));
        ExtractionDocumentSection section = new(new TextContent("section introduction"));
        ExtractionDocumentSection transparent = new();
        ExtractionDocumentTableElement table = new(new TextContent("table caption"), 1, 1);
        ExtractionDocumentTextElement cellText = new(new TextContent("leaf|value"));
        table.Cells[0, 0] = new ExtractionDocumentTableCell(cellText);
        transparent.Elements.Add(table);
        section.Elements.Add(transparent);
        document.Sections.Add(section);
 
        Assert.Equal("root introduction", Assert.IsType<TextContent>(document.Content).Text);
        Assert.Equal("section introduction", Assert.IsType<TextContent>(section.Content).Text);
        Assert.Null(transparent.Content);
        Assert.Same(table, Assert.Single(document.EnumerateContent()));
        Assert.Same(cellText, table.Cells[0, 0]!.Content);
        Assert.Equal("leaf|value", Assert.IsType<TextContent>(cellText.Content).Text);
 
        cellText.Content = new TextContent("edited");
        Assert.Equal("edited", Assert.IsType<TextContent>(table.Cells[0, 0]!.Content!.Content).Text);
        Assert.Equal("edited", Assert.IsType<TextContent>(cellText.Content).Text);
        Assert.Equal("table caption", Assert.IsType<TextContent>(table.Content).Text);
    }
 
    [Fact]
    public void NullPayloads_AreAbsentRatherThanFabricatedText()
    {
        ExtractionDocument document = new("empty");
        ExtractionDocumentSection section = new();
        ExtractionDocumentImageElement image = new();
        section.Elements.Add(image);
        document.Sections.Add(section);
 
        Assert.Null(document.Content);
        Assert.Null(section.Content);
        Assert.Null(image.Content);
        Assert.Null(image.Text);
        Assert.Null(image.AlternativeText);
        Assert.Same(image, Assert.Single(document.EnumerateContent()));
    }
 
    [Theory]
    [InlineData("root", false)]
    [InlineData("root", true)]
    [InlineData("section", false)]
    [InlineData("section", true)]
    [InlineData("table", false)]
    [InlineData("table", true)]
    public void StructuralLocalImagePayloads_RemainIndependentOfChildren(string owner, bool uri)
    {
        AIContent payload = uri ? new UriContent("urn:local:image", "image/png") :
            new DataContent(new byte[] { 1, 2, 3 }, "image/png");
        ExtractionDocument document = new("local-image", owner == "root" ? payload : null);
        ExtractionDocumentSection section = new(owner == "section" ? payload : null);
        ExtractionDocumentTableElement table = new(owner == "table" ? payload : null, 1, 1);
        ExtractionDocumentTextElement text = new(new TextContent("cell"));
        table.Cells[0, 0] = new ExtractionDocumentTableCell(text);
        section.Elements.Add(table);
        document.Sections.Add(section);
        AIContent? selected = owner switch
        {
            "root" => document.Content,
            "section" => section.Content,
            _ => table.Content
        };
        Assert.Same(payload, selected);
        Assert.Same(text, table.Cells[0, 0]!.Content);
    }
 
    [Fact]
    public void BorrowedPayloadMutation_IsAuthoritativeAndVisible()
    {
        TextContent payload = new("first");
        ExtractionDocument document = new("mutable");
        ExtractionDocumentSection section = new();
        ExtractionDocumentTextElement text = new(payload);
        section.Elements.Add(text);
        document.Sections.Add(section);
 
        payload.Text = "second";
 
        Assert.Equal("second", Assert.IsType<TextContent>(text.Content).Text);
        Assert.Same(payload, text.Content);
        text.Content = new TextContent("second");
        Assert.NotSame(payload, text.Content);
        Assert.Equal("second", Assert.IsType<TextContent>(text.Content).Text);
    }
 
    [Fact]
    public void OwnedDataBuffer_IsBorrowedByActualImageWithoutCopy()
    {
        byte[] owned = [1, 2, 3];
        DataContent payload = new(owned, "image/png");
        ExtractionDocumentImageElement image = new(payload, "OCR diagram") { AlternativeText = "diagram" };
 
        Assert.Same(payload, image.Content);
        Assert.True(MemoryMarshal.TryGetArray(payload.Data, out ArraySegment<byte> segment));
        Assert.Same(owned, segment.Array);
        Assert.Equal(0, segment.Offset);
        Assert.Equal(3, segment.Count);
        Assert.Equal(new byte[] { 1, 2, 3 }, payload.Data.ToArray());
        Assert.Equal("OCR diagram", image.Text);
    }
 
    [Fact]
    public void MutableProviderBuffer_RequiresExplicitCopyAtOwnershipBoundary()
    {
        byte[] providerBuffer = [1, 2, 3];
        ExtractionDocumentImageElement borrowed = new(new DataContent(providerBuffer, "image/png"));
 
        // The adapter, not ExtractionDocument or DataContent, owns the decision to retain a stable copy.
        byte[] retained = providerBuffer.ToArray();
        ExtractionDocumentImageElement retainedImage = new(new DataContent(retained, "image/png"));
        providerBuffer[0] = 9;
 
        DataContent borrowedPayload = Assert.IsType<DataContent>(borrowed.Content);
        DataContent retainedPayload = Assert.IsType<DataContent>(retainedImage.Content);
        Assert.Equal(new byte[] { 9, 2, 3 }, borrowedPayload.Data.ToArray());
        Assert.Equal(new byte[] { 1, 2, 3 }, retainedPayload.Data.ToArray());
        Assert.True(MemoryMarshal.TryGetArray(retainedPayload.Data, out ArraySegment<byte> segment));
        Assert.Same(retained, segment.Array);
        Assert.NotSame(providerBuffer, segment.Array);
    }
 
    [Theory]
    [InlineData(false, false)]
    [InlineData(false, true)]
    [InlineData(true, false)]
    [InlineData(true, true)]
    public void StandardImagePayloads_KeepCanonicalReferenceAndProvenanceIndependent(bool data, bool provenance)
    {
        AIContent payload = data ? new DataContent(new byte[] { 1, 2, 3 }, "image/png") :
            new UriContent(new Uri("urn:canonical:image"), "image/png");
        Uri? source = provenance ? new Uri("urn:provenance:image") : null;
        ExtractionDocumentImageElement image = new(payload, "OCR text")
        {
            Source = source,
            AlternativeText = "independent alternative"
        };
        Assert.Same(payload, image.Content);
        Assert.Same(source, image.Source);
        Assert.Equal("OCR text", image.Text);
        Assert.Equal("independent alternative", image.AlternativeText);
        if (payload is UriContent uri)
        {
            Assert.Equal("urn:canonical:image", uri.Uri.ToString());
            Assert.Equal("image/png", uri.MediaType);
        }
        else
        {
            Assert.Equal(new byte[] { 1, 2, 3 }, Assert.IsType<DataContent>(payload).Data.ToArray());
            Assert.Equal("image/png", Assert.IsType<DataContent>(payload).MediaType);
        }
    }
 
    [Theory]
    [InlineData("root")]
    [InlineData("section")]
    [InlineData("table")]
    [InlineData("custom")]
    [InlineData("image")]
    public void ArbitraryStandardPayload_RemainsInspectableAtEachOwner(string owner)
    {
        AIContent payload = new();
        ExtractionDocument document = new("unsupported", owner == "root" ? payload : null);
        ExtractionDocumentElement element = owner switch
        {
            "section" => new ExtractionDocumentSection(payload),
            "table" => new ExtractionDocumentTableElement(payload, 1, 1),
            "image" => new ExtractionDocumentImageElement(payload) { Source = new Uri("urn:must-not-mask-unsupported") },
            _ => new CustomElement(payload)
        };
        if (owner != "root")
        {
            ExtractionDocumentSection section = new();
            section.Elements.Add(element);
            document.Sections.Add(section);
            Assert.Same(element, Assert.Single(section.Elements));
            if (element is ExtractionDocumentSection)
            {
                Assert.Empty(document.EnumerateContent());
            }
            else
            {
                Assert.Same(element, Assert.Single(document.EnumerateContent()));
            }
        }
 
        Assert.Same(payload, owner == "root" ? document.Content : element.Content);
    }
 
    [Theory]
    [InlineData(false, false)]
    [InlineData(false, true)]
    [InlineData(true, false)]
    [InlineData(true, true)]
    public void NonImageDataAndUri_AreNotConvertedToText(bool uri, bool onImage)
    {
        AIContent payload = uri ? new UriContent("urn:raw:body", "application/json") :
            new DataContent(new byte[] { 123, 125 }, "application/json");
        ExtractionDocument document = new("raw", onImage ? null : payload);
        ExtractionDocumentImageElement image = new(payload, "semantic OCR")
        {
            Source = new Uri("urn:provenance:must-not-hide-invalid-type"),
            AlternativeText = "raw is not an image"
        };
        if (onImage)
        {
            ExtractionDocumentSection section = new();
            section.Elements.Add(image);
            document.Sections.Add(section);
            Assert.Same(image, Assert.Single(document.EnumerateContent()));
        }
 
        Assert.Equal("semantic OCR", image.Text);
        Assert.Same(payload, image.Content);
        Assert.Same(payload, onImage ? image.Content : document.Content);
        Assert.Equal("application/json", uri ? Assert.IsType<UriContent>(payload).MediaType : Assert.IsType<DataContent>(payload).MediaType);
    }
 
    [Fact]
    public void RealTableAndSourceMultiplicity_PreserveSharedPayloadAndDistinctElements()
    {
        ExtractionDocument document = new("table-identity");
        ExtractionDocumentSection section = new();
        ExtractionDocumentTableElement table = new(null, 1, 2);
        TextContent shared = new("same|payload");
        ExtractionDocumentTextElement first = new(shared);
        ExtractionDocumentTextElement second = new(shared);
        ExtractionDocumentSourcePage page = new("page", 1);
        document.SourcePages.Add(page);
        ExtractionDocumentSourceReference reference = new(page);
        first.Sources.Add(reference);
        first.Sources.Add(reference);
        second.Sources.Add(reference);
        table.Sources.Add(reference);
        ExtractionDocumentTableCell firstCell = new(first);
        firstCell.Sources.Add(reference);
        table.Cells[0, 0] = firstCell;
        table.Cells[0, 1] = new ExtractionDocumentTableCell(second);
        section.Elements.Add(table);
        document.Sections.Add(section);
 
        Assert.Same(firstCell, table.Cells[0, 0]);
        Assert.Same(first, table.Cells[0, 0]!.Content);
        Assert.Same(second, table.Cells[0, 1]!.Content);
        Assert.NotSame(first, second);
        Assert.Same(shared, first.Content);
        Assert.Same(shared, second.Content);
        Assert.Equal(new[] { reference, reference }, first.Sources);
        Assert.Same(reference, Assert.Single(firstCell.Sources));
        Assert.Same(reference, Assert.Single(table.Sources));
        Assert.Same(page, Assert.Single(document.SourcePages));
        shared.Text = "changed";
        Assert.Equal("changed", Assert.IsType<TextContent>(first.Content).Text);
        Assert.Equal("changed", Assert.IsType<TextContent>(second.Content).Text);
    }
 
    [Fact]
    public void ActualMeaiSerializationAndTextSelection_ConsumeDocumentPayloadsWithoutDocumentWireContract()
    {
        ExtractionDocumentTextElement text = new(new TextContent("**literal markdown**"));
        ExtractionDocumentImageElement image = new(new DataContent(new byte[] { 1, 2, 3 }, "image/png"), "diagram OCR")
        {
            AlternativeText = "diagram alternative",
            Source = new Uri("urn:provenance")
        };
        UriContent uriPayload = new("urn:canonical:image", "image/jpeg");
        ExtractionDocumentImageElement reference = new(uriPayload);
        ChatMessage message = new(ChatRole.User, [text.Content!, image.Content!, reference.Content!]);
 
        string json = JsonSerializer.Serialize(message, AIJsonUtilities.DefaultOptions);
        using JsonDocument wire = JsonDocument.Parse(json);
        JsonElement contents = wire.RootElement.GetProperty("contents");
        Assert.Equal(3, contents.GetArrayLength());
        Assert.Equal("text", contents[0].GetProperty("$type").GetString());
        Assert.Equal("**literal markdown**", contents[0].GetProperty("text").GetString());
        Assert.Equal("data:image/png;base64,AQID", contents[1].GetProperty("uri").GetString());
        Assert.Equal("urn:canonical:image", contents[2].GetProperty("uri").GetString());
        Assert.Equal("image/jpeg", contents[2].GetProperty("mediaType").GetString());
        Assert.Equal("**literal markdown**", message.Text);
        Assert.Same(image.Content, message.Contents[1]);
        Assert.Same(uriPayload, message.Contents[2]);
 
        // Image OCR is independently selected; text payloads need no alternate plain-text view.
        ChatMessage semantic = new(ChatRole.User, [text.Content!, new TextContent(image.Text!)]);
        Assert.Equal("**literal markdown**diagram OCR", semantic.Text);
        Assert.DoesNotContain("AQID", semantic.Text, StringComparison.Ordinal);
        Assert.DoesNotContain("urn:", semantic.Text, StringComparison.Ordinal);
        Assert.DoesNotContain("diagram alternative", semantic.Text, StringComparison.Ordinal);
        Assert.Equal("diagram alternative", image.AlternativeText);
        Assert.Equal("urn:provenance", image.Source!.ToString());
    }
 
    private sealed class CustomElement(AIContent? content) : ExtractionDocumentElement(content);
}