Bugfix Issue 1512 TestcasePlatforms Orphans On DeleteVersion - sebiboga/testlink-upgraded GitHub Wiki
Deleting a single test case version (modern BFF POST /api/testcases/?action=delete_version)
removes the tcversions + nodes_hierarchy entries but leaves orphaned
testcase_platforms rows that reference the deleted version. Reproduced on
every single-version delete (testcase TC Login, version with platforms
Android/iOS/Windows assigned).
- After
delete_version,SELECT tcversion_id,platform_id FROM testcase_platforms WHERE testcase_id=<tid>still returns the rows of the deleted version whiletcversions/nodes_hierarchyno longer contain it. - Manual cleanup was previously required:
DELETE FROM testcase_platforms WHERE tcversion_id=<dead version>;
The same hole exists on the whole-test-case delete path
(POST /api/testcases/?action=delete and legacy tcEdit doDelete), because both
funnel through the same core delete routine.
Environment: http://localhost:8082 (PHP built-in server, docroot = repo root),
MariaDB testlink, login admin/admin. Fresh-import DB; fixture rebuilt via SQL to
mirror the reported layout: testproject Platform Demo (prefix PD), suite Suite A, testcase TC Login (node 3), tcversion node 4 (tcversions.id=4, v1), step
node, platforms Android(1)/iOS(2)/Windows(3) enable_on_design=1, and
testcase_platforms rows (3,4,1),(3,4,2),(3,4,3).
- Login:
curl -X POST -c /tmp/opencode/cookies.txt -H "X-Requested-With: XMLHttpRequest" -d 'login=admin&password=admin' http://localhost:8082/api/auth/login→{"status":"ok"}. -
curl ... -X POST 'http://localhost:8082/api/testcases/?action=create_version' -d '{"tcase_id":3}'→{"status":"ok","tcversion_id":5}—create_versioncopies the platform rows to v5 viacopyPlatformsTo(lib/functions/testcase.class.php:2456). -
curl ... -X POST 'http://localhost:8082/api/testcases/?action=delete_version' -d '{"tcase_id":3,"tcversion_id":5}'→{"status":"ok",..., "versions_left":1}. - Bug observed (pre-fix):
testcase_platformsstill holds (5,1),(5,2),(5,3);SELECT id FROM tcversions WHERE id IN (4,5)→ only 4;nodes_hierarchyhas no id 5.`orphans`query (SELECT COUNT(*) FROM testcase_platforms WHERE tcversion_id NOT IN (SELECT id FROM tcversions)) → > 0.
Expected: deleting a version must also purge its testcase_platforms rows —
no orphans accumulate.
Chain (each hop = file:line):
- Modern BFF
delete_version:api/testcases/index.php:2254→$tcaseMgr->delete($tcaseId, $tcversionId). -
testcase::delete()lib/functions/testcase.class.php:1659→$this->_blind_delete($id, $version_id, $children)after_execution_delete-
deleteAllTestCaseRelations.
-
-
_blind_delete(lib/functions/testcase.class.php:1834-1946) executes DELETEs on:user_assignments,testplan_tcversions,tcsteps,testcase_script_links,testcase_keywords,req_coverage,tcversions.testcase_platformsis absent from the whole list. - Result: the version subtree is removed but
testcase_platformsrows (testcase_id+tcversion_id) pointing at the dead version survive. No other delete path touches them — the onlytestcase_platformsDELETEs inlib/aredeletePlatforms/deletePlatformsByLink(testcase.class.php:9858-9926), both UI-assignment-scoped, never called from a version delete.testcase_platformshas no FK constraints (verified viainformation_schema.KEY_COLUMN_USAGE), so nothing references or cascade-deletes it.
Why it broke now: platform rows are written since the #915 design-platform
assignment work: create_new_version → copyPlatformsTo inserts them on every
new version, and version delete was never taught to clean them up.
Blast radius:
- single-version deletes: modern BFF
delete_version+ legacytcView_viewerdelete_tc_version (doDeletewith a concrete version); - whole-TC deletes: modern BFF
action=delete(api/testcases/index.php:2205)- legacy
tcEditdoDelete →self::ALL_VERSIONSbranch of_blind_delete(testcase.class.php:1842-1847);
- legacy
- no other tables reference
testcase_platforms(zero FK rows).
Added a single DELETE inside _blind_delete
(lib/functions/testcase.class.php, right after the req_coverage delete and
before the final tcversions delete, which must stay last):
$sql[]="/* $debugMsg */
DELETE FROM {$this->tables['testcase_platforms']}
WHERE testcase_id = {$id}
AND tcversion_id IN ({$tcversion_list})";Why the core was fixed instead of the API:
-
$tcversion_listalready holds the single version id on the single-version path and the comma-joined version list onALL_VERSIONS, so one statement covers both delete flavors; -
testcase_id = $idscopes the purge; only versions being destroyed lose their platform rows, surviving versions keep theirs; - fixing at core level removes the orphan risk for the modern BFF and the
legacy screens (
tcView_viewer,tcEdit) in both single-version and whole-TC deletes. An API-only fix indelete_versionwould have leftaction=deleteand the legacy paths broken. - Rejected alternative: API-level
DELETE FROM testcase_platforms WHERE tcversion_id=<id>indelete_version— narrow, duplicates core logic, does not fix whole-TC / legacy paths.
Alternative considered but not chosen: deleting testcase_platforms before the
other version-scoped deletes — unnecessary, because the table has no FK
constraints; kept with the sibling DELETEs for readability.
| File | Change |
|---|---|
lib/functions/testcase.class.php |
+4 lines: testcase_platforms purge inside _blind_delete() (Refs #1512) |
| # | Case | Before | After |
|---|---|---|---|
| 1 |
delete_version single-version |
testcase_platforms orphans remain |
0 orphans; surviving version keeps platforms |
| 2 |
action=delete whole TC |
orphans remain (per version) | 0 orphans + TC node gone |
| 3 | create_version |
copies platforms to the new version | unchanged (rows present, then purged correctly on delete) |
| 4 | Modern Test Specification UI delete-this-version | orphans remain | v2 removed, testcase_platforms for v2 gone, v1 still shows Android/iOS/Windows |
| 5 | Event Viewer | — | no new Error/Warning rows (only pre-existing AUDIT=16 LOGIN rows) |
Proof data (measured on the fixed branch):
-
create_version→{"status":"ok","tcversion_id":6}; platforms (6,1),(6,2),(6,3) present. -
delete_version {tcase_id:3, tcversion_id:6}→{"status":"ok",..., "versions_left":1}; afterwardsSELECT tcversion_id,platform_id FROM testcase_platforms WHERE testcase_id=3→ only4/1, 4/2, 4/3;SELECT COUNT(*) FROM testcase_platforms WHERE tcversion_id NOT IN (SELECT id FROM tcversions)→ 0. - Whole-TC
action=delete {tcase_id:3}→{"status":"ok",...}; nodes 3/4 gone,testcase_platformsorphans 0. - Browser: Test Specification → TC Login → Create New Version (Ver. 2 shows
Android/iOS/Windows) → Delete this version → confirm → Ver. 1 remains with
Android/iOS/Windows intact (
docs/screenshots/issue-1512-verify.png). -
php -l lib/functions/testcase.class.php→ no syntax errors.
This page mirrors tmp/wiki-repo/Bugfix-Issue-1512-TestcasePlatforms-Orphans-On-DeleteVersion.md
(screenshots kept in the wiki clone; docs/ keeps the code reference).