Repository navigation
Modernize Test Case Tree Navigator tcProjectTree
Issue: #1770
Screen: gui/templates/testcases/tcProjectTree.html
BFF: api/tcprojecttree/index.php
Legacy source: lib/ajax/gettprojectnodes.php (now a session-guarded 302/405 shim)
Changelog: CHANGELOG (section "Screen — Test Case Tree Navigator tcProjectTree")
In 1.9.20 the "Test Case Tree" was the LEFT frame of several work areas: "add/remove test cases" (planAddTC_m1.tpl), "update test plan TC assignments" (planUpdateTC.tpl), "test urgency" (planUrgency.tpl) and "execution assignment" (tc_exec_assignment.tpl). The frame was an ExtJS tree populated lazily by lib/ajax/gettprojectnodes.php with parameters root_node, node, filter_node, show_tcases.
The legacy loader had no authorization:
- only
testlinkInitPage()(session check) ran - no project-scope check and no
mgt_view_tc/mgt_modify_tcright check -
display_children()filtered onparent_idalone, so the walk could be steered across projects - the returned labels were written into ExtJS HTML nodes with only basic escaping
This is the same class of exposure as #1696 (getrequirementnodes.php) and #1765 (getreqcoveragenodes.php).
Standalone Dashio page (gui/templates/testcases/tcProjectTree.html) backed by a read-only BFF. Key behaviors:
-
Lazy tree: top-level suites loaded from
init, nested suites and cases fromchildrenon demand (twisty + spinner) -
Readable-project switcher: the legacy frame used the session project and had none. The modern screen fetches
projectsviaaction=projectsand lets the caller switch to any project for which they have read rights -
State preserved in the URL:
tproject_id,show_tcases(0|1),filter_node. A reload restores the exact state -
Live counters: recursive "suites / test cases" chip, and per-suite counts (
tcase_qty) -
External IDs:
tc_external_idprefix appears whentreemenu_show_testcase_idis on (1.9.20 parity) - Gestures: Expand all / Collapse all / Hide/Show test cases / Refresh, suite focus filter, toolbar links to Test specification / Search / Project information, Close
-
Access control: the BFF checks
mgt_view_tcormgt_modify_tcon the addressed project before resolving it (403 no_rightfor real and bogus project ids alike). Every requested node is proven to live under that project root - Security: names are returned as JSON data and escaped on render by the screen (no inline HTML injection from the BFF). The legacy shim never replays the unauthorized read
- Deep links: project info, test specification (container), test case viewer
BFF actions:
-
GET ?action=projects— readable test projects for the switcher -
GET ?action=init&tproject_id=<N>[&filter_node=<M>][&show_tcases=0|1]— context + project node + top-level children -
GET ?action=children&tproject_id=<N>&node_id=<M>[&filter_node=<K>][&show_tcases=0|1]— children of a project/suite -
GET ?action=filter&tproject_id=<N>&node_id=<M>— validate a filter_node (suite of that project)
Legacy endpoint lib/ajax/gettprojectnodes.php is now a non-mutating shim: GET/HEAD → 302 to the modern screen (preserves root_node→tproject_id, filter_node, show_tcases), POST/other verbs → 405.
-
#1771 —
tc_external_idresolved through the wrong node level (version nodes hang off test cases). The BFF joins suite→case→version and pinsMAX(version node id). - #1772 — Hide/Show and suite filter were not written to the URL, so reload dropped them.
- #1773 — "Expand all" stopped at the first level; sticky mode + recursive walk fixes it.
-
#1774 — exclusion list mistyped 8 instead of 7, so requirement nodes leaked; replaced by an allow-list (
testsuite,testcase) so hidden node types never leak.
-
python3 tmp/suite_1770.py— 128 PASS, stdlib only - Browser pass (admin): expand/collapse/show-hide/filter/project switcher, every twisty, all open links, all state cards, no console errors/warnings
- Rights pass (
treenorights):projectsempty, init/children/filter → 403 - Event Viewer: no new Error/Warning rows

Fully expanded TREE1: 5 suites / 4 test cases, live counts, external-id column
(1: and 12345:), and the suite named <script>window.__xss=1;</script>Y
rendered as inert text.

The FOCUS A TEST SUITE gesture: the tree narrows to Tree Root, the info line
reads "Showing only Tree Root (3)" and the URL carries filter_node=64, so the
state survives a reload (#1772).

A user with no test case right: the denied card, the context card hidden, the project switcher disabled and every toolbar action disabled — and the SAME answer for a non-existent project, because the right is checked before the id resolves.