eva-agent/tests/test_links.py
Andrey Epifancev a51c509a87 classify_links не падает на битом HTML и адресах (I-1, #1)
На Python 3.9 `<![foo]>` вызывал NotImplementedError из ParserBase.error(), а адрес вида `http://[::1` — ValueError в
urljoin/urlsplit; одна такая конструкция роняла wiki_sync целиком. Теперь нестандартная секция пропускается до `>`,
битая ссылка — пропускается, остальные ссылки страницы сохраняются.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 05:46:28 +04:00

107 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Разбор ссылок страницы Eva: вложения и ссылки на страницы (design 6–7, 12)."""
import os
import unittest
from tests import _common # noqa: F401
import agent
BASE = "https://eva.test"
OBJ = "/files/obj/CmfDocument/CmfDocument%3Aa08/CmfDocument%3A0b1c2d3e-0000-4000-8000-000000000001"
HERE = os.path.dirname(os.path.abspath(__file__))
def page(name):
with open(os.path.join(HERE, "fixtures", "pages", name), encoding="utf-8") as fh:
return fh.read()
class ClassifyLinksTest(unittest.TestCase):
def test_image_relative_files(self):
att, links = agent.classify_links(page("image.html"), BASE)
self.assertEqual(att, [{"ref": OBJ + "/img.png?version=1", "url": BASE + OBJ + "/img.png", "name": "Схема"}])
self.assertEqual(links, [])
def test_thumbnail_maps_to_file(self):
att, _ = agent.classify_links(page("thumb_and_link.html"), BASE)
self.assertEqual(att[0]["url"], BASE + OBJ + "/model.xlsx")
self.assertEqual(att[0]["name"], "model.xlsx")
self.assertEqual(att[0]["ref"], OBJ + "/model.xlsx.meta/thumbnail.jpg?version=1")
self.assertEqual(att[2]["url"], BASE + OBJ + "/data.dbml")
self.assertEqual(att[2]["name"], "data.dbml")
def test_file_link_same_url(self):
att, _ = agent.classify_links(page("thumb_and_link.html"), BASE)
self.assertEqual(len(att), 3) # повторы не склеиваются
self.assertEqual(att[1]["url"], att[0]["url"])
self.assertEqual(att[1]["ref"], OBJ + "/model.xlsx?version=1")
self.assertEqual(att[1]["name"], "model.xlsx")
def test_static_ignored(self):
att, links = agent.classify_links('<img src="static/attach-file.png">', BASE)
self.assertEqual((att, links), ([], []))
def test_page_link_relative(self):
_, links = agent.classify_links(page("links.html"), BASE)
self.assertEqual(links[0], {"ref": "project/Document/DOC-019834", "code": "DOC-019834"})
self.assertEqual(links[1], {"ref": "https://eva.test/project/Document/doc-5", "code": "DOC-5"})
def test_foreign_host(self):
att, links = agent.classify_links(page("links.html"), BASE)
self.assertEqual(att, [])
self.assertEqual([l["code"] for l in links], ["DOC-019834", "DOC-5"])
att, links = agent.classify_links(
'<a href="https://other.test/files/x.png">x</a><a href="https://other.test/project/Document/DOC-1">y</a>',
BASE)
self.assertEqual((att, links), ([], []))
def test_name_unquoted(self):
att, _ = agent.classify_links(page("name_quoted.html"), BASE)
self.assertEqual(att[0]["name"], "ПМП.xlsx")
self.assertEqual(att[0]["url"], BASE + OBJ + "/%D0%9F%D0%9C%D0%9F.xlsx")
def test_ref_exact(self):
att, links = agent.classify_links(page("amp.html"), BASE)
self.assertEqual(att[0]["ref"], OBJ + "/a.png?version=1&x=2") # после unescape, как ищет хаб
self.assertEqual(links[0]["ref"], "project/Document/DOC-7?a=1&b=2")
self.assertEqual(att[0]["url"], BASE + OBJ + "/a.png")
def test_base_with_trailing_slash_and_title(self):
att, _ = agent.classify_links('<a href="%s/f.bin" title="Файл">текст</a>' % OBJ, BASE + "/")
self.assertEqual(att[0]["name"], "Файл")
def test_name_from_link_text(self):
att, _ = agent.classify_links('<a href="%s/f.bin"> мой <b>файл</b> </a>' % OBJ, BASE)
self.assertEqual(att[0]["name"], "мой файл")
def test_order_nested_img_in_link(self):
att, _ = agent.classify_links(
'<a href="%s/a.bin"><img src="%s/b.png"></a><img src="%s/c.png">' % (OBJ, OBJ, OBJ), BASE)
self.assertEqual([a["name"] for a in att], ["a.bin", "b.png", "c.png"])
def test_unknown_marked_section_does_not_crash(self):
"""I-1: на 3.9 `<![foo]>` уходит в ParserBase.error() → NotImplementedError; ссылки до и после сохраняются."""
html = ('<img src="%s/a.png"><![foo]><p>текст</p><![if !supportLists]>x<![endif]>'
'<a href="/project/Document/DOC-3">c</a><img src="%s/b.png">' % (OBJ, OBJ))
att, links = agent.classify_links(html, BASE)
self.assertEqual([a["name"] for a in att], ["a.png", "b.png"])
self.assertEqual(links, [{"ref": "/project/Document/DOC-3", "code": "DOC-3"}])
def test_marked_section_bad_name_and_unclosed(self):
for html in ('<img src="%s/a.png"><![ ]><img src="%s/b.png">' % (OBJ, OBJ),
'<img src="%s/a.png"><img src="%s/b.png"><![foo' % (OBJ, OBJ)):
with self.subTest(html=html):
att, _ = agent.classify_links(html, BASE)
self.assertEqual([a["name"] for a in att], ["a.png", "b.png"])
def test_bad_url_skipped(self):
"""I-1: `http://[::1` и порт не числом дают ValueError в urljoin/urlsplit — ссылка пропускается, остальные целы."""
html = ('<img src="http://[::1"><a href="https://[zz/files/x">x</a><img src="https://eva.test:abc/files/y.png">'
'<img src="%s/b.png"><a href="/project/Document/DOC-4">d</a>' % OBJ)
att, links = agent.classify_links(html, BASE)
self.assertEqual([a["name"] for a in att], ["b.png"])
self.assertEqual([l["code"] for l in links], ["DOC-4"])
if __name__ == "__main__":
unittest.main()