sdlc-hub/tests/test_wiki_history.py
Andrey Epifancev 7b18ae53ad Вики: таблицы версий и приём POST /agent/wiki/versions (#140)
- schema: wiki_version, wiki_version_stage, wiki_history (без миграций, IF NOT EXISTS)
- history.utc: время версии в UTC; без смещения или не строка — None
- convert.to_markdown(file_links=False): вложение текстом «📎 имя», список файлов пуст
- stage.accept_versions + POST /agent/wiki/versions (agent wiki): проверка формы, перевод вне транзакции
- эталоны baseline (routes, schema) и документы: design.md (П-1…П-12), ARCHITECTURE.md

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-28 11:57:37 +04:00

132 lines
7.2 KiB
Python

"""История правок вики (wiki-history, #140): приём версий `POST /agent/wiki/versions`, время `history.utc`."""
import json
import pytest
from sdlc_hub.core.client import HubError
from sdlc_hub.modules.wiki import history
from test_wiki_sync import CODES, TREE, api, first_sync, page # noqa: F401 — api — фикстура
SOSNOV = {"login": "sosnov-dvi@rshbeco.ru", "name": "Дмитрий Соснов"}
MOLCH = {"login": "molchanov-iv@rshbeco.ru", "name": "Илья Молчанов"}
def version(page_code, code, created_at, html="<p>текст</p>", author=SOSNOV, attachments=None, links=None):
return {"page_code": page_code, "code": code, "created_at": created_at, "author": author, "html": html,
"attachments": attachments or [], "links": links or []}
def stage_rows(api, cmd_id):
return api.rows("SELECT code, page_code, payload FROM wiki_version_stage WHERE command_id = ? ORDER BY code", cmd_id)
def test_versions_staged(api): # перевод и форма payload
cmd = api.claim()
out = api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [
version("DOC-3", "DOCH-1", "2026-10-27T23:48:02+03:00", "<p>статус <b>новый</b></p>")]})
assert out == {"accepted": 1}
(row,) = stage_rows(api, cmd["id"])
p = json.loads(row["payload"])
assert p == {"page_code": "DOC-3", "code": "DOCH-1", "created_at": "2026-10-27T20:48:02+00:00",
"author_login": "sosnov-dvi@rshbeco.ru", "author_name": "Дмитрий Соснов", "markdown": "статус **новый**\n"}
def test_versions_resend_same_batch(api): # Review Focus 2: повтор пачки — одна строка на код
cmd = api.claim()
batch = [version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z"), version("DOC-3", "DOCH-2", "2026-10-27T11:00:00Z")]
for _ in range(2):
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": batch})
assert [r["code"] for r in stage_rows(api, cmd["id"])] == ["DOCH-1", "DOCH-2"]
@pytest.mark.parametrize("raw,stored", [("2026-10-27T23:48:02.123456+03:00", "2026-10-27T20:48:02+00:00"),
("2026-10-27T20:48:02Z", "2026-10-27T20:48:02+00:00"),
("2026-10-27T23:48:02+0300", "2026-10-27T20:48:02+00:00")])
def test_versions_created_at_forms(api, raw, stored): # Review Focus 4 → created_at в payload == stored
cmd = api.claim()
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [version("DOC-3", "DOCH-1", raw)]})
(row,) = stage_rows(api, cmd["id"])
assert json.loads(row["payload"])["created_at"] == stored
def test_versions_created_at_without_offset_422(api):
cmd = api.claim()
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [
version("DOC-3", "DOCH-1", "2026-10-27T23:48:02")]})
assert e.value.status == 422 and e.value.error == "bad_versions" and e.value.detail == "DOCH-1: created_at"
def test_versions_author_nulls(api):
cmd = api.claim()
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [
version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z", author={"login": None, "name": None})]})
(row,) = stage_rows(api, cmd["id"])
p = json.loads(row["payload"])
assert p["author_login"] is None and p["author_name"] is None
@pytest.mark.parametrize("field,value", [("page_code", ""), ("page_code", 5), ("html", None), ("author", "Соснов"),
("author", {"login": 1, "name": "x"}), ("attachments", [{"ref": 1}]),
("links", "x")])
def test_versions_bad_field(api, field, value): # 422 bad_versions, detail == f"DOCH-1: {field}"
cmd = api.claim()
bad = version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z") | {field: value}
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [bad]})
assert e.value.status == 422 and e.value.error == "bad_versions" and e.value.detail == f"DOCH-1: {field}"
assert api.rows("SELECT COUNT(*) AS n FROM wiki_version_stage")[0]["n"] == 0
def test_versions_bad_batch(api): # [] и 51 версия → 422 bad_versions; command_id "1" → 422 bad_request;
# версия-не объект → detail "версия 1: не объект"; без code → "версия 1: code"
cmd = api.claim()
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": []})
assert e.value.status == 422 and e.value.error == "bad_versions"
many = [version("DOC-3", f"DOCH-{i}", "2026-10-27T10:00:00Z") for i in range(51)]
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": many})
assert e.value.status == 422 and e.value.error == "bad_versions"
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": "1", "versions": [version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z")]})
assert e.value.status == 422 and e.value.error == "bad_request"
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": ["x"]})
assert e.value.status == 422 and e.value.detail == "версия 1: не объект"
no_code = {k: v for k, v in version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z").items() if k != "code"}
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [no_code]})
assert e.value.status == 422 and e.value.detail == "версия 1: code"
def test_versions_not_claimed(api): # команда queued → 409 not_claimed; несуществующая → 404
(row,) = api.rows("SELECT id FROM command WHERE type = 'wiki_sync'")
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": row["id"], "versions": [version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z")]})
assert e.value.status == 409 and e.value.error == "not_claimed"
with pytest.raises(HubError) as e:
api.wiki.post("/agent/wiki/versions", {"command_id": 999999, "versions": [version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z")]})
assert e.value.status == 404
def test_versions_wrong_agent(api): # токеном агента jira (api.jira) → 401
cmd = api.claim()
with pytest.raises(HubError) as e:
api.jira.post("/agent/wiki/versions", {"command_id": cmd["id"], "versions": [version("DOC-3", "DOCH-1", "2026-10-27T10:00:00Z")]})
assert e.value.status == 401
@pytest.mark.parametrize("raw,expected", [
("2026-10-27T23:48:02.123456+03:00", "2026-10-27T20:48:02+00:00"),
("2026-10-27T20:48:02Z", "2026-10-27T20:48:02+00:00"),
("2026-10-27T23:48:02+0300", "2026-10-27T20:48:02+00:00"),
("2026-10-27T23:48:02", None), # без смещения
("не дата", None),
(None, None),
(1234, None),
])
def test_history_utc(raw, expected):
assert history.utc(raw) == expected