Files
zoneminder/tests/php/test_database_error_paths.php
T
Isaac ConnorandClaude Opus 5.5 0960b2064b fix: log the database error when dbQuery's prepare or query returns false
dbQuery()'s two error paths for a prepare() or query() that returns false
logged $pdo->errorInfo. $pdo is not defined there, so PHP warned twice and
the message ended without the error. They also read dbLastError() from the
connection only after ZM\Error() had logged; with a database log target that
inserts into Logs through the same connection, which replaces its errorInfo,
so dbLastError() came back as "00000" instead of the error.

Read the error once, before logging, and use it for both. The query() path
also said "Error preparing" for a statement it ran directly.

These paths run when PDO's error mode doesn't throw; dbConnect() sets
ERRMODE_EXCEPTION, so normally the catch below handles the failure.

tests/php/test_database_error_paths.php drives both paths with
ERRMODE_SILENT and checks for no PHP warning and the real error text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 07:58:26 -04:00

55 lines
2.2 KiB
PHP

<?php
// Tests dbQuery()'s error paths for a prepare() or query() that returns false
// rather than throwing (PDO::ERRMODE_SILENT). They logged $pdo->errorInfo, an
// undefined variable, so the message had no error in it and PHP warned; and
// dbLastError() was read from the connection after ZM\Error() had logged, which
// with a database log target is another statement on the same connection.
//
// Run: sudo -u www-data php tests/php/test_database_error_paths.php
// (from the repo root, on a tree with a readable zm.conf)
set_include_path(__DIR__.'/../../web/includes'.PATH_SEPARATOR.get_include_path());
require_once('config.php'); // connects to DB + loads config
$failures = 0;
$passes = 0;
function check($name, $ok, $detail = '') {
global $failures, $passes;
if ($ok) {
$passes++;
echo "ok - $name\n";
} else {
$failures++;
echo "not ok - $name".($detail === '' ? '' : " ($detail)")."\n";
}
}
$warnings = array();
set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$warnings) {
// Only warnings raised by database.php: the logger has its own CLI warnings.
if (basename($errfile) == 'database.php') $warnings[] = "$errstr at line $errline";
return true;
});
$conn = zmDbConn();
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
// query() path: no parameters.
$warnings = array();
check('a failing query returns NULL', dbQuery('SELEC 1') === NULL);
check('a failing query raises no PHP warning', !$warnings, implode('; ', $warnings));
check('dbLastError() has the error for a failing query', strpos(dbLastError(), 'syntax') !== false, dbLastError());
// prepare() path: with parameters. Emulated prepares only fail at execute, so
// use server-side ones to make prepare() itself fail.
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$warnings = array();
check('a failing prepare returns NULL', dbQuery('SELEC ?', array(1)) === NULL);
check('a failing prepare raises no PHP warning', !$warnings, implode('; ', $warnings));
check('dbLastError() has the error for a failing prepare', strpos(dbLastError(), 'syntax') !== false, dbLastError());
restore_error_handler();
echo "\n$passes passed, $failures failed\n";
exit($failures ? 1 : 0);