File: DocumentReviewFollowUpTests.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.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Documents;
using Xunit;
 
namespace Microsoft.Extensions.Documents.Tests;
 
public class DocumentReviewFollowUpTests
{
    [Fact]
    public void DataOnlyApi_HasConcreteCollectionsWithoutParentOrDisposalContract()
    {
        ExtractionDocument document = new("data");
        ExtractionDocumentSection section = new();
        Assert.IsType<List<ExtractionDocumentSection>>(document.Sections);
        Assert.IsType<Dictionary<string, object?>>(document.Metadata);
        Assert.IsType<List<ExtractionDocumentElement>>(section.Elements);
        Assert.IsType<List<ExtractionDocumentSourceReference>>(section.Sources);
        Assert.IsType<List<ExtractionDocumentSourceReference>>(new ExtractionDocumentTableCell().Sources);
        foreach (Type type in new[] { typeof(ExtractionDocument), typeof(ExtractionDocumentElement) })
        {
            Assert.False(typeof(IDisposable).IsAssignableFrom(type));
            Assert.Null(type.GetMethod("Dispose"));
            foreach (string name in new[] { "ParentSection", "Version", "Address", "LocalId", "IsDisposed", "IsDocumentOwned" })
            {
                Assert.Null(type.GetProperty(name));
            }
        }
 
        foreach (Type type in typeof(ExtractionDocument).Assembly.GetExportedTypes())
        {
            Assert.Null(type.GetMethod("ToMarkdown"));
        }
 
        Assert.Null(typeof(ExtractionDocument).Assembly.GetType("Microsoft.Extensions.Documents.ExtractionDocumentMarkdownFormatter"));
    }
 
