From 0bf6258309ad120394adfe3ab4cd3b92f62d788b Mon Sep 17 00:00:00 2001 From: tm-hdaoud Date: Thu, 28 Aug 2025 23:15:50 +0000 Subject: [PATCH 1/6] hub#18022 Ajout des standards de code et premier test --- .editorconfig | 22 + .githooks/pre-commit | 10 + .gitignore | 8 + Build/Scripts/runTests.sh | 675 ++++++++++++++++++ Build/php-cs-fixer/php-cs-fixer.php | 67 ++ Build/phpunit/FunctionalTests.xml | 27 + Build/phpunit/FunctionalTestsBootstrap.php | 33 + Build/phpunit/UnitTests.xml | 28 + Build/phpunit/UnitTestsBootstrap.php | 90 +++ CHANGELOG.md | 6 +- Classes/Command/ClearSysLogCommand.php | 31 +- .../Command/ExportCtypeListTypeCommand.php | 14 +- Classes/Command/FixDatabaseErrorsCommand.php | 11 +- Classes/Command/RunSqlScriptCommand.php | 40 +- Classes/Command/UpgradeWizardRunCommand.php | 32 +- ...ionService.php => SQLMigrationService.php} | 7 +- .../Updates/AbstractListTypeToCTypeUpdate.php | 26 +- .../Upgrades/CTypeToListTypeUpgradeWizard.php | 7 +- Classes/Upgrades/FixRedirectsUpgraeWizard.php | 23 +- .../GridElementsToContainerUpgradeWizard.php | 10 +- .../TruncateLogTableUpgradeWizard.php | 19 +- Classes/Utility/ConfigurationUtility.php | 14 +- .../Utility/UpgardeWizardsMappingUtility.php | 8 +- .../MigratePagesLanguageOverlayUpdate.php | 5 +- .../WorkspacesNotificationSettingsUpdate.php | 2 +- .../Private/Config/Fractor/fractor_v12.php | 6 +- .../Private/Config/Fractor/fractor_v13.php | 6 +- .../Private/Config/Rector/rector_v12.php | 12 +- .../Private/Config/Rector/rector_v13.php | 5 +- .../Fixtures/queries_to_migrate.sql | 25 + .../Service/SQLMigrationServiceTest.php | 46 ++ Tests/Unit/.gitkeep | 0 composer.json | 27 +- ext_emconf.php | 4 +- ext_localconf.php | 3 +- ext_tables.php | 3 +- 36 files changed, 1204 insertions(+), 148 deletions(-) create mode 100644 .editorconfig create mode 100644 .githooks/pre-commit create mode 100644 .gitignore create mode 100755 Build/Scripts/runTests.sh create mode 100644 Build/php-cs-fixer/php-cs-fixer.php create mode 100644 Build/phpunit/FunctionalTests.xml create mode 100644 Build/phpunit/FunctionalTestsBootstrap.php create mode 100644 Build/phpunit/UnitTests.xml create mode 100644 Build/phpunit/UnitTestsBootstrap.php rename Classes/Service/{SqlMigrationService.php => SQLMigrationService.php} (94%) create mode 100644 Tests/Functional/Fixtures/queries_to_migrate.sql create mode 100644 Tests/Functional/Service/SQLMigrationServiceTest.php create mode 100644 Tests/Unit/.gitkeep diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..396494f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +root = true + +[*] +# Unix-style newlines with a newline ending every file +end_of_line = lf +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +# TS/JS-Files +[*.{ts,js}] +indent_size = 2 + +# ReST-Files +[*.rst] +indent_size = 3 +max_line_length = 80 + +[{ext_conf_template.txt}] +indent_size = 2 \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..99265cc --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Pre-commit Git hook + +# Install all before tests +composer install + +composer php:cs +if [[ $? != 0 ]]; then + exit 1 +fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd8e702 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.Build +/.cache +/.idea +/composer.lock +/vendor +/typo3temp +/Build/phpunit/.phpunit.result.cache +.php-cs-fixer.cache \ No newline at end of file diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh new file mode 100755 index 0000000..da6bce3 --- /dev/null +++ b/Build/Scripts/runTests.sh @@ -0,0 +1,675 @@ +#!/usr/bin/env bash + +# +# TYPO3 core test runner based on docker. +# + +trap 'cleanUp;exit 2' SIGINT + +waitFor() { + local HOST=${1} + local PORT=${2} + local TESTCOMMAND=" + COUNT=0; + while ! nc -z ${HOST} ${PORT}; do + if [ \"\${COUNT}\" -gt 20 ]; then + echo \"Can not connect to ${HOST} port ${PORT}. Aborting.\"; + exit 1; + fi; + sleep 1; + COUNT=\$((COUNT + 1)); + done; + " + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name wait-for-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_ALPINE} /bin/sh -c "${TESTCOMMAND}" + if [[ $? -gt 0 ]]; then + kill -SIGINT -$$ + fi +} + +cleanUp() { + ATTACHED_CONTAINERS=$(${CONTAINER_BIN} ps --filter network=${NETWORK} --format='{{.Names}}') + for ATTACHED_CONTAINER in ${ATTACHED_CONTAINERS}; do + ${CONTAINER_BIN} kill ${ATTACHED_CONTAINER} >/dev/null + done + ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null +} + +handleDbmsOptions() { + # -a, -d, -i depend on each other. Validate input combinations and set defaults. + case ${DBMS} in + mariadb) + [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli" + if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then + echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.3" + if ! [[ ${DBMS_VERSION} =~ ^(10.3|10.4|10.5|10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then + echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + mysql) + [ -z "${DATABASE_DRIVER}" ] && DATABASE_DRIVER="mysqli" + if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then + echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="8.0" + if ! [[ ${DBMS_VERSION} =~ ^(8.0)$ ]]; then + echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + postgres) + if [ -n "${DATABASE_DRIVER}" ]; then + echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10" + if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then + echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + sqlite) + if [ -n "${DATABASE_DRIVER}" ]; then + echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + if [ -n "${DBMS_VERSION}" ]; then + echo "Invalid combination -d ${DBMS} -i ${DATABASE_DRIVER}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + fi + ;; + *) + echo "Invalid option -d ${DBMS}" >&2 + echo >&2 + echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + exit 1 + ;; + esac +} + +cleanCacheFiles() { + echo -n "Clean caches ... " + rm -rf \ + .Build/.cache \ + .php-cs-fixer.cache + echo "done" +} + +cleanTestFiles() { + # test related + echo -n "Clean test related files ... " + rm -rf \ + .Build/public/typo3temp/var/tests/ + echo "done" +} + +cleanRenderedDocumentationFiles() { + echo -n "Clean rendered documentation files ... " + rm -rf \ + Documentation-GENERATED-temp + echo "done" +} + +cleanComposer() { + rm -rf \ + .Build/vendor \ + .Build/bin \ + composer.lock +} + +stashComposerFiles() { + cp composer.json composer.json.orig + if [ -f "composer.json.testing" ]; then + cp composer.json composer.json.orig + fi +} + +restoreComposerFiles() { + cp composer.json composer.json.testing + mv composer.json.orig composer.json +} + +loadHelp() { + # Load help text into $HELP + read -r -d '' HELP < + Specifies which test suite to run + - cgl: cgl test and fix all php files + - clean: clean up build and testing related files + - cleanRenderedDocumentation: clean up rendered documentation files and folders (Documentation-GENERATED-temp) + - composer: Execute "composer" command, using -e for command arguments pass-through, ex. -e "ci:php:stan" + - composerInstall: "composer update", handy if host has no PHP + - composerInstallLowest: "composer update", handy if host has no PHP + - composerInstallHighest: "composer update", handy if host has no PHP + - coveralls: Generate coverage + - docsGenerate: Renders the extension ReST documentation. + - rector: Run rector + - fractor: Run Fractor + - functional: functional tests + - lint: PHP linting + - unit: PHP unit tests + + -a + Only with -s functional|functionalDeprecated + Specifies to use another driver, following combinations are available: + - mysql + - mysqli (default) + - pdo_mysql + - mariadb + - mysqli (default) + - pdo_mysql + + -b + Container environment: + - docker (default) + - podman + + -d + Only with -s functional|functionalDeprecated + Specifies on which DBMS tests are performed + - sqlite: (default): use sqlite + - mariadb: use mariadb + - mysql: use MySQL + - postgres: use postgres + + -i version + Specify a specific database version + With "-d mariadb": + - 10.3 short-term, maintained until 2023-05-25 (default) + - 10.4 short-term, maintained until 2024-06-18 + - 10.5 short-term, maintained until 2025-06-24 + - 10.6 long-term, maintained until 2026-06 + - 10.7 short-term, no longer maintained + - 10.8 short-term, maintained until 2023-05 + - 10.9 short-term, maintained until 2023-08 + - 10.10 short-term, maintained until 2023-11 + - 10.11 long-term, maintained until 2028-02 + - 11.0 development series + - 11.1 short-term development series + With "-d mysql": + - 8.0 maintained until 2026-04 (default) + With "-d postgres": + - 10 unmaintained since 2022-11-10 (default) + - 11 unmaintained since 2023-11-09 + - 12 maintained until 2024-11-14 + - 13 maintained until 2025-11-13 + - 14 maintained until 2026-11-12 + - 15 maintained until 2027-11-11 + - 16 maintained until 2028-11-09 + + -t <12|13> + Only with -s composerInstall|composerInstallMin|composerInstallMax + Specifies the TYPO3 CORE Version to be used + - 12: use TYPO3 v12 (default) + - 13: use TYPO3 v13 + + -p <8.1|8.2|8.3|8.4> + Specifies the PHP minor version to be used + - 8.1: use PHP 8.1 (default) + - 8.2: use PHP 8.2 + - 8.3: use PHP 8.3 + - 8.4: use PHP 8.4 + + -e "" + Only with -s docsGenerate|functional|unit + Additional options to send to phpunit (unit & functional tests). For phpunit, + options starting with "--" must be added after options starting with "-". + Example -e "--filter classCanBeRegistered" to enable verbose output AND filter tests + named "classCanBeRegistered" + + DEPRECATED - pass arguments after the `--` separator directly. For example, instead of + Build/Scripts/runTests.sh -s unit -e "--filter classCanBeRegistered" + use + Build/Scripts/runTests.sh -s unit -- --filter classCanBeRegistered + + -x + Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom + Send information to host instance for test or system under test break points. This is especially + useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port + can be selected with -y + + -y + Send xdebug information to a different port than default 9003 if an IDE like PhpStorm + is not listening on default port. + + -n + Only with -s cgl|composerNormalize|fractor + Activate dry-run in CGL check that does not actively change files and only prints broken ones. + + -u + Update existing typo3/core-testing-*:latest container images and remove dangling local volumes. + New images are published once in a while and only the latest ones are supported by core testing. + Use this if weird test errors occur. Also removes obsolete image versions of typo3/core-testing-*. + + -h + Show this help. + +Examples: + # Run all core unit tests + ./Build/Scripts/runTests.sh -s unit + + # Run all core units tests and enable xdebug (have a PhpStorm listening on port 9003!) + ./Build/Scripts/runTests.sh -x -s unit + + # Run unit tests in phpunit verbose mode with xdebug on PHP 8.1 and filter for test canRetrieveValueWithGP + ./Build/Scripts/runTests.sh -x -p 8.1 -- --filter 'classCanBeRegistered' + + # Run functional tests in phpunit with a filtered test method name in a specified file + # example will currently execute two tests, both of which start with the search term + ./Build/Scripts/runTests.sh -s functional -- --filter 'findRecordByImportSource' Tests/Functional/Repository/CategoryRepositoryTest.php + + # Run functional tests on postgres with xdebug, php 8.1 and execute a restricted set of tests + ./Build/Scripts/runTests.sh -x -p 8.1 -s functional -d postgres -- Tests/Functional/Repository/CategoryRepositoryTest.php + + # Run functional tests on postgres 11 + ./Build/Scripts/runTests.sh -s functional -d postgres -i 11 +EOF +} + +# Test if at least one of the supported container binaries exists, else exit out with error +if ! type "docker" >/dev/null 2>&1 && ! type "podman" >/dev/null 2>&1; then + echo "This script relies on docker or podman. Please install at least one of them" >&2 + exit 1 +fi + +# Go to the directory this script is located, so everything else is relative +# to this dir, no matter from where this script is called, then go up two dirs. +THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd "$THIS_SCRIPT_DIR" || exit 1 +cd ../../ || exit 1 +ROOT_DIR="${PWD}" + +# Option defaults +TEST_SUITE="" +TYPO3_VERSION="12" +DBMS="sqlite" +DBMS_VERSION="" +PHP_VERSION="8.1" +PHP_XDEBUG_ON=0 +PHP_XDEBUG_PORT=9003 +EXTRA_TEST_OPTIONS="" +DRY_RUN=0 +DATABASE_DRIVER="" +CONTAINER_BIN="" +COMPOSER_ROOT_VERSION="12.0.0-dev" +CONTAINER_INTERACTIVE="-it --init" +HOST_UID=$(id -u) +HOST_PID=$(id -g) +USERSET="" +SUFFIX=$(echo $RANDOM) +NETWORK="toumoro-tm-migration-${SUFFIX}" +CI_PARAMS="${CI_PARAMS:-}" +CONTAINER_HOST="host.docker.internal" +PHPSTAN_CONFIG_FILE="phpstan.neon" +IS_CI=0 + +# Option parsing updates above default vars +# Reset in case getopts has been used previously in the shell +OPTIND=1 +# Array for invalid options +INVALID_OPTIONS=() +# Simple option parsing based on getopts (! not getopt) +while getopts "a:b:s:d:i:p:e:t:xy:nhu" OPT; do + case ${OPT} in + s) + TEST_SUITE=${OPTARG} + ;; + a) + DATABASE_DRIVER=${OPTARG} + ;; + b) + if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then + INVALID_OPTIONS+=("-b ${OPTARG}") + fi + CONTAINER_BIN=${OPTARG} + ;; + d) + DBMS=${OPTARG} + ;; + i) + DBMS_VERSION=${OPTARG} + ;; + p) + PHP_VERSION=${OPTARG} + if ! [[ ${PHP_VERSION} =~ ^(8.1|8.2|8.3|8.4)$ ]]; then + INVALID_OPTIONS+=("-p ${OPTARG}") + fi + ;; + e) + EXTRA_TEST_OPTIONS=${OPTARG} + ;; + t) + TYPO3_VERSION=${OPTARG} + if ! [[ ${TYPO3_VERSION} =~ ^(12|13)$ ]]; then + INVALID_OPTIONS+=("-t ${OPTARG}") + fi + ;; + x) + PHP_XDEBUG_ON=1 + ;; + y) + PHP_XDEBUG_PORT=${OPTARG} + ;; + n) + DRY_RUN=1 + ;; + h) + loadHelp + echo "${HELP}" + exit 0 + ;; + u) + TEST_SUITE=update + ;; + \?) + INVALID_OPTIONS+=("-${OPTARG}") + ;; + :) + INVALID_OPTIONS+=("-${OPTARG}") + ;; + esac +done + +# Exit on invalid options +if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then + echo "Invalid option(s):" >&2 + for I in "${INVALID_OPTIONS[@]}"; do + echo ${I} >&2 + done + echo >&2 + echo "call \".Build/Scripts/runTests.sh -h\" to display help and valid options" + exit 1 +fi + +handleDbmsOptions + +# ENV var "CI" is set by gitlab-ci. Use it to force some CI details. +if [ "${CI}" == "true" ]; then + IS_CI=1 + CONTAINER_INTERACTIVE="" +fi + +# determine default container binary to use: 1. podman 2. docker +if [[ -z "${CONTAINER_BIN}" ]]; then + if type "podman" >/dev/null 2>&1; then + CONTAINER_BIN="podman" + elif type "docker" >/dev/null 2>&1; then + CONTAINER_BIN="docker" + fi +fi + +if [ $(uname) != "Darwin" ] && [ "${CONTAINER_BIN}" == "docker" ]; then + # Run docker jobs as current user to prevent permission issues. Not needed with podman. + USERSET="--user $HOST_UID" +fi + +if ! type ${CONTAINER_BIN} >/dev/null 2>&1; then + echo "Selected container environment \"${CONTAINER_BIN}\" not found. Please install \"${CONTAINER_BIN}\" or use -b option to select one." >&2 + exit 1 +fi + +# Create .cache dir: composer need this. +mkdir -p .cache +mkdir -p .Build/public/typo3temp/var/tests + +IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest" +IMAGE_ALPINE="docker.io/alpine:3.8" +IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest" +IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}" +IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}" +IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine" + +# Set $1 to first mass argument, this is the optional test file or test directory to execute +shift $((OPTIND - 1)) + +${CONTAINER_BIN} network create ${NETWORK} >/dev/null + +if [ "${CONTAINER_BIN}" == "docker" ]; then + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" +else + # podman + CONTAINER_HOST="host.containers.internal" + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" +fi + +if [ ${PHP_XDEBUG_ON} -eq 0 ]; then + XDEBUG_MODE="-e XDEBUG_MODE=off" + XDEBUG_CONFIG=" " +else + XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo" + XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal" +fi + +# Suite execution +case ${TEST_SUITE} in + cgl) + DRY_RUN_OPTIONS='' + if [ "${DRY_RUN}" -eq 1 ]; then + DRY_RUN_OPTIONS='--dry-run --diff' + fi + COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v ${DRY_RUN_OPTIONS} --config=Build/php-cs-fixer/php-cs-fixer.php --using-cache=no" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + clean) + rm -rf \ + var/ \ + .cache \ + composer.lock \ + .Build/ \ + Tests/Acceptance/Support/_generated/ \ + composer.json.testing \ + Documentation-GENERATED-temp + ;; + composer) + COMMAND=(composer "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + composerInstall) + cleanComposer + stashComposerFiles + COMMAND=(composer install "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + restoreComposerFiles + ;; + composerInstallHighest) + cleanComposer + stashComposerFiles + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-highest-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c " + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install \ + typo3/cms-core:^12.4.2 || exit 1 + fi + if [ ${TYPO3_VERSION} -eq 13 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install \ + typo3/cms-core:^13.1 || exit 1 + fi + composer update --no-progress --no-interaction || exit 1 + composer show || exit 1 + " + SUITE_EXIT_CODE=$? + restoreComposerFiles + ;; + composerInstallLowest) + cleanComposer + stashComposerFiles + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-lowest-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c " + if [ ${TYPO3_VERSION} -eq 12 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install \ + typo3/cms-core:^12.4.2 || exit 1 + fi + if [ ${TYPO3_VERSION} -eq 13 ]; then + composer require --no-ansi --no-interaction --no-progress --no-install \ + typo3/cms-core:^13.1 || exit 1 + fi + composer update --no-ansi --no-interaction --no-progress --with-dependencies --prefer-lowest || exit 1 + composer show || exit 1 + " + SUITE_EXIT_CODE=$? + restoreComposerFiles + ;; + docsGenerate) + mkdir -p Documentation-GENERATED-temp + chown -R ${HOST_UID}:${HOST_PID} Documentation-GENERATED-temp + COMMAND=(--config=Documentation --fail-on-log ${EXTRA_TEST_OPTIONS} "$@") + ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} --rm --pull always ${USERSET} -v "${ROOT_DIR}":/project ${IMAGE_DOCS} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + coveralls) + COMMAND=(php -dxdebug.mode=coverage ./.Build/bin/php-coveralls --coverage_clover=./.Build/logs/clover.xml --json_path=./.Build/logs/coveralls-upload.json -v) + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-coverals-${SUFFIX} -e XDEBUG_MODE=coverage -e XDEBUG_TRIGGER=foo -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + functional) + COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") + case ${DBMS} in + mariadb) + echo "Using driver: ${DATABASE_DRIVER}" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-func-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mariadb-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + mysql) + echo "Using driver: ${DATABASE_DRIVER}" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-func-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mysql-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + postgres) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-func-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null + waitFor postgres-func-${SUFFIX} 5432 + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + sqlite) + # create sqlite tmpfs mount typo3temp/var/tests/functional-sqlite-dbs/ to avoid permission issues + mkdir -p "${ROOT_DIR}/typo3temp/var/tests/functional-sqlite-dbs/" + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${ROOT_DIR}/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + esac + ;; + lint) + COMMAND="php -v | grep '^PHP'; find . -name '*.php' ! -path '*.Build/*' -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + rector) + DRY_RUN_OPTIONS='' + if [ "${DRY_RUN}" -eq 1 ]; then + DRY_RUN_OPTIONS='--dry-run' + fi + COMMAND="php -dxdebug.mode=off .Build/bin/rector process ${DRY_RUN_OPTIONS} --config=Build/rector/rector.php --no-progress-bar --ansi" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + fractor) + DRY_RUN_OPTIONS='' + if [ "${DRY_RUN}" -eq 1 ]; then + DRY_RUN_OPTIONS='--dry-run' + fi + COMMAND="php -dxdebug.mode=off .Build/bin/fractor process ${DRY_RUN_OPTIONS} --config=Build/fractor/fractor.php --ansi" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + SUITE_EXIT_CODE=$? + ;; + unit) + COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} "${COMMAND[@]}" + SUITE_EXIT_CODE=$? + ;; + update) + # pull typo3/core-testing-*:latest versions of those ones that exist locally + echo "> pull ghcr.io/typo3/core-testing-*:latest versions of those ones that exist locally" + ${CONTAINER_BIN} images ghcr.io/typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} ${CONTAINER_BIN} pull {} + echo "" + # remove "dangling" typo3/core-testing-* images (those tagged as ) + echo "> remove \"dangling\" ghcr.io/typo3/core-testing-* images (those tagged as )" + ${CONTAINER_BIN} images --filter "reference=ghcr.io/typo3/core-testing-*" --filter "dangling=true" --format "{{.ID}}" | xargs -I {} ${CONTAINER_BIN} rmi {} + echo "" + ;; + *) + loadHelp + echo "Invalid -s option argument ${TEST_SUITE}" >&2 + echo >&2 + echo "${HELP}" >&2 + exit 1 + ;; +esac + +cleanUp + +# Print summary +echo "" >&2 +echo "###########################################################################" >&2 +echo "Result of ${TEST_SUITE}" >&2 +if [[ ${IS_CI} -eq 1 ]]; then + echo "Environment: CI" >&2 +else + echo "Environment: local" >&2 +fi +echo "PHP: ${PHP_VERSION}" >&2 +echo "TYPO3: ${TYPO3_VERSION}" >&2 +echo "CONTAINER_BIN: ${CONTAINER_BIN}" +if [[ ${TEST_SUITE} =~ ^functional$ ]]; then + case "${DBMS}" in + mariadb|mysql) + echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver ${DATABASE_DRIVER}" >&2 + ;; + postgres) + echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver pdo_pgsql" >&2 + ;; + sqlite) + echo "DBMS: ${DBMS} driver pdo_sqlite" >&2 + ;; + esac +fi +if [[ -n ${EXTRA_TEST_OPTIONS} ]]; then + echo " Note: Using -e is deprecated. Simply add the options at the end of the command." + echo " Instead of: Build/Scripts/runTests.sh -s ${TEST_SUITE} -e '${EXTRA_TEST_OPTIONS}' $@" + echo " use: Build/Scripts/runTests.sh -s ${TEST_SUITE} -- ${EXTRA_TEST_OPTIONS} $@" +fi +if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then + echo "SUCCESS" >&2 +else + echo "FAILURE" >&2 +fi +echo "###########################################################################" >&2 +echo "" >&2 + +# Exit with code of test suite - This script return non-zero if the executed test failed. +exit $SUITE_EXIT_CODE \ No newline at end of file diff --git a/Build/php-cs-fixer/php-cs-fixer.php b/Build/php-cs-fixer/php-cs-fixer.php new file mode 100644 index 0000000..8a97445 --- /dev/null +++ b/Build/php-cs-fixer/php-cs-fixer.php @@ -0,0 +1,67 @@ +setFinder( + (new Finder())->in(__DIR__ . '/../../') + ) + ->setRiskyAllowed(true) + ->setRules([ + '@DoctrineAnnotation' => true, + '@PER-CS' => true, + 'array_syntax' => ['syntax' => 'short'], + 'blank_line_after_opening_tag' => true, + 'cast_spaces' => ['space' => 'none'], + 'compact_nullable_type_declaration' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'none'], + 'dir_constant' => true, + 'type_declaration_spaces' => true, + 'lowercase_cast' => true, + 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'modernize_types_casting' => true, + 'native_function_casing' => true, + 'new_with_parentheses' => true, + 'no_alias_functions' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'no_leading_import_slash' => true, + 'no_leading_namespace_whitespace' => true, + 'no_null_property_initialization' => true, + 'no_short_bool_cast' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_superfluous_elseif' => true, + 'no_trailing_comma_in_singleline' => true, + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_else' => true, + 'no_useless_nullsafe_operator' => true, + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']], + 'php_unit_mock_short_will_return' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], + 'phpdoc_no_access' => true, + 'phpdoc_no_empty_return' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'phpdoc_types' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'php_unit_data_provider_static' => [ + 'force' => true, + ], + 'return_type_declaration' => ['space_before' => 'none'], + 'single_quote' => true, + 'single_line_comment_style' => ['comment_types' => ['hash']], + 'single_trait_insert_per_statement' => true, + 'trailing_comma_in_multiline' => ['elements' => ['arrays']], + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], + 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false], + ]); diff --git a/Build/phpunit/FunctionalTests.xml b/Build/phpunit/FunctionalTests.xml new file mode 100644 index 0000000..4e5c10a --- /dev/null +++ b/Build/phpunit/FunctionalTests.xml @@ -0,0 +1,27 @@ + + + + ../../Tests/Functional/ + + + + + ../../Classes/ + + + \ No newline at end of file diff --git a/Build/phpunit/FunctionalTestsBootstrap.php b/Build/phpunit/FunctionalTestsBootstrap.php new file mode 100644 index 0000000..b3509f2 --- /dev/null +++ b/Build/phpunit/FunctionalTestsBootstrap.php @@ -0,0 +1,33 @@ +defineOriginalRootPath(); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests'); + $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient'); +})(); diff --git a/Build/phpunit/UnitTests.xml b/Build/phpunit/UnitTests.xml new file mode 100644 index 0000000..dfc4570 --- /dev/null +++ b/Build/phpunit/UnitTests.xml @@ -0,0 +1,28 @@ + + + + ../../Tests/Unit/ + + + + + ../../Classes/ + + + \ No newline at end of file diff --git a/Build/phpunit/UnitTestsBootstrap.php b/Build/phpunit/UnitTestsBootstrap.php new file mode 100644 index 0000000..acddbff --- /dev/null +++ b/Build/phpunit/UnitTestsBootstrap.php @@ -0,0 +1,90 @@ +getWebRoot(), '/')); + } + if (!getenv('TYPO3_PATH_WEB')) { + putenv('TYPO3_PATH_WEB=' . rtrim($testbase->getWebRoot(), '/')); + } + + $testbase->defineSitePath(); + + // We can use the "typo3/cms-composer-installers" constant "TYPO3_COMPOSER_MODE" to determine composer mode. + // This should be always true except for TYPO3 mono repository. + $composerMode = defined('TYPO3_COMPOSER_MODE') && TYPO3_COMPOSER_MODE === true; + + // @todo: Remove else branch when dropping support for v12 + $hasConsolidatedHttpEntryPoint = class_exists(CoreHttpApplication::class); + if ($hasConsolidatedHttpEntryPoint) { + \TYPO3\TestingFramework\Core\SystemEnvironmentBuilder::run(0, \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI, $composerMode); + } else { + $requestType = \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_BE | \TYPO3\CMS\Core\Core\SystemEnvironmentBuilder::REQUESTTYPE_CLI; + \TYPO3\TestingFramework\Core\SystemEnvironmentBuilder::run(0, $requestType, $composerMode); + } + + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3conf/ext'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/assets'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/var/tests'); + $testbase->createDirectory(\TYPO3\CMS\Core\Core\Environment::getPublicPath() . '/typo3temp/var/transient'); + + // Retrieve an instance of class loader and inject to core bootstrap + $classLoader = require $testbase->getPackagesPath() . '/autoload.php'; + \TYPO3\CMS\Core\Core\Bootstrap::initializeClassLoader($classLoader); + + // Initialize default TYPO3_CONF_VARS + $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager(); + $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration(); + + $cache = new \TYPO3\CMS\Core\Cache\Frontend\PhpFrontend( + 'core', + new \TYPO3\CMS\Core\Cache\Backend\NullBackend('production', []) + ); + $packageManager = \TYPO3\CMS\Core\Core\Bootstrap::createPackageManager( + \TYPO3\CMS\Core\Package\UnitTestPackageManager::class, + \TYPO3\CMS\Core\Core\Bootstrap::createPackageCache($cache) + ); + + \TYPO3\CMS\Core\Utility\GeneralUtility::setSingletonInstance(\TYPO3\CMS\Core\Package\PackageManager::class, $packageManager); + \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::setPackageManager($packageManager); + + $testbase->dumpClassLoadingInformation(); + + \TYPO3\CMS\Core\Utility\GeneralUtility::purgeInstances(); +})(); diff --git a/CHANGELOG.md b/CHANGELOG.md index 80aa0b2..c20563e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,4 +22,8 @@ All notable changes to this extension will be documented in this file. ## [13.4.5] - 2025-07-30 ### Added -- Update `run.sh` file to include the installation of the `tm_migration` package. \ No newline at end of file +- Update `run.sh` file to include the installation of the `tm_migration` package. + +## [13.4.6] - 2025-08-28 +### Added +- Unit tests and Coding Standards. \ No newline at end of file diff --git a/Classes/Command/ClearSysLogCommand.php b/Classes/Command/ClearSysLogCommand.php index 6112a18..902beca 100644 --- a/Classes/Command/ClearSysLogCommand.php +++ b/Classes/Command/ClearSysLogCommand.php @@ -6,12 +6,12 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Toumoro\TmMigration\Service\SQLMigrationService; use TYPO3\CMS\Core\Utility\GeneralUtility; -use Toumoro\TmMigration\Service\SqlMigrationService; /** * Class ClearSysLogCommand @@ -38,30 +38,29 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->io = new SymfonyStyle($input, $output); $limit = $input->getOption('limit'); $days = $input->getOption('days'); - $timestamp = $days?strtotime('-'.$days.' days'):''; + $timestamp = $days ? strtotime('-' . $days . ' days') : ''; - $sqlMigrationService = GeneralUtility::makeInstance(SqlMigrationService::class); - $statement = " DELETE FROM sys_log WHERE NOT EXISTS + $SQLMigrationService = GeneralUtility::makeInstance(SQLMigrationService::class); + $statement = ' DELETE FROM sys_log WHERE NOT EXISTS (SELECT * FROM sys_history WHERE sys_history.sys_log_uid=sys_log.uid) - AND recuid=0 "; - if($timestamp){ - $statement .= " AND tstamp < ".$timestamp; + AND recuid=0 '; + if ($timestamp) { + $statement .= ' AND tstamp < ' . $timestamp; } - if($limit){ - $statement .= " LIMIT ".$limit; + if ($limit) { + $statement .= ' LIMIT ' . $limit; } - $condition = $sqlMigrationService->migrate([$statement]) > 0; - if($condition){ + $condition = $SQLMigrationService->migrate([$statement]) > 0; + if ($condition) { $success = Command::SUCCESS; $this->io->info('sys_log clear executed with no errors.'); - } - else{ + } else { $success = Command::FAILURE; $this->io->info('sys_log clear failed to be executed.'); - } + } return $success; } -} \ No newline at end of file +} diff --git a/Classes/Command/ExportCtypeListTypeCommand.php b/Classes/Command/ExportCtypeListTypeCommand.php index 8c29384..25d28eb 100644 --- a/Classes/Command/ExportCtypeListTypeCommand.php +++ b/Classes/Command/ExportCtypeListTypeCommand.php @@ -32,7 +32,7 @@ final class ExportCtypeListTypeCommand extends Command protected const FIELDS = [ 'CType', 'list_type', - 'pids' + 'pids', ]; protected const FILE_NAME = 'export.csv'; protected const FILE_TYPE_JSON = 'json'; @@ -59,7 +59,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $listTypeAndCTypeArray = $this->getListTypeAndCTypeArray(); $fileType = $input->getOption('fileType'); - if($fileType && strtolower($fileType) == self::FILE_TYPE_JSON) { + if ($fileType && strtolower($fileType) == self::FILE_TYPE_JSON) { $this->exportJson($listTypeAndCTypeArray); } else { $contents = $this->export($listTypeAndCTypeArray, self::FIELDS); @@ -91,14 +91,14 @@ private function export($list, $fields): string private function exportJson($list): void { foreach ($list as $row) { - if($row['CType'] == 'list' || $row['list_type']) { + if ($row['CType'] == 'list' || $row['list_type']) { $tmp[] = $row['list_type'] . ':' . $row['list_type']; } } $this->io->info('Here is the configuration for List Type to CType Mapping.'); $this->io->text(implode(',', $tmp)); - echo(PHP_EOL); + echo PHP_EOL; } private function renderHeader($fields): array @@ -109,9 +109,9 @@ private function renderHeader($fields): array private function renderContent($row, $fields): array { $data = []; - + foreach ($fields as $field) { - if($field == 'pids') { + if ($field == 'pids') { $data[] = $row['pids']; } else { $data[] = $row[$field]; @@ -162,4 +162,4 @@ private function logError(string $message): void $this->io->error($message); $this->logger->error($message); } -} \ No newline at end of file +} diff --git a/Classes/Command/FixDatabaseErrorsCommand.php b/Classes/Command/FixDatabaseErrorsCommand.php index a0aa60b..8577e8e 100644 --- a/Classes/Command/FixDatabaseErrorsCommand.php +++ b/Classes/Command/FixDatabaseErrorsCommand.php @@ -51,7 +51,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int foreach ($records as $fields) { $sortingFields = $this->getSortingFields($table, $fields); $this->deleteRecords($table, $fields); - $this->insertRecord($table, array_merge($fields,$sortingFields)); + $this->insertRecord($table, array_merge($fields, $sortingFields)); $progressBar->advance(); } $progressBar->finish(); @@ -72,7 +72,7 @@ private function getSortingFields(string $table, array $fields): array $where[] = $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value)); } $result = $queryBuilder - ->select('sorting','sorting_foreign') + ->select('sorting', 'sorting_foreign') ->from($table) ->where(...$where) ->executeQuery(); @@ -131,7 +131,7 @@ private function getAffectedMmTables(): array ->having('COUNT(fieldname) > 1') ->executeQuery(); } elseif ($this->tableHasMmMatchFields($mmTable) === false) { - if(in_array($mmTable, ['sys_dmail_ttaddress_category_mm','sys_dmail_feuser_category_mm','sys_dmail_group_category_mm'])){ + if (in_array($mmTable, ['sys_dmail_ttaddress_category_mm', 'sys_dmail_feuser_category_mm', 'sys_dmail_group_category_mm'])) { $queryBuilder = $this->connectionPool->getQueryBuilderForTable($mmTable); $result = $queryBuilder ->select('uid_local', 'uid_foreign', 'tablenames') @@ -140,8 +140,7 @@ private function getAffectedMmTables(): array ->having('COUNT(uid_local) > 1') ->having('COUNT(uid_foreign) > 1') ->executeQuery(); - } - else{ + } else { $queryBuilder = $this->connectionPool->getQueryBuilderForTable($mmTable); $result = $queryBuilder ->select('uid_local', 'uid_foreign') @@ -218,4 +217,4 @@ private function logError(string $message): void $this->io->error($message); $this->logger->error($message); } -} \ No newline at end of file +} diff --git a/Classes/Command/RunSqlScriptCommand.php b/Classes/Command/RunSqlScriptCommand.php index daf8e5f..faead2a 100644 --- a/Classes/Command/RunSqlScriptCommand.php +++ b/Classes/Command/RunSqlScriptCommand.php @@ -6,13 +6,13 @@ use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputOption; -use TYPO3\CMS\Core\Core\Environment; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Toumoro\TmMigration\Service\SQLMigrationService; +use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Utility\GeneralUtility; -use Toumoro\TmMigration\Service\SqlMigrationService; /** * Class RunSqlScriptCommand @@ -49,12 +49,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $fileNames = $this->getFileNames($input); - if(!empty($fileNames)) { + if (!empty($fileNames)) { foreach ($fileNames as $fileName) { $content = file_get_contents($fileName); // check if the file is not empty - if($content) { + if ($content) { $queries = array_filter( array_map( @@ -63,18 +63,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int ) ); - $sqlMigrationService = GeneralUtility::makeInstance(SqlMigrationService::class); - $condition = $sqlMigrationService->migrate($queries) > 0; - if($condition) { + $SQLMigrationService = GeneralUtility::makeInstance(SQLMigrationService::class); + $condition = $SQLMigrationService->migrate($queries) > 0; + if ($condition) { $success = Command::SUCCESS; $this->io->info($fileName . ' executed with no errors.'); - } - else { + } else { $success = Command::FAILURE; $this->io->info($fileName . ' failed to be executed.'); - } - } - else { + } + } else { $success = Command::FAILURE; $this->io->info($fileName . ' is empty.'); } @@ -83,7 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $success = Command::FAILURE; $this->io->info('no sql file found !'); } - + return $success; } @@ -91,22 +89,22 @@ private function getFileNames($input): array { $file = $input->getOption('file'); - if(file_exists($file) && $file) { + if (file_exists($file) && $file) { $fileName = Environment::getProjectPath() . '/' . $file; - + return [ - $fileName ?? self::FILE_NAME + $fileName ?? self::FILE_NAME, ]; } $directory = $input->getOption('directory'); - - if(file_exists($directory) && $directory) { + + if (file_exists($directory) && $directory) { $files = glob($directory . '/*.sql', GLOB_MARK); - + return $files; } return []; } -} \ No newline at end of file +} diff --git a/Classes/Command/UpgradeWizardRunCommand.php b/Classes/Command/UpgradeWizardRunCommand.php index 59fcdd3..0e79b97 100644 --- a/Classes/Command/UpgradeWizardRunCommand.php +++ b/Classes/Command/UpgradeWizardRunCommand.php @@ -4,6 +4,7 @@ namespace Toumoro\TmMigration\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputArgument; @@ -11,6 +12,8 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; use Symfony\Component\Console\Style\SymfonyStyle; +use Toumoro\TmMigration\Utility\ConfigurationUtility; +use Toumoro\TmMigration\Utility\UpgardeWizardsMappingUtility; use TYPO3\CMS\Core\Authentication\CommandLineUserAuthentication; use TYPO3\CMS\Core\Configuration\Exception\SettingsWriteException; use TYPO3\CMS\Core\Core\Bootstrap; @@ -29,13 +32,10 @@ use TYPO3\CMS\Install\Updates\PrerequisiteCollection; use TYPO3\CMS\Install\Updates\RepeatableInterface; use TYPO3\CMS\Install\Updates\UpgradeWizardInterface; -use Toumoro\TmMigration\Utility\ConfigurationUtility; -use Toumoro\TmMigration\Utility\UpgardeWizardsMappingUtility; -use Symfony\Component\Console\Attribute\AsCommand; /** * Upgrade wizard command for running wizards - * + * * Class UpgradeWizardRunCommand */ #[AsCommand( @@ -110,9 +110,9 @@ protected function configure() 'wizardName', InputArgument::OPTIONAL )->setHelp( - 'This command allows running upgrade wizards on CLI. To run a single wizard add the ' . - 'identifier of the wizard as argument. The identifier of the wizard is the name it is ' . - 'registered with in ext_localconf.' + 'This command allows running upgrade wizards on CLI. To run a single wizard add the ' + . 'identifier of the wizard as argument. The identifier of the wizard is the name it is ' + . 'registered with in ext_localconf.' ); } @@ -214,9 +214,9 @@ protected function handlePrerequisites(array $instances): bool $result = $prerequisite->ensure(); if ($result === false) { $this->output->error( - 'Error running ' . - $prerequisite->getTitle() . - '. Please ensure this prerequisite manually and try again.' + 'Error running ' + . $prerequisite->getTitle() + . '. Please ensure this prerequisite manually and try again.' ); break; } @@ -323,7 +323,7 @@ private function getUpgradeWizardIdentifiers(): array $excludedWizards = []; - if($fromVersion) { + if ($fromVersion) { foreach ($upgradeWizardsMapping as $version => $wizards) { if (version_compare((string)$version, $fromVersion, '<')) { $excludedWizards = array_merge($excludedWizards, $wizards); @@ -331,15 +331,15 @@ private function getUpgradeWizardIdentifiers(): array } } - if(isset($excludedIdentifiers)) { + if (isset($excludedIdentifiers)) { try { - foreach($excludedIdentifiers as $identifier) { - if($this->isValid($identifier)) { + foreach ($excludedIdentifiers as $identifier) { + if ($this->isValid($identifier)) { array_push($excludedWizards, $identifier); } } - - } catch(\Exception $e) { + + } catch (\Exception $e) { throw new \Exception($e->getMessage(), 1533931000); } } diff --git a/Classes/Service/SqlMigrationService.php b/Classes/Service/SQLMigrationService.php similarity index 94% rename from Classes/Service/SqlMigrationService.php rename to Classes/Service/SQLMigrationService.php index 1cdbb51..00324ad 100644 --- a/Classes/Service/SqlMigrationService.php +++ b/Classes/Service/SQLMigrationService.php @@ -1,4 +1,5 @@ getListTypeToCTypeMapping() !== [] && - $this->columnsExistInContentTable() && - $this->hasContentElementsToUpdate() + $this->getListTypeToCTypeMapping() !== [] + && $this->columnsExistInContentTable() + && $this->hasContentElementsToUpdate() ) || ( - $this->getListTypeToCTypeMapping() !== [] && - $this->columnsExistInBackendUserGroupsTable() + $this->getListTypeToCTypeMapping() !== [] + && $this->columnsExistInBackendUserGroupsTable() && $this->hasNoLegacyBackendGroupsExplicitAllowDenyConfiguration() && $this->hasBackendUserGroupsToUpdate() ); @@ -84,14 +84,14 @@ public function updateNecessary(): bool public function executeUpdate(): bool { - if ($this->getListTypeToCTypeMapping() !== [] && - $this->columnsExistInContentTable() && - $this->hasContentElementsToUpdate() + if ($this->getListTypeToCTypeMapping() !== [] + && $this->columnsExistInContentTable() + && $this->hasContentElementsToUpdate() ) { $this->updateContentElements(); } - if ($this->getListTypeToCTypeMapping() !== [] && - $this->columnsExistInBackendUserGroupsTable() + if ($this->getListTypeToCTypeMapping() !== [] + && $this->columnsExistInBackendUserGroupsTable() && $this->hasNoLegacyBackendGroupsExplicitAllowDenyConfiguration() && $this->hasBackendUserGroupsToUpdate() ) { diff --git a/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php b/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php index a58d919..0937b55 100644 --- a/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php +++ b/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php @@ -15,7 +15,6 @@ #[UpgradeWizard('tmMigration_cTypeToListTypeUpgradeWizard')] final class CTypeToListTypeUpgradeWizard extends AbstractListTypeToCTypeUpdate { - private const MAPPING_ARRAY = 'cTypeToListTypeMappingArray'; public function getTitle(): string @@ -41,13 +40,13 @@ public function getDescription(): string * @return array */ protected function getListTypeToCTypeMapping(): array - { + { /** @var ExtensionConfiguration $extensionConfiguration */ $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class); $emConfiguration = $extensionConfiguration->get('tm_migration'); - if(isset($emConfiguration[self::MAPPING_ARRAY])) { + if (isset($emConfiguration[self::MAPPING_ARRAY])) { $tmpArray = explode(',', $emConfiguration[self::MAPPING_ARRAY]); foreach ($tmpArray as $item) { @@ -61,4 +60,4 @@ protected function getListTypeToCTypeMapping(): array return []; } -} \ No newline at end of file +} diff --git a/Classes/Upgrades/FixRedirectsUpgraeWizard.php b/Classes/Upgrades/FixRedirectsUpgraeWizard.php index db774ef..40a9b61 100644 --- a/Classes/Upgrades/FixRedirectsUpgraeWizard.php +++ b/Classes/Upgrades/FixRedirectsUpgraeWizard.php @@ -1,17 +1,18 @@ getBrokenRedirects()); + return (bool)count($this->getBrokenRedirects()); } /** @@ -64,7 +65,7 @@ public function executeUpdate(): bool public function getPrerequisites(): array { return [ - DatabaseUpdatedPrerequisite::class + DatabaseUpdatedPrerequisite::class, ]; } @@ -72,11 +73,11 @@ private function updateRedirects(): bool { $redirects = $this->getBrokenRedirects(); - if(!empty($redirects)) { + if (!empty($redirects)) { foreach ($redirects as $row) { $sourcePath = $row['source_path']; - + if (strpos($sourcePath, '/') !== 0) { $sourcePath = '/' . ltrim($sourcePath, '/'); } @@ -113,18 +114,18 @@ private function getBrokenRedirects(): array ->select('uid', 'source_path', 'target_statuscode') ->from(self::REDIRECT_TABLE) ->where( - $queryBuilder->expr()->notLike( + $queryBuilder->expr()->notLike( 'source_path', $queryBuilder->createNamedParameter('/%') ) ) ->orWhere( $queryBuilder->expr()->eq( - 'target_statuscode', + 'target_statuscode', $queryBuilder->createNamedParameter(0, ParameterType::INTEGER) ) ) ->executeQuery() ->fetchAllAssociative(); } -} \ No newline at end of file +} diff --git a/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php b/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php index 4d95072..3054068 100644 --- a/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php +++ b/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php @@ -57,7 +57,7 @@ public function updateNecessary(): bool public function getPrerequisites(): array { return [ - DatabaseUpdatedPrerequisite::class + DatabaseUpdatedPrerequisite::class, ]; } @@ -77,8 +77,7 @@ public function executeUpdate(): bool ->executeQuery() ->fetchAllAssociative(); - - if($result) { + if ($result) { foreach ($result as $gridElement) { $this->updateContainer($gridElement['uid'], $gridElement['tx_gridelements_backend_layout'], $gridElement['pi_flexform']); $this->updateChildren($gridElement['uid']); @@ -116,8 +115,7 @@ protected function updateChildren(int $uid): void ->executeQuery() ->fetchAllAssociative(); - - if($result) { + if ($result) { foreach ($result as $child) { $this->updateChild($child['uid'], $child['tx_gridelements_container'], $child['tx_gridelements_columns']); } @@ -138,4 +136,4 @@ protected function updateChild(int $uid, int $parent, int $colPos): void ) ->executeStatement(); } -} \ No newline at end of file +} diff --git a/Classes/Upgrades/TruncateLogTableUpgradeWizard.php b/Classes/Upgrades/TruncateLogTableUpgradeWizard.php index 7c3df2f..7222b63 100644 --- a/Classes/Upgrades/TruncateLogTableUpgradeWizard.php +++ b/Classes/Upgrades/TruncateLogTableUpgradeWizard.php @@ -1,8 +1,10 @@ getQueryBuilderForTable(self::LOG_TABLE); $queryBuilder->delete(self::LOG_TABLE); - if(ConfigurationUtility::getNumberOfDays()) { + if (ConfigurationUtility::getNumberOfDays()) { $numberOfDays = ConfigurationUtility::getNumberOfDays(); $deleteTimestamp = strtotime('-' . $numberOfDays . 'days'); - - $queryBuilder->where( + + $queryBuilder->where( $queryBuilder->expr()->lt( self::DATE_FIELD, $queryBuilder->createNamedParameter($deleteTimestamp, Connection::PARAM_INT) @@ -86,7 +87,7 @@ private function deleteLogTable(): bool ); } - try { + try { $queryBuilder->executeStatement(); } catch (DBALException $e) { throw new \RuntimeException(self::class . ' failed for table ' . self::LOG_TABLE . ' with error: ' . $e->getMessage(), 1308255491); @@ -94,4 +95,4 @@ private function deleteLogTable(): bool return true; } -} \ No newline at end of file +} diff --git a/Classes/Utility/ConfigurationUtility.php b/Classes/Utility/ConfigurationUtility.php index 6a8a2e1..c5e2ba1 100644 --- a/Classes/Utility/ConfigurationUtility.php +++ b/Classes/Utility/ConfigurationUtility.php @@ -1,5 +1,6 @@ get('tm_migration'); } - + public static function isDisableTruncateLogUpgradeWizard(): bool { $configuration = self::getExtensionConfiguration(); return $configuration['disableTruncateLogUpgradeWizard'] === '1'; } - + public static function getNumberOfDays(): string { $configuration = self::getExtensionConfiguration(); @@ -42,8 +42,8 @@ public static function getUpgradeWizardToExclude(): array { $configuration = self::getExtensionConfiguration(); - if(!empty($configuration['upgradeWizards']['exlcuded'])) { - return explode(',', $configuration['upgradeWizards']['exlcuded']); + if (!empty($configuration['upgradeWizards']['exlcuded'])) { + return explode(',', $configuration['upgradeWizards']['exlcuded']); } return []; @@ -55,4 +55,4 @@ public static function getUpgradeWizardFromVersion(): string return $configuration['upgradeWizards']['fromVersion'] ?? ''; } -} \ No newline at end of file +} diff --git a/Classes/Utility/UpgardeWizardsMappingUtility.php b/Classes/Utility/UpgardeWizardsMappingUtility.php index 883decb..f99d59f 100644 --- a/Classes/Utility/UpgardeWizardsMappingUtility.php +++ b/Classes/Utility/UpgardeWizardsMappingUtility.php @@ -1,10 +1,8 @@ withPaths([__DIR__ . '/packages/']) ->withSets([ - Typo3LevelSetList::UP_TO_TYPO3_12 + Typo3LevelSetList::UP_TO_TYPO3_12, ]) ->withSkip([ // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 diff --git a/Resources/Private/Config/Fractor/fractor_v13.php b/Resources/Private/Config/Fractor/fractor_v13.php index 5028007..3031dc1 100644 --- a/Resources/Private/Config/Fractor/fractor_v13.php +++ b/Resources/Private/Config/Fractor/fractor_v13.php @@ -1,17 +1,17 @@ withPaths([__DIR__ . '/packages/']) ->withSets([ - Typo3LevelSetList::UP_TO_TYPO3_13 + Typo3LevelSetList::UP_TO_TYPO3_13, ]) ->withSkip([ // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 diff --git a/Resources/Private/Config/Rector/rector_v12.php b/Resources/Private/Config/Rector/rector_v12.php index afc8031..d25170c 100644 --- a/Resources/Private/Config/Rector/rector_v12.php +++ b/Resources/Private/Config/Rector/rector_v12.php @@ -22,9 +22,9 @@ Typo3SetList::GENERAL, Typo3LevelSetList::UP_TO_TYPO3_12, ]) - # To have a better analysis from PHPStan, we teach it here some more things + // To have a better analysis from PHPStan, we teach it here some more things ->withPHPStanConfigs([ - Typo3Option::PHPSTAN_FOR_RECTOR_PATH + Typo3Option::PHPSTAN_FOR_RECTOR_PATH, ]) ->withRules([ AddVoidReturnTypeWhereNoReturnRector::class, @@ -33,9 +33,9 @@ ->withConfiguredRule(ExtEmConfRector::class, [ ExtEmConfRector::PHP_VERSION_CONSTRAINT => '8.1.0-8.3.99', ExtEmConfRector::TYPO3_VERSION_CONSTRAINT => '12.4.0-12.4.99', - ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [] + ExtEmConfRector::ADDITIONAL_VALUES_TO_BE_REMOVED => [], ]) - # If you use withImportNames(), you should consider excluding some TYPO3 files. + // If you use withImportNames(), you should consider excluding some TYPO3 files. ->withSkip([ /*custom Mehdi*/ __DIR__ . '/var/cache', @@ -45,7 +45,7 @@ // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 __DIR__ . '/**/Configuration/ExtensionBuilder/*', NameImportingPostRector::class => [ - 'ClassAliasMap.php', - ] + 'ClassAliasMap.php', + ], ]) ; diff --git a/Resources/Private/Config/Rector/rector_v13.php b/Resources/Private/Config/Rector/rector_v13.php index 3968552..4ff0b14 100644 --- a/Resources/Private/Config/Rector/rector_v13.php +++ b/Resources/Private/Config/Rector/rector_v13.php @@ -6,7 +6,6 @@ use Rector\PostRector\Rector\NameImportingPostRector; use Rector\TypeDeclaration\Rector\ClassMethod\AddVoidReturnTypeWhereNoReturnRector; use Rector\ValueObject\PhpVersion; -use Ssch\TYPO3Rector\CodeQuality\General\ConvertImplicitVariablesToExplicitGlobalsRector; use Ssch\TYPO3Rector\CodeQuality\General\ExtEmConfRector; use Ssch\TYPO3Rector\Configuration\Typo3Option; use Ssch\TYPO3Rector\Set\Typo3LevelSetList; @@ -41,6 +40,6 @@ __DIR__ . '/**/Configuration/ExtensionBuilder/*', NameImportingPostRector::class => [ 'ClassAliasMap.php', - ] + ], ]) -; \ No newline at end of file +; diff --git a/Tests/Functional/Fixtures/queries_to_migrate.sql b/Tests/Functional/Fixtures/queries_to_migrate.sql new file mode 100644 index 0000000..7ab14fc --- /dev/null +++ b/Tests/Functional/Fixtures/queries_to_migrate.sql @@ -0,0 +1,25 @@ +-- Create a sample table +CREATE TABLE IF NOT EXISTS tx_tmexample_table ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert some sample data +INSERT INTO tx_tmexample_table (title, description) VALUES +('First Item', 'This is the first item.'), +('Second Item', 'This is the second item.'); + +-- Another example table +CREATE TABLE IF NOT EXISTS tx_tmexample_users ( + uid INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL, + email VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert sample users +INSERT INTO tx_tmexample_users (username, email) VALUES +('user1', 'user1@example.com'), +('user2', 'user2@example.com'); diff --git a/Tests/Functional/Service/SQLMigrationServiceTest.php b/Tests/Functional/Service/SQLMigrationServiceTest.php new file mode 100644 index 0000000..aae53d0 --- /dev/null +++ b/Tests/Functional/Service/SQLMigrationServiceTest.php @@ -0,0 +1,46 @@ +queries = array_filter(array_map('trim', explode(';', $sqlContent))); + } + + #[Test] + public function checkSqlQueriesMigration(): void + { + $service = GeneralUtility::makeInstance(SQLMigrationService::class); + $executedCount = $service->migrate($this->queries); + + self::assertSame(count($this->queries), $executedCount, 'All SQL queries should be executed.'); + + $connection = GeneralUtility::makeInstance(ConnectionPool::class) + ->getConnectionByName('Default'); + + $result = $connection->fetchOne('SELECT COUNT(*) FROM tx_tmexample_table'); + self::assertGreaterThan(0, $result, 'Data should be inserted into tx_tmexample_table'); + } +} diff --git a/Tests/Unit/.gitkeep b/Tests/Unit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json index cff04fe..4f19e5c 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,13 @@ "a9f/typo3-fractor": "^v0.5.1", "ssch/typo3-rector": "^2.14.4 || ^v3.5.0", "wapplersystems/core-upgrader": "dev-release/v12 || dev-release/v13" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^2.2.0", + "typo3/testing-framework": "^9.2.0", + "phpunit/phpunit": "^11.5.33", + "typo3/coding-standards": "^0.8.0", + "friendsofphp/php-cs-fixer": "^3.86.0" }, "autoload": { "psr-4": { @@ -39,9 +46,27 @@ "replace": { "typo3-ter/tm-migration": "self.version" }, + "config": { + "vendor-dir": ".Build/vendor", + "bin-dir": ".Build/bin", + "allow-plugins": { + "typo3/class-alias-loader": true, + "typo3/cms-composer-installers": true, + "a9f/fractor-extension-installer": true + } + }, + "scripts": { + "post-install-cmd": [ + "git init", + "git config --local core.hooksPath .githooks/" + ], + "php:cs": "php-cs-fixer fix --config=Build/php-cs-fixer/php-cs-fixer.php -v --dry-run --using-cache no --diff", + "php:fix": "php-cs-fixer fix --config=Build/php-cs-fixer/php-cs-fixer.php" + }, "extra": { "typo3/cms": { - "extension-key": "tm_migration" + "extension-key": "tm_migration", + "web-dir": ".Build/Web" } } } diff --git a/ext_emconf.php b/ext_emconf.php index c9529e6..77004b5 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -7,12 +7,12 @@ 'author' => 'Haythem Daoud', 'author_email' => 'haythem.daoud@toumoro.com', 'state' => 'stable', - 'version' => '13.4.5', + 'version' => '13.4.6', 'constraints' => [ 'depends' => [ 'a9f/typo3-fractor' => '*', 'ssch/typo3-rector' => '*', - 'wapplersystems/core-upgrader' => '*' + 'wapplersystems/core-upgrader' => '*', ], 'conflicts' => [], 'suggests' => [], diff --git a/ext_localconf.php b/ext_localconf.php index 87cbf83..a2393b3 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,4 +1,5 @@ \Toumoro\TmMigration\Xclass\Updates\WorkspacesNotificationSettingsUpdate::class, ]; -}); \ No newline at end of file +}); diff --git a/ext_tables.php b/ext_tables.php index 885c73b..45a0d7e 100644 --- a/ext_tables.php +++ b/ext_tables.php @@ -1,2 +1,3 @@ Date: Thu, 28 Aug 2025 23:37:43 +0000 Subject: [PATCH 2/6] hub#18022 hook pour pre commit --- .githooks/pre-commit | 0 composer.json | 1 + 2 files changed, 1 insertion(+) mode change 100644 => 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json index 4f19e5c..f2262c4 100644 --- a/composer.json +++ b/composer.json @@ -57,6 +57,7 @@ }, "scripts": { "post-install-cmd": [ + "chmod +x .githooks/pre-commit", "git init", "git config --local core.hooksPath .githooks/" ], From 4b9c8b2753694b25b4658156bac5aabbaf152fdb Mon Sep 17 00:00:00 2001 From: tm-hdaoud Date: Tue, 21 Oct 2025 22:04:27 +0000 Subject: [PATCH 3/6] hub#18022 Add functional tests --- .github/workflows/ci.yml | 37 ++ .gitignore | 3 +- Build/Scripts/runTests.sh | 539 ++++++------------ Build/phpunit/FunctionalTests.xml | 3 + Classes/Command/RunSqlScriptCommand.php | 14 +- .../Upgrades/CTypeToListTypeUpgradeWizard.php | 17 +- .../GridElementsToContainerUpgradeWizard.php | 14 +- .../TruncateLogTableUpgradeWizard.php | 14 +- Classes/Utility/ConfigurationUtility.php | 7 - .../Command/ClearSysLogCommandTest.php | 114 ++++ .../ExportCtypeListTypeCommandTest.php | 148 +++++ .../Command/FixDatabaseErrorsCommandTest.php | 83 +++ .../Command/RunSqlScriptCommandTest.php | 142 +++++ .../Command/UpgradeWizardRunCommandTest.php | 100 ++++ .../Fixtures/Database/empty.sql} | 0 .../queries.sql} | 0 Tests/Functional/Fixtures/sys_log.csv | 7 + Tests/Functional/Fixtures/sys_redirect.csv | 7 + .../Service/SQLMigrationServiceTest.php | 2 +- .../Upgrades/FixRedirectsUpgraeWizardTest.php | 90 +++ ...idElementsToContainerUpgradeWizardTest.php | 105 ++++ .../TruncateLogTableUpgradeWizardTest.php | 99 ++++ Tests/Unit/ConfigurationUtilityTest.php | 56 ++ .../CTypeToListTypeUpgradeWizardTest.php | 96 ++++ composer.json | 11 +- ext_emconf.php | 6 +- 26 files changed, 1309 insertions(+), 405 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Tests/Functional/Command/ClearSysLogCommandTest.php create mode 100644 Tests/Functional/Command/ExportCtypeListTypeCommandTest.php create mode 100644 Tests/Functional/Command/FixDatabaseErrorsCommandTest.php create mode 100644 Tests/Functional/Command/RunSqlScriptCommandTest.php create mode 100644 Tests/Functional/Command/UpgradeWizardRunCommandTest.php rename Tests/{Unit/.gitkeep => Functional/Fixtures/Database/empty.sql} (100%) rename Tests/Functional/Fixtures/{queries_to_migrate.sql => Database/queries.sql} (100%) create mode 100644 Tests/Functional/Fixtures/sys_log.csv create mode 100644 Tests/Functional/Fixtures/sys_redirect.csv create mode 100644 Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php create mode 100644 Tests/Functional/Upgrades/GridElementsToContainerUpgradeWizardTest.php create mode 100644 Tests/Functional/Upgrades/TruncateLogTableUpgradeWizardTest.php create mode 100644 Tests/Unit/ConfigurationUtilityTest.php create mode 100644 Tests/Unit/Upgrades/CTypeToListTypeUpgradeWizardTest.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ae49d5e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + pull_request: + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + - name: Install Dependencies + run: composer install --prefer-dist --no-progress + + - name: ci:php + run: composer ci:php + + - name: ci:unit + run: composer php:unit + + - name: functional tests + run: | + chmod +x ./Build/Scripts/runTests.sh + RUNTESTS_DIR_BIN=.Build/bin/ ./Build/Scripts/runTests.sh -p 8.3 -d mysql \ No newline at end of file diff --git a/.gitignore b/.gitignore index cd8e702..cd5eae4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ /vendor /typo3temp /Build/phpunit/.phpunit.result.cache -.php-cs-fixer.cache \ No newline at end of file +.php-cs-fixer.cache +export.csv \ No newline at end of file diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index da6bce3..7aa4cd5 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # -# TYPO3 core test runner based on docker. +# TYPO3 core test runner based on docker or podman # trap 'cleanUp;exit 2' SIGINT @@ -31,7 +31,11 @@ cleanUp() { for ATTACHED_CONTAINER in ${ATTACHED_CONTAINERS}; do ${CONTAINER_BIN} kill ${ATTACHED_CONTAINER} >/dev/null done - ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null + if [ ${CONTAINER_BIN} = "docker" ]; then + ${CONTAINER_BIN} network rm ${NETWORK} >/dev/null + else + ${CONTAINER_BIN} network rm -f ${NETWORK} >/dev/null + fi } handleDbmsOptions() { @@ -42,14 +46,14 @@ handleDbmsOptions() { if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi - [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.3" - if ! [[ ${DBMS_VERSION} =~ ^(10.3|10.4|10.5|10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then + [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10.4" + if ! [[ ${DBMS_VERSION} =~ ^(10.4|10.5|10.6|10.7|10.8|10.9|10.10|10.11|11.0|11.1)$ ]]; then echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi ;; @@ -58,14 +62,14 @@ handleDbmsOptions() { if [ "${DATABASE_DRIVER}" != "mysqli" ] && [ "${DATABASE_DRIVER}" != "pdo_mysql" ]; then echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="8.0" - if ! [[ ${DBMS_VERSION} =~ ^(8.0)$ ]]; then + if ! [[ ${DBMS_VERSION} =~ ^(8.0|8.1|8.2|8.3)$ ]]; then echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi ;; @@ -73,14 +77,14 @@ handleDbmsOptions() { if [ -n "${DATABASE_DRIVER}" ]; then echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi [ -z "${DBMS_VERSION}" ] && DBMS_VERSION="10" if ! [[ ${DBMS_VERSION} =~ ^(10|11|12|13|14|15|16)$ ]]; then echo "Invalid combination -d ${DBMS} -i ${DBMS_VERSION}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi ;; @@ -88,96 +92,50 @@ handleDbmsOptions() { if [ -n "${DATABASE_DRIVER}" ]; then echo "Invalid combination -d ${DBMS} -a ${DATABASE_DRIVER}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi if [ -n "${DBMS_VERSION}" ]; then echo "Invalid combination -d ${DBMS} -i ${DATABASE_DRIVER}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 fi ;; *) echo "Invalid option -d ${DBMS}" >&2 echo >&2 - echo "Use \".Build/Scripts/runTests.sh -h\" to display help and valid options" >&2 + echo "${HELP_MESSAGE}" >&2 exit 1 ;; esac } -cleanCacheFiles() { - echo -n "Clean caches ... " - rm -rf \ - .Build/.cache \ - .php-cs-fixer.cache - echo "done" -} - -cleanTestFiles() { - # test related - echo -n "Clean test related files ... " - rm -rf \ - .Build/public/typo3temp/var/tests/ - echo "done" -} - -cleanRenderedDocumentationFiles() { - echo -n "Clean rendered documentation files ... " - rm -rf \ - Documentation-GENERATED-temp - echo "done" -} - -cleanComposer() { - rm -rf \ - .Build/vendor \ - .Build/bin \ - composer.lock -} - -stashComposerFiles() { - cp composer.json composer.json.orig - if [ -f "composer.json.testing" ]; then - cp composer.json composer.json.orig - fi -} - -restoreComposerFiles() { - cp composer.json composer.json.testing - mv composer.json.orig composer.json +getPhpImageVersion() { + case ${1} in + 8.2) + echo -n "1.12" + ;; + 8.3) + echo -n "1.13" + ;; + esac } loadHelp() { # Load help text into $HELP read -r -d '' HELP < - Specifies which test suite to run - - cgl: cgl test and fix all php files - - clean: clean up build and testing related files - - cleanRenderedDocumentation: clean up rendered documentation files and folders (Documentation-GENERATED-temp) - - composer: Execute "composer" command, using -e for command arguments pass-through, ex. -e "ci:php:stan" - - composerInstall: "composer update", handy if host has no PHP - - composerInstallLowest: "composer update", handy if host has no PHP - - composerInstallHighest: "composer update", handy if host has no PHP - - coveralls: Generate coverage - - docsGenerate: Renders the extension ReST documentation. - - rector: Run rector - - fractor: Run Fractor - - functional: functional tests - - lint: PHP linting - - unit: PHP unit tests + -b + Container environment: + - podman (default) + - docker -a - Only with -s functional|functionalDeprecated Specifies to use another driver, following combinations are available: - mysql - mysqli (default) @@ -186,13 +144,7 @@ Options: - mysqli (default) - pdo_mysql - -b - Container environment: - - docker (default) - - podman - -d - Only with -s functional|functionalDeprecated Specifies on which DBMS tests are performed - sqlite: (default): use sqlite - mariadb: use mariadb @@ -202,8 +154,7 @@ Options: -i version Specify a specific database version With "-d mariadb": - - 10.3 short-term, maintained until 2023-05-25 (default) - - 10.4 short-term, maintained until 2024-06-18 + - 10.4 short-term, maintained until 2024-06-18 (default) - 10.5 short-term, maintained until 2025-06-24 - 10.6 long-term, maintained until 2026-06 - 10.7 short-term, no longer maintained @@ -214,7 +165,10 @@ Options: - 11.0 development series - 11.1 short-term development series With "-d mysql": - - 8.0 maintained until 2026-04 (default) + - 8.0 maintained until 2026-04 (default) LTS + - 8.1 unmaintained since 2023-10 + - 8.2 unmaintained since 2024-01 + - 8.3 maintained until 2024-04 With "-d postgres": - 10 unmaintained since 2022-11-10 (default) - 11 unmaintained since 2023-11-09 @@ -224,34 +178,21 @@ Options: - 15 maintained until 2027-11-11 - 16 maintained until 2028-11-09 - -t <12|13> - Only with -s composerInstall|composerInstallMin|composerInstallMax - Specifies the TYPO3 CORE Version to be used - - 12: use TYPO3 v12 (default) - - 13: use TYPO3 v13 + -c + Hack functional tests into #numberOfChunks pieces and run tests of #chunk. + Example -c 3/13 - -p <8.1|8.2|8.3|8.4> + -p <8.2|8.3> Specifies the PHP minor version to be used - - 8.1: use PHP 8.1 (default) - - 8.2: use PHP 8.2 + - 8.2 (default): use PHP 8.2 - 8.3: use PHP 8.3 - - 8.4: use PHP 8.4 - - -e "" - Only with -s docsGenerate|functional|unit - Additional options to send to phpunit (unit & functional tests). For phpunit, - options starting with "--" must be added after options starting with "-". - Example -e "--filter classCanBeRegistered" to enable verbose output AND filter tests - named "classCanBeRegistered" - DEPRECATED - pass arguments after the `--` separator directly. For example, instead of - Build/Scripts/runTests.sh -s unit -e "--filter classCanBeRegistered" - use - Build/Scripts/runTests.sh -s unit -- --filter classCanBeRegistered + -e "" (DEPRECATED). + Additional options to send to phpunit. + DEPRECATED - pass arguments after the "--" separator directly. -x - Only with -s functional|functionalDeprecated|unit|unitDeprecated|unitRandom - Send information to host instance for test or system under test break points. This is especially + Send information to host instance for test break points. This is especially useful if a local PhpStorm instance is listening on default xdebug port 9003. A different port can be selected with -y @@ -259,96 +200,103 @@ Options: Send xdebug information to a different port than default 9003 if an IDE like PhpStorm is not listening on default port. - -n - Only with -s cgl|composerNormalize|fractor - Activate dry-run in CGL check that does not actively change files and only prints broken ones. - - -u - Update existing typo3/core-testing-*:latest container images and remove dangling local volumes. - New images are published once in a while and only the latest ones are supported by core testing. - Use this if weird test errors occur. Also removes obsolete image versions of typo3/core-testing-*. - -h Show this help. Examples: - # Run all core unit tests - ./Build/Scripts/runTests.sh -s unit - - # Run all core units tests and enable xdebug (have a PhpStorm listening on port 9003!) - ./Build/Scripts/runTests.sh -x -s unit - - # Run unit tests in phpunit verbose mode with xdebug on PHP 8.1 and filter for test canRetrieveValueWithGP - ./Build/Scripts/runTests.sh -x -p 8.1 -- --filter 'classCanBeRegistered' - - # Run functional tests in phpunit with a filtered test method name in a specified file - # example will currently execute two tests, both of which start with the search term - ./Build/Scripts/runTests.sh -s functional -- --filter 'findRecordByImportSource' Tests/Functional/Repository/CategoryRepositoryTest.php - - # Run functional tests on postgres with xdebug, php 8.1 and execute a restricted set of tests - ./Build/Scripts/runTests.sh -x -p 8.1 -s functional -d postgres -- Tests/Functional/Repository/CategoryRepositoryTest.php + # Run functional tests on postgres with xdebug, php 8.3 and execute a restricted set of tests + $THIS_SCRIPT_NAME -x -p 8.3 -d postgres -i 12 -- --filter PageActionsTest # Run functional tests on postgres 11 - ./Build/Scripts/runTests.sh -s functional -d postgres -i 11 + $THIS_SCRIPT_NAME -d postgres -i 11 + EOF } -# Test if at least one of the supported container binaries exists, else exit out with error +# Test if docker exists, else exit out with error if ! type "docker" >/dev/null 2>&1 && ! type "podman" >/dev/null 2>&1; then - echo "This script relies on docker or podman. Please install at least one of them" >&2 + echo "This script relies on docker or podman. Please install" >&2 exit 1 fi # Go to the directory this script is located, so everything else is relative -# to this dir, no matter from where this script is called, then go up two dirs. +# to this dir, no matter from where this script is called. Later, go to the +# project root. THIS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd "$THIS_SCRIPT_DIR" || exit 1 -cd ../../ || exit 1 -ROOT_DIR="${PWD}" -# Option defaults -TEST_SUITE="" -TYPO3_VERSION="12" +RUNTESTS_ENV="${RUNTESTS_ENV:=../../runTests.env}" +if [ -f "${RUNTESTS_ENV}" ] ; then + echo "Using runTests config from: ${RUNTESTS_ENV}" + source "${RUNTESTS_ENV}" + while IFS='=' read -r name value ; do + if [[ $name == RUNTESTS_DIR_* ]]; then + if [[ $value != */ ]]; then + export "$name=$value/" + fi + fi + done < <(env) +else + # Default files when runTests.env is missing, using TYPO3 core configuration: + RUNTESTS_DIR_ROOT="${RUNTESTS_DIR_ROOT:=../../}" + RUNTESTS_DIR_BUILDER="${RUNTESTS_DIR_BUILDER:=Build/Scripts/}" + RUNTESTS_DIR_BIN="${RUNTESTS_DIR_BIN:=vendor/bin/}" + RUNTESTS_DIR_TESTTEMP="${RUNTESTS_DIR_TESTTEMP:=typo3temp/var/tests/}" + RUNTESTS_DIR_CACHE="${RUNTESTS_DIR_TESTTEMP:=.cache/}" + RUNTESTS_PHPUNIT_FILE_FUNCTIONALTEST="${RUNTESTS_PHPUNIT_FILE_FUNCTIONALTEST:=Build/phpunit/FunctionalTests.xml}" + RUNTESTS_DIR_PHPUNIT_FUNCTIONAL="${RUNTESTS_DIR_PHPUNIT_FUNCTIONAL:=Build/phpunit/}" +fi + +# Now go into the actual "base" directory. +cd "$RUNTESTS_DIR_ROOT" || exit 1 +CORE_ROOT="${PWD}" + +# Default variables +TEST_SUITE="functional" DBMS="sqlite" DBMS_VERSION="" -PHP_VERSION="8.1" +PHP_VERSION="8.2" PHP_XDEBUG_ON=0 PHP_XDEBUG_PORT=9003 EXTRA_TEST_OPTIONS="" -DRY_RUN=0 DATABASE_DRIVER="" +CHUNKS=0 +THISCHUNK=0 CONTAINER_BIN="" -COMPOSER_ROOT_VERSION="12.0.0-dev" +COMPOSER_ROOT_VERSION="13.2.x-dev" +CONTAINER_SHELL="/bin/sh" CONTAINER_INTERACTIVE="-it --init" HOST_UID=$(id -u) HOST_PID=$(id -g) USERSET="" SUFFIX=$(echo $RANDOM) -NETWORK="toumoro-tm-migration-${SUFFIX}" +NETWORK="typo3-core-${SUFFIX}" CI_PARAMS="${CI_PARAMS:-}" CONTAINER_HOST="host.docker.internal" -PHPSTAN_CONFIG_FILE="phpstan.neon" -IS_CI=0 +THIS_SCRIPT_NAME="${RUNTESTS_DIR_BUILDER}runTests.sh" +HELP_MESSAGE="Use \"${THIS_SCRIPT_NAME} -h\" to display help and valid options" -# Option parsing updates above default vars -# Reset in case getopts has been used previously in the shell +# Option parsing OPTIND=1 -# Array for invalid options INVALID_OPTIONS=() -# Simple option parsing based on getopts (! not getopt) -while getopts "a:b:s:d:i:p:e:t:xy:nhu" OPT; do +while getopts ":a:b:c:d:i:p:e:xy:h" OPT; do case ${OPT} in - s) - TEST_SUITE=${OPTARG} + b) + if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + fi + CONTAINER_BIN=${OPTARG} ;; a) DATABASE_DRIVER=${OPTARG} ;; - b) - if ! [[ ${OPTARG} =~ ^(docker|podman)$ ]]; then - INVALID_OPTIONS+=("-b ${OPTARG}") + c) + if ! [[ ${OPTARG} =~ ^([0-9]+\/[0-9]+)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") + else + THISCHUNK=$(echo "${OPTARG}" | cut -d '/' -f1) + CHUNKS=$(echo "${OPTARG}" | cut -d '/' -f2) fi - CONTAINER_BIN=${OPTARG} ;; d) DBMS=${OPTARG} @@ -358,65 +306,49 @@ while getopts "a:b:s:d:i:p:e:t:xy:nhu" OPT; do ;; p) PHP_VERSION=${OPTARG} - if ! [[ ${PHP_VERSION} =~ ^(8.1|8.2|8.3|8.4)$ ]]; then - INVALID_OPTIONS+=("-p ${OPTARG}") + if ! [[ ${PHP_VERSION} =~ ^(8.2|8.3)$ ]]; then + INVALID_OPTIONS+=("${OPTARG}") fi ;; e) EXTRA_TEST_OPTIONS=${OPTARG} ;; - t) - TYPO3_VERSION=${OPTARG} - if ! [[ ${TYPO3_VERSION} =~ ^(12|13)$ ]]; then - INVALID_OPTIONS+=("-t ${OPTARG}") - fi - ;; x) PHP_XDEBUG_ON=1 ;; y) PHP_XDEBUG_PORT=${OPTARG} ;; - n) - DRY_RUN=1 - ;; h) loadHelp echo "${HELP}" exit 0 ;; - u) - TEST_SUITE=update - ;; - \?) - INVALID_OPTIONS+=("-${OPTARG}") + \") + INVALID_OPTIONS+=("${OPTARG}") ;; :) - INVALID_OPTIONS+=("-${OPTARG}") + INVALID_OPTIONS+=("${OPTARG}") ;; esac done -# Exit on invalid options if [ ${#INVALID_OPTIONS[@]} -ne 0 ]; then echo "Invalid option(s):" >&2 for I in "${INVALID_OPTIONS[@]}"; do - echo ${I} >&2 + echo "-${I}" >&2 done echo >&2 - echo "call \".Build/Scripts/runTests.sh -h\" to display help and valid options" + echo "${HELP_MESSAGE}" >&2 exit 1 fi handleDbmsOptions -# ENV var "CI" is set by gitlab-ci. Use it to force some CI details. if [ "${CI}" == "true" ]; then - IS_CI=1 CONTAINER_INTERACTIVE="" fi -# determine default container binary to use: 1. podman 2. docker if [[ -z "${CONTAINER_BIN}" ]]; then if type "podman" >/dev/null 2>&1; then CONTAINER_BIN="podman" @@ -425,38 +357,34 @@ if [[ -z "${CONTAINER_BIN}" ]]; then fi fi -if [ $(uname) != "Darwin" ] && [ "${CONTAINER_BIN}" == "docker" ]; then - # Run docker jobs as current user to prevent permission issues. Not needed with podman. +if [ $(uname) != "Darwin" ] && [ ${CONTAINER_BIN} = "docker" ]; then USERSET="--user $HOST_UID" fi if ! type ${CONTAINER_BIN} >/dev/null 2>&1; then - echo "Selected container environment \"${CONTAINER_BIN}\" not found. Please install \"${CONTAINER_BIN}\" or use -b option to select one." >&2 + echo "Selected container environment \"${CONTAINER_BIN}\" not found. Please install or use -b option to select one." >&2 exit 1 fi -# Create .cache dir: composer need this. -mkdir -p .cache -mkdir -p .Build/public/typo3temp/var/tests - -IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):latest" +IMAGE_PHP="ghcr.io/typo3/core-testing-$(echo "php${PHP_VERSION}" | sed -e 's/\.//'):$(getPhpImageVersion $PHP_VERSION)" IMAGE_ALPINE="docker.io/alpine:3.8" -IMAGE_DOCS="ghcr.io/typo3-documentation/render-guides:latest" +IMAGE_REDIS="docker.io/redis:4-alpine" +IMAGE_MEMCACHED="docker.io/memcached:1.5-alpine" IMAGE_MARIADB="docker.io/mariadb:${DBMS_VERSION}" IMAGE_MYSQL="docker.io/mysql:${DBMS_VERSION}" IMAGE_POSTGRES="docker.io/postgres:${DBMS_VERSION}-alpine" -# Set $1 to first mass argument, this is the optional test file or test directory to execute shift $((OPTIND - 1)) +mkdir -p .cache + ${CONTAINER_BIN} network create ${NETWORK} >/dev/null -if [ "${CONTAINER_BIN}" == "docker" ]; then - CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" +if [ ${CONTAINER_BIN} = "docker" ]; then + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host "${CONTAINER_HOST}:host-gateway" ${USERSET} -v ${CORE_ROOT}:${CORE_ROOT} -w ${CORE_ROOT}" else - # podman CONTAINER_HOST="host.containers.internal" - CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" + CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${CORE_ROOT}:${CORE_ROOT} -w ${CORE_ROOT}" fi if [ ${PHP_XDEBUG_ON} -eq 0 ]; then @@ -464,171 +392,52 @@ if [ ${PHP_XDEBUG_ON} -eq 0 ]; then XDEBUG_CONFIG=" " else XDEBUG_MODE="-e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=foo" - XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=host.docker.internal" + XDEBUG_CONFIG="client_port=${PHP_XDEBUG_PORT} client_host=${CONTAINER_HOST}" fi -# Suite execution -case ${TEST_SUITE} in - cgl) - DRY_RUN_OPTIONS='' - if [ "${DRY_RUN}" -eq 1 ]; then - DRY_RUN_OPTIONS='--dry-run --diff' - fi - COMMAND="php -dxdebug.mode=off .Build/bin/php-cs-fixer fix -v ${DRY_RUN_OPTIONS} --config=Build/php-cs-fixer/php-cs-fixer.php --using-cache=no" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" - SUITE_EXIT_CODE=$? - ;; - clean) - rm -rf \ - var/ \ - .cache \ - composer.lock \ - .Build/ \ - Tests/Acceptance/Support/_generated/ \ - composer.json.testing \ - Documentation-GENERATED-temp - ;; - composer) - COMMAND=(composer "$@") - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - composerInstall) - cleanComposer - stashComposerFiles - COMMAND=(composer install "$@") - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - restoreComposerFiles - ;; - composerInstallHighest) - cleanComposer - stashComposerFiles - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-highest-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c " - if [ ${TYPO3_VERSION} -eq 12 ]; then - composer require --no-ansi --no-interaction --no-progress --no-install \ - typo3/cms-core:^12.4.2 || exit 1 - fi - if [ ${TYPO3_VERSION} -eq 13 ]; then - composer require --no-ansi --no-interaction --no-progress --no-install \ - typo3/cms-core:^13.1 || exit 1 - fi - composer update --no-progress --no-interaction || exit 1 - composer show || exit 1 - " - SUITE_EXIT_CODE=$? - restoreComposerFiles - ;; - composerInstallLowest) - cleanComposer - stashComposerFiles - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-install-lowest-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c " - if [ ${TYPO3_VERSION} -eq 12 ]; then - composer require --no-ansi --no-interaction --no-progress --no-install \ - typo3/cms-core:^12.4.2 || exit 1 - fi - if [ ${TYPO3_VERSION} -eq 13 ]; then - composer require --no-ansi --no-interaction --no-progress --no-install \ - typo3/cms-core:^13.1 || exit 1 - fi - composer update --no-ansi --no-interaction --no-progress --with-dependencies --prefer-lowest || exit 1 - composer show || exit 1 - " - SUITE_EXIT_CODE=$? - restoreComposerFiles - ;; - docsGenerate) - mkdir -p Documentation-GENERATED-temp - chown -R ${HOST_UID}:${HOST_PID} Documentation-GENERATED-temp - COMMAND=(--config=Documentation --fail-on-log ${EXTRA_TEST_OPTIONS} "$@") - ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} --rm --pull always ${USERSET} -v "${ROOT_DIR}":/project ${IMAGE_DOCS} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - coveralls) - COMMAND=(php -dxdebug.mode=coverage ./.Build/bin/php-coveralls --coverage_clover=./.Build/logs/clover.xml --json_path=./.Build/logs/coveralls-upload.json -v) - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-coverals-${SUFFIX} -e XDEBUG_MODE=coverage -e XDEBUG_TRIGGER=foo -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - functional) - COMMAND=(.Build/bin/phpunit -c Build/phpunit/FunctionalTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") - case ${DBMS} in - mariadb) - echo "Using driver: ${DATABASE_DRIVER}" - ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null - waitFor mariadb-func-${SUFFIX} 3306 - CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mariadb-func-${SUFFIX} -e typo3DatabasePassword=funcp" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - mysql) - echo "Using driver: ${DATABASE_DRIVER}" - ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null - waitFor mysql-func-${SUFFIX} 3306 - CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mysql-func-${SUFFIX} -e typo3DatabasePassword=funcp" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - postgres) - ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-func-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null - waitFor postgres-func-${SUFFIX} 5432 - CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-${SUFFIX} -e typo3DatabasePassword=funcp" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - sqlite) - # create sqlite tmpfs mount typo3temp/var/tests/functional-sqlite-dbs/ to avoid permission issues - mkdir -p "${ROOT_DIR}/typo3temp/var/tests/functional-sqlite-dbs/" - CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${ROOT_DIR}/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" - SUITE_EXIT_CODE=$? - ;; - esac - ;; - lint) - COMMAND="php -v | grep '^PHP'; find . -name '*.php' ! -path '*.Build/*' -print0 | xargs -0 -n1 -P4 php -dxdebug.mode=off -l >/dev/null" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/bash -c "${COMMAND}" +# Suite execution: functional +if [ "${CHUNKS}" -gt 0 ]; then + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name func-splitter-${SUFFIX} -e CORE_ROOT="${CORE_ROOT}" -e RUNTESTS_DIRS_RPOJECT="${RUNTESTS_DIRS_RPOJECT}" ${IMAGE_PHP} php -dxdebug.mode=off ${RUNTESTS_DIR_BUILDER}splitFunctionalTests.php -v ${CHUNKS} + COMMAND=(${RUNTESTS_DIR_BIN}phpunit -c ${RUNTESTS_DIR_PHPUNIT_FUNCTIONAL}FunctionalTests-Job-${THISCHUNK}.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") +else + COMMAND=(${RUNTESTS_DIR_BIN}phpunit -c ${RUNTESTS_PHPUNIT_FILE_FUNCTIONALTEST} --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") +fi +${CONTAINER_BIN} run --rm ${CI_PARAMS} --name redis-func-${SUFFIX} --network ${NETWORK} -d ${IMAGE_REDIS} >/dev/null +${CONTAINER_BIN} run --rm ${CI_PARAMS} --name memcached-func-${SUFFIX} --network ${NETWORK} -d ${IMAGE_MEMCACHED} >/dev/null +waitFor redis-func-${SUFFIX} 6379 +waitFor memcached-func-${SUFFIX} 11211 +CONTAINER_COMMON_PARAMS="${CONTAINER_COMMON_PARAMS} -e typo3TestingRedisHost=redis-func-${SUFFIX} -e typo3TestingMemcachedHost=memcached-func-${SUFFIX}" +case ${DBMS} in + mariadb) + echo "Using driver: ${DATABASE_DRIVER}" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mariadb-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MARIADB} >/dev/null + waitFor mariadb-func-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mariadb-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; - rector) - DRY_RUN_OPTIONS='' - if [ "${DRY_RUN}" -eq 1 ]; then - DRY_RUN_OPTIONS='--dry-run' - fi - COMMAND="php -dxdebug.mode=off .Build/bin/rector process ${DRY_RUN_OPTIONS} --config=Build/rector/rector.php --no-progress-bar --ansi" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + mysql) + echo "Using driver: ${DATABASE_DRIVER}" + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name mysql-func-${SUFFIX} --network ${NETWORK} -d -e MYSQL_ROOT_PASSWORD=funcp --tmpfs /var/lib/mysql/:rw,noexec,nosuid ${IMAGE_MYSQL} >/dev/null + waitFor mysql-func-${SUFFIX} 3306 + CONTAINERPARAMS="-e typo3DatabaseDriver=${DATABASE_DRIVER} -e typo3DatabaseName=func_test -e typo3DatabaseUsername=root -e typo3DatabaseHost=mysql-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; - fractor) - DRY_RUN_OPTIONS='' - if [ "${DRY_RUN}" -eq 1 ]; then - DRY_RUN_OPTIONS='--dry-run' - fi - COMMAND="php -dxdebug.mode=off .Build/bin/fractor process ${DRY_RUN_OPTIONS} --config=Build/fractor/fractor.php --ansi" - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name composer-command-${SUFFIX} -e COMPOSER_CACHE_DIR=.cache/composer -e COMPOSER_ROOT_VERSION=${COMPOSER_ROOT_VERSION} ${IMAGE_PHP} /bin/sh -c "${COMMAND}" + postgres) + ${CONTAINER_BIN} run --rm ${CI_PARAMS} --name postgres-func-${SUFFIX} --network ${NETWORK} -d -e POSTGRES_PASSWORD=funcp -e POSTGRES_USER=funcu --tmpfs /var/lib/postgresql/data:rw,noexec,nosuid ${IMAGE_POSTGRES} >/dev/null + waitFor postgres-func-${SUFFIX} 5432 + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_pgsql -e typo3DatabaseName=bamboo -e typo3DatabaseUsername=funcu -e typo3DatabaseHost=postgres-func-${SUFFIX} -e typo3DatabasePassword=funcp" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; - unit) - COMMAND=(.Build/bin/phpunit -c Build/phpunit/UnitTests.xml --exclude-group not-${DBMS} ${EXTRA_TEST_OPTIONS} "$@") - ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name unit-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${IMAGE_PHP} "${COMMAND[@]}" + sqlite) + # create sqlite tmpfs mount (temp)functional-sqlite-dbs/ to avoid permission issues + mkdir -p "${CORE_ROOT}/${RUNTESTS_DIR_TESTTEMP}functional-sqlite-dbs/" + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${CORE_ROOT}/${RUNTESTS_DIR_TESTTEMP}functional-sqlite-dbs/:rw,noexec,nosuid" + ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; - update) - # pull typo3/core-testing-*:latest versions of those ones that exist locally - echo "> pull ghcr.io/typo3/core-testing-*:latest versions of those ones that exist locally" - ${CONTAINER_BIN} images ghcr.io/typo3/core-testing-*:latest --format "{{.Repository}}:latest" | xargs -I {} ${CONTAINER_BIN} pull {} - echo "" - # remove "dangling" typo3/core-testing-* images (those tagged as ) - echo "> remove \"dangling\" ghcr.io/typo3/core-testing-* images (those tagged as )" - ${CONTAINER_BIN} images --filter "reference=ghcr.io/typo3/core-testing-*" --filter "dangling=true" --format "{{.ID}}" | xargs -I {} ${CONTAINER_BIN} rmi {} - echo "" - ;; - *) - loadHelp - echo "Invalid -s option argument ${TEST_SUITE}" >&2 - echo >&2 - echo "${HELP}" >&2 - exit 1 - ;; esac cleanUp @@ -636,32 +445,21 @@ cleanUp # Print summary echo "" >&2 echo "###########################################################################" >&2 -echo "Result of ${TEST_SUITE}" >&2 -if [[ ${IS_CI} -eq 1 ]]; then - echo "Environment: CI" >&2 -else - echo "Environment: local" >&2 -fi +echo "Result of functional tests" >&2 +echo "Container runtime: ${CONTAINER_BIN}" >&2 echo "PHP: ${PHP_VERSION}" >&2 -echo "TYPO3: ${TYPO3_VERSION}" >&2 -echo "CONTAINER_BIN: ${CONTAINER_BIN}" -if [[ ${TEST_SUITE} =~ ^functional$ ]]; then - case "${DBMS}" in - mariadb|mysql) - echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver ${DATABASE_DRIVER}" >&2 - ;; - postgres) - echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver pdo_pgsql" >&2 - ;; - sqlite) - echo "DBMS: ${DBMS} driver pdo_sqlite" >&2 - ;; - esac -fi +case "${DBMS}" in + mariadb|mysql|postgres) + echo "DBMS: ${DBMS} version ${DBMS_VERSION} driver ${DATABASE_DRIVER}" >&2 + ;; + sqlite) + echo "DBMS: ${DBMS}" >&2 + ;; +esac if [[ -n ${EXTRA_TEST_OPTIONS} ]]; then echo " Note: Using -e is deprecated. Simply add the options at the end of the command." - echo " Instead of: Build/Scripts/runTests.sh -s ${TEST_SUITE} -e '${EXTRA_TEST_OPTIONS}' $@" - echo " use: Build/Scripts/runTests.sh -s ${TEST_SUITE} -- ${EXTRA_TEST_OPTIONS} $@" + echo " Instead of: ${THIS_SCRIPT_NAME} -e '${EXTRA_TEST_OPTIONS}' $@" + echo " use: ${THIS_SCRIPT_NAME} -- ${EXTRA_TEST_OPTIONS} $@" fi if [[ ${SUITE_EXIT_CODE} -eq 0 ]]; then echo "SUCCESS" >&2 @@ -671,5 +469,4 @@ fi echo "###########################################################################" >&2 echo "" >&2 -# Exit with code of test suite - This script return non-zero if the executed test failed. exit $SUITE_EXIT_CODE \ No newline at end of file diff --git a/Build/phpunit/FunctionalTests.xml b/Build/phpunit/FunctionalTests.xml index 4e5c10a..a67642a 100644 --- a/Build/phpunit/FunctionalTests.xml +++ b/Build/phpunit/FunctionalTests.xml @@ -14,6 +14,9 @@ displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnPhpunitDeprecations="true" > + + + ../../Tests/Functional/ diff --git a/Classes/Command/RunSqlScriptCommand.php b/Classes/Command/RunSqlScriptCommand.php index faead2a..e7c9288 100644 --- a/Classes/Command/RunSqlScriptCommand.php +++ b/Classes/Command/RunSqlScriptCommand.php @@ -89,17 +89,21 @@ private function getFileNames($input): array { $file = $input->getOption('file'); - if (file_exists($file) && $file) { + if ($file && file_exists($file)) { + + // If file path is already absolute, use it as-is + if (str_starts_with($file, '/')) { + return [$file]; + } + $fileName = Environment::getProjectPath() . '/' . $file; - return [ - $fileName ?? self::FILE_NAME, - ]; + return [$fileName ?? self::FILE_NAME]; } $directory = $input->getOption('directory'); - if (file_exists($directory) && $directory) { + if ($directory && file_exists($directory)) { $files = glob($directory . '/*.sql', GLOB_MARK); return $files; diff --git a/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php b/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php index 0937b55..539c9a8 100644 --- a/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php +++ b/Classes/Upgrades/CTypeToListTypeUpgradeWizard.php @@ -6,7 +6,7 @@ use Toumoro\TmMigration\Updates\AbstractListTypeToCTypeUpdate; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; -use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Install\Attribute\UpgradeWizard; /** @@ -16,6 +16,14 @@ final class CTypeToListTypeUpgradeWizard extends AbstractListTypeToCTypeUpdate { private const MAPPING_ARRAY = 'cTypeToListTypeMappingArray'; + private const DEFAULT_MAPPING_ARRAY = [ 'pi_plugin1' => 'new_content_element1' ]; + + public function __construct( + private readonly ConnectionPool $connectionPool, + private readonly ExtensionConfiguration $extensionConfiguration + ) { + parent::__construct($this->connectionPool); + } public function getTitle(): string { @@ -41,10 +49,7 @@ public function getDescription(): string */ protected function getListTypeToCTypeMapping(): array { - /** @var ExtensionConfiguration $extensionConfiguration */ - $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class); - - $emConfiguration = $extensionConfiguration->get('tm_migration'); + $emConfiguration = $this->extensionConfiguration->get('tm_migration'); if (isset($emConfiguration[self::MAPPING_ARRAY])) { $tmpArray = explode(',', $emConfiguration[self::MAPPING_ARRAY]); @@ -58,6 +63,6 @@ protected function getListTypeToCTypeMapping(): array return $cTypeListTypeMappingArray; } - return []; + return self::DEFAULT_MAPPING_ARRAY; } } diff --git a/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php b/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php index 3054068..f5b083a 100644 --- a/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php +++ b/Classes/Upgrades/GridElementsToContainerUpgradeWizard.php @@ -20,6 +20,10 @@ class GridElementsToContainerUpgradeWizard implements UpgradeWizardInterface { private const TT_CONTENT_TABLE = 'tt_content'; + public function __construct( + private readonly ConnectionPool $connectionPool + ) {} + /** * @inheritDoc */ @@ -41,7 +45,7 @@ public function getDescription(): string */ public function updateNecessary(): bool { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TT_CONTENT_TABLE); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TT_CONTENT_TABLE); return (bool)$queryBuilder->count('uid') ->from(self::TT_CONTENT_TABLE) ->where( @@ -66,7 +70,7 @@ public function getPrerequisites(): array */ public function executeUpdate(): bool { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TT_CONTENT_TABLE); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TT_CONTENT_TABLE); $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $result = $queryBuilder->select('*') @@ -89,7 +93,7 @@ public function executeUpdate(): bool protected function updateContainer(int $uid, string $identifier, ?string $flexForm): void { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TT_CONTENT_TABLE); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TT_CONTENT_TABLE); $queryBuilder->update(self::TT_CONTENT_TABLE) ->set('CType', $identifier) ->where( @@ -103,7 +107,7 @@ protected function updateContainer(int $uid, string $identifier, ?string $flexFo protected function updateChildren(int $uid): void { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TT_CONTENT_TABLE); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TT_CONTENT_TABLE); $result = $queryBuilder->select('*') ->from(self::TT_CONTENT_TABLE) ->where( @@ -124,7 +128,7 @@ protected function updateChildren(int $uid): void protected function updateChild(int $uid, int $parent, int $colPos): void { - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TT_CONTENT_TABLE); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TT_CONTENT_TABLE); $queryBuilder->update(self::TT_CONTENT_TABLE) ->set('tx_container_parent', $parent) ->set('colPos', (100 + $colPos)) diff --git a/Classes/Upgrades/TruncateLogTableUpgradeWizard.php b/Classes/Upgrades/TruncateLogTableUpgradeWizard.php index 7222b63..400291c 100644 --- a/Classes/Upgrades/TruncateLogTableUpgradeWizard.php +++ b/Classes/Upgrades/TruncateLogTableUpgradeWizard.php @@ -8,6 +8,7 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Toumoro\TmMigration\Utility\ConfigurationUtility; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -26,6 +27,13 @@ final class TruncateLogTableUpgradeWizard implements UpgradeWizardInterface, Log private const LOG_TABLE = 'sys_log'; private const DATE_FIELD = 'tstamp'; + /** + * @param ExtensionConfiguration $extensionConfiguration + */ + public function __construct( + private readonly ExtensionConfiguration $extensionConfiguration + ) {} + /** * @return string Title of this updater */ @@ -75,8 +83,10 @@ private function deleteLogTable(): bool $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::LOG_TABLE); $queryBuilder->delete(self::LOG_TABLE); - if (ConfigurationUtility::getNumberOfDays()) { - $numberOfDays = ConfigurationUtility::getNumberOfDays(); + $emConfiguration = $this->extensionConfiguration->get('tm_migration'); + + if ($emConfiguration['numberOfDays']) { + $numberOfDays = $emConfiguration['numberOfDays']; $deleteTimestamp = strtotime('-' . $numberOfDays . 'days'); $queryBuilder->where( diff --git a/Classes/Utility/ConfigurationUtility.php b/Classes/Utility/ConfigurationUtility.php index c5e2ba1..4dbd379 100644 --- a/Classes/Utility/ConfigurationUtility.php +++ b/Classes/Utility/ConfigurationUtility.php @@ -31,13 +31,6 @@ public static function isDisableTruncateLogUpgradeWizard(): bool return $configuration['disableTruncateLogUpgradeWizard'] === '1'; } - public static function getNumberOfDays(): string - { - $configuration = self::getExtensionConfiguration(); - - return $configuration['numberOfDays'] ?? ''; - } - public static function getUpgradeWizardToExclude(): array { $configuration = self::getExtensionConfiguration(); diff --git a/Tests/Functional/Command/ClearSysLogCommandTest.php b/Tests/Functional/Command/ClearSysLogCommandTest.php new file mode 100644 index 0000000..2231ce1 --- /dev/null +++ b/Tests/Functional/Command/ClearSysLogCommandTest.php @@ -0,0 +1,114 @@ +mockSQLService = $this->createMock(SQLMigrationService::class); + GeneralUtility::addInstance(SQLMigrationService::class, $this->mockSQLService); + + $this->subject = new ClearSysLogCommand(self::COMMAND_NAME); + $application = new Application(); + $application->add($this->subject); + + $command = $application->find('tmupgrade:clearsyslog'); + $this->commandTester = new CommandTester($command); + } + + #[Test] + public function isConsoleCommand(): void + { + self::assertInstanceOf(Command::class, $this->subject); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'Clear table sys_log.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function hasHelpText(): void + { + $expected = 'This command execute an SQL script that clears the sys_log database table. -d days -l limit.'; + self::assertSame($expected, $this->subject->getHelp()); + } + + #[Test] + public function testExecuteSuccess() + { + $this->mockSQLService + ->expects(self::once()) + ->method('migrate') + ->with(self::callback(function ($statements) { + $sql = $statements[0]; + $this->assertStringContainsString('DELETE FROM sys_log', $sql); + $this->assertStringContainsString('recuid=0', $sql); + return true; + })) + ->willReturn(1); + + $exitCode = $this->commandTester->execute(['--limit' => 100, '--days' => 7]); + self::assertSame(0, $exitCode); + } + + #[Test] + public function testExecuteFailure() + { + $this->mockSQLService + ->expects(self::once()) + ->method('migrate') + ->willReturn(0); + + $exitCode = $this->commandTester->execute([]); + self::assertSame(1, $exitCode); + } + + #[Test] + public function testSqlContainsLimitAndTimestamp() + { + $days = 3; + $limit = 50; + $expectedTimestamp = strtotime('-' . $days . ' days'); + + $this->mockSQLService + ->expects(self::once()) + ->method('migrate') + ->with(self::callback(function ($statements) use ($limit, $expectedTimestamp) { + $sql = $statements[0]; + $this->assertStringContainsString('LIMIT ' . $limit, $sql); + $this->assertStringContainsString((string)$expectedTimestamp, $sql); + return true; + })) + ->willReturn(1); + + $this->commandTester->execute(['--limit' => $limit, '--days' => $days]); + } +} diff --git a/Tests/Functional/Command/ExportCtypeListTypeCommandTest.php b/Tests/Functional/Command/ExportCtypeListTypeCommandTest.php new file mode 100644 index 0000000..ac8134b --- /dev/null +++ b/Tests/Functional/Command/ExportCtypeListTypeCommandTest.php @@ -0,0 +1,148 @@ +connectionPool = $this->createMock(ConnectionPool::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->subject = new ExportCtypeListTypeCommand($this->connectionPool, $this->logger, self::COMMAND_NAME); + $application = new Application(); + $application->add($this->subject); + + $command = $application->find(self::COMMAND_NAME); + $this->commandTester = new CommandTester($command); + } + + #[Test] + public function isConsoleCommand(): void + { + self::assertInstanceOf(Command::class, $this->subject); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'Export CTypes and List Types to JSON or CSV.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function executeGeneratesCsvFileSuccessfully(): void + { + $queryBuilder = $this->createMock(QueryBuilder::class); + $result = $this->createMock(Result::class); + $restrictionContainer = $this->createMock(DefaultRestrictionContainer::class); + $expressionBuilder = $this->createMock(ExpressionBuilder::class); + + $this->connectionPool + ->expects(self::once()) + ->method('getQueryBuilderForTable') + ->willReturn($queryBuilder); + + $queryBuilder->method('getRestrictions')->willReturn($restrictionContainer); + $restrictionContainer->method('removeAll')->willReturnSelf(); + + $queryBuilder->method('expr')->willReturn($expressionBuilder); + $expressionBuilder->method('notIn')->willReturn('not_in_expr'); + + $queryBuilder->method('selectLiteral')->willReturnSelf(); + $queryBuilder->method('addSelect')->willReturnSelf(); + $queryBuilder->method('from')->willReturnSelf(); + $queryBuilder->method('where')->willReturnSelf(); + $queryBuilder->method('groupBy')->willReturnSelf(); + $queryBuilder->method('addGroupBy')->willReturnSelf(); + $queryBuilder->method('createNamedParameter')->willReturn('param'); + $queryBuilder->method('executeQuery')->willReturn($result); + + $result->method('fetchAllAssociative')->willReturn([ + ['CType' => 'my_ctype', 'list_type' => 'my_list', 'pids' => '1,2,3'], + ]); + + $fileName = sys_get_temp_dir() . '/export_test.csv'; + + $exitCode = $this->commandTester->execute([ + '--fileName' => $fileName, + ]); + + self::assertFileExists($fileName); + self::assertSame(Command::SUCCESS, $exitCode); + + $content = file_get_contents($fileName); + self::assertStringContainsString('"CType","list_type","pids"', $content); + self::assertStringContainsString('my_ctype,my_list,1,2,3', str_replace('"', '', $content)); + + unlink($fileName); + } + + #[Test] + public function executeWithJsonOptionDisplaysListTypeMapping(): void + { + $queryBuilder = $this->createMock(QueryBuilder::class); + $result = $this->createMock(Result::class); + $restrictionContainer = $this->createMock(DefaultRestrictionContainer::class); + $expressionBuilder = $this->createMock(ExpressionBuilder::class); + + $this->connectionPool + ->expects(self::once()) + ->method('getQueryBuilderForTable') + ->willReturn($queryBuilder); + + $queryBuilder->method('getRestrictions')->willReturn($restrictionContainer); + $restrictionContainer->method('removeAll')->willReturnSelf(); + + $queryBuilder->method('expr')->willReturn($expressionBuilder); + $expressionBuilder->method('notIn')->willReturn('not_in_expr'); + + $queryBuilder->method('selectLiteral')->willReturnSelf(); + $queryBuilder->method('addSelect')->willReturnSelf(); + $queryBuilder->method('from')->willReturnSelf(); + $queryBuilder->method('where')->willReturnSelf(); + $queryBuilder->method('groupBy')->willReturnSelf(); + $queryBuilder->method('addGroupBy')->willReturnSelf(); + $queryBuilder->method('createNamedParameter')->willReturn('param'); + $queryBuilder->method('executeQuery')->willReturn($result); + + $result->method('fetchAllAssociative')->willReturn([ + ['CType' => 'list', 'list_type' => 'plugin_test', 'pids' => '99'], + ]); + + $exitCode = $this->commandTester->execute([ + '--fileType' => 'json', + ]); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString('plugin_test:plugin_test', $output); + self::assertSame(Command::SUCCESS, $exitCode); + } +} diff --git a/Tests/Functional/Command/FixDatabaseErrorsCommandTest.php b/Tests/Functional/Command/FixDatabaseErrorsCommandTest.php new file mode 100644 index 0000000..813cda2 --- /dev/null +++ b/Tests/Functional/Command/FixDatabaseErrorsCommandTest.php @@ -0,0 +1,83 @@ +connectionPool = $this->createMock(ConnectionPool::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->subject = new FixDatabaseErrorsCommand($this->connectionPool, $this->logger, self::COMMAND_NAME); + $application = new Application(); + $application->add($this->subject); + + $command = $application->find(self::COMMAND_NAME); + $this->commandTester = new CommandTester($command); + } + + #[Test] + public function isConsoleCommand(): void + { + self::assertInstanceOf(Command::class, $this->subject); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'Fix database updateschema errors'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function hasHelpText(): void + { + $expected = 'This command does nothing. It always succeeds.'; + self::assertSame($expected, $this->subject->getHelp()); + } + + #[Test] + public function executeWithNoAffectedTablesPrintsInfoAndSucceeds(): void + { + $exitCode = $this->commandTester->execute([]); + $output = $this->commandTester->getDisplay(); + + self::assertSame(Command::SUCCESS, $exitCode); + self::assertStringContainsString('Nothing to process', $output); + self::assertStringContainsString('finished!', $output); + } + + #[Test] + public function executeWithAffectedTablesProcessesThem(): void + { + $exitCode = $this->commandTester->execute([]); + $output = $this->commandTester->getDisplay(); + + self::assertSame(Command::SUCCESS, $exitCode); + self::assertStringContainsString('finished!', $output); + } +} diff --git a/Tests/Functional/Command/RunSqlScriptCommandTest.php b/Tests/Functional/Command/RunSqlScriptCommandTest.php new file mode 100644 index 0000000..355b90b --- /dev/null +++ b/Tests/Functional/Command/RunSqlScriptCommandTest.php @@ -0,0 +1,142 @@ +subject = new RunSqlScriptCommand(self::COMMAND_NAME); + $application = new Application(); + $application->add($this->subject); + + $command = $application->find(self::COMMAND_NAME); + $this->commandTester = new CommandTester($command); + } + + #[Test] + public function isConsoleCommand(): void + { + self::assertInstanceOf(Command::class, $this->subject); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'Run custom SQL scripts.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function hasHelpText(): void + { + $expected = <<subject->getHelp()); + } + + #[Test] + public function executesSqlFileSuccessfully(): void + { + $SQLFile = __DIR__ . '/../Fixtures/Database/queries.sql'; + + $mockMigrationService = $this->createMock(SQLMigrationService::class); + $mockMigrationService->expects(self::once()) + ->method('migrate') + ->willReturn(4); + + GeneralUtility::addInstance(SQLMigrationService::class, $mockMigrationService); + + $this->commandTester->execute([ + '--file' => $SQLFile, + ]); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString('executed with no errors', $output); + self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + } + + #[Test] + public function failsIfFileIsEmpty(): void + { + $emptySQLFile = __DIR__ . '/../Fixtures/Database/empty.sql'; + + $mockMigrationService = $this->createMock(SQLMigrationService::class); + $mockMigrationService->expects(self::never())->method('migrate'); + + GeneralUtility::addInstance(SQLMigrationService::class, $mockMigrationService); + + $this->commandTester->execute([ + '--file' => $emptySQLFile, + ]); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString('is empty', $output); + self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); + } + + #[Test] + public function failsIfNoFileFound(): void + { + $nonExistentSQLFile = __DIR__ . '/../Fixtures/Database/nonexistant.sql'; + + $mockMigrationService = $this->createMock(SQLMigrationService::class); + $mockMigrationService->expects(self::never())->method('migrate'); + + GeneralUtility::addInstance(SQLMigrationService::class, $mockMigrationService); + + $this->commandTester->execute([ + '--file' => $nonExistentSQLFile, + ]); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString('no sql file found', $output); + self::assertSame(Command::FAILURE, $this->commandTester->getStatusCode()); + } + + #[Test] + public function executesAllSqlFilesFromDirectory(): void + { + $SQLDir = __DIR__ . '/../Fixtures/Database/'; + + $mockMigrationService = $this->createMock(SQLMigrationService::class); + $mockMigrationService->expects(self::exactly(1)) + ->method('migrate') + ->willReturn(4); + + GeneralUtility::addInstance(SQLMigrationService::class, $mockMigrationService); + + $this->commandTester->execute([ + '--directory' => $SQLDir, + ]); + + $output = $this->commandTester->getDisplay(); + self::assertStringContainsString('executed with no errors', $output); + self::assertSame(Command::SUCCESS, $this->commandTester->getStatusCode()); + } +} diff --git a/Tests/Functional/Command/UpgradeWizardRunCommandTest.php b/Tests/Functional/Command/UpgradeWizardRunCommandTest.php new file mode 100644 index 0000000..034900e --- /dev/null +++ b/Tests/Functional/Command/UpgradeWizardRunCommandTest.php @@ -0,0 +1,100 @@ + 'pi_plugin1:new_content_element1,pi_plugin2:new_content_element2', + ]; + + $this->lateBootService = $this->createMock(LateBootService::class); + $this->databaseUpgradeWizardsService = $this->createMock(DatabaseUpgradeWizardsService::class); + $this->silentConfigurationUpgradeService = $this->createMock(SilentConfigurationUpgradeService::class); + + $mockUpgradeWizardsService = $this->createMock(UpgradeWizardsService::class); + + $mockContainer = $this->createMock(ContainerInterface::class); + $mockContainer->method('get') + ->with(UpgradeWizardsService::class) + ->willReturn($mockUpgradeWizardsService); + + $this->lateBootService->method('loadExtLocalconfDatabaseAndExtTables') + ->willReturn($mockContainer); + + $this->databaseUpgradeWizardsService->method('isDatabaseCharsetUtf8') + ->willReturn(true); + + $this->subject = new UpgradeWizardRunCommand( + $this->lateBootService, + $this->databaseUpgradeWizardsService, + $this->silentConfigurationUpgradeService, + self::COMMAND_NAME + ); + + $application = new Application(); + $application->add($this->subject); + + $command = $application->find(self::COMMAND_NAME); + $this->commandTester = new CommandTester($command); + } + + #[Test] + public function isConsoleCommand(): void + { + self::assertInstanceOf(Command::class, $this->subject); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'Run upgrade wizard. Without arguments all available wizards will be run.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function hasHelpText(): void + { + $expected = 'This command allows running upgrade wizards on CLI. To run a single wizard add the ' + . 'identifier of the wizard as argument. The identifier of the wizard is the name it is ' + . 'registered with in ext_localconf.'; + self::assertSame($expected, $this->subject->getHelp()); + } + + #[Test] + public function testExecuteSuccess(): void + { + $exitCode = $this->commandTester->execute([]); + self::assertSame(0, $exitCode); + } +} diff --git a/Tests/Unit/.gitkeep b/Tests/Functional/Fixtures/Database/empty.sql similarity index 100% rename from Tests/Unit/.gitkeep rename to Tests/Functional/Fixtures/Database/empty.sql diff --git a/Tests/Functional/Fixtures/queries_to_migrate.sql b/Tests/Functional/Fixtures/Database/queries.sql similarity index 100% rename from Tests/Functional/Fixtures/queries_to_migrate.sql rename to Tests/Functional/Fixtures/Database/queries.sql diff --git a/Tests/Functional/Fixtures/sys_log.csv b/Tests/Functional/Fixtures/sys_log.csv new file mode 100644 index 0000000..bca93da --- /dev/null +++ b/Tests/Functional/Fixtures/sys_log.csv @@ -0,0 +1,7 @@ +"sys_log" +,"uid","tstamp","details" +,1,1723500000,"Old log entry — older than 90 days (should be deleted)" +,2,1726000000,"Log entry — around 60 days old (should be deleted if threshold < 60)" +,3,1728500000,"Recent log entry — 30 days old (should be kept)" +,4,1730400000,"New entry — within the last week (should be kept)" +,5,1710000000,"Very old log entry — over 200 days old (should be deleted)" \ No newline at end of file diff --git a/Tests/Functional/Fixtures/sys_redirect.csv b/Tests/Functional/Fixtures/sys_redirect.csv new file mode 100644 index 0000000..9486952 --- /dev/null +++ b/Tests/Functional/Fixtures/sys_redirect.csv @@ -0,0 +1,7 @@ +"sys_redirect" +,"uid","pid","source_path","target_statuscode","target" +,1,0,"no-leading-slash",307,"/target1" +,2,0,"/wrong-status-code",0,"/target2" +,3,0,"/already-correct",301,"/target3" +,4,0,"^products/(.*)$",301,"target4" +,5,0,"https://www.google.com",301,"target5" \ No newline at end of file diff --git a/Tests/Functional/Service/SQLMigrationServiceTest.php b/Tests/Functional/Service/SQLMigrationServiceTest.php index aae53d0..69b0717 100644 --- a/Tests/Functional/Service/SQLMigrationServiceTest.php +++ b/Tests/Functional/Service/SQLMigrationServiceTest.php @@ -19,7 +19,7 @@ protected function setUp(): void { parent::setUp(); - $sqlFile = __DIR__ . '/../Fixtures/queries_to_migrate.sql'; + $sqlFile = __DIR__ . '/../Fixtures/Database/queries.sql'; $sqlContent = file_get_contents($sqlFile); if ($sqlContent === false) { diff --git a/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php b/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php new file mode 100644 index 0000000..afa9b34 --- /dev/null +++ b/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php @@ -0,0 +1,90 @@ +subject = new FixRedirectsUpgraeWizard(); + + $this->importCSVDataSet(__DIR__ . '/../Fixtures/sys_redirect.csv'); + } + + #[Test] + public function isUpgradeWizard(): void + { + self::assertInstanceOf(UpgradeWizardInterface::class, $this->subject); + } + + #[Test] + public function hasTitle(): void + { + $expected = 'Repair Invalid Redirects'; + self::assertSame($expected, $this->subject->getTitle()); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'This upgrade wizard identifies and corrects invalid or outdated redirect entries in the database.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function testWizardRepairsInvalidRedirects(): void + { + $wizard = new FixRedirectsUpgraeWizard(); + + self::assertTrue($wizard->updateNecessary()); + $wizard->executeUpdate(); + + $connection = $this->getConnectionPool()->getConnectionForTable('sys_redirect'); + $redirects = $connection->select( + ['uid', 'source_path', 'target_statuscode', 'target'], + 'sys_redirect' + )->fetchAllAssociative(); + + $redirectsByUid = []; + foreach ($redirects as $row) { + $redirectsByUid[$row['uid']] = $row; + } + + // 1. no-leading-slash + self::assertSame('/no-leading-slash', $redirectsByUid[1]['source_path']); + self::assertSame(307, (int)$redirectsByUid[1]['target_statuscode']); + self::assertSame('/target1', $redirectsByUid[1]['target']); + + // 2. wrong-status-code + self::assertSame('/wrong-status-code', $redirectsByUid[2]['source_path']); + self::assertSame(307, (int)$redirectsByUid[2]['target_statuscode']); + self::assertSame('/target2', $redirectsByUid[2]['target']); + + // 3. already-correct + self::assertSame('/already-correct', $redirectsByUid[3]['source_path']); + self::assertSame(301, (int)$redirectsByUid[3]['target_statuscode']); + self::assertSame('/target3', $redirectsByUid[3]['target']); + + // 4. regex pattern + self::assertSame('^products/(.*)$', $redirectsByUid[4]['source_path']); + self::assertSame(301, (int)$redirectsByUid[4]['target_statuscode']); + self::assertSame('target4', $redirectsByUid[4]['target']); + + // 5. external URL + self::assertSame('https://www.google.com', $redirectsByUid[5]['source_path']); + self::assertSame(301, (int)$redirectsByUid[5]['target_statuscode']); + self::assertSame('target5', $redirectsByUid[5]['target']); + } +} diff --git a/Tests/Functional/Upgrades/GridElementsToContainerUpgradeWizardTest.php b/Tests/Functional/Upgrades/GridElementsToContainerUpgradeWizardTest.php new file mode 100644 index 0000000..a32cd6c --- /dev/null +++ b/Tests/Functional/Upgrades/GridElementsToContainerUpgradeWizardTest.php @@ -0,0 +1,105 @@ +queryBuilderMock = $this->createMock(QueryBuilder::class); + + $restrictionMock = $this->createMock(QueryRestrictionContainerInterface::class); + $restrictionMock->method('removeAll')->willReturnSelf(); + $restrictionMock->method('add')->willReturnSelf(); + + $this->queryBuilderMock + ->method('getRestrictions') + ->willReturn($restrictionMock); + + $this->connectionPoolMock = $this->createMock(ConnectionPool::class); + $this->connectionPoolMock + ->method('getQueryBuilderForTable') + ->willReturn($this->queryBuilderMock); + + GeneralUtility::addInstance(ConnectionPool::class, $this->connectionPoolMock); + + $this->subject = new GridElementsToContainerUpgradeWizard($this->connectionPoolMock); + } + + #[Test] + public function isUpgradeWizard(): void + { + self::assertInstanceOf(UpgradeWizardInterface::class, $this->subject); + } + + #[Test] + public function hasTitle(): void + { + self::assertSame('Migrate Gridelements to Container', $this->subject->getTitle()); + } + + #[Test] + public function hasDescription(): void + { + self::assertSame( + 'This update wizard switches from EXT:gridelements to EXT:container.', + $this->subject->getDescription() + ); + } + + #[Test] + public function testExecuteUpdateProcessesFoundGridelements(): void + { + $gridElement = [ + 'uid' => 1, + 'tx_gridelements_backend_layout' => 'container_2col', + 'pi_flexform' => '', + ]; + + $result = $this->createMock(Result::class); + $result->method('fetchAllAssociative')->willReturn([$gridElement]); + + $this->queryBuilderMock + ->method('select')->willReturn($this->queryBuilderMock); + $this->queryBuilderMock + ->method('from')->willReturn($this->queryBuilderMock); + $this->queryBuilderMock + ->method('where')->willReturn($this->queryBuilderMock); + $this->queryBuilderMock + ->method('executeQuery')->willReturn($result); + + $wizard = $this->getMockBuilder(GridElementsToContainerUpgradeWizard::class) + ->setConstructorArgs([$this->connectionPoolMock]) + ->onlyMethods(['updateContainer', 'updateChildren']) + ->getMock(); + + $wizard->expects(self::once()) + ->method('updateContainer') + ->with(1, 'container_2col', ''); + + $wizard->expects(self::once()) + ->method('updateChildren') + ->with(1); + + self::assertTrue($wizard->executeUpdate()); + } +} diff --git a/Tests/Functional/Upgrades/TruncateLogTableUpgradeWizardTest.php b/Tests/Functional/Upgrades/TruncateLogTableUpgradeWizardTest.php new file mode 100644 index 0000000..8bb3d08 --- /dev/null +++ b/Tests/Functional/Upgrades/TruncateLogTableUpgradeWizardTest.php @@ -0,0 +1,99 @@ +extensionConfigurationMock = $this->createMock(ExtensionConfiguration::class); + GeneralUtility::addInstance(ExtensionConfiguration::class, $this->extensionConfigurationMock); + + $this->subject = new TruncateLogTableUpgradeWizard($this->extensionConfigurationMock); + + $this->importCSVDataSet(__DIR__ . '/../Fixtures/sys_log.csv'); + } + + #[Test] + public function isUpgradeWizard(): void + { + self::assertInstanceOf(UpgradeWizardInterface::class, $this->subject); + } + + #[Test] + public function hasTitle(): void + { + $expected = 'Truncate/Delete entries from Log Table'; + self::assertSame($expected, $this->subject->getTitle()); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'This update wizard truncates/deletes Log entries based on given lifetime.'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function updateIsNotNecessaryWhenDisabledInConfiguration(): void + { + $this->extensionConfigurationMock + ->method('get') + ->with('tm_migration') + ->willReturn(['disableTruncateLogUpgradeWizard' => '1']); + + $wizard = new TruncateLogTableUpgradeWizard($this->extensionConfigurationMock); + self::assertFalse($wizard->updateNecessary()); + } + + #[Test] + public function updateIsNecessaryWhenEnabledInConfiguration(): void + { + $this->extensionConfigurationMock + ->method('get') + ->with('tm_migration') + ->willReturn(['disableTruncateLogUpgradeWizard' => '0']); + + $wizard = new TruncateLogTableUpgradeWizard($this->extensionConfigurationMock); + self::assertTrue($wizard->updateNecessary()); + } + + #[Test] + public function executeUpdateDeletesEntriesOlderThanConfiguredDays(): void + { + $this->extensionConfigurationMock + ->method('get') + ->with('tm_migration') + ->willReturn([ + 'disableTruncateLogUpgradeWizard' => false, + 'numberOfDays' => '40', + ]); + + $wizard = new TruncateLogTableUpgradeWizard($this->extensionConfigurationMock); + $result = $wizard->executeUpdate(); + + self::assertTrue($result); + + $connection = $this->getConnectionPool()->getConnectionForTable('sys_log'); + $count = $connection->count('*', 'sys_log', []); + + self::assertSame(0, $count); + } +} diff --git a/Tests/Unit/ConfigurationUtilityTest.php b/Tests/Unit/ConfigurationUtilityTest.php new file mode 100644 index 0000000..c3aa97a --- /dev/null +++ b/Tests/Unit/ConfigurationUtilityTest.php @@ -0,0 +1,56 @@ +extensionConfigurationMock = $this->createMock(ExtensionConfiguration::class); + GeneralUtility::addInstance(ExtensionConfiguration::class, $this->extensionConfigurationMock); + } + + #[Test] + public function testIsDisableTruncateLogUpgradeWizardReturnsTrue(): void + { + $this->extensionConfigurationMock + ->method('get') + ->with('tm_migration') + ->willReturn(['disableTruncateLogUpgradeWizard' => '1']); + + self::assertTrue(ConfigurationUtility::isDisableTruncateLogUpgradeWizard()); + } + + #[Test] + public function testGetUpgradeWizardToExcludeHandlesEmptyConfiguration(): void + { + $this->extensionConfigurationMock + ->method('get') + ->willReturn([]); + + self::assertSame([], ConfigurationUtility::getUpgradeWizardToExclude()); + } + + #[Test] + public function testGetUpgradeWizardFromVersionReturnsExpectedValue(): void + { + $this->extensionConfigurationMock + ->method('get') + ->willReturn(['upgradeWizards' => ['fromVersion' => '12.4.0']]); + + self::assertSame('12.4.0', ConfigurationUtility::getUpgradeWizardFromVersion()); + } +} diff --git a/Tests/Unit/Upgrades/CTypeToListTypeUpgradeWizardTest.php b/Tests/Unit/Upgrades/CTypeToListTypeUpgradeWizardTest.php new file mode 100644 index 0000000..86e4687 --- /dev/null +++ b/Tests/Unit/Upgrades/CTypeToListTypeUpgradeWizardTest.php @@ -0,0 +1,96 @@ +connectionPool = $this->createMock(ConnectionPool::class); + $this->extensionConfiguration = $this->createMock(ExtensionConfiguration::class); + + $this->subject = new CTypeToListTypeUpgradeWizard( + $this->connectionPool, + $this->extensionConfiguration + ); + } + + #[Test] + public function isUpgradeWizard(): void + { + self::assertInstanceOf(UpgradeWizardInterface::class, $this->subject); + } + + #[Test] + public function hasTitle(): void + { + $expected = 'Migrate plugins to content elements.'; + self::assertSame($expected, $this->subject->getTitle()); + } + + #[Test] + public function hasDescription(): void + { + $expected = 'This command migrates plugins [list_type] to content elements [ctype].'; + self::assertSame($expected, $this->subject->getDescription()); + } + + #[Test] + public function testReturnsDefaultMappingWhenNoConfigurationExists(): void + { + $this->extensionConfiguration + ->method('get') + ->with('tm_migration') + ->willReturn(null); + + $method = new \ReflectionMethod($this->subject, 'getListTypeToCTypeMapping'); + $method->setAccessible(true); + + $result = $method->invoke($this->subject); + + $expected = [ + 'pi_plugin1' => 'new_content_element1', + ]; + + self::assertSame($expected, $result); + } + + #[Test] + public function testReturnsCustomMappingWhenConfigurationExists(): void + { + $this->extensionConfiguration + ->method('get') + ->with('tm_migration') + ->willReturn([ + 'cTypeToListTypeMappingArray' => 'pi_plugin1:new_content_element1,pi_plugin2:new_content_element2', + ]); + + $method = new \ReflectionMethod($this->subject, 'getListTypeToCTypeMapping'); + $method->setAccessible(true); + + $result = $method->invoke($this->subject); + + $expected = [ + 'pi_plugin1' => 'new_content_element1', + 'pi_plugin2' => 'new_content_element2', + ]; + + self::assertSame($expected, $result); + } +} diff --git a/composer.json b/composer.json index f2262c4..558660c 100644 --- a/composer.json +++ b/composer.json @@ -20,18 +20,20 @@ } ], "require": { - "php": "^8.1 || ^8.2", + "php": ">= 8.1 < 8.5", "typo3/cms-core": "^12.4.0 || ^13.4.0", "a9f/typo3-fractor": "^v0.5.1", "ssch/typo3-rector": "^2.14.4 || ^v3.5.0", - "wapplersystems/core-upgrader": "dev-release/v12 || dev-release/v13" + "wapplersystems/core-upgrader": "dev-release/v12 || dev-release/v13", + "typo3/cms-redirects": "^13.4" }, "require-dev": { "phpstan/phpdoc-parser": "^2.2.0", "typo3/testing-framework": "^9.2.0", "phpunit/phpunit": "^11.5.33", "typo3/coding-standards": "^0.8.0", - "friendsofphp/php-cs-fixer": "^3.86.0" + "friendsofphp/php-cs-fixer": "^3.86.0", + "dg/bypass-finals": "^1.9" }, "autoload": { "psr-4": { @@ -62,6 +64,7 @@ "git config --local core.hooksPath .githooks/" ], "php:cs": "php-cs-fixer fix --config=Build/php-cs-fixer/php-cs-fixer.php -v --dry-run --using-cache no --diff", + "php:unit": "phpunit -c Build/phpunit/UnitTests.xml", "php:fix": "php-cs-fixer fix --config=Build/php-cs-fixer/php-cs-fixer.php" }, "extra": { @@ -69,5 +72,5 @@ "extension-key": "tm_migration", "web-dir": ".Build/Web" } - } + } } diff --git a/ext_emconf.php b/ext_emconf.php index 77004b5..0d38548 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -10,9 +10,9 @@ 'version' => '13.4.6', 'constraints' => [ 'depends' => [ - 'a9f/typo3-fractor' => '*', - 'ssch/typo3-rector' => '*', - 'wapplersystems/core-upgrader' => '*', + 'a9f/typo3-fractor' => '0.5.0-0.9.0', + 'ssch/typo3-rector' => '2.0.0-3.9.0', + 'wapplersystems/core-upgrader' => 'dev-release/v12 || dev-release/v13', ], 'conflicts' => [], 'suggests' => [], From bb2525f48c012dbdcb599dade4ef76cc4bd3e55f Mon Sep 17 00:00:00 2001 From: tm-hdaoud Date: Tue, 21 Oct 2025 22:12:31 +0000 Subject: [PATCH 4/6] hub#18022 Ajout des standards de code et premier test --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae49d5e..c3997af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: run: composer install --prefer-dist --no-progress - name: ci:php - run: composer ci:php + run: composer php:cs - name: ci:unit run: composer php:unit From 31e5ff8d7cbccc41354c9fb0e221a6cdaf352a38 Mon Sep 17 00:00:00 2001 From: tm-hdaoud Date: Tue, 21 Oct 2025 22:15:07 +0000 Subject: [PATCH 5/6] hub#18022 Ajout des standards de code et premier test --- CHANGELOG.md | 5 +++++ Classes/Command/SeperateSyshistoryFromSyslogCommand.php | 3 +-- Classes/Upgrades/FixRedirectsUpgraeWizard.php | 2 +- Resources/Private/Config/Rector/rector_v12.php | 2 +- ext_emconf.php | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 798eb17..208b1b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this extension will be documented in this file. +## [13.4.10] - 2025-10-21 +### Added +- Add functional and unit tests. +- Add GitHub Actions workflow for CI. + ## [13.4.9] - 2025-09-25 ### Added - Rename `ClearSysLogCommand` to `SeperateSyshistoryFromSyslogCommand`. diff --git a/Classes/Command/SeperateSyshistoryFromSyslogCommand.php b/Classes/Command/SeperateSyshistoryFromSyslogCommand.php index 44fc2e2..6021321 100644 --- a/Classes/Command/SeperateSyshistoryFromSyslogCommand.php +++ b/Classes/Command/SeperateSyshistoryFromSyslogCommand.php @@ -56,8 +56,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($condition) { $success = Command::SUCCESS; $this->io->info('seperate sys_history from sys_log command executed with no errors.'); - } - else{ + } else { $success = Command::FAILURE; $this->io->info('seperate sys_history from sys_log command failed to be executed.'); } diff --git a/Classes/Upgrades/FixRedirectsUpgraeWizard.php b/Classes/Upgrades/FixRedirectsUpgraeWizard.php index 4d6f367..e3c0952 100644 --- a/Classes/Upgrades/FixRedirectsUpgraeWizard.php +++ b/Classes/Upgrades/FixRedirectsUpgraeWizard.php @@ -73,7 +73,7 @@ private function updateRedirects(): bool { $redirects = $this->getBrokenRedirects(); - if(!empty($redirects)) { + if (!empty($redirects)) { foreach ($redirects as $row) { $sourcePath = $row['source_path']; diff --git a/Resources/Private/Config/Rector/rector_v12.php b/Resources/Private/Config/Rector/rector_v12.php index 64fae72..ee23588 100644 --- a/Resources/Private/Config/Rector/rector_v12.php +++ b/Resources/Private/Config/Rector/rector_v12.php @@ -46,7 +46,7 @@ // @see https://github.com/sabbelasichon/typo3-rector/issues/2536 __DIR__ . '/**/Configuration/ExtensionBuilder/*', NameImportingPostRector::class => [ - 'ClassAliasMap.php', + 'ClassAliasMap.php', ], // Exlclude non autoloaded classes from DI injection // @see https://github.com/sabbelasichon/typo3-rector/issues/4604 diff --git a/ext_emconf.php b/ext_emconf.php index 9ac2241..f508b34 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -7,7 +7,7 @@ 'author' => 'Haythem Daoud', 'author_email' => 'haythem.daoud@toumoro.com', 'state' => 'stable', - 'version' => '13.4.9', + 'version' => '13.4.10', 'constraints' => [ 'depends' => [ 'a9f/typo3-fractor' => '0.5.0-0.9.0', From 104d9ba235b3828c331c7fee9c6f55791425ebde Mon Sep 17 00:00:00 2001 From: tm-hdaoud Date: Tue, 21 Oct 2025 22:32:59 +0000 Subject: [PATCH 6/6] hub#18022 Add functional tests --- ...perateSyshistoryFromSyslogCommandTest.php} | 16 ++++++------ Tests/Functional/Fixtures/sys_redirect.csv | 10 +++---- .../Upgrades/FixRedirectsUpgraeWizardTest.php | 26 +++---------------- 3 files changed, 16 insertions(+), 36 deletions(-) rename Tests/Functional/Command/{ClearSysLogCommandTest.php => SeperateSyshistoryFromSyslogCommandTest.php} (82%) diff --git a/Tests/Functional/Command/ClearSysLogCommandTest.php b/Tests/Functional/Command/SeperateSyshistoryFromSyslogCommandTest.php similarity index 82% rename from Tests/Functional/Command/ClearSysLogCommandTest.php rename to Tests/Functional/Command/SeperateSyshistoryFromSyslogCommandTest.php index 2231ce1..8a3c8fe 100644 --- a/Tests/Functional/Command/ClearSysLogCommandTest.php +++ b/Tests/Functional/Command/SeperateSyshistoryFromSyslogCommandTest.php @@ -8,21 +8,21 @@ use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Tester\CommandTester; -use Toumoro\TmMigration\Command\ClearSysLogCommand; +use Toumoro\TmMigration\Command\SeperateSyshistoryFromSyslogCommand; use Toumoro\TmMigration\Service\SQLMigrationService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; -final class ClearSysLogCommandTest extends FunctionalTestCase +final class SeperateSyshistoryFromSyslogCommandTest extends FunctionalTestCase { /** * @var non-empty-string */ - private const COMMAND_NAME = 'tmupgrade:clearsyslog'; + private const COMMAND_NAME = 'tmupgrade:sepearate-syshistory-from-syslog'; private $mockSQLService; - private ClearSysLogCommand $subject; + private SeperateSyshistoryFromSyslogCommand $subject; private CommandTester $commandTester; @@ -33,11 +33,11 @@ protected function setUp(): void $this->mockSQLService = $this->createMock(SQLMigrationService::class); GeneralUtility::addInstance(SQLMigrationService::class, $this->mockSQLService); - $this->subject = new ClearSysLogCommand(self::COMMAND_NAME); + $this->subject = new SeperateSyshistoryFromSyslogCommand(self::COMMAND_NAME); $application = new Application(); $application->add($this->subject); - $command = $application->find('tmupgrade:clearsyslog'); + $command = $application->find('tmupgrade:sepearate-syshistory-from-syslog'); $this->commandTester = new CommandTester($command); } @@ -50,14 +50,14 @@ public function isConsoleCommand(): void #[Test] public function hasDescription(): void { - $expected = 'Clear table sys_log.'; + $expected = 'Seperate sys_history entries from sys_log.'; self::assertSame($expected, $this->subject->getDescription()); } #[Test] public function hasHelpText(): void { - $expected = 'This command execute an SQL script that clears the sys_log database table. -d days -l limit.'; + $expected = 'This command execute an SQL script that seperates sys_history from sys_log table. -d days -l limit.'; self::assertSame($expected, $this->subject->getHelp()); } diff --git a/Tests/Functional/Fixtures/sys_redirect.csv b/Tests/Functional/Fixtures/sys_redirect.csv index 9486952..6883a3b 100644 --- a/Tests/Functional/Fixtures/sys_redirect.csv +++ b/Tests/Functional/Fixtures/sys_redirect.csv @@ -1,7 +1,5 @@ "sys_redirect" -,"uid","pid","source_path","target_statuscode","target" -,1,0,"no-leading-slash",307,"/target1" -,2,0,"/wrong-status-code",0,"/target2" -,3,0,"/already-correct",301,"/target3" -,4,0,"^products/(.*)$",301,"target4" -,5,0,"https://www.google.com",301,"target5" \ No newline at end of file +,"uid","pid","source_path","target_statuscode","target","is_regexp" +,1,0,"no-leading-slash",307,"/target1",0 +,2,0,"^products/(.*)$",301,"target2",1 +,3,0,"https://www.google.com",301,"target3",0 \ No newline at end of file diff --git a/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php b/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php index afa9b34..e587271 100644 --- a/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php +++ b/Tests/Functional/Upgrades/FixRedirectsUpgraeWizardTest.php @@ -64,27 +64,9 @@ public function testWizardRepairsInvalidRedirects(): void // 1. no-leading-slash self::assertSame('/no-leading-slash', $redirectsByUid[1]['source_path']); - self::assertSame(307, (int)$redirectsByUid[1]['target_statuscode']); - self::assertSame('/target1', $redirectsByUid[1]['target']); - - // 2. wrong-status-code - self::assertSame('/wrong-status-code', $redirectsByUid[2]['source_path']); - self::assertSame(307, (int)$redirectsByUid[2]['target_statuscode']); - self::assertSame('/target2', $redirectsByUid[2]['target']); - - // 3. already-correct - self::assertSame('/already-correct', $redirectsByUid[3]['source_path']); - self::assertSame(301, (int)$redirectsByUid[3]['target_statuscode']); - self::assertSame('/target3', $redirectsByUid[3]['target']); - - // 4. regex pattern - self::assertSame('^products/(.*)$', $redirectsByUid[4]['source_path']); - self::assertSame(301, (int)$redirectsByUid[4]['target_statuscode']); - self::assertSame('target4', $redirectsByUid[4]['target']); - - // 5. external URL - self::assertSame('https://www.google.com', $redirectsByUid[5]['source_path']); - self::assertSame(301, (int)$redirectsByUid[5]['target_statuscode']); - self::assertSame('target5', $redirectsByUid[5]['target']); + // 2. regex pattern + self::assertSame('^products/(.*)$', $redirectsByUid[2]['source_path']); + // 3. external URL + self::assertSame('https://www.google.com', $redirectsByUid[3]['source_path']); } }