eva-agent/tests/test_wiki_sync.py
Andrey Epifancev b98b963ae1 wiki_sync: опубликованные версии страниц хабу, result.versions (#7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 11:55:06 +04:00

478 lines
26 KiB
Python
Raw 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.

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()