    [Fact]
    public void OrdinaryLists_AllowSharedElementsAndObserveCurrentOrderWithoutAttachmentState()
    {
        ExtractionDocument first = new("first");
        ExtractionDocument second = new("second");
        ExtractionDocumentSection section = new();
        ExtractionDocumentTextElement text = new(new TextContent("original"));
        section.Elements.AddRange([text, text]);
        first.Sections.Add(section);
        second.Sections.Add(section);
        Assert.Equal(new[] { text, text }, first.EnumerateContent());
        Assert.Equal(new[] { text, text }, second.EnumerateContent());
        section.Elements.RemoveAt(0);
        text.Content = new TextContent("changed");
        first.Sections.Clear();
        Assert.Empty(first.EnumerateContent());
        Assert.Equal("changed", Assert.IsType<TextContent>(text.Content).Text);
        Assert.Same(text, Assert.Single(second.EnumerateContent()));
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void CyclicSections_AreRejectedDuringTraversal(bool self)
    {
        ExtractionDocument document = new("cycle");
        ExtractionDocumentSection root = new();
        ExtractionDocumentSection child = new();
        document.Sections.Add(root);
        root.Elements.Add(self ? root : child);
        if (!self)
        {
            child.Elements.Add(root);
        }
 
        Assert.Throws<InvalidOperationException>(() => document.EnumerateContent().ToArray());
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public void NullListEntries_FailExplicitlyAtTraversal(bool root)
    {
        ExtractionDocument document = new("invalid");
        ExtractionDocumentSection section = new();
        document.Sections.Add(root ? null! : section);
        if (!root)
        {
            section.Elements.Add(null!);
        }
 
        Assert.Throws<InvalidOperationException>(() => document.EnumerateContent().ToArray());
    }
 
    [Fact]
    public void NontransparentPaths_PreserveSharedSectionsAndRejectAncestorCycles()
    {
        ExtractionDocument document = new("shared");
        ExtractionDocumentSection root = new(new TextContent("local"));
        ExtractionDocumentSection shared = new();
        ExtractionDocumentTextElement text = new(new TextContent("body"));
        shared.Elements.Add(text);
        root.Elements.Add(shared);
        root.Elements.Add(shared);
        document.Sections.Add(root);
        Assert.Equal(new[] { text, text }, document.EnumerateContent());
 
        shared.Elements.Add(root);
        Assert.Throws<InvalidOperationException>(() => document.EnumerateContent().ToArray());
 
        shared.Elements.RemoveAt(1);
        document.Sections.Add(root);
        Assert.Equal(new[] { text, text, text, text }, document.EnumerateContent());
    }
 
    [Fact]
    public void NestedTables_SharedContentAndCyclesAreNotFlattenedByStructuralTraversal()
    {
        ExtractionDocumentTableElement outer = new(null, 1, 2);
        ExtractionDocumentTableElement inner = new(null, 1, 1);
        ExtractionDocumentTextElement shared = new(new TextContent("value"));
        outer.Cells[0, 0] = new(inner);
        outer.Cells[0, 1] = new(inner);
        inner.Cells[0, 0] = new(shared);
        Assert.Same(inner, outer.Cells[0, 0]!.Content);
        Assert.Same(inner, outer.Cells[0, 1]!.Content);
        Assert.Same(shared, inner.Cells[0, 0]!.Content);
        ExtractionDocument document = new("tables");
        ExtractionDocumentSection section = new();
        section.Elements.AddRange([outer, outer]);
        document.Sections.Add(section);
        Assert.Equal(new[] { outer, outer }, document.EnumerateContent());
        inner.Cells[0, 0] = new(outer);
        Assert.Equal(new[] { outer, outer }, document.EnumerateContent());
        Assert.Same(outer, inner.Cells[0, 0]!.Content);
    }
 
    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void SourceRelationships_AreExplicitRegardlessOfCompositionOrder(bool composeFirst)
    {
        ExtractionDocument document = new("explicit");
        ExtractionDocumentSection section = new();
        ExtractionDocumentTextElement text = new(new TextContent("visible"));
        ExtractionDocumentTableElement table = new(null, 1, 1);
        ExtractionDocumentTableCell cell = new(text);
        ExtractionDocumentSourcePage page = new("named-frame", null);
        ExtractionDocumentSourceReference reference = new(page);
        if (composeFirst)
        {
            table.Cells[0, 0] = cell;
            section.Elements.Add(table);
            document.Sections.Add(section);
        }
 
        cell.Sources.Add(reference);
        text.Sources.Add(reference);
        section.Sources.Add(reference);
        if (!composeFirst)
        {
            table.Cells[0, 0] = cell;
            section.Elements.Add(table);
            document.Sections.Add(section);
        }
 
        Assert.Empty(document.SourcePages);
        document.SourcePages.Add(page);
        Assert.Same(page, Assert.Single(document.SourcePages));
        cell.Sources.Clear();
        section.Elements.Clear();
        Assert.Same(page, Assert.Single(document.SourcePages));
        Assert.Same(reference, Assert.Single(text.Sources));
        Assert.Null(page.PageNumber);
    }
 
    [Fact]
    public void NamedUnnumberedAndOutOfOrderFrames_NeverInferAssemblyPageNumbers()
    {
        ExtractionDocument document = new("mixed");
        ExtractionDocumentSourcePage named = new("OneNote:Ideas", null);
        ExtractionDocumentSourcePage seven = new("pdf:7", 7);
        ExtractionDocumentSourcePage two = new("pdf:2", 2);
        document.SourcePages.Add(named);
        document.SourcePages.Add(seven);
        document.SourcePages.Add(two);
        ExtractionDocumentSection section = new();
        ExtractionDocumentTextElement text = new(new TextContent("named page"));
        text.Sources.Add(new(named));
        section.Elements.Add(text);
        document.Sections.Insert(0, section);
        Assert.Equal(new int?[] { null, 7, 2 }, document.SourcePages.Select(page => page.PageNumber));
        Assert.Equal("OneNote:Ideas", text.Sources[0].Page.Identifier);
        Assert.Null(text.Sources[0].Page.PageNumber);
    }
 
    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    public void SourcePage_RejectsNonPositiveNumbers(int number)
    {
        Assert.Throws<ArgumentOutOfRangeException>("pageNumber", () => new ExtractionDocumentSourcePage("frame", number));
    }
 
    [Fact]
    public void Geometry_ExplicitValuesHaveCoordinateEqualityHashAndOperators()
    {
        ExtractionDocumentPoint point = new(1.25, -2.5);
        ExtractionDocumentPoint equalPoint = new(1.25, -2.5);
        ExtractionDocumentPoint differentPoint = new(1.25, 2.5);
        Assert.True(point == equalPoint);
        Assert.False(point != equalPoint);
        Assert.True(point != differentPoint);
        Assert.False(point == differentPoint);
        Assert.True(point.Equals((object)equalPoint));
        Assert.False(point.Equals(null));
        Assert.False(point.Equals("not a point"));
        Assert.Equal(point.GetHashCode(), equalPoint.GetHashCode());
        Assert.True(new ExtractionDocumentPoint(0, -0.0) == default);
        Assert.Equal(default(ExtractionDocumentPoint).GetHashCode(), new ExtractionDocumentPoint(0, -0.0).GetHashCode());
        ExtractionDocumentPageDimensions size = new(200, 100);
        ExtractionDocumentPageDimensions equalSize = new(200, 100);
        ExtractionDocumentPageDimensions differentSize = new(100, 200);
        Assert.True(size == equalSize);
        Assert.False(size != equalSize);
        Assert.True(size != differentSize);
        Assert.False(size == differentSize);
        Assert.True(size.Equals((object)equalSize));
        Assert.False(size.Equals(point));
        Assert.False(size.Equals(null));
        Assert.Equal(size.GetHashCode(), equalSize.GetHashCode());
        Assert.Throws<ArgumentOutOfRangeException>("dimensions",
            () => new ExtractionDocumentSourcePage("frame", null, default(ExtractionDocumentPageDimensions)));
    }
}