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:
parent
1f1dad6911
commit
7d3f648d2c
@ -77,15 +77,17 @@ class Hub:
|
|||||||
def note_agent_update(self, body):
|
def note_agent_update(self, body):
|
||||||
"""update_state и launcher_version из claim: хранятся в памяти, агент шлёт их в каждом claim (R-3).
|
"""update_state и launcher_version из claim: хранятся в памяти, агент шлёт их в каждом claim (R-3).
|
||||||
Значение неверного типа игнорируется — claim из-за него не отвергается; сам claim отмечается (reported)."""
|
Значение неверного типа игнорируется — claim из-за него не отвергается; сам claim отмечается (reported)."""
|
||||||
self.agent_update["reported"] = True
|
new = dict(self.agent_update)
|
||||||
|
new["reported"] = True
|
||||||
us = body.get("update_state")
|
us = body.get("update_state")
|
||||||
if us is None or isinstance(us, dict):
|
if us is None or isinstance(us, dict):
|
||||||
if us is not None and isinstance(us.get("error"), str):
|
if us is not None and isinstance(us.get("error"), str):
|
||||||
us = {**us, "error": us["error"][:500]}
|
us = {**us, "error": us["error"][:500]}
|
||||||
self.agent_update["update_state"] = us
|
new["update_state"] = us
|
||||||
lv = body.get("launcher_version")
|
lv = body.get("launcher_version")
|
||||||
if lv is None or (isinstance(lv, int) and not isinstance(lv, bool)):
|
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):
|
def make_backup(self, label):
|
||||||
conn = self.conn()
|
conn = self.conn()
|
||||||
|
|||||||
@ -2,19 +2,19 @@ import json
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
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"
|
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"))
|
monkeypatch.setenv("JIRA_HUB_HOME", str(tmp_path / "home"))
|
||||||
assert cli.main(["init", "--config", str(FIXTURE)]) == 0
|
assert cli.main(["init", "--config", str(FIXTURE)]) == 0
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "transitions_map" in out and "агентский токен" in out
|
assert "transitions_map" in out and "агентский токен" in out
|
||||||
assert cli.main(["init", "--config", str(FIXTURE)]) == 1
|
assert cli.main(["init", "--config", str(FIXTURE)]) == 1
|
||||||
assert "уже инициализирован" in capsys.readouterr().err
|
assert "уже инициализирован" in capsys.readouterr().err
|
||||||
conn = db.connect(paths.db_path())
|
conn = open_db(paths.db_path())
|
||||||
assert settings.get(conn, "board_id") == 4216
|
assert settings.get(conn, "board_id") == 4216
|
||||||
(tmp_path / "pkg").mkdir()
|
(tmp_path / "pkg").mkdir()
|
||||||
(tmp_path / "pkg" / "tasks.csv").write_text("Код,Статус по факту,Вид работы\nAURORA-1,В работе,UI\n", encoding="utf-8")
|
(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 rep["notes"] == 1 and rep["missing_keys"] == ["AURORA-1"]
|
||||||
assert cli.main(["tokens"]) == 0
|
assert cli.main(["tokens"]) == 0
|
||||||
assert settings.tokens(conn)["client"] in capsys.readouterr().out
|
assert settings.tokens(conn)["client"] in capsys.readouterr().out
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def test_agent_publish(tmp_path, monkeypatch, capsys):
|
def test_agent_publish(tmp_path, monkeypatch, capsys):
|
||||||
|
|||||||
@ -162,6 +162,14 @@ def test_ensure_fields_text_compare_ignores_crlf(conn, mirror_value, param, expe
|
|||||||
assert ok is expected
|
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", [
|
@pytest.mark.parametrize("ctype, name, value, msg", [
|
||||||
("ensure_fields", "duedate", True, "duedate — строка"),
|
("ensure_fields", "duedate", True, "duedate — строка"),
|
||||||
("ensure_fields", "labels_add", "AWPCA", "labels_add — список строк"),
|
("ensure_fields", "labels_add", "AWPCA", "labels_add — список строк"),
|
||||||
|
|||||||
@ -186,6 +186,8 @@ def test_restore_failure_is_logged_not_raised(tmp_path, monkeypatch, caplog):
|
|||||||
"""Review Focus 2: и возврат agent.py.prev не удался — PublishError с текстом, причина возврата в логе."""
|
"""Review Focus 2: и возврат agent.py.prev не удался — PublishError с текстом, причина возврата в логе."""
|
||||||
rd = tmp_path / "release"
|
rd = tmp_path / "release"
|
||||||
release.publish(rd, agent_file(tmp_path, 8))
|
release.publish(rd, agent_file(tmp_path, 8))
|
||||||
|
before = _listing(rd)
|
||||||
|
new_src = agent_file(tmp_path, 9)
|
||||||
_fail_replace_into(monkeypatch, "manifest.json")
|
_fail_replace_into(monkeypatch, "manifest.json")
|
||||||
real = release.os.replace
|
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)
|
monkeypatch.setattr(release.os, "replace", fake)
|
||||||
with pytest.raises(release.PublishError, match="не удалось записать публикацию"):
|
with pytest.raises(release.PublishError, match="не удалось записать публикацию"):
|
||||||
release.publish(rd, agent_file(tmp_path, 9))
|
release.publish(rd, new_src)
|
||||||
assert "прежняя публикация не восстановлена" in caplog.text
|
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", [
|
@pytest.mark.parametrize("manifest", [
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jira_hub import db, settings
|
from jira_hub import settings
|
||||||
|
|
||||||
FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json"
|
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)
|
settings.init_from_config(conn, cfg, force=True)
|
||||||
|
|
||||||
|
|
||||||
def test_relay_dir_default_and_validation(tmp_path):
|
def test_relay_dir_default_and_validation(tmp_path, open_db):
|
||||||
conn = db.connect(tmp_path / "hub.sqlite")
|
conn = open_db(tmp_path / "hub.sqlite")
|
||||||
settings.ensure_defaults(conn)
|
settings.ensure_defaults(conn)
|
||||||
assert settings.get(conn, "relay_dir") is None
|
assert settings.get(conn, "relay_dir") is None
|
||||||
settings.validate("relay_dir", None)
|
settings.validate("relay_dir", None)
|
||||||
@ -63,4 +63,3 @@ def test_relay_dir_default_and_validation(tmp_path):
|
|||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
settings.validate("relay_dir", bad)
|
settings.validate("relay_dir", bad)
|
||||||
assert "relay_dir" not in settings.AGENT_KEYS
|
assert "relay_dir" not in settings.AGENT_KEYS
|
||||||
conn.close()
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user