"""Перевод HTML Eva в Markdown (wiki-module, spec wiki «Markdown-копия вики», design 10).""" import re from pathlib import Path from jira_hub.modules.wiki import convert FIX = Path(__file__).parent / "fixtures" / "wiki" FILE_URL = "https://eva.example.test/files/obj/CmfDocument/CmfDocument%3Aa08/CmfDocument%3A3f2a9c1e-0b7d-4e8a-9c21-5d6e7f8a9b0c/model.xlsx?version=1" IMG_REF = "/files/obj/CmfDocument/CmfDocument%3Aa08/CmfDocument%3A3f2a9c1e-0b7d-4e8a-9c21-5d6e7f8a9b0c/image2026-7-27_14-20-6.png?version=1" THUMB_REF = "/files/obj/CmfDocument/CmfDocument%3Aa08/CmfDocument%3A3f2a9c1e-0b7d-4e8a-9c21-5d6e7f8a9b0c/model.xlsx.meta/thumbnail.jpg?version=1" FILE_REF = "/files/obj/CmfDocument/CmfDocument%3Aa08/CmfDocument%3A3f2a9c1e-0b7d-4e8a-9c21-5d6e7f8a9b0c/model.xlsx?version=1" def _html(name): return (FIX / name).read_text(encoding="utf-8") def test_image_becomes_attachment(): md, atts = convert.to_markdown( _html("page_image.html"), [{"ref": IMG_REF, "url": "https://eva.example.test" + IMG_REF, "name": "image2026-7-27_14-20-6.png"}], [], ) assert "[📎 image2026-7-27_14-20-6.png](attachment:@0)" in md.splitlines() assert atts == [{"url": "https://eva.example.test" + IMG_REF, "name": "image2026-7-27_14-20-6.png"}] def test_thumbnail_and_file_one_link(): md, atts = convert.to_markdown( _html("page_file.html"), [ {"ref": THUMB_REF, "url": FILE_URL, "name": "model.xlsx"}, {"ref": FILE_REF, "url": FILE_URL, "name": "model.xlsx"}, ], [], ) assert [a["name"] for a in atts] == ["model.xlsx"] and len(atts) == 1 assert md.count("](attachment:@0)") == 1 assert "[📎 model.xlsx](attachment:@0)" in md assert "attach-file" not in md def test_page_link(): md, _ = convert.to_markdown( _html("page_links.html"), [], [{"ref": "project/Document/DOC-019834", "code": "DOC-019834"}], ) assert "[Модель данных](page:DOC-019834)" in md def test_external_link_kept(): md, _ = convert.to_markdown(_html("page_links.html"), [], []) assert "(https://example.org)" in md def test_no_raw_html(): for name in ("page_image.html", "page_file.html", "page_links.html"): md, _ = convert.to_markdown( _html(name), [{"ref": IMG_REF, "url": "https://eva.example.test" + IMG_REF, "name": "image.png"}, {"ref": THUMB_REF, "url": FILE_URL, "name": "model.xlsx"}, {"ref": FILE_REF, "url": FILE_URL, "name": "model.xlsx"}], [{"ref": "project/Document/DOC-019834", "code": "DOC-019834"}], ) assert re.search(r"<[a-zA-Z/]", md) is None, name def test_table_kept(): md, _ = convert.to_markdown(_html("page_links.html"), [], []) table_lines = [line for line in md.splitlines() if "|" in line] assert table_lines and any("---" in line for line in table_lines) def test_headings_atx(): md, _ = convert.to_markdown(_html("page_links.html"), [], []) assert "# Требования" in md.splitlines() def test_static_links_removed(): html = '

Текст и ссылка

' md, atts = convert.to_markdown(html, [], []) assert "static" not in md and atts == [] def test_fill_ids(): assert convert.fill_ids("[x](attachment:@1)", [7, 9]) == "[x](attachment:9)" # --- fix round 1 (review Task 6: I1, I2, I3, M3, M4) --- def test_unsafe_schemes_dropped_from_links(): html = ('

x1 x2 ' 'x3 x4 ' 'x5

') md, _ = convert.to_markdown(html, [], []) for text in ("x1", "x2", "x3", "x4", "x5"): assert text in md assert "(javascript" not in md.lower() and "(data:" not in md and "(vbscript" not in md.lower() assert "java" not in md.lower().replace("x4", "") # схема не осталась ни в каком виде def test_unsafe_scheme_img_dropped(): md, atts = convert.to_markdown('

azb

', [], []) assert md.strip() == "ab" and atts == [] def test_attachment_name_and_link_text_escaped(): md, atts = convert.to_markdown( '

x

