From 519f33c2bf25e71160826c0e9f668f4b6768e17b Mon Sep 17 00:00:00 2001 From: Simon Mundy Date: Wed, 25 Mar 2026 20:45:25 +1100 Subject: [PATCH] Achieve 100% test coverage for Adapter/* classes Add targeted tests and fix coverage attribution to close the remaining 29 uncovered lines across Adapter/*. New test assets: - TestConnection: extends AbstractConnection directly (not via AbstractPdoConnection) to test inherited methods like setConnectionParameters() and getResource() auto-connect - TestPlatform: extends AbstractPlatform without overriding quoteValue(), allowing direct coverage of the base class throw/escape paths Test changes: - AbstractConnectionTest: switch from ConnectionWrapper (which goes through AbstractPdoConnection overrides) to TestConnection so AbstractConnection methods are exercised directly - PdoTest: add missing #[CoversMethod] for formatParameterName, fix phpstan method.resultUnused warning - StatementTest: add double-execute test (bindParametersFromContainer early-return path) and clone-with-container test - Sql92Test: add tests for AbstractPlatform::quoteValue() throw and escape paths via TestPlatform - ProfilerTest: split combined test into separate string/container tests, remove dead TypeError assertion (covered by union type) - ParameterContainerTest: remove stale @phpstan-ignore Config: - phpunit.xml.dist: re-enable AdapterAwareTraitTest (the test works, the exclusion was a leftover) --- phpunit.xml.dist | 1 - .../Adapter/Driver/AbstractConnectionTest.php | 33 ++++++----- test/unit/Adapter/Driver/Pdo/PdoTest.php | 2 + .../unit/Adapter/Driver/Pdo/StatementTest.php | 26 +++++++++ .../Driver/TestAsset/TestConnection.php | 56 +++++++++++++++++++ test/unit/Adapter/ParameterContainerTest.php | 1 - test/unit/Adapter/Platform/Sql92Test.php | 16 ++++++ .../Platform/TestAsset/TestPlatform.php | 31 ++++++++++ test/unit/Adapter/Profiler/ProfilerTest.php | 10 ++-- 9 files changed, 154 insertions(+), 22 deletions(-) create mode 100644 test/unit/Adapter/Driver/TestAsset/TestConnection.php create mode 100644 test/unit/Adapter/Platform/TestAsset/TestPlatform.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c5f21759..d6274b18 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,6 @@ ./test/unit/Adapter/AdapterServiceFactoryTest.php ./test/unit/Adapter/Driver/Pdo/ConnectionIntegrationTest.php ./test/unit/Adapter/Driver/Pdo/StatementIntegrationTest.php - ./test/unit/Adapter/AdapterAwareTraitTest.php ./test/integration diff --git a/test/unit/Adapter/Driver/AbstractConnectionTest.php b/test/unit/Adapter/Driver/AbstractConnectionTest.php index 07ff65bf..d1f01d01 100644 --- a/test/unit/Adapter/Driver/AbstractConnectionTest.php +++ b/test/unit/Adapter/Driver/AbstractConnectionTest.php @@ -6,8 +6,7 @@ use PhpDb\Adapter\Driver\AbstractConnection; use PhpDb\Adapter\Profiler\ProfilerInterface; -use PhpDbTest\TestAsset\ConnectionWrapper; -use PhpDbTest\TestAsset\PdoStubDriver; +use PhpDbTest\Adapter\Driver\TestAsset\TestConnection; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -25,7 +24,8 @@ final class AbstractConnectionTest extends TestCase { public function testDisconnectNullsResourceWhenConnected(): void { - $connection = new ConnectionWrapper(new PdoStubDriver()); + $connection = new TestConnection(); + $connection->connect(); self::assertTrue($connection->isConnected()); @@ -36,8 +36,7 @@ public function testDisconnectNullsResourceWhenConnected(): void public function testDisconnectIsNoOpWhenNotConnected(): void { - $connection = new ConnectionWrapper(); - $connection->disconnect(); + $connection = new TestConnection(); $result = $connection->disconnect(); @@ -46,28 +45,29 @@ public function testDisconnectIsNoOpWhenNotConnected(): void public function testGetConnectionParametersReturnsEmptyByDefault(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); self::assertSame([], $connection->getConnectionParameters()); } - public function testGetDriverNameReturnsDriverAttribute(): void + public function testGetDriverNameReturnsValueWhenSet(): void { - $connection = new ConnectionWrapper(new PdoStubDriver()); + $connection = new TestConnection(); + $connection->setConnectionParameters(['driver' => 'sqlite']); - self::assertSame('sqlite', $connection->getDriverName()); + self::assertNull($connection->getDriverName()); } public function testGetProfilerReturnsNullByDefault(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); self::assertNull($connection->getProfiler()); } public function testSetProfilerStoresAndReturnsProfiler(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); $profiler = $this->createMock(ProfilerInterface::class); $result = $connection->setProfiler($profiler); @@ -78,16 +78,19 @@ public function testSetProfilerStoresAndReturnsProfiler(): void public function testGetResourceAutoConnectsWhenNotConnected(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); + + self::assertFalse($connection->isConnected()); $resource = $connection->getResource(); - self::assertNotNull($resource); + self::assertTrue($connection->isConnected()); + self::assertSame('fake-resource', $resource); } public function testSetConnectionParametersStoresAndReturnsConnection(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); $params = ['host' => 'localhost', 'port' => 3306]; $result = $connection->setConnectionParameters($params); @@ -98,7 +101,7 @@ public function testSetConnectionParametersStoresAndReturnsConnection(): void public function testInTransactionReturnsFalseByDefault(): void { - $connection = new ConnectionWrapper(); + $connection = new TestConnection(); self::assertFalse($connection->inTransaction()); } diff --git a/test/unit/Adapter/Driver/Pdo/PdoTest.php b/test/unit/Adapter/Driver/Pdo/PdoTest.php index 068f7edd..77695281 100644 --- a/test/unit/Adapter/Driver/Pdo/PdoTest.php +++ b/test/unit/Adapter/Driver/Pdo/PdoTest.php @@ -33,6 +33,7 @@ #[CoversMethod(AbstractPdo::class, 'getLastGeneratedValue')] #[CoversMethod(AbstractPdo::class, 'setProfiler')] #[CoversMethod(AbstractPdo::class, 'getProfiler')] +#[CoversMethod(AbstractPdo::class, 'formatParameterName')] #[Group('unit')] final class PdoTest extends TestCase { @@ -181,6 +182,7 @@ public function testGetProfilerThrowsWhenNotInitialized(): void $pdo = new TestPdo([]); $this->expectException(Error::class); + /** @phpstan-ignore method.resultUnused */ $pdo->getProfiler(); } diff --git a/test/unit/Adapter/Driver/Pdo/StatementTest.php b/test/unit/Adapter/Driver/Pdo/StatementTest.php index 9672aac8..b70d3816 100644 --- a/test/unit/Adapter/Driver/Pdo/StatementTest.php +++ b/test/unit/Adapter/Driver/Pdo/StatementTest.php @@ -503,4 +503,30 @@ public function testExecuteAutoPrepares(): void self::assertInstanceOf(Result::class, $result); self::assertTrue($this->statement->isPrepared()); } + + public function testSecondExecuteSkipsBindingWhenAlreadyBound(): void + { + $pdo = new SqliteMemoryPdo(); + $this->statement->setDriver(new TestPdo(new TestConnection($pdo))); + $this->statement->initialize($pdo); + $this->statement->setSql('SELECT :val'); + $this->statement->setParameterContainer(new ParameterContainer(['val' => 'test'])); + + $result1 = $this->statement->execute(); + self::assertInstanceOf(Result::class, $result1); + + $result2 = $this->statement->execute(); + self::assertInstanceOf(Result::class, $result2); + } + + public function testCloneClonesParameterContainerWhenSet(): void + { + $container = new ParameterContainer(['key' => 'value']); + $statement = new Statement($container); + + $clone = clone $statement; + + self::assertNotSame($container, $clone->getParameterContainer()); + self::assertSame('value', $clone->getParameterContainer()->offsetGet('key')); + } } diff --git a/test/unit/Adapter/Driver/TestAsset/TestConnection.php b/test/unit/Adapter/Driver/TestAsset/TestConnection.php new file mode 100644 index 00000000..bf81ffa2 --- /dev/null +++ b/test/unit/Adapter/Driver/TestAsset/TestConnection.php @@ -0,0 +1,56 @@ +resource = 'fake-resource'; + + return $this; + } + + public function execute(string $sql): ?ResultInterface + { + return null; + } + + public function getCurrentSchema(): string|false + { + return false; + } + + public function getLastGeneratedValue(?string $name = null): string|int|false|null + { + return false; + } + + public function isConnected(): bool + { + return $this->resource !== null; + } + + public function rollback(): ConnectionInterface + { + return $this; + } +} diff --git a/test/unit/Adapter/ParameterContainerTest.php b/test/unit/Adapter/ParameterContainerTest.php index 6ff1ae5f..d80c00d8 100644 --- a/test/unit/Adapter/ParameterContainerTest.php +++ b/test/unit/Adapter/ParameterContainerTest.php @@ -312,7 +312,6 @@ public function testOffsetSetThrowsOnInvalidKeyType(): void $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Keys must be string, integer or null'); - /** @phpstan-ignore argument.type */ $container->offsetSet(1.5, 'value'); } diff --git a/test/unit/Adapter/Platform/Sql92Test.php b/test/unit/Adapter/Platform/Sql92Test.php index ce2cde61..3ebff997 100644 --- a/test/unit/Adapter/Platform/Sql92Test.php +++ b/test/unit/Adapter/Platform/Sql92Test.php @@ -9,6 +9,7 @@ use PhpDb\Adapter\Exception\VunerablePlatformQuoteException; use PhpDb\Adapter\Platform\AbstractPlatform; use PhpDb\Adapter\Platform\Sql92; +use PhpDbTest\Adapter\Platform\TestAsset\TestPlatform; use PhpDbTest\TestAsset\TestSql92Platform; use PHPUnit\Framework\Attributes\CoversMethod; use PHPUnit\Framework\Attributes\Group; @@ -177,4 +178,19 @@ public function testQuoteValueEscapesSpecialCharacters(): void self::assertStringStartsWith("'", $quoted); self::assertStringEndsWith("'", $quoted); } + + public function testAbstractPlatformQuoteValueThrowsWithoutDriver(): void + { + $platform = new TestPlatform(); + + $this->expectException(VunerablePlatformQuoteException::class); + $platform->quoteValue('value'); + } + + public function testAbstractPlatformQuoteValueEscapesWithDriver(): void + { + $platform = new TestPlatform($this->createStub(DriverInterface::class)); + + self::assertSame("'test\\'value'", $platform->quoteValue("test'value")); + } } diff --git a/test/unit/Adapter/Platform/TestAsset/TestPlatform.php b/test/unit/Adapter/Platform/TestAsset/TestPlatform.php new file mode 100644 index 00000000..9b87d9d5 --- /dev/null +++ b/test/unit/Adapter/Platform/TestAsset/TestPlatform.php @@ -0,0 +1,31 @@ +profiler = new Profiler(); } - public function testProfilerStart(): void + public function testProfilerStartWithString(): void { $ret = $this->profiler->profilerStart('SELECT * FROM FOO'); self::assertSame($this->profiler, $ret); + } + + public function testProfilerStartWithStatementContainer(): void + { $ret = $this->profiler->profilerStart(new StatementContainer()); self::assertSame($this->profiler, $ret); - - $this->expectException(TypeError::class); - $this->profiler->profilerStart(5); } public function testProfilerFinish(): void