File size: 5,718 Bytes
9078630
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from __future__ import annotations

from io import BytesIO
from zipfile import ZipFile

from docx_hidden_content_extractor import inspect_docx_hidden_content


W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"


def _minimal_docx(parts: dict[str, str]) -> bytes:
    content_types = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
  <Default Extension="xml" ContentType="application/xml"/>
  <Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>"""
    rels = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>"""
    output = BytesIO()
    with ZipFile(output, "w") as docx:
        docx.writestr("[Content_Types].xml", content_types)
        docx.writestr("_rels/.rels", rels)
        for part_name, xml in parts.items():
            docx.writestr(part_name, xml)
    return output.getvalue()


def _text_part(text: str) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="{W_NS}"><w:body><w:p><w:r><w:t>{text}</w:t></w:r></w:p></w:body></w:document>"""


def _header_or_footer(text: str) -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:hdr xmlns:w="{W_NS}"><w:p><w:r><w:t>{text}</w:t></w:r></w:p></w:hdr>"""


def _comments(text: str, author: str = "SYNTHETIC-REVIEWER") -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:comments xmlns:w="{W_NS}"><w:comment w:id="0" w:author="{author}" w:initials="SR"><w:p><w:r><w:t>{text}</w:t></w:r></w:p></w:comment></w:comments>"""


def _tracked_changes() -> str:
    return f"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="{W_NS}"><w:body><w:p><w:del><w:r><w:delText>OUDE-SYNTHETISCHE-WAARDE</w:delText></w:r></w:del><w:ins><w:r><w:t>NIEUWE-SYNTHETISCHE-WAARDE</w:t></w:r></w:ins></w:p></w:body></w:document>"""


def test_extracts_headers_footers_comments_and_tracked_changes_without_cleaning():
    docx_bytes = _minimal_docx({
        "word/document.xml": _tracked_changes(),
        "word/header1.xml": _header_or_footer("SYNTHETIC-HEADER-DOSSIER-001"),
        "word/footer1.xml": _header_or_footer("SYNTHETIC-FOOTER-CLIENT-001"),
        "word/comments.xml": _comments("SYNTHETIC-COMMENT-RISK-001"),
    })

    result = inspect_docx_hidden_content(docx_bytes)

    assert result["valid_docx"] is True
    assert result["local_only"] is True
    assert result["ai_processing"] is False
    assert result["cloud_processing"] is False
    assert result["extraction_only"] is True
    assert result["cleaning_applied"] is False
    assert result["export_blocking"] is False
    assert result["detected"] == {
        "headers_detected": True,
        "footers_detected": True,
        "comments_detected": True,
        "tracked_changes_detected": True,
    }
    assert result["headers"][0]["text"] == "SYNTHETIC-HEADER-DOSSIER-001"
    assert result["footers"][0]["text"] == "SYNTHETIC-FOOTER-CLIENT-001"
    assert result["comments"][0]["text"] == "SYNTHETIC-COMMENT-RISK-001"
    assert result["comments"][0]["authors"] == ["SR", "SYNTHETIC-REVIEWER"]
    assert {change["element"] for change in result["tracked_changes"]} >= {"del", "delText", "ins"}
    assert any("OUDE-SYNTHETISCHE-WAARDE" in change["text"] for change in result["tracked_changes"])


def test_header_footer_comment_absence_is_reported_without_warnings():
    docx_bytes = _minimal_docx({"word/document.xml": _text_part("Alleen synthetische bodytekst.")})

    result = inspect_docx_hidden_content(docx_bytes)

    assert result["detected"] == {
        "headers_detected": False,
        "footers_detected": False,
        "comments_detected": False,
        "tracked_changes_detected": False,
    }
    assert result["headers"] == []
    assert result["footers"] == []
    assert result["comments"] == []
    assert result["tracked_changes"] == []
    assert result["warnings"] == []


def test_invalid_docx_is_reported_without_side_effects():
    result = inspect_docx_hidden_content(b"not a zip")

    assert result["valid_docx"] is False
    assert result["validation_issues"] == ["DOCX content is not a valid OOXML package."]
    assert result["local_only"] is True
    assert result["ai_processing"] is False
    assert result["cloud_processing"] is False
    assert result["cleaning_applied"] is False
    assert result["export_blocking"] is False


def test_non_bytes_input_is_rejected_safely():
    result = inspect_docx_hidden_content("not bytes")

    assert result["valid_docx"] is False
    assert result["validation_issues"] == ["DOCX content must be bytes."]
    assert result["headers"] == []
    assert result["footers"] == []
    assert result["comments"] == []
    assert result["tracked_changes"] == []


def test_extractor_uses_synthetic_values_only():
    docx_bytes = _minimal_docx({
        "word/document.xml": _text_part("SYNTHETIC-DOCX-INSPECTION-001"),
        "word/comments.xml": _comments("SYNTHETIC-COMMENT-ONLY"),
    })

    result = inspect_docx_hidden_content(docx_bytes)
    rendered = repr(result)

    assert "SYNTHETIC" in rendered
    assert "Jan Jansen" not in rendered
    assert "Piet de Vries" not in rendered
    assert "123456782" not in rendered