', [{"ref": "/files/a", "url": "https://eva.example.test/files/a", "name": "a]b[c).png"}], [], ) assert "[📎 a\\]b\\[c).png](attachment:@0)" in md.splitlines() assert atts[0]["name"] == "a]b[c).png" md, _ = convert.to_markdown( '

a ] b [x](http://evil)

', [], [{"ref": "project/Document/DOC-1", "code": "DOC-1"}], ) assert "[a \\] b \\[x\\](http://evil)](page:DOC-1)" in md.splitlines() def test_newlines_in_link_text_collapsed(): md, _ = convert.to_markdown( '

первая\nвторая

', [], [{"ref": "project/Document/DOC-1", "code": "DOC-1"}], ) assert "[первая вторая](page:DOC-1)" in md.splitlines() def test_literal_attachment_marker_in_text_is_safe(): md, atts = convert.to_markdown("

[x](attachment:@5) и attachment:@0

", [], []) assert atts == [] assert convert.fill_ids(md, []) == md assert "(attachment:@5)" not in md.replace("\\(attachment:\\@5)", "") def test_fill_ids_by_index_not_prefix(): ids = list(range(100, 111)) assert convert.fill_ids("[a](attachment:@1) [b](attachment:@10)", ids) == \ "[a](attachment:101) [b](attachment:110)" def test_fill_ids_out_of_range_raises(): try: convert.fill_ids("[a](attachment:@3)", [1, 2]) except ValueError as exc: assert "@3" in str(exc) else: raise AssertionError("ожидалась ValueError") def test_none_lists_are_empty(): md, atts = convert.to_markdown("

текст

", None, None) assert md == "текст\n" and atts == [] def test_literal_tag_text_has_no_raw_html(): md, _ = convert.to_markdown("

<tag> и <script>alert(1)</script>

", [], []) assert re.search(r"<[a-zA-Z/]", md) is None assert "<tag>" in md or "<tag" in md # --- fix round 2 (re-review 1: B1, B2, B3) --- def _no_forged_marker(md, ids=(7,)): """Авторский текст не порождает ссылку вложения: fill_ids ничего не меняет и шаблона нет.""" assert "](attachment:@" not in md assert convert.fill_ids(md, list(ids)) == md def test_page_link_text_escaped_once(): md, _ = convert.to_markdown( '

a_b *x* c

', [], [{"ref": "project/Document/DOC-1", "code": "DOC-1"}], ) assert "[a\\_b \\*x\\* c](page:DOC-1)" in md.splitlines() assert "\\\\" not in md def test_page_link_at_sign_not_double_escaped(): md, _ = convert.to_markdown( '

user@example.com

', [], [{"ref": "project/Document/DOC-1", "code": "DOC-1"}], ) assert "[user@example.com](page:DOC-1)" in md.splitlines() def test_attachment_name_star_and_underscore_at_word_edge_escaped(): md, _ = convert.to_markdown( '

x

', [{"ref": "/files/a", "url": "https://eva.example.test/files/a", "name": "_x_*.png"}], [], ) assert "[📎 \\_x\\_\\*.png](attachment:@0)" in md.splitlines() def test_attachment_name_intraword_underscore_kept(): md, _ = convert.to_markdown( '

x

', [{"ref": "/files/a", "url": "https://eva.example.test/files/a", "name": "my_file.png"}], [], ) assert "[📎 my_file.png](attachment:@0)" in md.splitlines() def test_author_href_attachment_scheme_is_text(): md, atts = convert.to_markdown('

x

', [], []) assert md.strip() == "x" and atts == [] _no_forged_marker(md) def test_author_img_src_attachment_scheme_dropped(): md, atts = convert.to_markdown('

azb

', [], []) assert md.strip() == "ab" and atts == [] _no_forged_marker(md) def test_author_alt_cannot_forge_marker(): md, _ = convert.to_markdown('

a](attachment:@0)

', [], []) _no_forged_marker(md) def test_author_title_cannot_forge_marker(): md, _ = convert.to_markdown('

t

', [], []) _no_forged_marker(md) def test_author_code_cannot_forge_marker(): md, _ = convert.to_markdown("

](attachment:@0)

", [], []) _no_forged_marker(md) def test_author_text_cannot_forge_marker(): md, _ = convert.to_markdown("

](attachment:@0) и ](attachment:@0)

", [], []) _no_forged_marker(md) def test_author_private_chars_cannot_forge_marker(): md, _ = convert.to_markdown("

0

", [], []) _no_forged_marker(md) def test_plain_at_sign_kept(): md, _ = convert.to_markdown("

user@example.com и @ivan

", [], []) assert "user@example.com и @ivan" in md def test_code_keeps_literal_text(): md, _ = convert.to_markdown("

<script> a@b

", [], []) assert "`