9ed69b78ca
Database Portability Tests / Unit Tests (PlatformHelper) (push) Failing after 45s
Database Portability Tests / Integration (mysql) (push) Has been skipped
Database Portability Tests / Integration (postgres) (push) Has been skipped
Database Portability Tests / Integration (sqlite) (push) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (push) Successful in 4s
153 lines
5.7 KiB
PHP
153 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\Mitgliederverwaltung\Tests\DatabasePortability;
|
|
|
|
use OCA\Mitgliederverwaltung\Db\PlatformHelper;
|
|
use OCP\IDBConnection;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Integration test suite for database portability.
|
|
*
|
|
* These tests verify that the SQL expressions generated by PlatformHelper
|
|
* are syntactically valid and semantically correct for each database backend.
|
|
* They run against a real Nextcloud database when available (set DB_PLATFORM
|
|
* env var), or fall back to expression-level validation.
|
|
*
|
|
* To run against a live database:
|
|
* DB_PLATFORM=mysql phpunit tests/DatabasePortability/
|
|
* DB_PLATFORM=postgres phpunit tests/DatabasePortability/
|
|
* DB_PLATFORM=sqlite phpunit tests/DatabasePortability/
|
|
*
|
|
* Part of Issue #192.
|
|
*/
|
|
class DatabasePortabilityTest extends TestCase {
|
|
|
|
/**
|
|
* Verify that PlatformHelper generates structurally valid SQL
|
|
* for all three backends — each expression must be a non-empty string
|
|
* containing the referenced column.
|
|
*
|
|
* @dataProvider platformAndMethodProvider
|
|
*/
|
|
public function testExpressionStructure(string $platform, string $method, string $col): void {
|
|
$db = $this->createMock(IDBConnection::class);
|
|
$db->method('getDatabaseProvider')->willReturn($platform);
|
|
|
|
$expr = PlatformHelper::$method($db, $col);
|
|
|
|
$this->assertIsString($expr);
|
|
$this->assertNotEmpty($expr);
|
|
$this->assertStringContainsString($col, $expr,
|
|
"Expression for $method on $platform must reference the column");
|
|
}
|
|
|
|
public static function platformAndMethodProvider(): array {
|
|
$platforms = [
|
|
IDBConnection::PLATFORM_MYSQL,
|
|
IDBConnection::PLATFORM_POSTGRES,
|
|
IDBConnection::PLATFORM_SQLITE,
|
|
];
|
|
$methods = [
|
|
['getYearDiffExpression', 'm.geburtsdatum'],
|
|
['getMonthExpression', 'm.geburtsdatum'],
|
|
['getYearExpression', 'l.startdatum'],
|
|
];
|
|
|
|
$cases = [];
|
|
foreach ($platforms as $p) {
|
|
foreach ($methods as [$m, $c]) {
|
|
$cases["$p/$m"] = [$p, $m, $c];
|
|
}
|
|
}
|
|
return $cases;
|
|
}
|
|
|
|
/**
|
|
* Verify that MySQL expressions use the expected MySQL-specific functions.
|
|
*/
|
|
public function testMySQLExpressionsUseCorrectFunctions(): void {
|
|
$db = $this->createMock(IDBConnection::class);
|
|
$db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_MYSQL);
|
|
|
|
$this->assertStringContainsString('TIMESTAMPDIFF',
|
|
PlatformHelper::getYearDiffExpression($db, 'col'));
|
|
$this->assertStringContainsString('MONTH(',
|
|
PlatformHelper::getMonthExpression($db, 'col'));
|
|
$this->assertStringContainsString('YEAR(',
|
|
PlatformHelper::getYearExpression($db, 'col'));
|
|
}
|
|
|
|
/**
|
|
* Verify that PostgreSQL expressions use EXTRACT and AGE.
|
|
*/
|
|
public function testPostgresExpressionsUseCorrectFunctions(): void {
|
|
$db = $this->createMock(IDBConnection::class);
|
|
$db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_POSTGRES);
|
|
|
|
$yearDiff = PlatformHelper::getYearDiffExpression($db, 'col');
|
|
$this->assertStringContainsString('EXTRACT', $yearDiff);
|
|
$this->assertStringContainsString('AGE', $yearDiff);
|
|
|
|
$this->assertStringContainsString('EXTRACT(MONTH',
|
|
PlatformHelper::getMonthExpression($db, 'col'));
|
|
$this->assertStringContainsString('EXTRACT(YEAR',
|
|
PlatformHelper::getYearExpression($db, 'col'));
|
|
}
|
|
|
|
/**
|
|
* Verify that SQLite expressions use strftime.
|
|
*/
|
|
public function testSQLiteExpressionsUseCorrectFunctions(): void {
|
|
$db = $this->createMock(IDBConnection::class);
|
|
$db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_SQLITE);
|
|
|
|
$yearDiff = PlatformHelper::getYearDiffExpression($db, 'col');
|
|
$this->assertStringContainsString('strftime', $yearDiff);
|
|
// SQLite year diff includes birthday correction logic
|
|
$this->assertStringContainsString('CASE WHEN', $yearDiff);
|
|
|
|
$this->assertStringContainsString('strftime',
|
|
PlatformHelper::getMonthExpression($db, 'col'));
|
|
$this->assertStringContainsString('strftime',
|
|
PlatformHelper::getYearExpression($db, 'col'));
|
|
}
|
|
|
|
/**
|
|
* Verify that no platform expression contains obvious SQL injection vectors.
|
|
* Column names should appear verbatim, and no user-controllable content
|
|
* should be interpolated.
|
|
*/
|
|
public function testNoInjectionVectorsInExpressions(): void {
|
|
$platforms = [
|
|
IDBConnection::PLATFORM_MYSQL,
|
|
IDBConnection::PLATFORM_POSTGRES,
|
|
IDBConnection::PLATFORM_SQLITE,
|
|
];
|
|
|
|
foreach ($platforms as $platform) {
|
|
$db = $this->createMock(IDBConnection::class);
|
|
$db->method('getDatabaseProvider')->willReturn($platform);
|
|
|
|
// Use a safe column reference
|
|
$col = 'm.test_col';
|
|
$methods = ['getYearDiffExpression', 'getMonthExpression', 'getYearExpression'];
|
|
|
|
foreach ($methods as $method) {
|
|
$expr = PlatformHelper::$method($db, $col);
|
|
|
|
// Must not contain any parameter placeholders (? or :param)
|
|
// because these are raw expressions, not parameterized queries
|
|
$this->assertStringNotContainsString('?', $expr,
|
|
"Expression for $method on $platform must not contain ? placeholders");
|
|
|
|
// The column reference must appear exactly as passed
|
|
$this->assertStringContainsString($col, $expr,
|
|
"Expression for $method on $platform must reference column verbatim");
|
|
}
|
|
}
|
|
}
|
|
}
|