developer(ET): auto-commit from developer run_id=363
This commit is contained in:
158
tests/test_tracker_issue_link.py
Normal file
158
tests/test_tracker_issue_link.py
Normal file
@@ -0,0 +1,158 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user