Skip to content

Commit 75e002f

Browse files
bcordisclaude
andcommitted
feat: add MySQL service to CI and support JTEST_DB env vars
CI now runs a MySQL 8.4 service container and imports the Proclaim schema, enabling integration tests to run in CI alongside unit tests. Bootstrap checks JTEST_DB_* env vars (CI) first, then falls back to build.properties → Joomla configuration.php (local dev). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9df13eb commit 75e002f

File tree

2 files changed

+82
-69
lines changed

2 files changed

+82
-69
lines changed

.github/workflows/ci.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ jobs:
2121
name: PHP Lint & Tests
2222
runs-on: ubuntu-latest
2323

24+
services:
25+
mysql:
26+
image: mysql:8.4
27+
env:
28+
MYSQL_USER: proclaim_test
29+
MYSQL_PASSWORD: proclaim_test
30+
MYSQL_ROOT_PASSWORD: proclaim_test
31+
MYSQL_DATABASE: proclaim_test
32+
ports:
33+
- 3306:3306
34+
options: >-
35+
--health-cmd "mysqladmin ping -h 127.0.0.1"
36+
--health-interval 10s
37+
--health-timeout 5s
38+
--health-retries 5
39+
2440
steps:
2541
- name: Checkout repository
2642
uses: actions/checkout@v4
@@ -29,7 +45,7 @@ jobs:
2945
uses: shivammathur/setup-php@v2
3046
with:
3147
php-version: '8.3'
32-
extensions: mbstring, json, xml
48+
extensions: mbstring, json, xml, mysqli
3349
coverage: none
3450

3551
- name: Get Composer cache directory
@@ -49,13 +65,21 @@ jobs:
4965
- name: Install Composer dependencies
5066
run: composer install --dev --no-interaction --prefer-dist
5167

68+
- name: Import database schema
69+
run: mysql -h 127.0.0.1 -u proclaim_test -pproclaim_test proclaim_test < admin/sql/install.mysql.utf8.sql
70+
5271
- name: Check PHP syntax
5372
run: composer lint:syntax
5473

55-
- name: Run PHPUnit unit tests
56-
run: composer test:unit
74+
- name: Run PHPUnit tests
75+
run: composer test
5776
env:
5877
JOOMLA_CMS_PATH: /home/runner/work/Proclaim/joomla-cms
78+
JTEST_DB_HOST: 127.0.0.1
79+
JTEST_DB_NAME: proclaim_test
80+
JTEST_DB_USER: proclaim_test
81+
JTEST_DB_PASSWORD: proclaim_test
82+
JTEST_DB_PREFIX: ''
5983

6084
js-checks:
6185
name: JS Lint & Tests

tests/unit/bootstrap.php

Lines changed: 55 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -259,72 +259,76 @@
259259
require_once __DIR__ . '/ProclaimTestCase.php';
260260

261261
// ---------------------------------------------------------------------------
262-
// Optional: Bootstrap a real database connection from build.properties
262+
// Optional: Bootstrap a real database connection
263+
// Sources: 1) JTEST_DB_* env vars (CI), 2) build.properties → Joomla config
263264
// ---------------------------------------------------------------------------
264265

