xref: /trunk/test/aoo-test/tests/smoke/test_convert.py (revision 2cb6d6f119bbe2f78742f1c5c8538cb0d4e72ede)
1################################################################
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20################################################################
21
22"""The worker's convert op: ODF and foreign formats out, OOXML in, properties passed through, and the document always closed."""
23import zipfile
24
25import pytest
26
27from aootest.client import WorkerError
28from aootest.odfversion import MODES, odf_versions
29
30pytestmark = pytest.mark.smoke
31
32DOCX = {
33    "[Content_Types].xml": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
34    '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
35    '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
36    '<Default Extension="xml" ContentType="application/xml"/>'
37    '<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>'
38    '</Types>',
39    "_rels/.rels": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
40    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
41    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>'
42    '</Relationships>',
43    "word/document.xml": '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
44    '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>'
45    '<w:p><w:r><w:t>Imported from OOXML</w:t></w:r></w:p></w:body></w:document>',
46}
47
48
49@pytest.fixture
50def office(aoo_offices):
51    return aoo_offices.get(None)
52
53
54@pytest.fixture
55def source(office, tmp_path):
56    """A small Writer document with known text."""
57    path = tmp_path / "source.odt"
58    office.call("new_and_store", {"app": "writer", "dst": str(path), "filter": "writer8", "text": "Hello Convert"}, timeout=120)
59    return path
60
61
62def open_documents(office):
63    return office.call("open_documents")["documents"]
64
65
66def test_odf_to_odf_keeps_the_text_and_leaves_nothing_open(office, source, tmp_path):
67    dst = tmp_path / "out.odt"
68    assert office.call("convert", {"src": str(source), "dst": str(dst), "filter": "writer8"}, timeout=120) == {"dst": str(dst)}
69    with zipfile.ZipFile(dst) as z:
70        assert "Hello Convert" in z.read("content.xml").decode("utf-8")
71    assert open_documents(office) == []
72
73
74def test_text_export(office, source, tmp_path):
75    dst = tmp_path / "out.txt"
76    office.call("convert", {"src": str(source), "dst": str(dst), "filter": "Text"}, timeout=120)
77    assert "Hello Convert" in dst.read_text(encoding="utf-8", errors="replace")
78
79
80def test_pdf_export(office, source, tmp_path):
81    dst = tmp_path / "out.pdf"
82    office.call("convert", {"src": str(source), "dst": str(dst), "filter": "writer_pdf_Export"}, timeout=180)
83    data = dst.read_bytes()
84    assert data.startswith(b"%PDF-") and b"%%EOF" in data[-1024:]
85
86
87def test_binary_word_export(office, source, tmp_path):
88    dst = tmp_path / "out.doc"
89    office.call("convert", {"src": str(source), "dst": str(dst), "filter": "MS Word 97"}, timeout=180)
90    assert dst.read_bytes().startswith(bytes.fromhex("D0CF11E0A1B11AE1"))
91
92
93def test_spreadsheet_to_csv(office, tmp_path):
94    ods = tmp_path / "s.ods"
95    office.call("new_and_store", {"app": "calc", "dst": str(ods), "filter": "calc8", "text": "cell one"}, timeout=120)
96    csv = tmp_path / "s.csv"
97    office.call("convert", {"src": str(ods), "dst": str(csv), "filter": "Text - txt - csv (StarCalc)"}, timeout=120)
98    assert "cell one" in csv.read_text(encoding="utf-8", errors="replace")
99
100
101def test_ooxml_is_imported_by_type_detection(office, tmp_path):
102    docx = tmp_path / "in.docx"
103    with zipfile.ZipFile(docx, "w", zipfile.ZIP_DEFLATED) as z:
104        for name, text in DOCX.items():
105            z.writestr(name, text)
106    dst = tmp_path / "out.odt"
107    office.call("convert", {"src": str(docx), "dst": str(dst), "filter": "writer8"}, timeout=180)
108    with zipfile.ZipFile(dst) as z:
109        assert "Imported from OOXML" in z.read("content.xml").decode("utf-8")
110
111
112@pytest.mark.parametrize("mode", [3, 4])
113def test_the_save_mode_applies_to_a_conversion(aoo_offices, source, tmp_path, mode):
114    dst = tmp_path / "out.odt"
115    aoo_offices.get(mode).call("convert", {"src": str(source), "dst": str(dst), "filter": "writer8"}, timeout=120)
116    assert odf_versions(dst) == (MODES[mode], MODES[mode])
117
118
119def test_store_properties_reach_the_filter(office, source, tmp_path):
120    enc = tmp_path / "enc.odt"
121    office.call("convert", {"src": str(source), "dst": str(enc), "filter": "writer8", "store_props": {"Password": "secret"}}, timeout=120)
122    with zipfile.ZipFile(enc) as z:
123        assert b"encryption-data" in z.read("META-INF/manifest.xml")
124        assert "Hello Convert" not in z.read("content.xml").decode("latin-1")
125    # without the password the load fails cleanly; with it the text comes back
126    with pytest.raises(WorkerError) as e:
127        office.call("convert", {"src": str(enc), "dst": str(tmp_path / "nopw.odt"), "filter": "writer8"}, timeout=120)
128    assert e.value.kind == "uno"
129    back = tmp_path / "back.odt"
130    office.call("convert", {"src": str(enc), "dst": str(back), "filter": "writer8", "load_props": {"Password": "secret"}}, timeout=120)
131    with zipfile.ZipFile(back) as z:
132        assert "Hello Convert" in z.read("content.xml").decode("utf-8")
133    assert open_documents(office) == []
134
135
136def test_a_missing_source_is_a_uno_error_and_the_office_stays_usable(office, tmp_path):
137    with pytest.raises(WorkerError) as e:
138        office.call("convert", {"src": str(tmp_path / "nothing.odt"), "dst": str(tmp_path / "o.odt"), "filter": "writer8"}, timeout=60)
139    assert e.value.kind == "uno"
140    assert office.call("ping") == {"pong": True} and open_documents(office) == []
141
142
143def test_an_unknown_filter_closes_the_document_it_loaded(office, source, tmp_path):
144    with pytest.raises(WorkerError) as e:
145        office.call("convert", {"src": str(source), "dst": str(tmp_path / "o.x"), "filter": "no such filter"}, timeout=60)
146    assert e.value.kind == "uno"
147    assert open_documents(office) == []
148
149
150@pytest.mark.parametrize("content", [b"", b"this is not a zip file at all\n"], ids=["empty", "garbage"])
151def test_a_damaged_input_ends_cleanly(office, tmp_path, content):
152    """Either a uno error or a converted document is fine; a hang, a crash, a leaked document or a dead office is not."""
153    src = tmp_path / "damaged.odt"
154    src.write_bytes(content)
155    dst = tmp_path / "out.odt"
156    try:
157        office.call("convert", {"src": str(src), "dst": str(dst), "filter": "writer8"}, timeout=60)
158    except WorkerError as e:
159        assert e.kind == "uno"
160    else:
161        assert zipfile.is_zipfile(dst)
162    assert office.call("ping") == {"pong": True} and open_documents(office) == []
163