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