using System.IO.Compression; using System.Text; using ClawdDotNet.Core.Documents; using Shouldly; namespace ClawdDotNet.Core.Tests.Documents; /// /// #692: The text of what really comes in — a Word order confirmation, a price list as a /// spreadsheet — without running anything in the file and without being unpacked to death. /// public sealed class DocumentTextTests { private const string W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; private const string S = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; public static byte[] Zip(params (string Name, string Content)[] parts) { using var buffer = new MemoryStream(); using (var zip = new ZipArchive(buffer, ZipArchiveMode.Create, leaveOpen: true)) foreach (var (name, content) in parts) { using var stream = zip.CreateEntry(name).Open(); stream.Write(Encoding.UTF8.GetBytes(content)); } return buffer.ToArray(); } public static byte[] Docx(string body) => Zip(("word/document.xml", $"""{body}""")); [Fact] public void A_word_document_comes_out_as_paragraphs_and_table_rows() { var doc = Docx(""" Order confirmation No. 4711due 2026-10-15 ItemQty Paper A440 Thank you. """); var text = DocumentText.Extract(doc, "confirmation.docx", 10_000); text.Ok.ShouldBeTrue(text.Problem); text.Kind.ShouldBe("Word document"); text.Text.ShouldContain("Order confirmation"); text.Text.ShouldContain("No. 4711\tdue 2026-10-15"); text.Text.ShouldContain("| Item | Qty |"); text.Text.ShouldContain("| Paper A4 | 40 |"); text.Text.ShouldEndWith("Thank you."); } [Fact] public void A_spreadsheet_comes_out_sheet_by_sheet_with_every_cell_at_its_column() { var xlsx = Zip( ("xl/workbook.xml", $""""""), ("xl/_rels/workbook.xml.rels", """"""), ("xl/sharedStrings.xml", $"""ItemPaperignoredPrice"""), ("xl/worksheets/sheet1.xml", $""" 02 11B2*34.5 """), ("xl/worksheets/sheet2.xml", $"""Order by Friday""")); var text = DocumentText.Extract(xlsx, "prices.xlsx", 10_000); text.Ok.ShouldBeTrue(text.Problem); text.Text.ShouldContain("## Sheet: Prices\nItem\t\tPrice\nPaper\tTRUE\t4.5".Replace("\n", Environment.NewLine)); text.Text.ShouldContain("## Sheet: Notes"); text.Text.ShouldContain("Order by Friday"); text.Text.ShouldNotContain("ignored", customMessage: "phonetic hints are no content"); text.Text.ShouldNotContain("B2*3", customMessage: "the stored value is read, the formula is not"); } [Fact] public void A_document_with_a_dtd_is_refused_not_resolved() { var doc = Zip(("word/document.xml", $"""]>&e;""")); var text = DocumentText.Extract(doc, "evil.docx", 10_000); text.Ok.ShouldBeFalse(); text.Problem!.ShouldContain("could not be read"); } [Fact] public void A_part_that_unpacks_beyond_the_limit_is_stopped() { var huge = new string('a', (int)DocumentText.MaxPartBytes + 1024); var doc = Docx($"{huge}"); DocumentText.Extract(doc, "bomb.docx", 100).Ok.ShouldBeFalse(); } [Fact] public void Text_is_cut_at_the_limit_and_odd_bytes_are_no_error() { var cut = DocumentText.Extract(Encoding.UTF8.GetBytes(new string('x', 500)), "a.csv", 100); cut.Truncated.ShouldBeTrue(); cut.Text.Length.ShouldBe(100); DocumentText.Extract([0x50, 0x72, 0x65, 0x69, 0x73, 0x20, 0xE4], "latin.txt", 100).Text.ShouldBe("Preis ä"); } [Fact] public void Pdf_and_unknown_files_are_named_not_guessed() { DocumentText.Extract([1, 2, 3], "invoice.pdf", 100).Problem!.ShouldContain("PDF"); DocumentText.Extract([1, 2, 3], "image.png", 100).Ok.ShouldBeFalse(); DocumentText.Extract([1, 2, 3], "broken.docx", 100).Ok.ShouldBeFalse(); } }