265266
(function () use ($componentRoot) {
266-
$propsFile = $componentRoot . '/build.properties';
267-
268-
if (!file_exists($propsFile)) {
269-
return;
267+
$host = '';
268+
$dbName = '';
269+
$user = '';
270+
$pass = '';
271+
$prefix = '';
272+
273+
// Source 1: JTEST_DB_* environment variables (CI)
274+
if (getenv('JTEST_DB_HOST') && getenv('JTEST_DB_NAME')) {
275+
$host = getenv('JTEST_DB_HOST');
276+
$dbName = getenv('JTEST_DB_NAME');
277+
$user = getenv('JTEST_DB_USER') ?: '';
278+
$pass = getenv('JTEST_DB_PASSWORD') ?: '';
279+
$prefix = getenv('JTEST_DB_PREFIX') ?: '';
270280
}
271281

272-
// Parse build.properties
273-
$props = [];
274-
$lines = file($propsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
275-
276-
foreach ($lines as $line) {
277-
$trimmed = trim($line);
278-
279-
if ($trimmed === '' || str_starts_with($trimmed, '#')) {
280-
continue;
281-
}
282+
// Source 2: build.properties → Joomla configuration.php
283+
if ($dbName === '') {
284+
$propsFile = $componentRoot . '/build.properties';
282285

283-
$eq = strpos($trimmed, '=');
286+
if (file_exists($propsFile)) {
287+
$props = [];
288+
$lines = file($propsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
284289

285-
if ($eq === false) {
286-
continue;
287-
}
290+
foreach ($lines as $line) {
291+
$trimmed = trim($line);
288292

289-
$props[trim(substr($trimmed, 0, $eq))] = trim(substr($trimmed, $eq + 1));
290-
}
293+
if ($trimmed === '' || str_starts_with($trimmed, '#')) {
294+
continue;
295+
}
291296

292-
// Find the first Joomla installation path (for database config)
293-
$joomlaPath = '';
297+
$eq = strpos($trimmed, '=');
294298

295-
if (!empty($props['builder.joomla_paths'])) {
296-
$paths = array_map('trim', explode(',', $props['builder.joomla_paths']));
297-
$joomlaPath = $paths[0] ?? '';
298-
} elseif (!empty($props['builder.joomla_path'])) {
299-
$joomlaPath = $props['builder.joomla_path'];
300-
}
299+
if ($eq !== false) {
300+
$props[trim(substr($trimmed, 0, $eq))] = trim(substr($trimmed, $eq + 1));
301+
}
302+
}
301303

302-
if ($joomlaPath === '' || !is_dir($joomlaPath)) {
303-
return;
304-
}
304+
$joomlaPath = '';
305305

306-
$configFile = rtrim($joomlaPath, '/') . '/configuration.php';
306+
if (!empty($props['builder.joomla_paths'])) {
307+
$paths = array_map('trim', explode(',', $props['builder.joomla_paths']));
308+
$joomlaPath = $paths[0] ?? '';
309+
} elseif (!empty($props['builder.joomla_path'])) {
310+
$joomlaPath = $props['builder.joomla_path'];
311+
}
307312

308-
if (!file_exists($configFile)) {
309-
return;
310-
}
313+
$configFile = $joomlaPath !== '' && is_dir($joomlaPath)
314+
? rtrim($joomlaPath, '/') . '/configuration.php'
315+
: '';
311316

312-
// Load Joomla's configuration
313-
require_once $configFile;
317+
if ($configFile !== '' && file_exists($configFile)) {
318+
require_once $configFile;
314319

315-
if (!class_exists('JConfig', false)) {
316-
return;
320+
if (class_exists('JConfig', false)) {
321+
$config = new \JConfig();
322+
$host = $config->host ?? 'localhost';
323+
$dbName = $config->db ?? '';
324+
$user = $config->user ?? '';
325+
$pass = $config->password ?? '';
326+
$prefix = $config->dbprefix ?? '';
327+
}
328+
}
329+
}
317330
}
318331

319-
$config = new \JConfig();
320-
321-
// Create a real database connection
322-
$host = $config->host ?? 'localhost';
323-
$dbName = $config->db ?? '';
324-
$user = $config->user ?? '';
325-
$pass = $config->password ?? '';
326-
$prefix = $config->dbprefix ?? '';
327-
328332
if ($dbName === '' || $user === '') {
329333
return;
330334
}
@@ -338,36 +342,21 @@
338342
}
339343

340344
try {
341-
$options = [
345+
$db = \Joomla\Database\DatabaseDriver::getInstance([
342346
'driver' => 'mysqli',
343347
'host' => $host,
344348
'port' => $port,
345349
'user' => $user,
346350
'password' => $pass,
347351
'database' => $dbName,
348352
'prefix' => $prefix,
349-
];
350-
351-
$db = \Joomla\Database\DatabaseDriver::getInstance($options);
353+
]);
352354
$db->connect();
353355

354-
// Register in DI container for Factory::getContainer()->get(DatabaseInterface::class)
355-
try {
356-
$container = \Joomla\CMS\Factory::getContainer();
357-
358-
if ($container instanceof \Joomla\DI\Container) {
359-
$container->set(\Joomla\Database\DatabaseInterface::class, $db);
360-
}
361-
} catch (\Throwable) {
362-
// Container not available or key protected — tests use direct DB access
363-
}
364-
365-
// Store reference for tests that need direct access
366356
\define('PROCLAIM_TEST_DB_AVAILABLE', true);
367357

368358
fwrite(STDERR, "Database connected: $dbName@$host:$port" . PHP_EOL);
369359
} catch (\Throwable $e) {
370-
// Database not available — integration tests will skip gracefully
371360
\define('PROCLAIM_TEST_DB_AVAILABLE', false);
372361
fwrite(STDERR, "Database not available: " . $e->getMessage() . PHP_EOL);
373362
}

0 commit comments

Comments
 (0)