159 lines
6.5 KiB
Python
159 lines
6.5 KiB
Python
"""ORCH-067 — Group C: clickable issue number in the live card (AC-10/AC-11/AC-14).
|
|
|
|
The issue number in the card header is now a Plane hyperlink
|
|
(``<a href=".../issues/<id>/">ORCH-NNN</a>``) when a usable browser URL can be
|
|
built, and degrades fail-safe to the html-escaped raw number when any piece is
|
|
missing (web base / non-loopback / workspace / project_id / plane_issue_id). The
|
|
card must NEVER break under parse_mode=HTML: a title with '<'/'&'/'>' stays
|
|
escaped while the <a> markup stays valid. Network is isolated (no HTTP from the
|
|
render path here); the DB is a temp SQLite.
|
|
|
|
Test ids TC-10, TC-11, TC-16 from 04-test-plan.yaml.
|
|
"""
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")
|
|
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
|
|
|
|
_test_db = os.path.join(tempfile.gettempdir(), "test_orchestrator_card_link.db")
|
|
os.environ["ORCH_DB_PATH"] = _test_db
|
|
|
|
from types import SimpleNamespace # noqa: E402
|
|
|
|
import pytest # noqa: E402
|
|
|
|
import src.db as db_module # noqa: E402
|
|
import src.projects as projects_mod # noqa: E402
|
|
from src.db import init_db, get_db # noqa: E402
|
|
from src import notifications as N # noqa: E402
|
|
|
|
# orchestrator repo -> default project registry uuid (src/projects.py).
|
|
_ORCH_PROJECT_ID = "8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a"
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_db(monkeypatch):
|
|
monkeypatch.setattr(db_module.settings, "db_path", _test_db, raising=False)
|
|
if os.path.exists(_test_db):
|
|
os.unlink(_test_db)
|
|
init_db()
|
|
# Keep the render path fully offline (no live overlay HTTP).
|
|
monkeypatch.setattr(N._get_settings(), "tracker_live_status", False,
|
|
raising=False)
|
|
# Pin the repo->project resolution so cross-file tests that reload the
|
|
# ORCH_PROJECTS_JSON registry can't strip 'orchestrator' out from under us.
|
|
monkeypatch.setattr(
|
|
projects_mod, "get_project_by_repo",
|
|
lambda repo: (SimpleNamespace(plane_project_id=_ORCH_PROJECT_ID)
|
|
if repo == "orchestrator" else None),
|
|
)
|
|
yield
|
|
if os.path.exists(_test_db):
|
|
os.unlink(_test_db)
|
|
|
|
|
|
def _set(monkeypatch, **kw):
|
|
s = N._get_settings()
|
|
for k, v in kw.items():
|
|
monkeypatch.setattr(s, k, v, raising=False)
|
|
|
|
|
|
def _mk_task(wid="ORCH-067", repo="orchestrator", title="card link",
|
|
plane_issue_id="issue-uuid-1", stage="development"):
|
|
conn = get_db()
|
|
cur = conn.execute(
|
|
"INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage, title, "
|
|
"plane_issue_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
("p1", wid, repo, "feature/ORCH-067-x", stage, title, plane_issue_id),
|
|
)
|
|
tid = cur.lastrowid
|
|
conn.commit()
|
|
conn.close()
|
|
return tid
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-10 / AC-10 — full data -> clickable <a> wrapping the issue number
|
|
# --------------------------------------------------------------------------- #
|
|
def test_tc10_card_number_is_clickable(monkeypatch):
|
|
_set(monkeypatch, plane_web_url="https://plane.example.org",
|
|
plane_api_url="http://localhost:8091", plane_workspace_slug="acme")
|
|
tid = _mk_task(plane_issue_id="abcd-issue-uuid")
|
|
|
|
text = N.render_task_tracker(tid)
|
|
expected_url = (
|
|
f"https://plane.example.org/acme/projects/{_ORCH_PROJECT_ID}"
|
|
f"/issues/abcd-issue-uuid/"
|
|
)
|
|
assert f'<a href="{expected_url}">ORCH-067</a>' in text
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-11 / AC-11 — fail-safe: any missing piece -> escaped number, no <a>, no crash
|
|
# --------------------------------------------------------------------------- #
|
|
@pytest.mark.parametrize("override,reason", [
|
|
({"plane_web_url": "", "plane_api_url": ""}, "no web base"),
|
|
({"plane_web_url": "http://localhost:8091", "plane_api_url": ""}, "loopback base"),
|
|
({"plane_workspace_slug": ""}, "no workspace"),
|
|
])
|
|
def test_tc11_card_number_degrades_settings(monkeypatch, override, reason):
|
|
_set(monkeypatch, plane_web_url="https://plane.example.org",
|
|
plane_api_url="http://localhost:8091", plane_workspace_slug="acme")
|
|
_set(monkeypatch, **override)
|
|
tid = _mk_task(plane_issue_id="abcd-issue-uuid")
|
|
|
|
text = N.render_task_tracker(tid)
|
|
assert "ORCH-067" in text # raw number still shown
|
|
assert "<a href=" not in text, reason # but NOT a link
|
|
assert "localhost" not in text # never leak a loopback URL
|
|
|
|
|
|
def test_tc11_card_number_degrades_no_issue_id(monkeypatch):
|
|
# Missing plane_issue_id -> the number is shown unlinked, render survives.
|
|
_set(monkeypatch, plane_web_url="https://plane.example.org",
|
|
plane_workspace_slug="acme")
|
|
tid = _mk_task(plane_issue_id=None)
|
|
text = N.render_task_tracker(tid)
|
|
assert "ORCH-067" in text
|
|
assert "<a href=" not in text
|
|
|
|
|
|
def test_tc11_card_number_degrades_unknown_repo(monkeypatch):
|
|
# repo not in the registry -> no project_id -> number unlinked, no crash.
|
|
_set(monkeypatch, plane_web_url="https://plane.example.org",
|
|
plane_workspace_slug="acme")
|
|
tid = _mk_task(repo="not-a-real-repo", plane_issue_id="abcd-issue-uuid")
|
|
text = N.render_task_tracker(tid)
|
|
assert "ORCH-067" in text
|
|
assert "<a href=" not in text
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# TC-16 / AC-14 — HTML escaping: title with '<b>'/'&'/'>' stays safe + valid <a>
|
|
# --------------------------------------------------------------------------- #
|
|
def test_tc16_title_escaped_link_valid(monkeypatch):
|
|
_set(monkeypatch, plane_web_url="https://plane.example.org",
|
|
plane_workspace_slug="acme")
|
|
tid = _mk_task(title="<b>drop & </b> table >", plane_issue_id="iss-1")
|
|
|
|
text = N.render_task_tracker(tid)
|
|
# Raw title markup is escaped -> cannot break parse_mode=HTML.
|
|
assert "<b>" not in text
|
|
assert "<b>" in text
|
|
assert "&" in text
|
|
# The card's own anchor markup stays well-formed (balanced tags).
|
|
assert text.count("<a href=") == text.count("</a>")
|
|
assert text.count("<a href=") >= 1 # the clickable number is present
|
|
|
|
|
|
def test_tc16_ampersand_in_work_item_id_escaped(monkeypatch):
|
|
# A '&' in the work_item_id is escaped in the (unlinked) fail-safe path too.
|
|
_set(monkeypatch, plane_web_url="", plane_api_url="",
|
|
plane_workspace_slug="acme")
|
|
tid = _mk_task(wid="ORCH&67", plane_issue_id="iss-1")
|
|
text = N.render_task_tracker(tid)
|
|
assert "ORCH&67" in text
|
|
assert "<a href=" not in text # no link (no web base)
|