diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f0b89d9..8f17675 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -46,13 +46,36 @@ jobs: - name: Run PHPStan run: composer phpstan - phpunit: + phpunit-unit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-php - name: Run PHPUnit - run: composer phpunit -- --coverage-clover=coverage.xml + run: composer phpunit:unit -- --coverage-clover=coverage.xml + - name: Codecov + uses: codecov/codecov-action@v4 + with: + files: ./coverage.xml + + phpunit-integration: + runs-on: ubuntu-latest + services: + mariadb: + image: mariadb:10.5 + env: + MARIADB_ROOT_PASSWORD: swagbraintree + MYSQL_DATABASE: swagbraintree_test + ports: ['3306:3306'] + env: + DATABASE_URL: mysql://root:swagbraintree@127.0.0.1:3306/swagbraintree + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-php + - name: Setup database + run: composer setup:test + - name: Run PHPUnit + run: composer phpunit:integration -- --coverage-clover=coverage.xml - name: Codecov uses: codecov/codecov-action@v4 with: @@ -60,9 +83,20 @@ jobs: infection: runs-on: ubuntu-latest + services: + mariadb: + image: mariadb:10.5 + env: + MARIADB_ROOT_PASSWORD: swagbraintree + MYSQL_DATABASE: swagbraintree_test + ports: ['3306:3306'] + env: + DATABASE_URL: mysql://root:swagbraintree@127.0.0.1:3306/swagbraintree steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-php + - name: Setup database + run: composer setup:test - name: Run Infection run: composer infection -- --min-msi=95 env: diff --git a/composer.json b/composer.json index ed6e86f..cd2687f 100644 --- a/composer.json +++ b/composer.json @@ -80,12 +80,19 @@ "setup": [ "bin/console doctrine:schema:drop --force --full-database", "bin/console doctrine:migrations:migrate -n", - "@setup:url" + "@setup:url", + "@setup:test" ], "setup:url": [ "bin/console setup:url", "npm run dev" - ] + ], + "setup:test": [ + "bin/console doctrine:schema:drop --env=test --force --full-database", + "bin/console doctrine:migrations:migrate --env=test -n" + ], + "phpunit:unit": "@phpunit --testsuite=SwagBraintreeUnitTest", + "phpunit:integration": "@phpunit --testsuite=SwagBraintreeIntegrationTest" }, "conflict": { "symfony/symfony": "*" diff --git a/devenv.nix b/devenv.nix index 5896094..e095de6 100644 --- a/devenv.nix +++ b/devenv.nix @@ -87,7 +87,10 @@ in { services.mysql = { enable = true; package = pkgs.mariadb_105; - initialDatabases = lib.mkDefault [{ name = "swagbraintree"; }]; + initialDatabases = lib.mkDefault [ + { name = "swagbraintree"; } + { name = "swagbraintree_test"; } + ]; ensureUsers = lib.mkDefault [ { name = "swagbraintree"; diff --git a/phpstan.neon b/phpstan.neon index 208d861..a23b7d6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -28,6 +28,10 @@ parameters: message: '#Multiple class/interface/trait is not allowed in single file#' path: tests/unit + - # don't want to prefix this + message: '#Only abstract classes can be extended#' + path: tests/integration/DoctrineUuidTest.php + rules: # rules from https://github.com/symplify/phpstan-rules # domain diff --git a/src/Doctrine/RespectfulUuidGenerator.php b/src/Doctrine/RespectfulUuidGenerator.php deleted file mode 100644 index 38b3476..0000000 --- a/src/Doctrine/RespectfulUuidGenerator.php +++ /dev/null @@ -1,30 +0,0 @@ -getId() ?? Uuid::v7(); - } -} diff --git a/src/Entity/Contract/EntityIdTrait.php b/src/Entity/Contract/EntityIdTrait.php index 4d4cd1c..659ff0e 100644 --- a/src/Entity/Contract/EntityIdTrait.php +++ b/src/Entity/Contract/EntityIdTrait.php @@ -3,7 +3,6 @@ namespace Swag\Braintree\Entity\Contract; use Doctrine\ORM\Mapping as ORM; -use Swag\Braintree\Doctrine\RespectfulUuidGenerator; use Symfony\Component\Uid\Uuid; #[ORM\MappedSuperclass] @@ -12,17 +11,17 @@ trait EntityIdTrait #[ORM\Id] #[ORM\Column(type: 'uuid', unique: true)] #[ORM\GeneratedValue(strategy: 'CUSTOM')] - #[ORM\CustomIdGenerator(class: RespectfulUuidGenerator::class)] + #[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')] // will never be null in the database // null only signalizes the uuid-generator to generate a new uuid for the database - private ?Uuid $id = null; + private Uuid $id; public function getId(): ?Uuid { - return $this->id; + return $this->id ?? null; } - public function setId(?Uuid $id): self + public function setId(Uuid $id): self { $this->id = $id; diff --git a/src/Entity/Contract/EntityInterface.php b/src/Entity/Contract/EntityInterface.php index 5a9f9a0..472c785 100644 --- a/src/Entity/Contract/EntityInterface.php +++ b/src/Entity/Contract/EntityInterface.php @@ -8,7 +8,7 @@ interface EntityInterface { public function getId(): ?Uuid; - public function setId(?Uuid $id): self; + public function setId(Uuid $id): self; public function getCreatedAt(): \DateTimeInterface; diff --git a/tests/integration/DoctrineUuidTest.php b/tests/integration/DoctrineUuidTest.php new file mode 100644 index 0000000..20b8daf --- /dev/null +++ b/tests/integration/DoctrineUuidTest.php @@ -0,0 +1,54 @@ +getEntityManager(); + + $shop = $this->createShop(); + $config = (new ConfigEntity()) + ->setShop($shop); + + $em->persist($config); + $em->flush(); + + static::assertNotNull($config->getId()); + $id = (string) $config->getId(); + + $em->persist($config); + $em->flush(); + + static::assertSame($id, (string) $config->getId()); + } + + public function testWithRef(): void + { + $em = $this->getEntityManager(); + + $shop = $this->createShop(); + $config = (new ConfigEntity()) + ->setShop($shop); + + $em->persist($config); + $em->flush(); + + static::assertNotNull($config->getId()); + $id = (string) $config->getId(); + + $configRef = $em->getReference(ConfigEntity::class, $id); + $configRef->setThreeDSecureEnforced(true); + + $em->persist($configRef); + $em->flush(); + + $config = $em->find(ConfigEntity::class, $id); + + static::assertSame($id, (string) $config->getId()); + static::assertTrue($config->isThreeDSecureEnforced()); + } +} diff --git a/tests/integration/SwagBraintreeTestCase.php b/tests/integration/SwagBraintreeTestCase.php new file mode 100644 index 0000000..2f7d1e4 --- /dev/null +++ b/tests/integration/SwagBraintreeTestCase.php @@ -0,0 +1,62 @@ +getConnection(); + $tables = $conn->createSchemaManager()->listTableNames(); + /** @phpstan-ignore-next-line - yes `getTableName` will work */ + $migrationTable = static::getContainer()->get('doctrine.migrations.dependency_factory') + ->getConfiguration() + ->getMetadataStorageConfiguration() + ->getTableName(); + + $conn->executeStatement('SET FOREIGN_KEY_CHECKS=0;'); + + try { + foreach ($tables as $table) { + if ($table === $migrationTable) { + continue; + } + + $conn->executeStatement(\sprintf( + 'TRUNCATE TABLE %s', + $conn->quoteIdentifier($table), + )); + } + } finally { + $conn->executeStatement('SET FOREIGN_KEY_CHECKS=1;'); + } + + parent::tearDown(); + } + + protected static function getEntityManager(): EntityManagerInterface + { + return static::getContainer()->get('doctrine.orm.default_entity_manager'); + } + + protected static function createShop(string $url = 'https://shop.example.com', string $secret = 'definitly-a-secure-secret'): ShopEntity + { + $shopId = \bin2hex(\random_bytes(10)); + + $shop = new ShopEntity( + $shopId, + $url, + $secret, + ); + + $em = static::getEntityManager(); + $em->persist($shop); + $em->flush(); + + return $shop; + } +} diff --git a/tests/unit/Doctrine/RespectfulUuidGeneratorTest.php b/tests/unit/Doctrine/RespectfulUuidGeneratorTest.php deleted file mode 100644 index cd8852a..0000000 --- a/tests/unit/Doctrine/RespectfulUuidGeneratorTest.php +++ /dev/null @@ -1,99 +0,0 @@ -getTestEntity(); - - $generator = new RespectfulUuidGenerator(); - $id = $generator->generateId($this->createMock(EntityManager::class), $entity); - - static::assertNotNull($id); - static::assertTrue(Uuid::isValid($id->toRfc4122())); - } - - public function testGenerateIdWithNullEntity(): void - { - $generator = new RespectfulUuidGenerator(); - $id = $generator->generateId($this->createMock(EntityManager::class), null); - - static::assertNull($id); - } - - public function testGenerateIdWithNonEntity(): void - { - $entity = new \stdClass(); - - $generator = new RespectfulUuidGenerator(); - - static::expectException(\RuntimeException::class); - static::expectExceptionMessage('Class stdClass not supported for respectful uuid generation. Use Swag\Braintree\Entity\Contract\EntityInterface instead'); - - $generator->generateId($this->createMock(EntityManager::class), $entity); - } - - public function testGenerateIdWithExistingId(): void - { - $uuid = Uuid::v7(); - - $entity = $this->getTestEntity($uuid); - - $generator = new RespectfulUuidGenerator(); - $id = $generator->generateId($this->createMock(EntityManager::class), $entity); - - static::assertNotNull($id); - static::assertSame($uuid, $id); - } - - private function getTestEntity(?Uuid $id = null): EntityInterface - { - return new class($id) implements EntityInterface { - public function __construct(private ?Uuid $id = null) - { - } - - public function getId(): ?Uuid - { - return $this->id; - } - - public function setId(?Uuid $id): EntityInterface - { - $this->id = $id; - - return $this; - } - - public function getCreatedAt(): \DateTimeInterface - { - return new \DateTime(); - } - - public function setCreatedAt(\DateTimeInterface $createdAt): EntityInterface - { - return $this; - } - - public function getUpdatedAt(): ?\DateTimeInterface - { - return null; - } - - public function setUpdatedAt(?\DateTimeInterface $updatedAt): EntityInterface - { - return $this; - } - }; - } -}