From 286c351dc2b88b9e90a3ec81a85e6c2b303502b4 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Fri, 3 Nov 2023 08:31:39 +0100 Subject: [PATCH] chore: remove Makefile --- .env | 4 + .github/workflows/ci.yml | 230 +++++++----------- .gitignore | 4 +- Makefile | 176 -------------- README.md | 130 +++++----- bin/console | 10 + composer.json | 22 +- config/cli-config.php | 29 --- docker-compose.yaml | 50 +--- docker/Dockerfile | 62 ----- docker/build.sh | 56 ----- docker/mongo-init.js | 13 - docker/xdebug.ini | 12 - docs/phpstorm-xdebug-config.png | Bin 72874 -> 0 bytes ...doctrine.xml.dist => phpunit.dama.xml.dist | 0 tests/Fixtures/Kernel.php | 34 ++- .../Migrations/Version20230513160345.php | 91 ------- .../Migrations/Version20230513160346.php | 63 ----- .../Bundle/Maker/MakeFactoryTest.php | 50 ++-- .../Functional/Bundle/Maker/MakerTestCase.php | 2 +- .../Functional/FactoryDoctrineCascadeTest.php | 2 +- tests/Functional/FactoryTest.php | 2 +- tests/Functional/FakerTest.php | 7 + tests/Functional/ModelFactoryServiceTest.php | 2 +- tests/Functional/ODMAnonymousFactoryTest.php | 2 +- tests/Functional/ODMGlobalStateTest.php | 2 +- tests/Functional/ODMModelFactoryTest.php | 2 +- tests/Functional/ODMProxyTest.php | 2 +- tests/Functional/ODMRepositoryProxyTest.php | 2 +- tests/Functional/ORMAnonymousFactoryTest.php | 2 +- tests/Functional/ORMDatabaseResetterTest.php | 11 +- tests/Functional/ORMGlobalStateTest.php | 2 +- tests/Functional/ORMModelFactoryTest.php | 2 +- tests/Functional/ORMProxyTest.php | 2 +- tests/Functional/ORMRepositoryProxyTest.php | 2 +- tests/Functional/StoryTest.php | 2 +- tests/Functional/WithMigrationTest.php | 10 +- tests/bootstrap.php | 28 ++- 38 files changed, 300 insertions(+), 822 deletions(-) create mode 100644 .env delete mode 100644 Makefile create mode 100755 bin/console delete mode 100644 config/cli-config.php delete mode 100644 docker/Dockerfile delete mode 100755 docker/build.sh delete mode 100644 docker/mongo-init.js delete mode 100644 docker/xdebug.ini delete mode 100644 docs/phpstorm-xdebug-config.png rename phpunit-dama-doctrine.xml.dist => phpunit.dama.xml.dist (100%) delete mode 100644 tests/Fixtures/Migrations/Version20230513160345.php delete mode 100644 tests/Fixtures/Migrations/Version20230513160346.php diff --git a/.env b/.env new file mode 100644 index 00000000..187d16b5 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +DATABASE_URL="mysql://root:1234@127.0.0.1:3307/foundry_test?serverVersion=5.7.42" +MONGO_URL="mongodb://127.0.0.1:27018/dbName?compressors=disabled&gssapiServiceName=mongodb" +USE_FOUNDRY_BUNDLE=1 +TEST_MIGRATIONS=0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 556d1101..803123f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,76 +8,76 @@ on: jobs: tests: - name: PHP ${{ matrix.php }}, SF ${{ matrix.symfony }} - ${{ matrix.deps }} ${{ matrix.use-orm == '1' && (matrix.orm-db == 'postgres' && '- ORM (postgres)' || '- ORM (mysql)') || '' }} ${{ matrix.use-odm == '1' && '- ODM' || '' }} ${{ matrix.use-dama == '1' && '- DAMA' || '' }} + name: P:${{ matrix.php }}, S:${{ matrix.symfony }}, D:${{ matrix.database }}${{ matrix.deps == 'lowest' && ' (lowest)' || '' }}${{ matrix.use-dama == 1 && contains(matrix.database, 'sql') && ' (dama)' || '' }} runs-on: ubuntu-latest strategy: - fail-fast: false matrix: - php: [8.0, 8.1, 8.2] - symfony: [5.4.*, 6.2.*, 6.3.*] - deps: [highest] - use-orm: [1] - use-odm: [1] - use-dama: [1] - orm-db: [postgres] - exclude: - - {use-orm: 0, use-odm: 0} # tested directly in a test case - - {use-orm: 0, use-dama: 1} # cannot happen - # conflicts - - {php: 8.0, symfony: 6.2.*} - - {php: 8.0, symfony: 6.3.*} + php: [ 8.1, 8.2 ] + deps: [ highest ] + symfony: [ 6.3.*, 6.4.* ] + database: [ mysql, mongo ] + use-dama: [ 1 ] include: - - {php: 8.0, symfony: 5.4.*, use-orm: 1, use-odm: 0, use-dama: 0, deps: lowest, orm-db: postgres} - - {php: 8.0, symfony: 5.4.*, use-orm: 1, use-odm: 1, use-dama: 0, deps: lowest, orm-db: postgres} - - {php: 8.0, symfony: 5.4.*, use-orm: 0, use-odm: 1, use-dama: 0, deps: lowest, orm-db: postgres} - - {php: 8.2, symfony: 6.3.*, use-orm: 1, use-odm: 0, use-dama: 0, deps: highest, orm-db: postgres} - - {php: 8.2, symfony: 6.3.*, use-orm: 1, use-odm: 1, use-dama: 0, deps: highest, orm-db: postgres} - - {php: 8.2, symfony: 6.3.*, use-orm: 1, use-odm: 0, use-dama: 1, deps: highest, orm-db: postgres} - - {php: 8.2, symfony: 6.3.*, use-orm: 0, use-odm: 1, use-dama: 0, deps: highest, orm-db: postgres} - - {php: 8.2, symfony: 6.3.*, use-orm: 1, use-odm: 0, use-dama: 1, deps: highest, orm-db: mysql} - - {php: 8.2, symfony: 6.3.*, use-orm: 1, use-odm: 0, use-dama: 0, deps: highest, orm-db: mysql} - + - php: 8.0 + deps: lowest + symfony: '5.4.*' + database: mysql|mongo + use-dama: 1 + - php: 8.2 + deps: highest + symfony: '*' + database: none + use-dama: 1 + - php: 8.2 + deps: highest + symfony: '*' + database: mysql|mongo + use-dama: 1 + - php: 8.2 + deps: highest + symfony: '*' + database: pgsql|mongo + use-dama: 1 + - php: 8.2 + deps: highest + symfony: '*' + database: mysql + use-dama: 0 + - php: 8.2 + deps: highest + symfony: '*' + database: pgsql + use-dama: 0 + env: + DATABASE_URL: ${{ contains(matrix.database, 'mysql') && 'mysql://root:root@localhost:3306/foundry?serverVersion=5.7.42' || contains(matrix.database, 'pgsql') && 'postgresql://root:root@localhost:5432/foundry?serverVersion=15' || '' }} + MONGO_URL: ${{ contains(matrix.database, 'mongo') && 'mongodb://127.0.0.1:27017/dbName?compressors=disabled&gssapiServiceName=mongodb' || '' }} + USE_DAMA_DOCTRINE_TEST_BUNDLE: ${{ matrix.use-dama == 1 && contains(matrix.database, 'sql') && 1 || 0 }} services: - mysql: - image: mysql:5.7.42 - env: - MYSQL_ROOT_PASSWORD: 1234 - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - postgres: - image: postgres:15 + image: ${{ contains(matrix.database, 'pgsql') && 'postgres:15' || '' }} env: - POSTGRES_USER: postgres - POSTGRES_DB: zenstruck_foundry_${{ matrix.use-dama }}_${{ matrix.orm-db }} - POSTGRES_PASSWORD: 1234 - options: --health-cmd pg_isready --health-interval=10s --health-timeout=5s --health-retries=5 + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: foundry ports: - 5432:5432 - + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 mongo: - image: mongo:4 + image: ${{ contains(matrix.database, 'mongo') && 'mongo:4' || '' }} ports: - 27017:27017 - - env: - MYSQL_URL: mysql://root:1234@127.0.0.1:3306/zenstruck_foundry?serverVersion=5.7.42 - PGSQL_URL: postgresql://postgres:1234@127.0.0.1:5432/zenstruck_foundry_${{ matrix.use-dama }}_${{ matrix.orm-db }}?charset=utf8&serverVersion=15 - MONGO_URL: mongodb://127.0.0.1:27017/dbName?compressors=disabled&gssapiServiceName=mongodb - steps: - name: Checkout code uses: actions/checkout@v3 - - name: Verify MySQL version - run: mysql --host 127.0.0.1 -uroot -p1234 -e "STATUS" - - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: pgsql, sqlite, mongodb coverage: none tools: flex @@ -89,51 +89,49 @@ jobs: env: SYMFONY_REQUIRE: ${{ matrix.symfony }} - - name: 'Test' - run: | - if [ "${{ matrix.use-dama }}" == "1" ]; then - CONFIGURATION="--configuration phpunit-dama-doctrine.xml.dist" - fi + - name: Set up MySQL + if: contains(matrix.database, 'mysql') + run: sudo /etc/init.d/mysql start - vendor/bin/simple-phpunit ${CONFIGURATION} + - name: Test + run: vendor/bin/phpunit -c "${PHPUNIT_CONFIG_FILE}" + shell: bash env: - USE_ORM: ${{ matrix.use-orm }} - USE_ODM: ${{ matrix.use-odm }} - USE_FOUNDRY_BUNDLE: 1 - DATABASE_URL: ${{ matrix.orm-db == 'postgres' && env.PGSQL_URL || env.MYSQL_URL }} + TEST_MIGRATIONS: 1 + PHPUNIT_CONFIG_FILE: ${{ env.USE_DAMA_DOCTRINE_TEST_BUNDLE == 1 && 'phpunit.dama.xml.dist' || 'phpunit.xml.dist' }} code-coverage: name: Code Coverage runs-on: ubuntu-latest + env: + DATABASE_URL: postgresql://root:root@localhost:5432/foundry?serverVersion=15 + MONGO_URL: mongodb://127.0.0.1:27017/dbName?compressors=disabled&gssapiServiceName=mongodb services: - mysql: - image: mysql:5.7 - env: - MYSQL_ROOT_PASSWORD: 1234 - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 mongo: image: mongo:4 ports: - 27017:27017 - - env: - DATABASE_URL: mysql://root:1234@127.0.0.1:3306/zenstruck_foundry?serverVersion=5.7 - MONGO_URL: mongodb://127.0.0.1:27017/dbName?compressors=disabled&gssapiServiceName=mongodb - + postgres: + image: postgres:15 + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: foundry + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout code uses: actions/checkout@v3 - - name: Verify MySQL version - run: mysql --host 127.0.0.1 -uroot -p1234 -e "STATUS" - - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 - extensions: pgsql, sqlite + php-version: 8.2 coverage: xdebug ini-values: xdebug.mode=coverage @@ -141,44 +139,22 @@ jobs: uses: ramsey/composer-install@v2 with: composer-options: --prefer-dist - dependency-versions: "highest" - - name: 'Coverage' - run: vendor/bin/simple-phpunit -v --configuration phpunit-dama-doctrine.xml.dist --coverage-text --coverage-clover=foundry.clover + - name: Test with coverage + run: vendor/bin/phpunit -c phpunit.dama.xml.dist --coverage-text --coverage-clover coverage.xml + shell: bash env: - USE_ORM: 1 - USE_ODM: 1 - USE_FOUNDRY_BUNDLE: 1 + USE_FOUNDRY_BUNDLE: 0 + SYMFONY_DEPRECATIONS_HELPER: disabled - name: Publish coverage report to Codecov uses: codecov/codecov-action@v3 with: - file: ./*.clover + file: ./coverage.xml composer-validate: uses: zenstruck/.github/.github/workflows/php-composer-validate.yml@main - build-docs: - name: Build Documentation - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.0 - coverage: none - - - name: Install dependencies - uses: ramsey/composer-install@v2 - with: - composer-options: --prefer-dist - - - name: Build docs - run: bin/build-docs - static-analysis: name: Static Analysis runs-on: ubuntu-latest @@ -189,7 +165,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.1 coverage: none - name: Install dependencies @@ -209,47 +185,6 @@ jobs: - name: Run Psalm on factories generated with maker run: bin/tools/psalm/vendor/vimeo/psalm/psalm - test-docker-stack: - name: CI with docker stack - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php: ['8.0', '8.1', '8.2'] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Run test suite with docker - run: | - echo "PHP_VERSION=${{ matrix.php }}" > .env - make validate - - push_docker_images: - name: Push docker images after CI complete on main branch - needs: [tests, composer-validate, build-docs, static-analysis, test-docker-stack] - runs-on: ubuntu-latest - if: ${{ github.event_name == 'push' && github.event.ref == 'refs/heads/1.x' }} - strategy: - fail-fast: false - matrix: - php: ['8.0', '8.1', '8.2'] - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Login to ghcr.io - uses: docker/login-action@v2 - with: - registry: ghcr.io/zenstruck - username: token - password: ${{ secrets.PACKAGE_PUSH_TOKEN }} - - - name: Build and push images - run: ./docker/build.sh push ${{ matrix.php }} - env: - GITHUB_TOKEN: ${{ secrets.PACKAGE_PUSH_TOKEN }} - fixcs: name: Run php-cs-fixer needs: sync-with-template @@ -271,4 +206,3 @@ jobs: with: key: ${{ secrets.GPG_PRIVATE_KEY }} token: ${{ secrets.COMPOSER_TOKEN }} - phpcsconfig: false diff --git a/.gitignore b/.gitignore index 9060a923..f73429c2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,7 @@ /tests/Fixtures/tmp /var/ /docs/output/ -/.env /docker/.makefile +/.env.local +/docker-compose.override.yaml +/tests/Fixtures/Migrations/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 5bad441a..00000000 --- a/Makefile +++ /dev/null @@ -1,176 +0,0 @@ -.DEFAULT_GOAL := help - -SHELL=/bin/bash - -# DB variables -PGSQL_URL="postgresql://zenstruck:zenstruck@postgres:5432/zenstruck_foundry?serverVersion=15" -MONGO_URL="mongodb://mongo:mongo@mongo:27017/mongo?compressors=disabled&gssapiServiceName=mongodb&authSource=mongo" - -# Default test context variables -USE_FOUNDRY_BUNDLE=1 -USE_ORM=1 -USE_ODM=1 -USE_DAMA_DOCTRINE_TEST_BUNDLE=1 -SYMFONY_REQUIRE=5.4.* -PHP_VERSION=8.0 -PREFER_LOWEST=false - -# Override test context variables with `.env` file -ifneq (,$(wildcard .env)) - include .env - export $(shell sed 's/=.*//' .env) -endif - -ifeq (${PREFER_LOWEST},1) - COMPOSER_UPDATE_OPTIONS=--prefer-dist --prefer-lowest -else - COMPOSER_UPDATE_OPTIONS=--prefer-dist -endif - -DOCKER_PHP_CONTAINER_FLAG := docker/.makefile/.docker-containers-${PHP_VERSION} -PHPSTAN_BIN := bin/tools/phpstan/vendor/phpstan/phpstan/phpstan -PSALM_BIN := bin/tools/psalm/vendor/vimeo/psalm/psalm - -ifeq ($(USE_DAMA_DOCTRINE_TEST_BUNDLE),1) - PHPUNIT_CONFIG_FILE='phpunit-dama-doctrine.xml.dist' -else - PHPUNIT_CONFIG_FILE='phpunit.xml.dist' -endif - -# Define docker executable -ifeq ($(shell docker --help | grep "compose"),) - DOCKER_COMPOSE := COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -else - DOCKER_COMPOSE := COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -endif - -# Create special context for CI -INTERACTIVE:=$(shell [ -t 0 ] && echo 1) -ifdef INTERACTIVE - DC_EXEC=$(DOCKER_COMPOSE) exec -e USE_FOUNDRY_BUNDLE=${USE_FOUNDRY_BUNDLE} -e DATABASE_URL=${PGSQL_URL} -e MONGO_URL=${MONGO_URL} -else - # CI needs to be ran in no-tty mode - DC_EXEC=$(DOCKER_COMPOSE) exec -e USE_FOUNDRY_BUNDLE=${USE_FOUNDRY_BUNDLE} -e DATABASE_URL=${PGSQL_URL} -e MONGO_URL=${MONGO_URL} -T -endif - -PHP=php${PHP_VERSION} -DOCKER_PHP=${DC_EXEC} ${PHP} -DOCKER_PHP_WITHOUT_XDEBUG=${DOCKER_PHP} php -d 'xdebug.mode=off' - -ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) -$(eval $(RUN_ARGS):;@:) - -.PHONY: help -help: - @fgrep -h "###" $(MAKEFILE_LIST) | fgrep -v fgrep | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - -.PHONY: validate -validate: sca psalm test database-validate-mapping ### Run sca, full test suite and validate migrations - -.PHONY: test -test: vendor ### Run PHPUnit tests suite - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DC_EXEC} -e USE_ORM=${USE_ORM} -e USE_ODM=${USE_ODM} ${PHP} vendor/bin/simple-phpunit --configuration ${PHPUNIT_CONFIG_FILE} $(ARGS) - -.PHONY: sca -sca: phpstan ### Run static analysis - -.PHONY: phpstan -phpstan: $(PHPSTAN_BIN) - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP_WITHOUT_XDEBUG} $(PHPSTAN_BIN) analyse - -$(PHPSTAN_BIN): vendor bin/tools/phpstan/composer.json bin/tools/phpstan/composer.lock - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP_WITHOUT_XDEBUG} /usr/bin/composer bin phpstan install - @touch -c $@ bin/tools/phpstan/composer.json bin/tools/phpstan/composer.lock - -# Psalm is only used to validate factories generated thanks to make:factory command. -.PHONY: psalm -psalm: $(PSALM_BIN) - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP_WITHOUT_XDEBUG} $(PSALM_BIN) - -$(PSALM_BIN): vendor bin/tools/psalm/composer.json bin/tools/psalm/composer.lock - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP_WITHOUT_XDEBUG} /usr/bin/composer bin psalm install - @touch -c $@ bin/tools/psalm/composer.json bin/tools/psalm/composer.lock - -.PHONY: docs -docs: vendor ### Generate documentation to docs/output - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP} ./bin/build-docs - -.PHONY: database-generate-migration -database-generate-migration: database-drop-schema ### Generate new migration based on mapping in Zenstruck\Foundry\Tests\Fixtures\Entity - @${DOCKER_PHP} vendor/bin/doctrine-migrations migrations:migrate --no-interaction --allow-no-migration # first, let's load into db existing migrations - @${DOCKER_PHP} vendor/bin/doctrine-migrations migrations:diff --no-interaction - @${DOCKER_PHP} vendor/bin/doctrine-migrations migrations:migrate --no-interaction # load the new migration - @${DOCKER_PHP} bin/doctrine orm:validate-schema - -.PHONY: database-validate-mapping -database-validate-mapping: database-drop-schema ### Validate mapping in Zenstruck\Foundry\Tests\Fixtures\Entity - @${DOCKER_PHP} vendor/bin/doctrine-migrations migrations:migrate --no-interaction --allow-no-migration - @${DOCKER_PHP} bin/doctrine orm:validate-schema - -.PHONY: database-drop-schema -database-drop-schema: vendor ### Drop database schema - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP} bin/doctrine orm:schema-tool:drop --force - @${DOCKER_PHP} vendor/bin/doctrine-migrations migrations:sync-metadata-storage # prevents the next command to fail if migrations table does not exist - @${DOCKER_PHP} bin/doctrine dbal:run-sql "TRUNCATE doctrine_migration_versions" --quiet - -.PHONY: composer -composer: ### Run composer command - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DOCKER_PHP_WITHOUT_XDEBUG} /usr/bin/composer $(ARGS) - -vendor: $(DOCKER_PHP_CONTAINER_FLAG) composer.json $(wildcard composer.lock) $(wildcard .env) - @$(MAKE) --no-print-directory docker-start-if-not-running - @${DC_EXEC} -e SYMFONY_REQUIRE=${SYMFONY_REQUIRE} ${PHP} php -d 'xdebug.mode=off' /usr/bin/composer update ${COMPOSER_UPDATE_OPTIONS} - @touch -c $@ composer.json .env composer.lock - -.PHONY: docker-start-if-not-running -docker-start-if-not-running: - @if [ -f "$(DOCKER_PHP_CONTAINER_FLAG)" ] ; then \ - if $(DOCKER_COMPOSE) ps -a | grep "${PHP}" | grep -q -v 'Up '; then \ - $(MAKE) --no-print-directory docker-start; \ - fi; \ - fi - -.PHONY: docker-build -docker-build: ### Build and start containers - @rm -f $(DOCKER_PHP_CONTAINER_FLAG) - @$(MAKE) --no-print-directory $(DOCKER_PHP_CONTAINER_FLAG) - -.PHONY: docker-start -docker-start: ### Start containers - @echo -e "\nStarting containers. This could take up to one minute.\n" - @$(DOCKER_COMPOSE) up --detach --no-build --remove-orphans postgres mongo "${PHP}"; - -.PHONY: docker-stop -docker-stop: ### Stop containers - @rm $(DOCKER_PHP_CONTAINER_FLAG) - @$(DOCKER_COMPOSE) stop - -.PHONY: docker-purge -docker-purge: docker-stop ### Purge containers - @$(DOCKER_COMPOSE) down --volumes - -$(DOCKER_PHP_CONTAINER_FLAG): - @./docker/build.sh load "${PHP_VERSION}" - @$(MAKE) --no-print-directory docker-start - @echo "" - @$(DOCKER_COMPOSE) ps - @echo "" - @${DOCKER_PHP} php -v - @echo "" - @mkdir -p docker/.makefile/ - @touch $@ - -.PHONY: clear -clear: ### Start from a fresh install (use it for troubleshooting) - rm -rf composer.lock vendor/ bin/tools/*/vendor/ docker/.makefile - -%: # black hole to prevent extra args warning - @: diff --git a/README.md b/README.md index dc1af9c5..97fd06c0 100644 --- a/README.md +++ b/README.md @@ -30,89 +30,89 @@ Want to watch a screencast 🎥 about it? Check out https://symfonycasts.com/fou ## How to contribute -The test suite of this library needs one or more database, and static analysis needs to be ran on the smaller PHP version -supported (currently PHP 7.2), then it comes with a full docker stack. - -### Install docker - -You must [install docker](https://docs.docker.com/engine/install/) and [install docker-compose](https://docs.docker.com/compose/install/) -at first before running the tests. - -### Run tests - -The library is shipped with a `Makefile` to run tests. -Each target will build and start the docker stack and install composer only if needed. - -```shell -$ make help -validate Run sca, full test suite and validate migrations -test Run PHPUnit tests suite -sca Run static analysis -docs Generate documentation to docs/output -database-generate-migration Generate new migration based on mapping in Zenstruck\Foundry\Tests\Fixtures\Entity -database-validate-mapping Validate mapping in Zenstruck\Foundry\Tests\Fixtures\Entity -database-drop-schema Drop database schema -composer Run composer command -docker-start Build and run containers -docker-stop Stop containers -docker-purge Purge containers -clear Start from a fresh install (use it for troubleshooting) -``` +### Running the Test Suite -Use double-dash to pass any PHPUnit options or arguments with `make`: -```shell -$ make test -- --stop-on-failure -$ make test -- --filter FactoryTest -# don't use "=" options value. ie: don't do this: -$ make test -- --filter=FactoryTest -``` +The test suite of this library needs one or more databases, then it comes with a docker compose configuration. -Same syntax is available for composer: -```shell -$ make composer -- info symfony/* -``` +> [!NOTE] +> Docker and PHP installed locally (with `pgsql` & `mongodb` extensions) is required. -#### Run tests in different environments +You can start the containers and run the test suite: -You can create a `.env` file to change the context in which tests will execute: -```dotenv -USE_ORM=1 -USE_ODM=1 -USE_DAMA_DOCTRINE_TEST_BUNDLE=1 -SYMFONY_REQUIRE=5.4.* # allowed values: 5.4.* | 6.0.* | 6.1.* | 6.2.* -PHP_VERSION=8.0 # allowed values: 8.0 | 8.1 | 8.2 -PREFER_LOWEST=1 # force composer to request lowest dependencies -``` +```bash +# start the container +$ docker compose up --detach -### Change docker's ports +# install dependencies +$ composer update -You can also add these variables to the `.env` file to change the ports used by docker: -```dotenv -PGSQL_PORT=5434 -MONGO_PORT=27018 +# run test suite with all available permutations +$ composer test + +# run only one permutation +$ vendor/bin/phpunit + +# run test suite with dama/doctrine-test-bundle +$ vendor/bin/phpunit -c phpunit.dama.xml.dist + +# run test suite with postgreSQL instead of MySQL +$ DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" vendor/bin/phpunit ``` -### Execute commands in php container +### Overriding the default configuration + +You can override default environment variables by creating a `.env.local` file, to easily enable permutations: -You can execute any command into the php container using docker compose: -```shell -$ docker-compose exec php [your commmand] # or "docker compose" depending on your compose version +```bash +# .env.local +DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" + +# run test suite with postgreSQL +$ vendor/bin/phpunit ``` -### Using xdebug with PhpStorm +The `.env.local` file can also be used to override the port of the database containers, +if it does not meet your local requirements. You'll also need to override docker compose configuration: -The php container is shipped with xdebug activated. You can use step by step debugging session with PhpStorm: you should -create a server called `FOUNDRY` in your PHP Remote Debug, with the IDE key `xdebug_foundry` +Here is an example to use MySQL on port `3308`: -![PhpStorm with xdebug](docs/phpstorm-xdebug-config.png) +```yaml +# docker-compose.override.yml +version: '3.9' -### Troubleshooting +services: + mysql: + ports: + - "3308:3306" +``` -IF any problem occurs with the docker stack or a `make` target, try to run `make clear`. +```dotenv +# .env.local +DATABASE_URL="mysql://root:1234@127.0.0.1:3308/foundry_test?serverVersion=5.7.42" +``` ## Migrations -Whenever an entity in the fixtures is added or updated a migration must be generated with `make migrations-generate` +Whenever an entity in the fixtures is added or updated a migration must be generated. +You can use the following code: + +> [!WARNING] +> Migrations in Foundry's test suite are made for PostgreSQL. +> So you need to enable PostgreSQL instead of MySQL to use them. + +```bash +# Apply already existing migrations +$ vendor/bin/doctrine-migrations migrations:migrate --no-interaction --allow-no-migration + +# Create a new migration file in /tests/Fixtures/Migrations +$ vendor/bin/doctrine-migrations migrations:diff --no-interaction + +# Load the new migration +$ vendor/bin/doctrine-migrations migrations:migrate --no-interaction + +# Validates the schema +$ bin/doctrine orm:validate-schema +``` ## Credit diff --git a/bin/console b/bin/console new file mode 100755 index 00000000..5944a814 --- /dev/null +++ b/bin/console @@ -0,0 +1,10 @@ +#!/usr/bin/env php +run(); diff --git a/composer.json b/composer.json index d3604ff5..17e00032 100644 --- a/composer.json +++ b/composer.json @@ -26,10 +26,12 @@ "dama/doctrine-test-bundle": "^7.0", "doctrine/doctrine-bundle": "^2.5", "doctrine/doctrine-migrations-bundle": "^2.2|^3.0", - "doctrine/mongodb-odm": "^2.0", + "doctrine/mongodb-odm": "^2.4", "doctrine/mongodb-odm-bundle": "^4.4.0", - "doctrine/orm": "^2.9", + "doctrine/orm": "^2.11", "matthiasnoback/symfony-dependency-injection-test": "^4.1", + "phpunit/phpunit": "^9.5.0", + "symfony/dotenv": "^5.4|^6.0|^7.0", "symfony/framework-bundle": "^5.4|^6.0|^7.0", "symfony/maker-bundle": "^1.49", "symfony/phpunit-bridge": "^5.4|^6.0|^7.0", @@ -65,6 +67,22 @@ "forward-command": false } }, + "scripts": { + "test": [ + "@test-no-dama", + "@test-dama", + "@test-no-foundry-bundle" + ], + "test-no-dama": "vendor/bin/phpunit", + "test-dama": "vendor/bin/phpunit -c phpunit.dama.xml.dist", + "test-no-foundry-bundle": "USE_FOUNDRY_BUNDLE=0 vendor/bin/phpunit -c phpunit.dama.xml.dist" + }, + "scripts-descriptions": { + "test": "Run all test permutations", + "test-no-dama": "Test without dama/doctrine-test-bundle", + "test-dama": "Test with dama/doctrine-test-bundle", + "test-no-foundry-bundle": "Test without foundry bundle" + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/config/cli-config.php b/config/cli-config.php deleted file mode 100644 index 1b2b22f8..00000000 --- a/config/cli-config.php +++ /dev/null @@ -1,29 +0,0 @@ -= 80100) { - $entities[] = '/app/tests/Fixtures/PHP81'; -} -$ORMconfig = ORMSetup::createAttributeMetadataConfiguration($entities, true); -$entityManager = EntityManager::create(['memory' => true, 'url' => getenv('DATABASE_URL')], $ORMconfig); - -return DependencyFactory::fromEntityManager( - new ConfigurationArray([ - 'table_storage' => [ - 'table_name' => 'doctrine_migration_versions', - ], - - 'migrations_paths' => [ - 'Zenstruck\Foundry\Tests\Fixtures\Migrations' => './tests/Fixtures/Migrations', - ], - ]), - new ExistingEntityManager($entityManager) -); diff --git a/docker-compose.yaml b/docker-compose.yaml index e2d2b1f7..618efea2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,41 +1,21 @@ version: '3.9' services: - php8.0: &php - container_name: foundry_php8.0 - image: ghcr.io/zenstruck/foundry/php:8.0 - depends_on: - postgres: - condition: service_healthy - mongo: - condition: service_healthy - volumes: - - .:/app - working_dir: /app + mysql: + image: mysql:5.7 + ports: + - "3307:3306" environment: - PHP_IDE_CONFIG: "serverName=FOUNDRY" - - php8.1: - <<: *php - container_name: foundry_php8.1 - image: ghcr.io/zenstruck/foundry/php:8.1 - - php8.2: - <<: *php - container_name: foundry_php8.2 - image: ghcr.io/zenstruck/foundry/php:8.2 + MYSQL_ROOT_PASSWORD: 1234 postgres: - container_name: foundry_postgres image: postgres:15 environment: POSTGRES_DB: zenstruck_foundry POSTGRES_PASSWORD: zenstruck POSTGRES_USER: zenstruck - volumes: - - db-data:/var/lib/postgresql/data:rw ports: - - ${PGSQL_PORT:-5432}:5432 + - "5433:5432" healthcheck: test: 'pg_isready -d zenstruck_foundry' timeout: 120s @@ -43,24 +23,10 @@ services: interval: 2s mongo: - container_name: foundry_mongo - image: mongo:4.4 - tmpfs: - - /data - environment: - - MONGO_INITDB_ROOT_USERNAME=admin - - MONGO_INITDB_ROOT_PASSWORD=admin - - MONGO_INITDB_DATABASE=mongo - - MONGO_NON_ROOT_USERNAME=mongo - - MONGO_NON_ROOT_PASSWORD=mongo + image: mongo:4 ports: - - ${MONGO_PORT:-27017}:27017 - volumes: - - ./docker/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro + - "27018:27017" healthcheck: test: echo 'db.runCommand("ping").ok' | mongo mongo:27017/test --quiet timeout: 10s retries: 10 - -volumes: - db-data: diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 17384486..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -ARG PHP_VERSION - -FROM php:${PHP_VERSION}-cli-bullseye - -COPY --from=composer:2.4 /usr/bin/composer /usr/bin/composer - -RUN set -eux ; \ - apt-get update ; \ - apt-get install --no-install-recommends -y \ - $PHPIZE_DEPS \ - git \ - curl \ - zip \ - unzip \ - libicu-dev \ - git \ - curl \ - unzip \ - procps \ - dialog \ - apt-utils \ - libpq-dev \ - libcurl4-openssl-dev \ - pkg-config \ - libssl-dev \ - ; \ - docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql; \ - docker-php-ext-install pdo pdo_mysql pdo_pgsql pgsql; \ - pecl install -f mongodb-1.15; \ - pecl install xdebug ; \ - docker-php-ext-enable mongodb xdebug ; \ - pecl clear-cache ; \ - apt-get remove -y $PHPIZE_DEPS zlib1g-dev libcurl4-openssl-dev pkg-config libssl-dev ; \ - rm -rf /var/lib/apt/lists/* ; \ - apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false - -LABEL org.opencontainers.image.source="https://github.com/zenstruck/foundry" - -# We want these last commands to be at the bottom of the file because they depend on the host -# And we want to benefit of the cache layering as much as possible - -COPY docker/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini - -# Set user rights -ARG USER=docker - -# this may be overridden to match host's machine user -ARG UID=1001 -ARG XDEBUG_HOST="172.17.0.1" - -RUN addgroup --system --gid ${UID} ${USER} ; \ - adduser --system --home /home/${USER} --disabled-password --uid ${UID} --ingroup ${USER} ${USER} ; \ - mkdir -p /app/var ; \ - chown -R ${USER}:${USER} /app ; \ - sed -i "s/%xdebug_host%/${XDEBUG_HOST}/g" /usr/local/etc/php/conf.d/xdebug.ini - -USER ${USER} - -RUN composer global require --no-progress --no-scripts --no-plugins symfony/flex ; \ - composer global config --no-plugins allow-plugins.symfony/flex true - -CMD tail -f /dev/null diff --git a/docker/build.sh b/docker/build.sh deleted file mode 100755 index ef1ff7d6..00000000 --- a/docker/build.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -# Build PHP image with buildx -# usage: ./docker/build.sh [load|push] [8.0|8.1|8.2] - -set -e - -build() { - ACTION=${1} - PHP_VERSION=${2} - - DOCKER_IMAGE_TAG="ghcr.io/zenstruck/foundry/php:${PHP_VERSION}" - - # From inside the containers, docker host ip is different in linux and macos - UNAME=$(uname -s) - if [ "${UNAME}" = "Linux" ]; then - XDEBUG_HOST="172.17.0.1" - elif [ "${UNAME}" = "Darwin" ]; then - XDEBUG_HOST="host.docker.internal" - fi - - docker pull ${DOCKER_IMAGE_TAG} || true - - DOCKER_BUILDKIT=1 docker buildx build \ - --tag "${DOCKER_IMAGE_TAG}" \ - --cache-from "${DOCKER_IMAGE_TAG}" \ - --build-arg BUILDKIT_INLINE_CACHE=1 \ - --build-arg UID="$(id -u)" \ - --build-arg PHP_VERSION="${PHP_VERSION}" \ - --build-arg XDEBUG_HOST="${XDEBUG_HOST}" \ - --file docker/Dockerfile \ - "--${ACTION}" \ - . -} - -main() { - ACTION="${1:-load}" - PHP_VERSION="${2:-8.0}" - - if [ "${ACTION}" != 'push' ] && [ "${ACTION}" != 'load' ]; then - echo "Error: action \"${1}\" invalid. Allowed actions are push|load" - exit 1; - fi - - if [ "${PHP_VERSION}" != '8.0' ] && [ "${PHP_VERSION}" != '8.1' ] && [ "${PHP_VERSION}" != '8.2' ]; then - echo "Error: given php version \"${2}\" invalid. Only 8.0, 8.1 and 8.2 are accepted." - exit 1; - fi - - # ensure default builder is used, it is needed to benefit from cache layering - docker buildx use default - - build "${ACTION}" "${PHP_VERSION}" -} - -main "$@" diff --git a/docker/mongo-init.js b/docker/mongo-init.js deleted file mode 100644 index d2c2ecbe..00000000 --- a/docker/mongo-init.js +++ /dev/null @@ -1,13 +0,0 @@ -db.createUser( - { - user: "mongo", - pwd: "mongo", - roles: [ - { - role: "readWrite", - db: "mongo" - } - ] - } -); - diff --git a/docker/xdebug.ini b/docker/xdebug.ini deleted file mode 100644 index d969fde6..00000000 --- a/docker/xdebug.ini +++ /dev/null @@ -1,12 +0,0 @@ -memory_limit=1024M - -[xdebug] -xdebug.mode=debug -xdebug.start_with_request=yes -xdebug.client_host=%xdebug_host% -xdebug.client_port=9003 -xdebug.idekey=xdebug_foundry - -# prevents a warning to be displayed in CLI -# when phpstorm does not listen incoming connexions -xdebug.log_level=0 diff --git a/docs/phpstorm-xdebug-config.png b/docs/phpstorm-xdebug-config.png deleted file mode 100644 index d28c1f434da238eeaace6449633336bb9746bf2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72874 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 HQ$iB}@eowo diff --git a/phpunit-dama-doctrine.xml.dist b/phpunit.dama.xml.dist similarity index 100% rename from phpunit-dama-doctrine.xml.dist rename to phpunit.dama.xml.dist diff --git a/tests/Fixtures/Kernel.php b/tests/Fixtures/Kernel.php index d1652f89..6ce61d6b 100644 --- a/tests/Fixtures/Kernel.php +++ b/tests/Fixtures/Kernel.php @@ -54,7 +54,7 @@ public function registerBundles(): iterable { yield new FrameworkBundle(); - if ($this->enableDoctrine && \getenv('USE_ORM')) { + if ($this->enableDoctrine && \getenv('DATABASE_URL')) { yield new DoctrineBundle(); } @@ -68,11 +68,11 @@ public function registerBundles(): iterable yield new DAMADoctrineTestBundle(); } - if (ORMDatabaseResetter::RESET_MODE_MIGRATE === $this->ormResetMode && $this->enableDoctrine && \getenv('USE_ORM')) { + if ($this->withMigrations()) { yield new DoctrineMigrationsBundle(); } - if ($this->enableDoctrine && \getenv('USE_ODM')) { + if ($this->enableDoctrine && \getenv('MONGO_URL')) { yield new DoctrineMongoDBBundle(); } } @@ -81,7 +81,18 @@ public function getCacheDir(): string { return \sprintf( "{$this->getProjectDir()}/var/cache/test/%s", - \md5(\json_encode([$this->enableDoctrine, $this->ormResetMode, $this->factoriesRegistered, $this->defaultMakeFactoryNamespace], \JSON_THROW_ON_ERROR)) + \md5( + \json_encode( + [ + $this->enableDoctrine, + $this->ormResetMode, + $this->factoriesRegistered, + $this->defaultMakeFactoryNamespace, + $this->withMigrations(), + ], + \JSON_THROW_ON_ERROR + ) + ) ); } @@ -107,7 +118,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load 'test' => true, ]); - if ($this->enableDoctrine && \getenv('USE_ORM')) { + if ($this->enableDoctrine && \getenv('DATABASE_URL')) { $mappings = [ 'Test' => [ 'is_bundle' => false, @@ -148,14 +159,14 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load } $globalState = []; - if ($this->enableDoctrine && \getenv('USE_ORM')) { + if ($this->enableDoctrine && \getenv('DATABASE_URL')) { $globalState[] = TagStory::class; $globalState[] = TagStoryAsInvokableService::class; $foundryConfig['database_resetter'] = ['orm' => ['reset_mode' => $this->ormResetMode]]; } - if ($this->enableDoctrine && \getenv('USE_ODM') && !\getenv('USE_DAMA_DOCTRINE_TEST_BUNDLE')) { + if ($this->enableDoctrine && \getenv('MONGO_URL') && !\getenv('USE_DAMA_DOCTRINE_TEST_BUNDLE')) { $globalState[] = ODMTagStory::class; $globalState[] = ODMTagStoryAsAService::class; $c->register(ODMTagStoryAsAService::class)->addTag('foundry.story'); @@ -166,7 +177,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load $c->loadFromExtension('zenstruck_foundry', $foundryConfig); } - if (ORMDatabaseResetter::RESET_MODE_MIGRATE === $this->ormResetMode) { + if ($this->withMigrations()) { $c->loadFromExtension('doctrine_migrations', [ 'migrations_paths' => [ 'Zenstruck\Foundry\Tests\Fixtures\Migrations' => '%kernel.project_dir%/tests/Fixtures/Migrations', @@ -174,7 +185,7 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ]); } - if ($this->enableDoctrine && \getenv('USE_ODM')) { + if ($this->enableDoctrine && \getenv('MONGO_URL')) { $mappings = [ 'Test' => [ 'is_bundle' => false, @@ -209,4 +220,9 @@ protected function configureContainer(ContainerBuilder $c, LoaderInterface $load ]); } } + + private function withMigrations(): bool + { + return $this->enableDoctrine && \getenv('DATABASE_URL') && \getenv('TEST_MIGRATIONS'); + } } diff --git a/tests/Fixtures/Migrations/Version20230513160345.php b/tests/Fixtures/Migrations/Version20230513160345.php deleted file mode 100644 index c821d424..00000000 --- a/tests/Fixtures/Migrations/Version20230513160345.php +++ /dev/null @@ -1,91 +0,0 @@ -addSql('CREATE SEQUENCE categories_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE comments_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE contacts_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE entity_for_relations_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE entity_with_relations_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE posts_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE tags_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE categories (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE productcategory_product (productcategory_id INT NOT NULL, product_id INT NOT NULL, PRIMARY KEY(productcategory_id, product_id))'); - $this->addSql('CREATE INDEX IDX_5BC2A6A2E26A32B1 ON productcategory_product (productcategory_id)'); - $this->addSql('CREATE INDEX IDX_5BC2A6A24584665A ON productcategory_product (product_id)'); - $this->addSql('CREATE TABLE comments (id INT NOT NULL, user_id INT NOT NULL, post_id INT NOT NULL, body TEXT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, approved BOOLEAN NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_5F9E962AA76ED395 ON comments (user_id)'); - $this->addSql('CREATE INDEX IDX_5F9E962A4B89032C ON comments (post_id)'); - $this->addSql('CREATE TABLE contacts (id INT NOT NULL, name VARCHAR(255) NOT NULL, address_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE entity_for_relations (id INT NOT NULL, manyToOne_id INT DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_C63B81552E3A088A ON entity_for_relations (manyToOne_id)'); - $this->addSql('CREATE TABLE entity_with_relations (id INT NOT NULL, oneToOne_id INT NOT NULL, oneToOneNullable_id INT DEFAULT NULL, manyToOne_id INT NOT NULL, manyToOneNullable_id INT DEFAULT NULL, manyToOneNullableDefault_id INT DEFAULT NULL, manyToOneWithNotExistingFactory_id INT NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_A9C9EC969017888C ON entity_with_relations (oneToOne_id)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_A9C9EC96DA2BFB84 ON entity_with_relations (oneToOneNullable_id)'); - $this->addSql('CREATE INDEX IDX_A9C9EC962E3A088A ON entity_with_relations (manyToOne_id)'); - $this->addSql('CREATE INDEX IDX_A9C9EC968097B86C ON entity_with_relations (manyToOneNullable_id)'); - $this->addSql('CREATE INDEX IDX_A9C9EC968572C13C ON entity_with_relations (manyToOneNullableDefault_id)'); - $this->addSql('CREATE INDEX IDX_A9C9EC96FF92FDCA ON entity_with_relations (manyToOneWithNotExistingFactory_id)'); - $this->addSql('CREATE TABLE entitywithrelations_category (entitywithrelations_id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY(entitywithrelations_id, category_id))'); - $this->addSql('CREATE INDEX IDX_CD6EBFAB337AA4F7 ON entitywithrelations_category (entitywithrelations_id)'); - $this->addSql('CREATE INDEX IDX_CD6EBFAB12469DE2 ON entitywithrelations_category (category_id)'); - $this->addSql('CREATE TABLE posts (id INT NOT NULL, category_id INT DEFAULT NULL, secondary_category_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, body TEXT NOT NULL, shortDescription VARCHAR(255) DEFAULT NULL, viewCount INT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, publishedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, mostRelevantRelatedPost_id INT DEFAULT NULL, lessRelevantRelatedPost_id INT DEFAULT NULL, type VARCHAR(255) NOT NULL, specificProperty VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_885DBAFA12469DE2 ON posts (category_id)'); - $this->addSql('CREATE INDEX IDX_885DBAFAEA0D7566 ON posts (secondary_category_id)'); - $this->addSql('CREATE INDEX IDX_885DBAFAD126F51 ON posts (mostRelevantRelatedPost_id)'); - $this->addSql('CREATE INDEX IDX_885DBAFA20DBE482 ON posts (lessRelevantRelatedPost_id)'); - $this->addSql('CREATE TABLE post_tag (post_id INT NOT NULL, tag_id INT NOT NULL, PRIMARY KEY(post_id, tag_id))'); - $this->addSql('CREATE INDEX IDX_5ACE3AF04B89032C ON post_tag (post_id)'); - $this->addSql('CREATE INDEX IDX_5ACE3AF0BAD26311 ON post_tag (tag_id)'); - $this->addSql('CREATE TABLE post_tag_secondary (post_id INT NOT NULL, tag_id INT NOT NULL, PRIMARY KEY(post_id, tag_id))'); - $this->addSql('CREATE INDEX IDX_1515F0214B89032C ON post_tag_secondary (post_id)'); - $this->addSql('CREATE INDEX IDX_1515F021BAD26311 ON post_tag_secondary (tag_id)'); - $this->addSql('CREATE TABLE post_post (post_source INT NOT NULL, post_target INT NOT NULL, PRIMARY KEY(post_source, post_target))'); - $this->addSql('CREATE INDEX IDX_93DF0B866FA89B16 ON post_post (post_source)'); - $this->addSql('CREATE INDEX IDX_93DF0B86764DCB99 ON post_post (post_target)'); - $this->addSql('CREATE TABLE product_tag (product_id INT NOT NULL, tag_id INT NOT NULL, PRIMARY KEY(product_id, tag_id))'); - $this->addSql('CREATE INDEX IDX_E3A6E39C4584665A ON product_tag (product_id)'); - $this->addSql('CREATE INDEX IDX_E3A6E39CBAD26311 ON product_tag (tag_id)'); - $this->addSql('CREATE TABLE tags (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE users (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('ALTER TABLE comments ADD CONSTRAINT FK_5F9E962AA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE comments ADD CONSTRAINT FK_5F9E962A4B89032C FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_for_relations ADD CONSTRAINT FK_C63B81552E3A088A FOREIGN KEY (manyToOne_id) REFERENCES entity_with_relations (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC969017888C FOREIGN KEY (oneToOne_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC96DA2BFB84 FOREIGN KEY (oneToOneNullable_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC962E3A088A FOREIGN KEY (manyToOne_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC968097B86C FOREIGN KEY (manyToOneNullable_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC968572C13C FOREIGN KEY (manyToOneNullableDefault_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entitywithrelations_category ADD CONSTRAINT FK_CD6EBFAB337AA4F7 FOREIGN KEY (entitywithrelations_id) REFERENCES entity_with_relations (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entitywithrelations_category ADD CONSTRAINT FK_CD6EBFAB12469DE2 FOREIGN KEY (category_id) REFERENCES categories (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE posts ADD CONSTRAINT FK_885DBAFA12469DE2 FOREIGN KEY (category_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE posts ADD CONSTRAINT FK_885DBAFAEA0D7566 FOREIGN KEY (secondary_category_id) REFERENCES categories (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE posts ADD CONSTRAINT FK_885DBAFAD126F51 FOREIGN KEY (mostRelevantRelatedPost_id) REFERENCES posts (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE posts ADD CONSTRAINT FK_885DBAFA20DBE482 FOREIGN KEY (lessRelevantRelatedPost_id) REFERENCES posts (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_tag ADD CONSTRAINT FK_5ACE3AF04B89032C FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_tag ADD CONSTRAINT FK_5ACE3AF0BAD26311 FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_tag_secondary ADD CONSTRAINT FK_1515F0214B89032C FOREIGN KEY (post_id) REFERENCES posts (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_tag_secondary ADD CONSTRAINT FK_1515F021BAD26311 FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_post ADD CONSTRAINT FK_93DF0B866FA89B16 FOREIGN KEY (post_source) REFERENCES posts (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE post_post ADD CONSTRAINT FK_93DF0B86764DCB99 FOREIGN KEY (post_target) REFERENCES posts (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - } - - public function down(Schema $schema): void - { - } -} diff --git a/tests/Fixtures/Migrations/Version20230513160346.php b/tests/Fixtures/Migrations/Version20230513160346.php deleted file mode 100644 index 5aca419c..00000000 --- a/tests/Fixtures/Migrations/Version20230513160346.php +++ /dev/null @@ -1,63 +0,0 @@ -addSql('CREATE SEQUENCE brand_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE category_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE image_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE product_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE review_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE tag_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE SEQUENCE variant_cascade_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE brand_cascade (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE category_cascade (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE image_cascade (id INT NOT NULL, path VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE product_cascade (id INT NOT NULL, brand_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_D7FE16D844F5D008 ON product_cascade (brand_id)'); - $this->addSql('CREATE TABLE review_cascade (id INT NOT NULL, product_id INT DEFAULT NULL, ranking INT NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_9DC9B99F4584665A ON review_cascade (product_id)'); - $this->addSql('CREATE TABLE tag_cascade (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE TABLE variant_cascade (id INT NOT NULL, product_id INT DEFAULT NULL, image_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_6982202E4584665A ON variant_cascade (product_id)'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_6982202E3DA5256D ON variant_cascade (image_id)'); - $this->addSql('ALTER TABLE product_cascade ADD CONSTRAINT FK_D7FE16D844F5D008 FOREIGN KEY (brand_id) REFERENCES brand_cascade (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE review_cascade ADD CONSTRAINT FK_9DC9B99F4584665A FOREIGN KEY (product_id) REFERENCES product_cascade (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE variant_cascade ADD CONSTRAINT FK_6982202E4584665A FOREIGN KEY (product_id) REFERENCES product_cascade (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE variant_cascade ADD CONSTRAINT FK_6982202E3DA5256D FOREIGN KEY (image_id) REFERENCES image_cascade (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE productcategory_product ADD CONSTRAINT FK_5BC2A6A2E26A32B1 FOREIGN KEY (productcategory_id) REFERENCES category_cascade (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE productcategory_product ADD CONSTRAINT FK_5BC2A6A24584665A FOREIGN KEY (product_id) REFERENCES product_cascade (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE entity_with_relations ADD CONSTRAINT FK_A9C9EC96FF92FDCA FOREIGN KEY (manyToOneWithNotExistingFactory_id) REFERENCES brand_cascade (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE product_tag ADD CONSTRAINT FK_E3A6E39C4584665A FOREIGN KEY (product_id) REFERENCES product_cascade (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - $this->addSql('ALTER TABLE product_tag ADD CONSTRAINT FK_E3A6E39CBAD26311 FOREIGN KEY (tag_id) REFERENCES tag_cascade (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); - - $this->addSql('CREATE SEQUENCE entity_with_property_name_different_from_construct_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE entity_with_property_name_different_from_construct (id INT NOT NULL, entity_id INT DEFAULT NULL, someField VARCHAR(255) NOT NULL, address_value VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); - $this->addSql('CREATE INDEX IDX_AA016C6381257D5D ON entity_with_property_name_different_from_construct (entity_id)'); - $this->addSql('ALTER TABLE entity_with_property_name_different_from_construct ADD CONSTRAINT FK_AA016C6381257D5D FOREIGN KEY (entity_id) REFERENCES entity_for_relations (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); - - if (PHP_VERSION_ID < 80100) { - return; - } - - $this->addSql('CREATE SEQUENCE entity_with_enum_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql('CREATE TABLE entity_with_enum (id INT NOT NULL, enum VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); - } - - public function down(Schema $schema): void - { - } -} diff --git a/tests/Functional/Bundle/Maker/MakeFactoryTest.php b/tests/Functional/Bundle/Maker/MakeFactoryTest.php index eac8febd..c50a2d42 100644 --- a/tests/Functional/Bundle/Maker/MakeFactoryTest.php +++ b/tests/Functional/Bundle/Maker/MakeFactoryTest.php @@ -73,7 +73,7 @@ protected function tearDown(): void */ public function can_create_factory(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -94,7 +94,7 @@ public function can_create_factory(): void */ public function can_create_factory_interactively(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -123,7 +123,7 @@ public function can_create_factory_interactively(): void */ public function can_create_factory_in_test_dir(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -139,7 +139,7 @@ public function can_create_factory_in_test_dir(): void */ public function can_create_factory_in_a_sub_directory(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -157,7 +157,7 @@ public function can_create_factory_in_a_sub_directory(): void */ public function can_create_factory_in_test_dir_interactively(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -175,7 +175,7 @@ public function can_create_factory_in_test_dir_interactively(): void */ public function can_create_factory_with_static_analysis_annotations(string $scaTool): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -194,7 +194,7 @@ public function can_create_factory_with_static_analysis_annotations(string $scaT */ public function can_create_factory_for_entity_with_repository(string $scaTool): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -267,7 +267,7 @@ public function can_create_factory_for_not_persisted_class_interactively(): void */ public function can_customize_namespace(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -286,7 +286,7 @@ public function can_customize_namespace(): void */ public function can_customize_namespace_with_test_flag(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -305,7 +305,7 @@ public function can_customize_namespace_with_test_flag(): void */ public function can_customize_namespace_with_root_namespace_prefix(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -324,7 +324,7 @@ public function can_customize_namespace_with_root_namespace_prefix(): void */ public function can_customize_namespace_with_test_flag_with_root_namespace_prefix(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -346,7 +346,7 @@ public function can_create_all_factories_for_doctrine_objects(): void $tester = $this->makeFactoryCommandTester(); $inputs = ['All']; // which factory to generate? - if (\getenv('USE_ORM')) { + if (\getenv('DATABASE_URL')) { $inputs[] = 'OtherTagFactory'; // name collision handling (only for ORM, the collision is caused my an ORM class } @@ -355,13 +355,13 @@ public function can_create_all_factories_for_doctrine_objects(): void $expectedFactories = []; - if (\getenv('USE_ORM')) { + if (\getenv('DATABASE_URL')) { $expectedFactories = ['Cascade/BrandFactory', 'CategoryFactory', 'CommentFactory', 'ContactFactory', 'EntityForRelationsFactory', 'UserFactory', 'TagFactory', 'Cascade/TagFactory']; } - if (\getenv('USE_ODM')) { + if (\getenv('MONGO_URL')) { $expectedFactories = [...$expectedFactories, 'ODMCategoryFactory', 'ODMCommentFactory', 'ODMPostFactory', 'ODMUserFactory']; - $expectedFactories[] = \getenv('USE_ORM') ? 'OtherTagFactory' : 'TagFactory'; + $expectedFactories[] = \getenv('DATABASE_URL') ? 'OtherTagFactory' : 'TagFactory'; } self::assertGreaterThan(0, \count($expectedFactories)); @@ -376,7 +376,7 @@ public function can_create_all_factories_for_doctrine_objects(): void */ public function can_create_factory_for_odm(string $class, string $file): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } @@ -418,7 +418,7 @@ public function can_create_factory_with_auto_activated_not_persisted_option(): v */ public function can_create_factory_with_relation_defaults(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -434,7 +434,7 @@ public function can_create_factory_with_relation_defaults(): void */ public function can_create_factory_with_relation_for_all_fields(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } @@ -461,11 +461,11 @@ public function can_create_factory_with_embeddable(string $objectClass, string $ public function objectsWithEmbeddableProvider(): iterable { - if (\getenv('USE_ORM')) { + if (\getenv('DATABASE_URL')) { yield 'orm' => [Contact::class, 'ContactFactory', 'AddressFactory']; } - if (\getenv('USE_ODM')) { + if (\getenv('MONGO_URL')) { yield 'odm' => [ODMPost::class, 'ODMPostFactory', 'ODMUserFactory', [CommentFactory::class]]; } } @@ -487,11 +487,11 @@ public function can_create_factory_with_default_enum(string $class, bool $noPers public function canCreateFactoryWithDefaultEnumProvider(): iterable { - if (\getenv('USE_ORM')) { + if (\getenv('DATABASE_URL')) { yield 'orm' => [EntityWithEnum::class]; } - if (\getenv('USE_ODM')) { + if (\getenv('MONGO_URL')) { yield 'odm' => [DocumentWithEnum::class]; } @@ -503,7 +503,7 @@ public function canCreateFactoryWithDefaultEnumProvider(): iterable */ public function can_create_factory_for_orm_embedded_class(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } @@ -520,7 +520,7 @@ public function can_create_factory_for_orm_embedded_class(): void */ public function it_handles_name_collision(): void { - if (!\getenv('USE_ORM') || !\getenv('USE_ODM')) { + if (!\getenv('DATABASE_URL') || !\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm and doctrine/orm should be enabled enabled.'); } @@ -548,7 +548,7 @@ public function it_handles_name_collision(): void */ public function it_uses_default_namespace_from_configuration(string $defaultNamespace): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } diff --git a/tests/Functional/Bundle/Maker/MakerTestCase.php b/tests/Functional/Bundle/Maker/MakerTestCase.php index 280d3fc3..15cf0a68 100644 --- a/tests/Functional/Bundle/Maker/MakerTestCase.php +++ b/tests/Functional/Bundle/Maker/MakerTestCase.php @@ -29,7 +29,7 @@ public function skipIfNotUsingFoundryBundle(): void $this->markTestSkipped('ZenstruckFoundryBundle not enabled.'); } - if (!\getenv('USE_ORM') && !\getenv('USE_ODM')) { + if (!\getenv('DATABASE_URL') && !\getenv('MONGO_URL')) { $this->markTestSkipped('Generating factories for classes not managed by doctrine is not supported.'); } } diff --git a/tests/Functional/FactoryDoctrineCascadeTest.php b/tests/Functional/FactoryDoctrineCascadeTest.php index fe6a1e7d..39f0d01a 100644 --- a/tests/Functional/FactoryDoctrineCascadeTest.php +++ b/tests/Functional/FactoryDoctrineCascadeTest.php @@ -34,7 +34,7 @@ final class FactoryDoctrineCascadeTest extends KernelTestCase protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/FactoryTest.php b/tests/Functional/FactoryTest.php index 33c4f760..da374eee 100644 --- a/tests/Functional/FactoryTest.php +++ b/tests/Functional/FactoryTest.php @@ -37,7 +37,7 @@ final class FactoryTest extends KernelTestCase protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/FakerTest.php b/tests/Functional/FakerTest.php index 59e274ea..515a01ea 100644 --- a/tests/Functional/FakerTest.php +++ b/tests/Functional/FakerTest.php @@ -22,6 +22,13 @@ final class FakerTest extends KernelTestCase { use Factories; + protected function setUp(): void + { + if (!\getenv('USE_FOUNDRY_BUNDLE')) { + $this->markTestSkipped('ZenstruckFoundryBundle not enabled.'); + } + } + /** * @test */ diff --git a/tests/Functional/ModelFactoryServiceTest.php b/tests/Functional/ModelFactoryServiceTest.php index 703ad574..06f2e178 100644 --- a/tests/Functional/ModelFactoryServiceTest.php +++ b/tests/Functional/ModelFactoryServiceTest.php @@ -28,7 +28,7 @@ final class ModelFactoryServiceTest extends KernelTestCase protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/ODMAnonymousFactoryTest.php b/tests/Functional/ODMAnonymousFactoryTest.php index c7d92c8f..edab8759 100644 --- a/tests/Functional/ODMAnonymousFactoryTest.php +++ b/tests/Functional/ODMAnonymousFactoryTest.php @@ -23,7 +23,7 @@ final class ODMAnonymousFactoryTest extends AnonymousFactoryTest { protected function setUp(): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } } diff --git a/tests/Functional/ODMGlobalStateTest.php b/tests/Functional/ODMGlobalStateTest.php index 43bb8a49..c858c97a 100644 --- a/tests/Functional/ODMGlobalStateTest.php +++ b/tests/Functional/ODMGlobalStateTest.php @@ -19,7 +19,7 @@ final class ODMGlobalStateTest extends GlobalStateTest { protected function setUp(): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } diff --git a/tests/Functional/ODMModelFactoryTest.php b/tests/Functional/ODMModelFactoryTest.php index f7f29dc2..0cd87773 100644 --- a/tests/Functional/ODMModelFactoryTest.php +++ b/tests/Functional/ODMModelFactoryTest.php @@ -28,7 +28,7 @@ final class ODMModelFactoryTest extends ModelFactoryTest { protected function setUp(): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } } diff --git a/tests/Functional/ODMProxyTest.php b/tests/Functional/ODMProxyTest.php index 19e5e186..643fa122 100644 --- a/tests/Functional/ODMProxyTest.php +++ b/tests/Functional/ODMProxyTest.php @@ -21,7 +21,7 @@ final class ODMProxyTest extends ProxyTest { protected function setUp(): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } } diff --git a/tests/Functional/ODMRepositoryProxyTest.php b/tests/Functional/ODMRepositoryProxyTest.php index 1f0a00ce..e84ac223 100644 --- a/tests/Functional/ODMRepositoryProxyTest.php +++ b/tests/Functional/ODMRepositoryProxyTest.php @@ -21,7 +21,7 @@ final class ODMRepositoryProxyTest extends RepositoryProxyTest { protected function setUp(): void { - if (!\getenv('USE_ODM')) { + if (!\getenv('MONGO_URL')) { self::markTestSkipped('doctrine/odm not enabled.'); } } diff --git a/tests/Functional/ORMAnonymousFactoryTest.php b/tests/Functional/ORMAnonymousFactoryTest.php index 7cd1ddea..5a93193a 100644 --- a/tests/Functional/ORMAnonymousFactoryTest.php +++ b/tests/Functional/ORMAnonymousFactoryTest.php @@ -23,7 +23,7 @@ final class ORMAnonymousFactoryTest extends AnonymousFactoryTest { protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/ORMDatabaseResetterTest.php b/tests/Functional/ORMDatabaseResetterTest.php index 59453d72..e1b19005 100644 --- a/tests/Functional/ORMDatabaseResetterTest.php +++ b/tests/Functional/ORMDatabaseResetterTest.php @@ -28,13 +28,9 @@ public static function setUpBeforeClass(): void self::markTestSkipped('The database should not be reset if dama/doctrine-test-bundle is enabled.'); } - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm is not enabled.'); } - - if (!\str_starts_with(\getenv('DATABASE_URL'), 'postgres')) { - self::markTestSkipped('Can only test migrations with postgresql.'); - } } /** @@ -60,7 +56,10 @@ public function it_resets_database_correctly(string $resetMode): void public function databaseResetterProvider(): iterable { yield [ORMDatabaseResetter::RESET_MODE_SCHEMA]; - yield [ORMDatabaseResetter::RESET_MODE_MIGRATE]; + + if (getenv('TEST_MIGRATIONS')) { + yield [ORMDatabaseResetter::RESET_MODE_MIGRATE]; + } } protected static function createKernel(array $options = []): KernelInterface diff --git a/tests/Functional/ORMGlobalStateTest.php b/tests/Functional/ORMGlobalStateTest.php index a7429c61..a2d4d8bb 100644 --- a/tests/Functional/ORMGlobalStateTest.php +++ b/tests/Functional/ORMGlobalStateTest.php @@ -18,7 +18,7 @@ final class ORMGlobalStateTest extends GlobalStateTest { protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } diff --git a/tests/Functional/ORMModelFactoryTest.php b/tests/Functional/ORMModelFactoryTest.php index ef345a8c..84a7328a 100644 --- a/tests/Functional/ORMModelFactoryTest.php +++ b/tests/Functional/ORMModelFactoryTest.php @@ -37,7 +37,7 @@ final class ORMModelFactoryTest extends ModelFactoryTest { protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/ORMProxyTest.php b/tests/Functional/ORMProxyTest.php index 251295c3..1436ec99 100644 --- a/tests/Functional/ORMProxyTest.php +++ b/tests/Functional/ORMProxyTest.php @@ -26,7 +26,7 @@ final class ORMProxyTest extends ProxyTest { protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/ORMRepositoryProxyTest.php b/tests/Functional/ORMRepositoryProxyTest.php index f3cc23ff..2a6339fd 100644 --- a/tests/Functional/ORMRepositoryProxyTest.php +++ b/tests/Functional/ORMRepositoryProxyTest.php @@ -26,7 +26,7 @@ final class ORMRepositoryProxyTest extends RepositoryProxyTest { protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/StoryTest.php b/tests/Functional/StoryTest.php index 652e1aa8..99f1f51c 100644 --- a/tests/Functional/StoryTest.php +++ b/tests/Functional/StoryTest.php @@ -34,7 +34,7 @@ final class StoryTest extends KernelTestCase protected function setUp(): void { - if (!\getenv('USE_ORM')) { + if (!\getenv('DATABASE_URL')) { self::markTestSkipped('doctrine/orm not enabled.'); } } diff --git a/tests/Functional/WithMigrationTest.php b/tests/Functional/WithMigrationTest.php index 8f23b3e1..616f9b7f 100644 --- a/tests/Functional/WithMigrationTest.php +++ b/tests/Functional/WithMigrationTest.php @@ -16,6 +16,7 @@ use Doctrine\ORM\Tools\SchemaValidator; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\HttpKernel\KernelInterface; +use Zenstruck\Foundry\Test\Factories; use Zenstruck\Foundry\Test\ORMDatabaseResetter; use Zenstruck\Foundry\Test\ResetDatabase; use Zenstruck\Foundry\Tests\Fixtures\Factories\PostFactory; @@ -24,6 +25,7 @@ final class WithMigrationTest extends KernelTestCase { use ResetDatabase; + use Factories; /** * @test @@ -48,15 +50,11 @@ public function it_can_use_schema_reset_with_migration(): void protected static function createKernel(array $options = []): KernelInterface { // ResetDatabase trait uses @beforeClass annotation which would always be called before setUpBeforeClass() - // but it also calls "static::createKernel()" which we can use to skip test if USE_ORM is false. - if (!\getenv('USE_ORM')) { + // but it also calls "static::createKernel()" which we can use to skip test if DATABASE_URL is false. + if (!\getenv('DATABASE_URL') || !\getenv('TEST_MIGRATIONS')) { self::markTestSkipped('doctrine/orm not enabled.'); } - if (!\str_starts_with(\getenv('DATABASE_URL'), 'postgres')) { - self::markTestSkipped('Can only test migrations with postgresql.'); - } - return Kernel::create(true, ORMDatabaseResetter::RESET_MODE_MIGRATE); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index aac0fe40..5ef599e3 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -9,8 +9,34 @@ * file that was distributed with this source code. */ +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Dotenv\Dotenv; use Symfony\Component\Filesystem\Filesystem; +use Zenstruck\Foundry\Tests\Fixtures\Kernel; require \dirname(__DIR__).'/vendor/autoload.php'; -(new Filesystem())->remove(__DIR__.'/../var'); +(new Dotenv())->usePutenv()->loadEnv(__DIR__.'/../.env'); + +$fs = new Filesystem(); + +$fs->remove(__DIR__.'/../var'); + +if (\getenv('DATABASE_URL') && \getenv('TEST_MIGRATIONS')) { + $fs->remove(__DIR__.'/Fixtures/Migrations'); + $fs->mkdir(__DIR__.'/Fixtures/Migrations'); + + $kernel = new Kernel('test', true); + $kernel->boot(); + + $application = new Application($kernel); + $application->setAutoExit(false); + + $application->run(new StringInput('doctrine:database:create --if-not-exists --no-interaction'), new NullOutput()); + $application->run(new StringInput('doctrine:schema:drop --force --no-interaction'), new NullOutput()); + $application->run(new StringInput('doctrine:migrations:diff --no-interaction'), new NullOutput()); + + $kernel->shutdown(); +}