Repository navigation
Bugfix Issue 659 AddTestCaseToTestPlan UndefExternalId
Issue: #659
Branch: fix/issue-659 · Commit: 98fac3e3b (fix, +2 lines)
Status: FIXED & VERIFIED (2026-08-24)
Calling tl.addTestCaseToTestPlan with a valid test case but a nonexistent
version produced a degraded error message and an E_WARNING row in the
Event Viewer on every occurrence:
(addTestCaseToTestPlan) - Version (99) does not exist for Test Case (:issue659_tc).
E_WARNING
Undefined variable $tcase_external_id - .../lib/api/xmlrpc/v1/xmlrpc.class.php - Line 3522
Measured during reproduction (fresh DB): fixtures created via the XMLRPC API
(project id=1 prefix I659, plan id=2, suite id=3, case id=4 = I659-1
version 1), then a single call with version: 99 → events id=2, log_level=2,
message above. The %s slot meant to carry the external id rendered as an
empty string, leaving a dangling colon before the case name.
-
addTestCaseToTestPlan()opens at xmlrpc.class.php:3462; its local init block (:3467-3473) never defines$tcase_external_id. - The version-existence block (:3513-3524) rejects unknown versions at :3520
and builds its message at :3522 with
sprintf(TCASE_VERSION_NUMBER_KO_STR, $version_number, $tcase_external_id, ...)— reading the variable that was never assigned anywhere in this method. - Locale format (
locale/en_GB/strings.txt:3207)"Version (%s) does not exist for Test Case (%s:%s)."→ PHP coerces the undefined variable to NULL → empty slot →(:name); under PHP 8.3 logging each occurrence also writes an E_WARNING toevents.
Whole-file grep confirmed $tcase_external_id = exists exactly once outside
this bug site — inside checkTestCaseAncestry() (:3824), a different method.
Legacy 1.9.20 code predating strict PHP; same family as #457's audit findings.
Exactly one faulty call site (:3522). No other screen or API method shares the line. Sibling patterns available:
-
checkTestCaseAncestry():3823-3824 — derives the id via$this->tcaseMgr->getExternalID($tcase_id)✔ established pattern; - :5904 uses
$this->args[self::$testCaseExternalIDParamName]✘ not safe here: callers may passtestcaseidinstead of the external id.
Chosen fix (+2 lines inserted after old :3521; they land as post-fix lines :3522-3523, immediately before the sprintf):
$dummy = $this->tcaseMgr->getExternalID( $tcase_id, $tproject_id );
$tcase_external_id = isset( $dummy[0] ) ? $dummy[0] : '';Why this way:
- Mirrors the in-file sibling pattern (
checkTestCaseAncestry()), so the code stays consistent with how the same variable is computed elsewhere. - Passes the already-in-scope
$tproject_id(assigned at :3486 before this block, and validated bycheckTestCaseAncestry()just above): supplying it letsgetExternalID()'s static prefix/root cache (lib/functions/testcase.class.php:5699-5707) resolve without the redundant tree-walk query thatgetPrefix($id, null)performs when no project is given, and keeps same-project repeat calls on correct cache hits. - The
issetguard coversgetExternalID()returning[]when the case has no last version info (testcase.class.php:5714-5715).
Rejected alternatives:
- Using
$this->args[self::$testCaseExternalIDParamName]like :5904 — breaks fortestcaseidcallers; - Suppressing warnings globally — hides future defects;
- Refactoring the check block — larger diff with zero behavioral gain.
| # | Check | Result |
|---|---|---|
| R1 | version=99 post-fix | fault 5051, message (I659-1:issue659_tc), no new E_WARNING |
| R2 | version=1 valid link | success; testplan_tcversions gains the link row |
| R3 | cross-project case O659-1 vs plan of project 1 | ancestry rejection unchanged (7007, correct ids) |
| — | Event Viewer watermark across all runs | zero new log_level=2 rows |
| — | php -l xmlrpc.class.php |
clean |
Regression suite: tmp/TLU_Test_Cases.md → "Suite 659 — 6/6 PASS".