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();
}
}