478 lines
26 KiB
Python
478 lines
26 KiB
Python
import io
|
||
import json
|
||
import os
|
||
import tempfile
|
||
import unittest
|
||
from contextlib import redirect_stdout
|
||
|
||
from _common import SETTINGS, agent, eva_cfg, no_sleep
|
||
from fake_eva import FakeEva
|
||
import fake_hub
|
||
from fake_hub import FakeHub
|
||
|
||
GOOD = {"page_code": "DOC-1", "code": "DOCH-1", "created_at": "2026-10-27T17:59:00+03:00",
|
||
"author": {"login": None, "name": None}, "html": "", "attachments": [], "links": []}
|
||
|
||
|
||
class FakeHubVersionsTest(unittest.TestCase):
|
||
def test_check_versions(self):
|
||
claimed = {"type": "wiki_sync", "status": "claimed"}
|
||
self.assertIsNone(fake_hub.check_versions(claimed, 1, [GOOD]))
|
||
self.assertEqual(fake_hub.check_versions(claimed, 1, [GOOD] * 51)[0], 422)
|
||
self.assertEqual(fake_hub.check_versions({"type": "wiki_sync", "status": "done"}, 1, [GOOD])[0], 409)
|
||
self.assertEqual(fake_hub.check_versions(claimed, 1, [dict(GOOD, created_at="2026-10-27T17:59:00")]),
|
||
(422, {"error": "bad_versions", "detail": "DOCH-1: created_at"}))
|
||
self.assertEqual(fake_hub.check_versions(claimed, 1, [dict(GOOD, author={"login": 1, "name": None})]),
|
||
(422, {"error": "bad_versions", "detail": "DOCH-1: author"}))
|
||
|
||
|
||
class SyncBase(unittest.TestCase):
|
||
def setUp(self):
|
||
self.eva = FakeEva()
|
||
self.hub = FakeHub(SETTINGS)
|
||
self.addCleanup(self.eva.stop)
|
||
self.addCleanup(self.hub.stop)
|
||
self.cfg = eva_cfg(self.eva.base_url, hub_url=self.hub.base_url, hub_token=self.hub.token)
|
||
self.client = agent.EvaClient(self.cfg)
|
||
self.client.sleep = no_sleep
|
||
self.ex = agent.Executor(self.client, agent.HubClient(self.cfg))
|
||
|
||
def sync(self, root="DOC-1", label="tz", **extra):
|
||
params = dict({"label": label, "root": root}, **extra)
|
||
cid = self.hub.enqueue("wiki_sync", params)
|
||
self.hub.queue.remove(cid)
|
||
self.hub.commands[cid]["status"] = "claimed"
|
||
self.cid = cid
|
||
return self.ex.run({"id": cid, "type": "wiki_sync", "params": params})
|
||
|
||
def sent_versions(self):
|
||
return [v for body in self.hub.versions for v in body["versions"]]
|
||
|
||
def history_calls(self, method):
|
||
return [r for r in self.eva.api_calls if r["path"] == "/api/?m=" + method]
|
||
|
||
def sent(self):
|
||
return [p for body in self.hub.pages for p in body["pages"]]
|
||
|
||
|
||
class WikiSyncTest(SyncBase):
|
||
def test_sync_tree_order(self):
|
||
e = self.eva
|
||
e.add_doc(1, "DOC-1", "Корень", project="P", text="<p>корень</p>")
|
||
e.add_doc(2, "DOC-2", "Б", parent="DOC-1", project="P", orderno=2, text="<p>б</p>")
|
||
e.add_doc(3, "DOC-3", "А", parent="DOC-1", project="P", orderno=1, text="<p>а</p>")
|
||
e.add_doc(4, "DOC-4", "Внук", parent="DOC-3", project="P", text="<p>внук</p>")
|
||
e.add_doc(5, "DOC-5", "Чужой", parent="DOC-1", project="OTHER")
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(out["result"]["codes"], ["DOC-1", "DOC-3", "DOC-4", "DOC-2"])
|
||
self.assertEqual(out["result"]["root_title"], "Корень")
|
||
self.assertEqual(out["result"]["eva_calls"], self.client.calls)
|
||
pages = {p["code"]: p for p in self.sent()}
|
||
self.assertEqual([p["code"] for p in self.sent()], out["result"]["codes"])
|
||
self.assertIsNone(pages["DOC-1"]["parent_code"])
|
||
self.assertEqual(pages["DOC-3"]["parent_code"], "DOC-1")
|
||
self.assertEqual(pages["DOC-4"]["parent_code"], "DOC-3")
|
||
self.assertEqual(pages["DOC-3"]["orderno"], 1)
|
||
self.assertIsNone(pages["DOC-4"]["orderno"])
|
||
self.assertEqual(pages["DOC-3"]["url"], self.eva.base_url + "/project/Document/DOC-3")
|
||
self.assertEqual(pages["DOC-3"]["eva_id"], "3")
|
||
self.assertEqual(pages["DOC-3"]["title"], "А")
|
||
self.assertEqual(pages["DOC-3"]["html"], "<p>а</p>")
|
||
self.assertEqual(self.hub.pages[0]["command_id"], self.cid)
|
||
|
||
def test_sync_passes_links_and_attachments(self):
|
||
self.eva.add_doc(1, "DOC-1", "К", project="P", text=(
|
||
'<p><a href="/project/Document/DOC-7">x</a><img src="/files/a/b.png" alt="Схема"></p>'))
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
page = self.sent()[0]
|
||
self.assertEqual(page["links"], [{"ref": "/project/Document/DOC-7", "code": "DOC-7"}])
|
||
self.assertEqual(page["attachments"], [{"ref": "/files/a/b.png", "url": self.eva.base_url + "/files/a/b.png",
|
||
"name": "Схема"}])
|
||
|
||
def test_sync_batches_50(self):
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
for i in range(2, 121):
|
||
self.eva.add_doc(i, "DOC-%d" % i, "С%03d" % i, parent="DOC-1", project="P", orderno=i)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual([len(b["pages"]) for b in self.hub.pages], [50, 50, 20])
|
||
self.assertEqual(len(out["result"]["codes"]), 120)
|
||
self.assertEqual(out["result"]["codes"][0], "DOC-1")
|
||
|
||
def test_sync_batch_bytes(self):
|
||
big = "<p>" + "x" * (1500 * 1024) + "</p>"
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P", text=big)
|
||
for i in (2, 3):
|
||
self.eva.add_doc(i, "DOC-%d" % i, "С", parent="DOC-1", project="P", orderno=i, text=big)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
sizes = [len(json.dumps(b, ensure_ascii=False).encode("utf-8")) for b in self.hub.pages]
|
||
self.assertTrue(all(s <= 4 * 1024 * 1024 for s in sizes), sizes)
|
||
self.assertEqual([len(b["pages"]) for b in self.hub.pages], [2, 1])
|
||
|
||
def test_tree_cycle_and_two_parents(self):
|
||
e = self.eva
|
||
e.add_doc(1, "DOC-1", "Корень", project="P")
|
||
e.add_doc(3, "DOC-3", "Три", parent="DOC-1", project="P", orderno=1)
|
||
e.add_doc(10, "DOC-A", "А", parent="DOC-B", tree_parent="DOC-1", project="P", orderno=2)
|
||
e.add_doc(11, "DOC-B", "Б", tree_parent="DOC-A", project="P")
|
||
e.add_doc(12, "DOC-C", "Ц", parent="DOC-1", tree_parent="DOC-3", project="P", orderno=3)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
codes = out["result"]["codes"]
|
||
self.assertEqual(len(codes), len(set(codes)))
|
||
self.assertEqual(sorted(codes), ["DOC-1", "DOC-3", "DOC-A", "DOC-B", "DOC-C"])
|
||
self.assertEqual([p["code"] for p in self.sent()], codes)
|
||
|
||
def fill_forms_tree(self):
|
||
e = self.eva
|
||
e.add_doc(101, "DOC-1", "Корень", project="P")
|
||
e.add_doc(103, "DOC-3", "Три", parent="DOC-1", project="P", orderno=1) # только parent
|
||
e.add_doc(104, "DOC-4", "Внук", tree_parent="DOC-3", project="P") # только tree_parent
|
||
e.add_doc(110, "DOC-A", "А", parent="DOC-B", tree_parent="DOC-1", project="P", orderno=2)
|
||
e.add_doc(111, "DOC-B", "Б", tree_parent="DOC-A", project="P") # цикл A <-> B
|
||
e.add_doc(112, "DOC-C", "Ц", parent="DOC-1", tree_parent="DOC-3", project="P", orderno=3) # два родителя
|
||
|
||
def test_all_ref_forms(self):
|
||
"""Ссылки parent/tree_parent бывают {id, code}, {id} и голой строкой-id; id документа не равен коду."""
|
||
for form in ("full", "id_only", "string", "code_id"):
|
||
with self.subTest(form=form):
|
||
self.setUp()
|
||
self.eva.ref_form = form
|
||
self.fill_forms_tree()
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
codes = out["result"]["codes"]
|
||
self.assertEqual(codes[:4], ["DOC-1", "DOC-3", "DOC-C", "DOC-4"])
|
||
self.assertEqual(len(codes), len(set(codes)))
|
||
self.assertEqual(sorted(codes), ["DOC-1", "DOC-3", "DOC-4", "DOC-A", "DOC-B", "DOC-C"])
|
||
parents = {p["code"]: p["parent_code"] for p in self.sent()}
|
||
self.assertEqual(parents["DOC-4"], "DOC-3")
|
||
self.assertEqual(parents["DOC-A"], "DOC-1")
|
||
self.assertEqual(parents["DOC-B"], "DOC-A")
|
||
self.assertEqual(parents["DOC-C"], "DOC-3") # первый родитель по обходу в глубину
|
||
self.assertEqual(self.sent()[0]["eva_id"], "101")
|
||
self.eva.stop()
|
||
self.hub.stop()
|
||
|
||
def test_root_without_project(self):
|
||
e = self.eva
|
||
e.add_doc(1, "DOC-1", "Корень", text="<p>к</p>")
|
||
e.add_doc(2, "DOC-2", "Б", tree_parent="DOC-1", orderno=2)
|
||
e.add_doc(3, "DOC-3", "А", tree_parent="DOC-1", orderno=1)
|
||
e.add_doc(4, "DOC-4", "Внук", tree_parent="DOC-3")
|
||
e.add_doc(5, "DOC-5", "Посторонний", tree_parent="DOC-9")
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(out["result"]["codes"], ["DOC-1", "DOC-3", "DOC-4", "DOC-2"])
|
||
self.assertEqual({p["code"]: p["parent_code"] for p in self.sent()},
|
||
{"DOC-1": None, "DOC-3": "DOC-1", "DOC-4": "DOC-3", "DOC-2": "DOC-1"})
|
||
|
||
def test_root_missing(self):
|
||
out = self.sync("DOC-9")
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertEqual(out["error"], "страница DOC-9 не найдена или нет прав")
|
||
self.assertEqual(self.hub.pages, [])
|
||
|
||
def test_eva_failure_reported(self):
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
self.eva.fail_next(503, 4)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("503", out["error"])
|
||
self.assertNotIn(FakeEva.token, out["error"])
|
||
self.assertEqual(self.hub.pages, [])
|
||
|
||
def test_hub_rejects_batch(self):
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
self.hub.reject_pages = (422, {"error": "bad_pages", "detail": "DOC-1: html"})
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("422", out["error"])
|
||
self.assertIn("DOC-1: html", out["error"])
|
||
|
||
def test_eva_rpc_error_reported(self):
|
||
"""M-1: `error` JSON-RPC Eva — failed «Eva: <текст>», без повтора и без «внутренней ошибки агента»."""
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
self.eva.rpc_error_next("нет прав, токен eva-secret")
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertEqual(out["error"], "Eva: CmfDocument.get: нет прав, токен ***")
|
||
self.assertFalse(out.get("retryable"))
|
||
self.assertEqual(self.hub.pages, [])
|
||
|
||
def test_eva_rpc_error_in_runner(self):
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
self.eva.rpc_error_next("нет прав")
|
||
runner = agent.Runner(self.cfg, self.ex.hub, self.client, agent.Journal(os.devnull))
|
||
runner.executor = self.ex
|
||
self.assertEqual(runner.run_once({"id": 1, "type": "wiki_sync", "params": {"root": "DOC-1"}})["error"],
|
||
"Eva: CmfDocument.get: нет прав")
|
||
|
||
def test_hub_413_single_page(self):
|
||
"""M-3: хаб не принял тело (413) — в ошибке названа страница."""
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
self.hub.reject_pages = (413, {"error": "too_large", "detail": "тело больше 16777216 байт"})
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("страница DOC-1 больше 16 МБ", out["error"])
|
||
self.assertFalse(out.get("retryable"))
|
||
|
||
def test_hub_413_batch(self):
|
||
self.eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
for i in (2, 3):
|
||
self.eva.add_doc(i, "DOC-%d" % i, "С", parent="DOC-1", project="P", orderno=i)
|
||
self.hub.reject_pages = (413, {"error": "too_large", "detail": "тело больше 16777216 байт"})
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("пачка страниц DOC-1…DOC-3 (3 шт.) больше 16 МБ", out["error"])
|
||
|
||
def test_empty_name_falls_back_to_code(self):
|
||
self.eva.add_doc(1, "DOC-1", "", project="P") # хаб отклоняет пустой title (stage._check)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(self.sent()[0]["title"], "DOC-1")
|
||
self.assertEqual(out["result"]["root_title"], "DOC-1")
|
||
|
||
def test_page_vanished(self):
|
||
e = self.eva
|
||
e.add_doc(1, "DOC-1", "Корень", project="P")
|
||
e.add_doc(2, "DOC-2", "Б", parent="DOC-1", project="P")
|
||
real = self.client.get_doc
|
||
self.client.get_doc = lambda code, fields: None if code == "DOC-2" else real(code, fields)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("DOC-2", out["error"])
|
||
|
||
def test_counts(self):
|
||
e = self.eva
|
||
e.add_doc(1, "DOC-1", "Корень", project="P")
|
||
e.add_doc(2, "DOC-2", "Б", parent="DOC-1", project="P")
|
||
e.fail_next(429, 2, retry_after=0)
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(out["jira_calls"], len(e.requests))
|
||
self.assertEqual(out["jira_calls"], self.client.calls)
|
||
self.assertEqual(out["rate_limited"], 2)
|
||
|
||
|
||
class WikiSyncRecoverTest(unittest.TestCase):
|
||
def test_recover_resyncs(self):
|
||
eva, hub = FakeEva(), FakeHub(SETTINGS)
|
||
tmp = tempfile.TemporaryDirectory()
|
||
for x in (eva.stop, hub.stop, tmp.cleanup):
|
||
self.addCleanup(x)
|
||
eva.add_doc(1, "DOC-1", "Корень", project="P")
|
||
for i in range(2, 61):
|
||
eva.add_doc(i, "DOC-%d" % i, "С", parent="DOC-1", project="P", orderno=i)
|
||
cid = hub.enqueue("wiki_sync", {"label": "tz", "root": "DOC-1"})
|
||
hub.queue.remove(cid)
|
||
hub.commands[cid]["status"] = "claimed"
|
||
hub.in_flight = cid
|
||
journal = agent.Journal(os.path.join(tmp.name, "journal.jsonl"))
|
||
journal.claimed(cid)
|
||
journal.started(cid) # перезапуск посреди выполнения: finished нет
|
||
cfg = eva_cfg(eva.base_url, hub_url=hub.base_url, hub_token=hub.token)
|
||
runner = agent.Runner(cfg, agent.HubClient(cfg), agent.EvaClient(cfg), journal)
|
||
runner.sleep = lambda s: None
|
||
runner.eva.sleep = no_sleep
|
||
runner.idle_limit = 1
|
||
runner.loop()
|
||
self.assertEqual(hub.results[cid]["status"], "done", hub.results[cid])
|
||
self.assertEqual(len(hub.results[cid]["result"]["codes"]), 60)
|
||
self.assertEqual([len(b["pages"]) for b in hub.pages], [50, 10])
|
||
self.assertEqual(len([r for r in journal.records() if r["kind"] == "finished"]), 1)
|
||
self.assertEqual(journal.unfinished_started(), [])
|
||
|
||
|
||
class WikiVersionsTest(SyncBase):
|
||
def tree2(self):
|
||
self.eva.add_doc("CmfDocument:1", "DOC-1", "Корень", project="P")
|
||
self.eva.add_doc("CmfDocument:2", "DOC-2", "Б", parent="DOC-1", project="P", orderno=1)
|
||
|
||
def test_versions_skipped_without_known(self):
|
||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-11")
|
||
out = self.sync()
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertNotIn("versions", out["result"])
|
||
self.assertEqual(self.hub.versions, [])
|
||
self.assertFalse([r for r in self.eva.api_calls if "CmfDocumentHistory" in r["path"]])
|
||
|
||
def test_versions_first_load(self): # сценарий «Первая загрузка истории»
|
||
self.tree2(); e = self.eva
|
||
e.add_version("CmfDocument:1", "DOCH-11", "2026-10-27T17:59:00.123456+03:00", "<p>v1</p>")
|
||
e.add_version("CmfDocument:1", "DOCH-12", "2026-10-27T18:02:00+03:00", "<p>v2</p>")
|
||
e.add_version("CmfDocument:1", "DOCH-13", "2026-10-27T18:05:00+03:00", "<p>v3</p>")
|
||
e.add_version("CmfDocument:1", "DOCH-19", "2026-10-27T18:10:00+03:00", "<p>черновик</p>", has_published=False)
|
||
e.add_version("CmfDocument:2", "DOCH-21", "2026-08-01T10:00:00+03:00", "<p>a</p>")
|
||
e.add_version("CmfDocument:2", "DOCH-22", "2026-08-02T10:00:00+03:00", "<p>b</p>")
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(out["result"]["versions"], 5)
|
||
self.assertEqual([v["code"] for v in self.sent_versions()], ["DOCH-11", "DOCH-12", "DOCH-13", "DOCH-21", "DOCH-22"])
|
||
self.assertEqual(self.sent_versions()[0], {
|
||
"page_code": "DOC-1", "code": "DOCH-11", "created_at": "2026-10-27T17:59:00.123456+03:00",
|
||
"author": {"login": "sosnov-dvi@rshbeco.ru", "name": "Дмитрий Соснов"},
|
||
"html": "<p>v1</p>", "attachments": [], "links": []})
|
||
self.assertEqual(self.hub.versions[0]["command_id"], self.cid)
|
||
self.assertEqual(self.hub.wiki_posts, ["/agent/wiki/pages", "/agent/wiki/versions"])
|
||
self.assertEqual([r["body"]["kwargs"]["filter"] for r in self.history_calls("CmfDocumentHistory.list")],
|
||
[[["parent_id", "==", "CmfDocument:1"]], [["parent_id", "==", "CmfDocument:2"]]])
|
||
self.assertEqual(len(self.history_calls("CmfDocumentHistory.get")), 5) # черновик не читается
|
||
self.assertEqual(out["result"]["eva_calls"], self.client.calls)
|
||
self.assertEqual(out["result"]["eva_calls"], len(self.eva.api_calls))
|
||
|
||
def test_versions_incremental(self): # сценарий «Догрузка новых версий»
|
||
self.tree2()
|
||
for code in ("DOCH-10", "DOCH-11", "DOCH-12"):
|
||
self.eva.add_version("CmfDocument:1", code)
|
||
out = self.sync(known_versions={"DOC-1": ["DOCH-10", "DOCH-11"], "DOC-77": ["DOCH-1"]})
|
||
self.assertEqual(out["result"]["versions"], 1)
|
||
self.assertEqual([v["code"] for v in self.sent_versions()], ["DOCH-12"])
|
||
self.assertEqual([r["body"]["kwargs"]["filter"] for r in self.history_calls("CmfDocumentHistory.get")],
|
||
[[["code", "==", "DOCH-12"]]])
|
||
|
||
def test_versions_none_in_eva(self):
|
||
self.tree2()
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["result"]["versions"], 0)
|
||
self.assertEqual(self.hub.wiki_posts, ["/agent/wiki/pages"])
|
||
|
||
def test_versions_author_missing(self):
|
||
self.tree2(); e = self.eva
|
||
e.add_version("CmfDocument:1", "DOCH-1", author=None)
|
||
e.add_version("CmfDocument:1", "DOCH-2", author="CmfPerson:6c2b")
|
||
e.add_version("CmfDocument:1", "DOCH-3", author={"id": "CmfPerson:1", "_acl_fields": {"login": "deny", "name": "deny"}, "_acl_obj": "deny"})
|
||
e.add_version("CmfDocument:1", "DOCH-4", author={"id": "CmfPerson:2", "login": "x@y.ru", "_acl_fields": {"name": "deny"}})
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual([v["author"] for v in self.sent_versions()],
|
||
[{"login": None, "name": None}] * 3 + [{"login": "x@y.ru", "name": None}])
|
||
|
||
def test_versions_without_code_or_time_skipped(self):
|
||
self.tree2(); e = self.eva
|
||
e.add_version("CmfDocument:1", None)
|
||
e.add_version("CmfDocument:1", "DOCH-31", created_at=None)
|
||
e.add_version("CmfDocument:1", "DOCH-32")
|
||
buf = io.StringIO()
|
||
with redirect_stdout(buf):
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["result"]["versions"], 1)
|
||
self.assertEqual([v["code"] for v in self.sent_versions()], ["DOCH-32"])
|
||
self.assertIn("версия без code у DOC-1 — пропущена", buf.getvalue())
|
||
self.assertIn("версия DOCH-31 у DOC-1 без cmf_created_at — пропущена", buf.getvalue())
|
||
|
||
def test_has_published_strict(self):
|
||
self.tree2()
|
||
for i, flag in enumerate(("true", 1, None, False)):
|
||
self.eva.add_version("CmfDocument:1", "DOCH-%d" % i, has_published=flag)
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["result"]["versions"], 0)
|
||
self.assertEqual(self.history_calls("CmfDocumentHistory.get"), [])
|
||
|
||
def test_version_html_and_links(self):
|
||
self.tree2()
|
||
self.eva.add_version("CmfDocument:1", "DOCH-1", text='<a href="/project/Document/DOC-7">x</a><img src="/files/a/b.png" alt="Схема">')
|
||
self.eva.add_version("CmfDocument:1", "DOCH-2", text=None)
|
||
self.sync(known_versions={})
|
||
v1, v2 = self.sent_versions()
|
||
self.assertEqual(v1["links"], [{"ref": "/project/Document/DOC-7", "code": "DOC-7"}])
|
||
self.assertEqual(v1["attachments"], [{"ref": "/files/a/b.png", "url": self.eva.base_url + "/files/a/b.png", "name": "Схема"}])
|
||
self.assertEqual((v2["html"], v2["attachments"], v2["links"]), ("", [], []))
|
||
|
||
def test_versions_batches_50(self):
|
||
self.tree2()
|
||
for i in range(120):
|
||
self.eva.add_version("CmfDocument:1", "DOCH-%03d" % i)
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual([len(b["versions"]) for b in self.hub.versions], [50, 50, 20])
|
||
self.assertEqual(out["result"]["versions"], 120)
|
||
|
||
def test_versions_batch_bytes(self):
|
||
self.tree2()
|
||
big = "<p>" + "x" * (1500 * 1024) + "</p>"
|
||
for i in (1, 2, 3):
|
||
self.eva.add_version("CmfDocument:1", "DOCH-%d" % i, text=big)
|
||
self.sync(known_versions={})
|
||
sizes = [len(json.dumps(b, ensure_ascii=False).encode("utf-8")) for b in self.hub.versions]
|
||
self.assertTrue(all(s <= 4 * 1024 * 1024 for s in sizes), sizes)
|
||
self.assertEqual([len(b["versions"]) for b in self.hub.versions], [2, 1])
|
||
|
||
def test_versions_413(self):
|
||
for n, expect in ((1, "версия DOCH-11 больше 16 МБ"), (3, "пачка версий DOCH-11…DOCH-13 (3 шт.) больше 16 МБ")):
|
||
with self.subTest(n=n):
|
||
self.setUp(); self.tree2()
|
||
for i in range(n):
|
||
self.eva.add_version("CmfDocument:1", "DOCH-%d" % (11 + i))
|
||
self.hub.reject_versions = (413, {"error": "too_large", "detail": "тело больше 16777216 байт"})
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn(expect, out["error"])
|
||
self.assertFalse(out.get("retryable"))
|
||
|
||
def test_versions_hub_409(self):
|
||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-1")
|
||
self.hub.reject_versions = (409, {"error": "not_claimed", "detail": "команда 1 не выдана агенту как wiki_sync"})
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertIn("409", out["error"])
|
||
self.assertIn("не выдана агенту", out["error"])
|
||
|
||
def test_versions_eva_rpc_error(self):
|
||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-1")
|
||
self.eva.rpc_error_on("CmfDocumentHistory.list", "нет прав")
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertEqual(out["error"], "Eva: CmfDocumentHistory.list: нет прав")
|
||
self.assertEqual(len(self.hub.pages), 1) # страницы ушли, версии — нет
|
||
self.assertEqual(self.hub.versions, [])
|
||
|
||
def test_version_vanished(self):
|
||
self.tree2(); self.eva.add_version("CmfDocument:1", "DOCH-12")
|
||
self.client.get_version = lambda code: None
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertEqual(out["error"], "версия DOCH-12 страницы DOC-1 не найдена или нет прав")
|
||
|
||
def test_page_without_id(self):
|
||
self.eva.add_doc("CmfDocument:1", "DOC-1", "Корень", project="P")
|
||
self.eva.add_doc(None, "DOC-2", "Б", parent="DOC-1", project="P")
|
||
buf = io.StringIO()
|
||
with redirect_stdout(buf):
|
||
out = self.sync(known_versions={})
|
||
self.assertEqual(out["status"], "done", out)
|
||
self.assertEqual(len(self.history_calls("CmfDocumentHistory.list")), 1)
|
||
self.assertIn("у страницы DOC-2 нет id — версии не запрошены", buf.getvalue())
|
||
|
||
def test_known_versions_malformed(self):
|
||
for bad in (None, [], {"DOC-1": "DOCH-1"}, {"DOC-1": [1]}):
|
||
with self.subTest(bad=bad):
|
||
self.setUp(); self.tree2()
|
||
out = self.sync(known_versions=bad)
|
||
self.assertEqual(out["status"], "failed")
|
||
self.assertEqual(out["error"], "wiki_sync: known_versions — не объект {код страницы: [коды версий]}")
|
||
self.assertEqual(self.eva.api_calls, [])
|
||
self.assertEqual(self.hub.pages, [])
|
||
|
||
def test_versions_hub_503_retried_by_runner(self):
|
||
self.tree2()
|
||
self.eva.add_version("CmfDocument:1", "DOCH-1"); self.eva.add_version("CmfDocument:2", "DOCH-2")
|
||
self.hub.fail_versions = 1
|
||
cid = self.hub.enqueue("wiki_sync", {"label": "tz", "root": "DOC-1", "known_versions": {}})
|
||
tmp = tempfile.TemporaryDirectory()
|
||
self.addCleanup(tmp.cleanup)
|
||
runner = agent.Runner(self.cfg, self.ex.hub, self.client, agent.Journal(os.path.join(tmp.name, "journal.jsonl")))
|
||
runner.sleep = lambda s: None # и пауза RETRY_MIN_WAIT перед повтором (wait_seconds)
|
||
runner.idle_limit = 1
|
||
runner.loop()
|
||
self.assertEqual(self.hub.results[cid]["status"], "done", self.hub.results[cid])
|
||
self.assertEqual(self.hub.results[cid]["result"]["versions"], 2)
|
||
self.assertEqual(len(self.hub.pages), 2) # команда выполнена заново целиком
|
||
self.assertEqual([len(b["versions"]) for b in self.hub.versions], [2])
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|