From b7685a180cf28210031ec7bc99bb99e4f11d7d59 Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Thu, 2 Mar 2023 10:27:10 -0500 Subject: [PATCH 1/3] Add declare strict types --- src/ConnectionAdapter.php | 2 +- src/ConnectionAdapterConfig.php | 2 +- src/DatabaseRepresentation/Row.php | 2 +- src/DatabaseTestCase.php | 5 +---- src/Exception/ConnectionNotYetEstablished.php | 2 +- src/Exception/InvalidFixture.php | 2 +- src/Exception/MissingRequiredExtension.php | 2 +- src/Exception/UnableToGetTable.php | 2 +- src/Fixture.php | 2 +- src/FixtureRecord.php | 2 +- src/LoadFixture.php | 2 +- src/PdoConnectionAdapter.php | 2 +- src/PdoDriver.php | 2 +- src/SingleRecordFixture.php | 2 +- 14 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/ConnectionAdapter.php b/src/ConnectionAdapter.php index 078773f..407e12b 100644 --- a/src/ConnectionAdapter.php +++ b/src/ConnectionAdapter.php @@ -1,4 +1,4 @@ - Date: Thu, 2 Mar 2023 16:43:56 -0500 Subject: [PATCH 2/3] Add support for amphp/postgres --- .gitignore | 3 +- CHANGELOG.md | 28 ++++++++ composer.json | 6 +- docker/php/Dockerfile | 2 +- src/AbstractConnectionAdapter.php | 67 +++++++++++++++++++ src/AmpPostgresConnectionAdapter.php | 65 ++++++++++++++++++ .../MissingRequiredComposerPackage.php | 9 +++ src/PdoConnectionAdapter.php | 57 +++------------- .../AmpPostgresConnectionAdapterTest.php | 29 ++++++++ .../Helper/PostgresConnectionConfig.php | 19 ++++++ .../PostgresPdoConnectionAdapterTest.php | 13 ++-- 11 files changed, 240 insertions(+), 58 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src/AbstractConnectionAdapter.php create mode 100644 src/AmpPostgresConnectionAdapter.php create mode 100644 src/Exception/MissingRequiredComposerPackage.php create mode 100644 tests/Integration/AmpPostgresConnectionAdapterTest.php create mode 100644 tests/Integration/Helper/PostgresConnectionConfig.php diff --git a/.gitignore b/.gitignore index 1649674..046fe40 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .. /vendor .phpunit.cache -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +composer.lock \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..19a32ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased Changes + +### Added + +- Introduces a `Cspray\DatabaseTestCase\AbstractConnectionAdapter` for implementing functionality common across all `Cspray\DatabaseTestCase\ConnectionAdapter` implementations. +- Provides the `Cspray\DatabaseTestCase\AmpPostgresConnectionAdapter` for working with the amphp/postgres library. + +### Changed + +- The `Cspray\DatabaseTestCase\PdoConnectionAdapter` now extends the new `AbstractConnectionAdapter` and has been simplified. +- Added `declare(strict_types=1)` in all files it was missing. + +## [0.1.0](https://github.com/cspray/database-test-case/releases/tag/0.1.0) - 2023-03-02 + +### Added + +- Adds a `Cspray\DatabaseTestCase\DatabaseTestCase` that allows for testing database interactions. +- Adds a `Cspray\DatabaseTestCase\ConnectionAdapter` interface that is responsible for actual calls to an underlying connection. +- Provides a `Cspray\DatabaseTestCase\PdoConnectionAdapter` with support for PostgreSQL databases. +- Provides a mechanism for loading fixtures per test, using the Attribute `#[LoadFixture]` and providing an implementation of `Cspray\DatabaseTestCase\Fixture`. +- Provides a mechanism for retrieving the state of a database table at a given point in time with `Cspray\DatabaseTestCase\DatabaseRepresentation\Table`. \ No newline at end of file diff --git a/composer.json b/composer.json index 17847d4..cb42dcb 100644 --- a/composer.json +++ b/composer.json @@ -7,6 +7,8 @@ "database" ], "license": ["MIT"], + "minimum-stability": "beta", + "prefer-stable": true, "require": { "php": "^8.2", "phpunit/phpunit": "^10.0" @@ -14,6 +16,7 @@ "require-dev": { "ext-pdo": "*", "ext-pdo_pgsql": "*", + "amphp/postgres": "^v2.0.0-beta.2", "roave/security-advisories": "dev-latest" }, "autoload": { @@ -27,6 +30,7 @@ } }, "suggest": { - "ext-pdo": "To enable the PdoConnectionAdapter" + "ext-pdo": "To enable the PdoConnectionAdapter", + "amphp/postgres": "To enable the AmpPostgresConnectionAdapter" } } \ No newline at end of file diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index 6e41a56..4c719e0 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -4,7 +4,7 @@ RUN apt-get update -y \ && apt-get upgrade -y RUN apt-get install git libsodium-dev libzip-dev libpq-dev -y -RUN docker-php-ext-install sodium zip pdo pdo_pgsql +RUN docker-php-ext-install sodium zip pdo pdo_pgsql pgsql RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini" diff --git a/src/AbstractConnectionAdapter.php b/src/AbstractConnectionAdapter.php new file mode 100644 index 0000000..1d6247a --- /dev/null +++ b/src/AbstractConnectionAdapter.php @@ -0,0 +1,67 @@ +getFixtureRecords() as $fixtureRecord) { + $sql = $this->generateInsertSqlForParameters($fixtureRecord); + $parameters = $fixtureRecord->parameters; + $this->executeInsertSql($sql, $parameters); + } + } + } + + final public function getTable(string $name) : Table { + try { + $table = Table::forName($name); + foreach ($this->executeSelectAllSql($name) as $row) { + $r = null; + foreach ($row as $col => $val) { + $r = $r === null ? Row::forValue($col, $val) : $r->withValue($col, $val); + } + $table = $table->withRow($r); + } + return $table; + } catch (Throwable $throwable) { + throw new UnableToGetTable( + message: sprintf('Unable to fetch table "%s", please check previous Exception for more details.', $name), + previous: $throwable + ); + } + } + + protected function generateInsertSqlForParameters(FixtureRecord $fixtureRecord) : string { + $table = $fixtureRecord->table; + $parameters = $fixtureRecord->parameters; + $colsString = implode( + ', ', + array_keys($parameters) + ); + $paramString = implode( + ', ', + array_map(static fn(string $col) => ':' . $col, array_keys($parameters)) + ); + return <<> + * @throws Throwable + */ + abstract protected function executeSelectAllSql(string $table) : array; + +} diff --git a/src/AmpPostgresConnectionAdapter.php b/src/AmpPostgresConnectionAdapter.php new file mode 100644 index 0000000..cf4d18b --- /dev/null +++ b/src/AmpPostgresConnectionAdapter.php @@ -0,0 +1,65 @@ +connection = connect( + PostgresConfig::fromString(sprintf( + 'db=%s host=%s port=%d user=%s pass=%s', + $this->adapterConfig->database, + $this->adapterConfig->host, + $this->adapterConfig->port, + $this->adapterConfig->user, + $this->adapterConfig->password + )) + ); + } + + public function onTestStart() : void { + $this->connection->query('START TRANSACTION'); + } + + public function onTestStop() : void { + $this->connection->query('ROLLBACK'); + } + + public function closeConnection() : void { + $this->connection->close(); + $this->connection = null; + } + + public function getUnderlyingConnection() : PostgresLink { + return $this->connection; + } + + protected function executeInsertSql(string $sql, array $parameters) : void { + $statement = $this->connection->prepare($sql); + $statement->execute($parameters); + } + + protected function executeSelectAllSql(string $table) : array { + $result = $this->connection->query(sprintf('SELECT * FROM %s', $table)); + $rows = []; + while ($row = $result->fetchRow()) { + $rows[] = $row; + } + return $rows; + } +} diff --git a/src/Exception/MissingRequiredComposerPackage.php b/src/Exception/MissingRequiredComposerPackage.php new file mode 100644 index 0000000..91ddf6b --- /dev/null +++ b/src/Exception/MissingRequiredComposerPackage.php @@ -0,0 +1,9 @@ +connection = null; } - public function loadFixture(Fixture $fixture, Fixture ...$additionalFixture) : void { - /** @var Fixture $f */ - foreach ([$fixture, ...$additionalFixture] as $f) { - foreach ($f->getFixtureRecords() as $fixtureRecord) { - $statement = $this->connection->prepare($this->generateInsertSqlForParameters($fixtureRecord)); - foreach ($fixtureRecord->parameters as $col => $val) { - $statement->bindValue($col, $val); - } - $statement->execute(); - } - } - } public function getUnderlyingConnection() : PDO { return $this->connection; } - public function getTable(string $name) : Table { - try { - $query = sprintf('SELECT * FROM %s', $name); - $result = $this->connection->query($query)->fetchAll(PDO::FETCH_ASSOC); - $table = Table::forName($name); - foreach ($result as $row) { - $r = null; - foreach ($row as $col => $val) { - $r = $r === null ? Row::forValue($col, $val) : $r->withValue($col, $val); - } - $table = $table->withRow($r); - } - return $table; - } catch (PDOException $pdoException) { - throw new UnableToGetTable( - message: sprintf('Unable to fetch table "%s", please check previous Exception for more details.', $name), - previous: $pdoException - ); + + protected function executeInsertSql(string $sql, array $parameters) : void { + $statement = $this->getUnderlyingConnection()->prepare($sql); + foreach ($parameters as $col => $val) { + $statement->bindValue($col, $val); } + $statement->execute(); } - private function generateInsertSqlForParameters(FixtureRecord $fixtureRecord) : string { - $table = $fixtureRecord->table; - $parameters = $fixtureRecord->parameters; - $colsString = implode( - ', ', - array_keys($parameters) - ); - $paramString = implode( - ', ', - array_map(static fn(string $col) => ':' . $col, array_keys($parameters)) - ); - return <<connection->query($query)->fetchAll(PDO::FETCH_ASSOC); } - } diff --git a/tests/Integration/AmpPostgresConnectionAdapterTest.php b/tests/Integration/AmpPostgresConnectionAdapterTest.php new file mode 100644 index 0000000..203bd21 --- /dev/null +++ b/tests/Integration/AmpPostgresConnectionAdapterTest.php @@ -0,0 +1,29 @@ +query('SELECT COUNT(*) AS count FROM ' . $table)->fetchRow()['count']; + } + + protected static function getConnectionAdapter() : ConnectionAdapter { + return new AmpPostgresConnectionAdapter( + new PostgresConnectionConfig() + ); + } +} \ No newline at end of file diff --git a/tests/Integration/Helper/PostgresConnectionConfig.php b/tests/Integration/Helper/PostgresConnectionConfig.php new file mode 100644 index 0000000..333ec6d --- /dev/null +++ b/tests/Integration/Helper/PostgresConnectionConfig.php @@ -0,0 +1,19 @@ +query('SELECT COUNT(*) AS "count" FROM my_table') + $connection = self::getUnderlyingConnection(); + assert($connection instanceof PDO); + return $connection->query('SELECT COUNT(*) AS "count" FROM ' . $table) ->fetch(PDO::FETCH_ASSOC)['count']; } } From 5690ca36aa299f5cb464abfc5446dc45a0033766 Mon Sep 17 00:00:00 2001 From: Charles Sprayberry Date: Thu, 2 Mar 2023 16:51:02 -0500 Subject: [PATCH 3/3] Check for interface, not class --- src/AmpPostgresConnectionAdapter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AmpPostgresConnectionAdapter.php b/src/AmpPostgresConnectionAdapter.php index cf4d18b..3f45fe2 100644 --- a/src/AmpPostgresConnectionAdapter.php +++ b/src/AmpPostgresConnectionAdapter.php @@ -7,7 +7,7 @@ use Cspray\DatabaseTestCase\Exception\MissingRequiredComposerPackage; use function Amp\Postgres\connect; -if (! class_exists(PostgresLink::class)) { +if (! interface_exists(PostgresLink::class)) { throw new MissingRequiredComposerPackage('You must install amphp/postgres to use ' . AmpPostgresConnectionAdapter::class); }