Bugfix Issue 777 TestCase ProjectRoot ExternalID - sebiboga/testlink-upgraded GitHub Wiki
Bugfix β Issue #777: api/testcases create under project root β E_WARNING testproject.class.php:1045 + DB 1064 (empty external id)
Issue: #777
Branch: fix/issue-777 Β· Commits: aee98f47b (fix), 6eb98377a (regression suite)
Status: FIXED & VERIFIED (2026-08-30)
Symptom
Creating a test case directly under the test project root via the modern BFF
(POST /api/testcases/?action=create with parent_id = the test project root id)
returned HTTP 200 but with an HTML error body instead of JSON:
DB Access Error - debug_print_backtrace()
#0 .../lib/functions/testcase.class.php(783): database->exec_query()
#1 .../lib/functions/testcase.class.php(432): testcase->createVersion()
#2 .../api/testcases/index.php(902): testcase->create(1, ...)
The events table (Event Viewer) received several E_WARNINGs
(testcase.class.php:687, testproject.class.php:1045) and an
ERROR ON exec_query() 1064 "SQL syntax ... empty tc_external_id".
Orphaned nodes_hierarchy rows were left behind and no tcversions row was written.
Creating a test case under a testsuite parent worked fine (positive control).
Root cause chain
api/testcases/index.php:894createaction acceptsparent_id= the test project root id β$checkWrite($parentId)(line 809owningProjectOf) resolves that node to the project and only demands themgt_modify_tcright, so the request is not rejected.testcase::create()βcreate_tcase_only()(lib/functions/testcase.class.php:395).lib/functions/testcase.class.php:687:$path2root = $this->tree_manager->get_path($parent_id); $tproject_id = $path2root[0]['parent_id'];tree::get_path()(lib/functions/tree.class.php:408/462) returns the node chain reversed and excluding the root node (recursion stops whenparent_idis 0/NULL). Forparent_id= the project root,get_path()returns[].$tproject_id = null(E_WARNING"Undefined array key 0" / "Trying to access array offset on null" attestcase.class.php:687).testproject::generateTestCaseNumber(null)(testproject.class.php:1012βintval(null)=0) runsUPDATE ... SET tc_counter=tc_counter+1 WHERE id = 0(0 rows) thenSELECT tc_counter ... WHERE id=0(0 rows) β$rs[0]['tc_counter']attestproject.class.php:1045βE_WARNING, returns null.createVersion()(testcase.class.php:432/783) receives an emptytc_external_idβINSERT INTO tcversions (...,tc_external_id,...) VALUES(...,, ...)β SQL 1064.
Why it breaks (regression source): the _get_path intval() guard that stops recursion
at root nodes makes the root itself invisible in the returned path. Every other
get_path() consumer resolves from a child, where [0] is the node just below the root
and [0]['parent_id'] is the project id β so they always see a non-empty path. Only the
"parent IS the root" case yields an empty path.
Blast radius: get_path($parent_id) inside create_tcase_only() is the ONLY broken
consumer for root parents. create_tcase_only() has 2 call sites: testcase::create()
(testcase.class.php:395) and the copy/move path (testcase.class.php:2223). Both benefit;
neither passes a non-testproject root in normal operation.
Approach β resolve owning project when parent IS the test project root (minimal fix)
lib/functions/testcase.class.php create_tcase_only() (lines ~685-697): when
tree::get_path($parent_id) returns an empty/null path (parent is the tree root, i.e. a
test case created directly under the test project root), fall back to
$tproject_id = $parent_id; otherwise keep the original $path2root[0]['parent_id']
resolution (byte-for-byte unchanged for the testsuite/nested paths).
$path2root = $this->tree_manager->get_path($parent_id);
if( is_null($path2root) || count($path2root) == 0 )
{
$tproject_id = $parent_id;
}
else
{
$tproject_id = $path2root[0]['parent_id'];
}
Alternatives rejected:
- Reject the API request when
parent_idis the root: changes behaviour (top-level test cases are legal in legacy TestLink) and does not fix the sharedcreate_tcase_only()copy/move path. - Change
generateTestCaseNumber()to handle null: addresses the symptom at the wrong layer and would affect other callers.
Files changed
| File | Change |
|---|---|
lib/functions/testcase.class.php |
+12/-2 in create_tcase_only(): fall back to $parent_id when get_path() returns empty |
tmp/TLU_Test_Cases.md |
+Suite 777 (4 test cases, 4/4 PASS, re-executed on fresh DB) |
Verification
php -l lib/functions/testcase.class.phpβ no syntax errors.- Primary (fresh DB, admin browser session): created project
Repro Projectid=1, thenPOST /api/testcases/?action=create {"parent_id":1,...}β HTTP 200{"status":"ok","id":5,"message":"Test case created"};tcversionsrow written with non-emptytc_external_id=2(pre-fix: DB 1064 page, notcversionsrow). - Regression matrix:
- TC directly under project root β succeeds, correct external id β
- TC under a testsuite (
suite_createid=8 then create id=9) β HTTP 200, tcversiontc_external_id=3β - Nested SuiteβSuiteβTC (Sub Suite id=12, Nested TC id=13) β HTTP 200, tcversion
tc_external_id=4β - Event Viewer /
events: only 2 AUDIT (login + tproject created) rows, 0 ERROR / 0 WARNING after all creates β
