Repository navigation
Bugfix Issue 958 LegacyRest UpdateBuild 409
Bugfix — Issue #958: legacy REST (v2/v3) updateBuild always 409 — checkNameExistence array read as bool
Le legacy REST updateBuild handler (v2 lib/api/rest/v2/tlRestApi.class.php and
v3 lib/api/rest/v3/RestApi.class.php) always answered HTTP 409
API_BUILDNAME_ALREADY_EXISTS whenever the request body contained name — even
for a globally-unique rename, and even for a no-op rename to the current name.
The update path was only reachable when the payload omitted name entirely.
Reproduced on a fresh database (TestLink 2.0.1, builds project-scoped per #503) with a
DB-backed harness booting config.inc.php + database + new build($db)
(/tmp/repro958.php):
- Fixtures:
testprojectsid=1 (prefix RSTTP);buildsid=1Build A(tp=1). - Call the same expression the handler evaluates:
checkNameExistence(1, 'Build B', 1)(unique rename, self excluded) → returnsarray(1) { ["status_ok"]=>int(1) }(no conflict). - The legacy branch
if( $this->buildMgr->checkNameExistence(...) )treats any non-empty array as truthy → duplicate branch fires →statusOK=false, messageAPI_BUILDNAME_ALREADY_EXISTS, HTTP 409. Rename blocked. - Also
checkNameExistence(1,'Build A',1)(same-name self-excluded) → same 409.
Two facts combine:
-
lib/functions/build.class.php:674-700—build::checkNameExistence($tproject_id, $build_name, $build_id=null, ...)returns a status array, not a boolean:$status = array(); $status['status_ok'] = $rn == 0 ? 1 : 0; // 1 = name free, 0 = duplicate found return $status;
-
Both legacy REST update handlers test that array as a boolean:
- v2
lib/api/rest/v2/tlRestApi.class.php:1348 - v3
lib/api/rest/v3/RestApi.class.php:753
// before (identical in v2 and v3) if( $this->buildMgr->checkNameExistence( intval($build['testproject_id']),$item->name,$id) ) { $statusOK = false; ... $this->app->status(409); // v2 / $response->withStatus(409); v3 }
- v2
In PHP any non-empty array is truthy (measured: var_dump((bool)['status_ok'=>1])
→ bool(true), also for ['status_ok'=>0]), so the 409 branch fired on every
rename — even when the check SQL (WHERE testproject_id=N AND UPPER(name)='…' AND id <> M) correctly returned zero rows, i.e. status_ok=1 = no conflict. The actual
build::update() was never reached for any name-bearing request.
This is the same defect family as #948 (already fixed on the modernized BFF at
api/builds/index.php:490-493 with $chk = checkNameExistence(...); if(!$chk['status_ok'])).
The legacy v2/v3 REST surface kept the unfixed variant. createBuild in both handlers is
NOT affected: it uses tplanMgr->get_build_id_by_name() which returns an int id (> 0
check). XMLRPC / testplan / testproject checkNameExistence call sites compare against
0/'' and are safe.
Apply the array-key read, mirroring the verified #948 idiom, in both handlers:
// v2 lib/api/rest/v2/tlRestApi.class.php + v3 lib/api/rest/v3/RestApi.class.php
$checkName = $this->buildMgr->checkNameExistence(
intval($build['testproject_id']),$item->name,$id);
if( !$checkName['status_ok'] ) { // 0 = genuine duplicate -> 409
$statusOK = false;
...
// v2: $this->app->status(409); v3: $response->withStatus(409);
}Semantics preserved: unique rename / same-name self-excluded / cross-case → status_ok=1
→ !1=false → proceed to update (200 path); genuine same-project duplicate → status_ok=0
→ 409 (the only case that was coincidentally correct before).
-
php -l lib/api/rest/v2/tlRestApi.class.phpandphp -l lib/api/rest/v3/RestApi.class.php→ no syntax errors. -
DB-backed harness
/tmp/verify958.phpexercising the exact patched predicate → 5/5 PASS:Case Expect Got unique rename Build A→Build B(id=1, self-excl)pass pass PASS same-name rename Build A(id=1, self-excl)pass pass PASS same-name diff-case build a(id=1, self-excl)pass pass PASS duplicate by id=2 Build B(same tp)409 409 PASS same name in OTHER project (tp=2) pass pass PASS -
Event Viewer:
eventstable shows no new ERROR/WARNING rows from the clean run.
-
lib/api/rest/v2/tlRestApi.class.php— updateBuild name-existence check fixed. -
lib/api/rest/v3/RestApi.class.php— updateBuild name-existence check fixed. -
tmp/TLU_Test_Cases.md— regression suiteRegression — Issue #958(7/7 PASS). -
.ci_target_issue— run target marker (→ 958).
Commits on branch fix/issue-958-updatebuild-409: 7697f5354 (fix), 7e6fddb57 (tests).
- HTTP-level reproduction of the legacy REST surface is not possible in the CI sandbox:
v2 requires
Slim/Slim.php(Slim 2 runtime no longer shipped; only Slim 4 for v3 lives invendor/), and v3index.phpfatals on the custom-API example (custom/api/RestApiCustomExample.class.php:9undefined constantlib) — both pre-existing and out of scope. The fixed code path is exercised against the real DB with the identical predicate the handlers execute.