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..3f45fe2 --- /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/ConnectionAdapter.php b/src/ConnectionAdapter.php index 078773f..407e12b 100644 --- a/src/ConnectionAdapter.php +++ b/src/ConnectionAdapter.php @@ -1,4 +1,4 @@ -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/src/PdoDriver.php b/src/PdoDriver.php index 8272c6e..edab1e7 100644 --- a/src/PdoDriver.php +++ b/src/PdoDriver.php @@ -1,4 +1,4 @@ -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']; } }