SimpleTranslationUI / tests /test_formats.py
billingsmoore's picture
Switch translation backend from Gemini to OpenRouter, add test suite
36fc86c
Raw
History Blame Contribute Delete
6.58 kB
import json
from pathlib import Path
import pytest
from engine.formats import (
export_to_docx,
export_to_json,
export_to_txt,
load_source_file,
parse_json,
)
TIBETAN = "ΰ½¦ΰ½„ΰ½¦ΰΌ‹ΰ½’ΰΎ’ΰΎ±ΰ½¦ΰΌ‹ΰ½†ΰ½Όΰ½¦ΰΌ‹ΰ½‘ΰ½„ΰΌ‹ΰ½šΰ½Όΰ½‚ΰ½¦ΰΌ‹ΰ½€ΰΎ±ΰ½²ΰΌ‹ΰ½˜ΰ½†ΰ½Όΰ½‚ΰΌ‹ΰ½’ΰΎ£ΰ½˜ΰ½¦ΰΌ‹ΰ½£ΰΌ"
# ── load_source_file ──────────────────────────────────────────────────────
def test_load_txt(tmp_path):
path = tmp_path / "in.txt"
path.write_text(f"{TIBETAN}\n\nSecond paragraph.", encoding="utf-8")
segments = load_source_file(str(path))
assert segments == [
{"source": TIBETAN, "target": ""},
{"source": "Second paragraph.", "target": ""},
]
def test_load_docx(tmp_path):
docx = pytest.importorskip("docx")
path = tmp_path / "in.docx"
doc = docx.Document()
doc.add_paragraph(TIBETAN)
doc.add_paragraph("Second paragraph.")
doc.add_paragraph("") # blank paragraphs should be dropped, not turned into segments
doc.save(str(path))
segments = load_source_file(str(path))
assert segments == [
{"source": TIBETAN, "target": ""},
{"source": "Second paragraph.", "target": ""},
]
def test_load_pdf(tmp_path):
pytest.importorskip("pypdf")
reportlab_canvas = pytest.importorskip("reportlab.pdfgen.canvas")
from reportlab.lib.pagesizes import letter
path = tmp_path / "in.pdf"
c = reportlab_canvas.Canvas(str(path), pagesize=letter)
c.drawString(72, 700, "Hello world.")
c.save()
segments = load_source_file(str(path))
assert len(segments) == 1
assert "Hello world." in segments[0]["source"]
assert segments[0]["target"] == ""
def test_load_unsupported_extension_raises(tmp_path):
path = tmp_path / "in.rtf"
path.write_text("whatever", encoding="utf-8")
with pytest.raises(ValueError, match="Unsupported file type"):
load_source_file(str(path))
def test_load_empty_file_raises(tmp_path):
path = tmp_path / "empty.txt"
path.write_text(" \n\n ", encoding="utf-8")
with pytest.raises(ValueError, match="No text could be extracted"):
load_source_file(str(path))
def test_load_respects_max_chars(tmp_path):
path = tmp_path / "in.txt"
long_text = ("a" * 50 + "ΰ₯€ ") * 10
path.write_text(long_text, encoding="utf-8")
segments = load_source_file(str(path), max_chars=100)
assert len(segments) > 1
assert all(len(s["source"]) <= 110 for s in segments)
# ── the committed sample files (samples/sample_tibetan.*) ─────────────────
SAMPLES_DIR = Path(__file__).parent.parent / "samples"
@pytest.mark.parametrize("ext", ["txt", "docx", "pdf"])
def test_sample_files_load_successfully(ext):
path = SAMPLES_DIR / f"sample_tibetan.{ext}"
segments = load_source_file(str(path))
assert len(segments) > 0
assert all(seg["target"] == "" for seg in segments)
combined = "".join(seg["source"] for seg in segments)
assert "སངས་དྷྒྱས" in combined # "Buddha" appears in the refuge verse
# ── parse_json ─────────────────────────────────────────────────────────────
def test_parse_json_valid_list():
content = json.dumps([{"source": "src1", "target": "tgt1"}, {"source": "src2"}])
segments = parse_json(content)
assert segments == [
{"source": "src1", "target": "tgt1"},
{"source": "src2", "target": ""},
]
def test_parse_json_accepts_bytes():
content = json.dumps([{"source": "src1", "target": "tgt1"}]).encode("utf-8")
segments = parse_json(content)
assert segments == [{"source": "src1", "target": "tgt1"}]
def test_parse_json_rejects_non_list():
with pytest.raises(ValueError, match="non-empty list"):
parse_json(json.dumps({"source": "a"}))
def test_parse_json_rejects_empty_list():
with pytest.raises(ValueError, match="non-empty list"):
parse_json(json.dumps([]))
def test_parse_json_rejects_missing_source():
with pytest.raises(ValueError, match="Item 0 is missing 'source'"):
parse_json(json.dumps([{"target": "only a target"}]))
def test_parse_json_rejects_non_dict_item():
with pytest.raises(ValueError, match="Item 1 is missing 'source'"):
parse_json(json.dumps([{"source": "ok"}, "not a dict"]))
# ── export_to_txt / export_to_docx / export_to_json ───────────────────────
def test_export_to_txt_joins_nonempty_targets():
segments = [{"source": "s1", "target": "t1"}, {"source": "s2", "target": ""}, {"source": "s3", "target": "t3"}]
assert export_to_txt(segments, "target") == "t1\n\nt3\n"
def test_export_to_txt_all_empty_returns_empty_string():
segments = [{"source": "s1", "target": ""}, {"source": "s2", "target": " "}]
assert export_to_txt(segments, "target") == ""
def test_export_to_txt_source_field():
segments = [{"source": "s1", "target": "t1"}, {"source": "s2", "target": "t2"}]
assert export_to_txt(segments, "source") == "s1\n\ns2\n"
def test_export_to_docx_roundtrips_through_load(tmp_path):
docx = pytest.importorskip("docx")
segments = [{"source": "s1", "target": "First translation."}, {"source": "s2", "target": "Second translation."}]
data = export_to_docx(segments, "target")
assert isinstance(data, bytes) and len(data) > 0
out_path = tmp_path / "out.docx"
out_path.write_bytes(data)
doc = docx.Document(str(out_path))
texts = [p.text for p in doc.paragraphs if p.text.strip()]
assert texts == ["First translation.", "Second translation."]
def test_export_to_docx_skips_empty_targets():
segments = [{"source": "s1", "target": ""}, {"source": "s2", "target": "kept"}]
data = export_to_docx(segments, "target")
docx = pytest.importorskip("docx")
import io
doc = docx.Document(io.BytesIO(data))
texts = [p.text for p in doc.paragraphs if p.text.strip()]
assert texts == ["kept"]
def test_export_to_json_roundtrip():
segments = [{"source": "s1", "target": "t1"}, {"source": "s2", "target": ""}]
text = export_to_json(segments)
assert json.loads(text) == segments
def test_export_to_json_preserves_unicode_without_escaping():
segments = [{"source": TIBETAN, "target": ""}]
text = export_to_json(segments)
assert TIBETAN in text # ensure_ascii=False