142 lines
6.9 KiB
Python
142 lines
6.9 KiB
Python
import json
|
||
import os
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from sdlc_hub.core import api, cli, paths, release, settings
|
||
|
||
FIXTURE = Path(__file__).parent / "fixtures" / "config.example.json"
|
||
|
||
|
||
def test_init_import_tokens(tmp_path, monkeypatch, capsys, open_db):
|
||
monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "home"))
|
||
assert cli.main(["init", "--config", str(FIXTURE)]) == 0
|
||
out = capsys.readouterr().out
|
||
assert "link_types" in out and "transitions_map" not in out and "агент jira: " in out
|
||
assert cli.main(["init", "--config", str(FIXTURE)]) == 1
|
||
assert "уже инициализирован" in capsys.readouterr().err
|
||
conn = open_db(paths.db_path())
|
||
assert [(x["label"], x["name"], x["board_id"]) for x in api.project_rows(conn)] == [("AWPCA", "АРМ КА", 4216)]
|
||
assert settings.get(conn, "board_id") is None
|
||
assert "проект AWPCA «АРМ КА», доска 4216" in out
|
||
assert cli.main(["init", "--config", str(FIXTURE), "--force"]) == 0
|
||
capsys.readouterr()
|
||
assert [(x["label"], x["board_id"]) for x in api.project_rows(conn)] == [("AWPCA", 4216)]
|
||
(tmp_path / "pkg").mkdir()
|
||
(tmp_path / "pkg" / "tasks.csv").write_text("Код,Статус по факту,Вид работы\nAURORA-1,В работе,UI\n", encoding="utf-8")
|
||
assert cli.main(["import", str(tmp_path / "pkg")]) == 0
|
||
rep = json.loads(capsys.readouterr().out)
|
||
assert rep["notes"] == 1 and rep["missing_keys"] == ["AURORA-1"]
|
||
assert cli.main(["tokens"]) == 0
|
||
assert settings.tokens(conn)["client"] in capsys.readouterr().out
|
||
|
||
|
||
def test_agent_publish(tmp_path, monkeypatch, capsys):
|
||
monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "home"))
|
||
src = tmp_path / "agent.py"
|
||
src.write_text("AGENT_VERSION = 6\n", encoding="utf-8")
|
||
assert cli.main(["agent", "publish", str(src)]) == 0
|
||
out = capsys.readouterr().out
|
||
assert out.startswith("опубликована v6, sha256 ") and "…, 18 байт" in out
|
||
assert (release.module_dir(paths.release_root(), "jira") / "manifest.json").exists()
|
||
assert cli.main(["agent", "publish", str(src)]) == 1
|
||
assert "опубликована v6, новая должна быть больше" in capsys.readouterr().err
|
||
|
||
|
||
def test_agent_publish_write_failure(tmp_path, monkeypatch, capsys):
|
||
"""Диск полон при записи манифеста: сообщение и код 1 без трейсбека, опубликованной остаётся v8 (M-1 #25)."""
|
||
monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "home"))
|
||
src = tmp_path / "agent.py"
|
||
src.write_text("AGENT_VERSION = 8\n", encoding="utf-8")
|
||
assert cli.main(["agent", "publish", str(src)]) == 0
|
||
capsys.readouterr()
|
||
real = os.replace
|
||
|
||
def fake(s, d, *a, **kw):
|
||
if Path(d).name == "manifest.json":
|
||
raise OSError(28, "No space left on device")
|
||
return real(s, d, *a, **kw)
|
||
|
||
monkeypatch.setattr(os, "replace", fake)
|
||
src.write_text("AGENT_VERSION = 9\n", encoding="utf-8")
|
||
assert cli.main(["agent", "publish", str(src)]) == 1
|
||
err = capsys.readouterr().err
|
||
assert err.startswith("не удалось записать публикацию: ") and "Traceback" not in err
|
||
monkeypatch.setattr(os, "replace", real)
|
||
assert release.load(release.module_dir(paths.release_root(), "jira"))["version"] == 8
|
||
assert sorted(p.name for p in release.module_dir(paths.release_root(), "jira").iterdir()) == ["agent.py", "manifest.json"]
|
||
|
||
|
||
def test_mcp_module_required(monkeypatch, capsys):
|
||
from sdlc_hub.core import mcp as core_mcp
|
||
from sdlc_hub.modules.jira import mcp as jira_mcp
|
||
seen = []
|
||
monkeypatch.setattr(core_mcp, "PROJECT", None) # run() пишет глобал; monkeypatch вернёт прежнее значение
|
||
monkeypatch.setattr(jira_mcp.mcp, "run", lambda transport="stdio": seen.append(core_mcp.PROJECT))
|
||
assert cli.main(["mcp", "--project", "AWPCA"]) == 2
|
||
err = capsys.readouterr().err
|
||
assert "укажите модуль: sdlc-hub mcp <модуль>" in err and "jira, wiki" in err
|
||
assert seen == []
|
||
assert cli.main(["mcp", "jira", "--project", "AWPCA"]) == 0
|
||
assert seen == ["AWPCA"]
|
||
monkeypatch.setattr(core_mcp, "PROJECT", "STALE")
|
||
assert cli.main(["mcp", "jira"]) == 0
|
||
assert seen[-1] is None
|
||
|
||
|
||
def test_mcp_unknown_module(monkeypatch, capsys):
|
||
from sdlc_hub.modules.jira import mcp as jira_mcp
|
||
seen = []
|
||
monkeypatch.setattr(jira_mcp.mcp, "run", lambda transport="stdio": seen.append(transport))
|
||
assert cli.main(["mcp", "nope"]) == 2
|
||
err = capsys.readouterr().err
|
||
assert "неизвестный модуль nope" in err and "jira" in err
|
||
assert seen == []
|
||
|
||
|
||
def test_agent_publish_migrates_old_layout(tmp_path, monkeypatch, capsys):
|
||
"""M-3: корневые agent.py/manifest.json прежней раскладки переезжают в release/jira/ до проверки «только вверх»."""
|
||
monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path))
|
||
rel = tmp_path / "release"
|
||
rel.mkdir()
|
||
old = tmp_path / "old.py"
|
||
old.write_text("AGENT_VERSION = 14\n", encoding="utf-8")
|
||
release.publish(rel, old) # прежняя раскладка: release/agent.py + manifest.json
|
||
new = tmp_path / "new.py"
|
||
new.write_text("AGENT_VERSION = 13\n", encoding="utf-8")
|
||
assert cli.main(["agent", "publish", str(new)]) == 1 # v13 < v14: «только вверх» видит прежнюю публикацию
|
||
assert not (rel / "agent.py").exists() and not (rel / "manifest.json").exists()
|
||
assert json.loads((rel / "jira" / "manifest.json").read_text(encoding="utf-8"))["version"] == 14
|
||
|
||
|
||
def _homes(monkeypatch, tmp_path, legacy=True, new=False):
|
||
monkeypatch.delenv("SDLC_HUB_HOME", raising=False)
|
||
monkeypatch.setenv("HOME", str(tmp_path))
|
||
share = tmp_path / ".local" / "share"
|
||
if legacy: (share / "jira-hub").mkdir(parents=True)
|
||
if new: (share / "sdlc-hub").mkdir(parents=True)
|
||
return share
|
||
|
||
|
||
@pytest.mark.parametrize("argv", [["serve"], ["tokens"], ["mcp", "jira"], ["agent", "publish", "x.py"]])
|
||
def test_legacy_home_refused(monkeypatch, tmp_path, capsys, argv):
|
||
share = _homes(monkeypatch, tmp_path)
|
||
assert cli.main(argv) == 2
|
||
err = capsys.readouterr().err
|
||
assert "~/.local/share/jira-hub" in err and "scripts/cutover.sh" in err
|
||
assert not (share / "sdlc-hub").exists()
|
||
assert sorted(p.name for p in (share / "jira-hub").iterdir()) == []
|
||
|
||
|
||
def test_both_homes_warn(monkeypatch, tmp_path, capsys):
|
||
_homes(monkeypatch, tmp_path, new=True)
|
||
cli.main(["tokens"])
|
||
assert "прежний каталог ~/.local/share/jira-hub можно удалить" in capsys.readouterr().err
|
||
|
||
|
||
def test_env_home_skips_legacy_check(monkeypatch, tmp_path, capsys):
|
||
_homes(monkeypatch, tmp_path)
|
||
monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "x"))
|
||
assert cli.main(["tokens"]) == 0
|