fix: финальное ревью — M-1/T3.1 атомарная подмена agent_update, T1.1/T1.2 open_db, T2.3, T4.1 (#32)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-15 10:56:51 +04:00
parent 1f1dad6911
commit 7d3f648d2c
5 changed files with 28 additions and 12 deletions

View File

@ -77,15 +77,17 @@ class Hub:
def note_agent_update(self, body):
"""update_state и launcher_version из claim: хранятся в памяти, агент шлёт их в каждом claim (R-3).
Значение неверного типа игнорируется — claim из-за него не отвергается; сам claim отмечается (reported)."""
self.agent_update["reported"] = True
new = dict(self.agent_update)
new["reported"] = True
us = body.get("update_state")
if us is None or isinstance(us, dict):
if us is not None and isinstance(us.get("error"), str):
us = {**us, "error": us["error"][:500]}
self.agent_update["update_state"] = us
new["update_state"] = us
lv = body.get("launcher_version")
if lv is None or (isinstance(lv, int) and not isinstance(lv, bool)):
self.agent_update["launcher_version"] = lv
new["launcher_version"] = lv
self.agent_update = new
def make_backup(self, label):
conn = self.conn()

View File

@ -2,19 +2,19 @@ import json
import os
from pathlib import Path
from jira_hub import cli, db, paths, release, settings
from jira_hub import cli, paths, release, settings
FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json"
def test_init_import_tokens(tmp_path, monkeypatch, capsys):
def test_init_import_tokens(tmp_path, monkeypatch, capsys, open_db):
monkeypatch.setenv("JIRA_HUB_HOME", str(tmp_path / "home"))
assert cli.main(["init", "--config", str(FIXTURE)]) == 0
out = capsys.readouterr().out
assert "transitions_map" in out and "агентский токен" in out
assert cli.main(["init", "--config", str(FIXTURE)]) == 1
assert "уже инициализирован" in capsys.readouterr().err
conn = db.connect(paths.db_path())
conn = open_db(paths.db_path())
assert settings.get(conn, "board_id") == 4216
(tmp_path / "pkg").mkdir()
(tmp_path / "pkg" / "tasks.csv").write_text("Код,Статус по факту,Вид работы\nAURORA-1,В работе,UI\n", encoding="utf-8")
@ -23,7 +23,6 @@ def test_init_import_tokens(tmp_path, monkeypatch, capsys):
assert rep["notes"] == 1 and rep["missing_keys"] == ["AURORA-1"]
assert cli.main(["tokens"]) == 0
assert settings.tokens(conn)["client"] in capsys.readouterr().out
conn.close()
def test_agent_publish(tmp_path, monkeypatch, capsys):

View File

@ -162,6 +162,14 @@ def test_ensure_fields_text_compare_ignores_crlf(conn, mirror_value, param, expe
assert ok is expected
def test_ensure_fields_summary_difference_is_detected(conn):
"""T4.1 #32: CRLF в summary игнорируется, но реальное расхождение текста — False."""
conn.execute("UPDATE issue SET summary = ? WHERE key = 'A-1'", ("x\r\ny",))
ok, _ = commands.check_confirmed(conn, {"type": "ensure_fields",
"params": {"key": "A-1", "summary": "x\nz"}}, LT)
assert ok is False
@pytest.mark.parametrize("ctype, name, value, msg", [
("ensure_fields", "duedate", True, "duedate — строка"),
("ensure_fields", "labels_add", "AWPCA", "labels_add — список строк"),

View File

@ -186,6 +186,8 @@ def test_restore_failure_is_logged_not_raised(tmp_path, monkeypatch, caplog):
"""Review Focus 2: и возврат agent.py.prev не удался — PublishError с текстом, причина возврата в логе."""
rd = tmp_path / "release"
release.publish(rd, agent_file(tmp_path, 8))
before = _listing(rd)
new_src = agent_file(tmp_path, 9)
_fail_replace_into(monkeypatch, "manifest.json")
real = release.os.replace
@ -196,8 +198,14 @@ def test_restore_failure_is_logged_not_raised(tmp_path, monkeypatch, caplog):
monkeypatch.setattr(release.os, "replace", fake)
with pytest.raises(release.PublishError, match="не удалось записать публикацию"):
release.publish(rd, agent_file(tmp_path, 9))
release.publish(rd, new_src)
assert "прежняя публикация не восстановлена" in caplog.text
monkeypatch.undo()
after = _listing(rd) # задокументированное состояние: новый agent.py, старый agent.py.prev, старый манифест
assert sorted(after) == ["agent.py", "agent.py.prev", "manifest.json"]
assert after["agent.py"] == new_src.read_bytes()
assert after["agent.py.prev"] == before["agent.py"]
assert after["manifest.json"] == before["manifest.json"]
@pytest.mark.parametrize("manifest", [

View File

@ -3,7 +3,7 @@ from pathlib import Path
import pytest
from jira_hub import db, settings
from jira_hub import settings
FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json"
@ -53,8 +53,8 @@ def test_init_from_config(conn):
settings.init_from_config(conn, cfg, force=True)
def test_relay_dir_default_and_validation(tmp_path):
conn = db.connect(tmp_path / "hub.sqlite")
def test_relay_dir_default_and_validation(tmp_path, open_db):
conn = open_db(tmp_path / "hub.sqlite")
settings.ensure_defaults(conn)
assert settings.get(conn, "relay_dir") is None
settings.validate("relay_dir", None)
@ -63,4 +63,3 @@ def test_relay_dir_default_and_validation(tmp_path):
with pytest.raises(ValueError):
settings.validate("relay_dir", bad)
assert "relay_dir" not in settings.AGENT_KEYS
conn.close()