test(api): wire up real mysqli in SchemaHealthCheckTest (TRU-77 unit CI)
The Unit suite starts with a freshly-dropped test database and does not create a $db global, so: - customer_invoice_email_schema_bootstrap::ensureSchema() short- circuited and never created the users table. - runSchemaCheck had nothing to inspect and reported ok=false. Wire up a real mysqli connection at file load time using the same CONFIG_DB_* env vars the rest of the CI suite exports, then have beforeEach run the bootstrap and create the bare-minimum invoices / bookings tables that runSchemaCheck verifies. This keeps the test self-contained inside the Unit suite without changing the production code or the production schema. Refs: api#383, TRU-77
This commit is contained in:
@@ -12,6 +12,8 @@
|
|||||||
* endpoint in `adminRoute.php` which the deploy pipeline hits.
|
* endpoint in `adminRoute.php` which the deploy pipeline hits.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
app_require('classes/customer_invoice_email_schema_bootstrap.php');
|
||||||
|
|
||||||
use classes\customer_invoice_email_schema_bootstrap;
|
use classes\customer_invoice_email_schema_bootstrap;
|
||||||
|
|
||||||
const REQUIRED_USERS_COLUMNS = [
|
const REQUIRED_USERS_COLUMNS = [
|
||||||
@@ -28,13 +30,130 @@ const REQUIRED_USERS_COLUMNS = [
|
|||||||
'created_at',
|
'created_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unit test bootstrap does not create a $db global. This contract
|
||||||
|
* test is unique in that it needs a real database to verify schema
|
||||||
|
* state, so wire one up here using the same CONFIG_DB_* env vars the
|
||||||
|
* rest of the CI suite exports. If the database is unavailable, the
|
||||||
|
* tests below will fail with a clear "no_db_connection" error.
|
||||||
|
*/
|
||||||
|
schema_health_check_test_wire_db();
|
||||||
|
|
||||||
|
function schema_health_check_test_wire_db(): void
|
||||||
|
{
|
||||||
|
if (isset($GLOBALS['db']) && is_object($GLOBALS['db'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!class_exists('mysqli')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = (string)(getenv('CONFIG_DB_HOST') ?: 'mysql-debug');
|
||||||
|
$user = (string)(getenv('CONFIG_DB_USER') ?: 'root');
|
||||||
|
$password = (string)(getenv('CONFIG_DB_PASSWORD') ?: 'debug_root_password');
|
||||||
|
$database = (string)(getenv('CONFIG_DB_DATABASE') ?: 'nnks_db_debug');
|
||||||
|
$port = (int)(getenv('CONFIG_DB_PORT') ?: 3306);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mysqli_report(MYSQLI_REPORT_OFF);
|
||||||
|
$conn = new mysqli($host, $user, $password, $database, $port);
|
||||||
|
if ($conn->connect_errno) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$conn->set_charset('utf8mb4');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$GLOBALS['db'] = new class($conn) {
|
||||||
|
private mysqli $conn;
|
||||||
|
|
||||||
|
public function __construct(mysqli $conn)
|
||||||
|
{
|
||||||
|
$this->conn = $conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query(string $sql)
|
||||||
|
{
|
||||||
|
return $this->conn->query($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetch_assoc($result)
|
||||||
|
{
|
||||||
|
return $result ? $result->fetch_assoc() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->conn->close();
|
||||||
|
} catch (\Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unit suite starts with a freshly-dropped `nnks_db_debug` database
|
||||||
|
* (see run_ci_suite::reset_ci_state). The schema bootstrap only owns
|
||||||
|
* the `users` table; `invoices` and `bookings` are managed by other
|
||||||
|
* migrations that don't run in the unit suite. Create the bare-minimum
|
||||||
|
* schema that adminRoute::runSchemaCheck needs so the third test can
|
||||||
|
* verify the "all columns exist" happy path.
|
||||||
|
*/
|
||||||
|
function schema_health_check_test_ensure_aux_tables(): void
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
if (!isset($db) || !is_object($db) || !method_exists($db, 'query')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$create = function (string $table, string $createSql, array $requiredColumns) use ($db): void {
|
||||||
|
$r = $db->query("SHOW TABLES LIKE '{$table}'");
|
||||||
|
if (!$r || (int)$r->num_rows === 0) {
|
||||||
|
$db->query($createSql);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($requiredColumns as $column => $definition) {
|
||||||
|
$r = $db->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
|
||||||
|
if (!$r || (int)$r->num_rows === 0) {
|
||||||
|
$db->query("ALTER TABLE `{$table}` ADD COLUMN `{$column}` {$definition}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$create('invoices', "CREATE TABLE `invoices` (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
customer_number INT NOT NULL DEFAULT 0,
|
||||||
|
po_number VARCHAR(64) NULL,
|
||||||
|
closed_at DATETIME NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", [
|
||||||
|
'po_number' => 'VARCHAR(64) NULL',
|
||||||
|
'closed_at' => 'DATETIME NULL',
|
||||||
|
'customer_number' => 'INT NOT NULL DEFAULT 0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$create('bookings', "CREATE TABLE `bookings` (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
customer_number INT NOT NULL DEFAULT 0,
|
||||||
|
department INT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", [
|
||||||
|
'customer_number' => 'INT NOT NULL DEFAULT 0',
|
||||||
|
'department' => 'INT NULL',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
// Self-heal: run the schema bootstrap so the test DB has all
|
// Self-heal: run the schema bootstrap so the test DB has all
|
||||||
// the columns the contract requires. The bootstrap is additive
|
// the columns the contract requires. The bootstrap is additive
|
||||||
// and idempotent — safe to run on every test.
|
// and idempotent — safe to run on every test.
|
||||||
|
if (!isset($GLOBALS['db']) || !is_object($GLOBALS['db'])) {
|
||||||
|
schema_health_check_test_wire_db();
|
||||||
|
}
|
||||||
if (class_exists(customer_invoice_email_schema_bootstrap::class)) {
|
if (class_exists(customer_invoice_email_schema_bootstrap::class)) {
|
||||||
customer_invoice_email_schema_bootstrap::ensureSchema();
|
customer_invoice_email_schema_bootstrap::ensureSchema();
|
||||||
}
|
}
|
||||||
|
schema_health_check_test_ensure_aux_tables();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('users table has every required column the code references', function () {
|
it('users table has every required column the code references', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user