diff --git a/.env b/.env index 47f3c7f..2bf8529 100644 --- a/.env +++ b/.env @@ -23,7 +23,7 @@ APP_SECRET=630dc0d699fd37e720aff268f75583ed APP_TIMEZONE= ###> doctrine/doctrine-bundle ### -DATABASE_DRIVER=mysql # or postgresql, or sqlite +DATABASE_DRIVER=mysql # or pgsql, or sqlite # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url # For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=UTF-8" # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" (without the quotes so Symfony can resolve it if it's an absolute path) diff --git a/.env.test b/.env.test index e8bda91..4853a1e 100644 --- a/.env.test +++ b/.env.test @@ -5,8 +5,9 @@ SYMFONY_DEPRECATIONS_HELPER=999999 PANTHER_APP_ENV=panther PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots -DATABASE_URL="mysql://davis:davis@127.0.0.1:3306/davis_test?serverVersion=10.9.3-MariaDB&charset=utf8mb4" +DATABASE_DRIVER=sqlite +DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_test.db" MAILER_DSN=smtp://localhost:465?encryption=ssl&auth_mode=login&username=&password= -API_KEY=change_me \ No newline at end of file +API_KEY=change_me diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53394fd..bd7b6db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,18 +75,58 @@ jobs: run: vendor/bin/php-cs-fixer fix --ansi phpunit: - name: PHPUnit (PHP ${{ matrix.php }}) + name: PHPUnit / SQLite (PHP ${{ matrix.php }}) runs-on: ubuntu-latest container: image: php:${{ matrix.php }}-alpine options: >- --tmpfs /tmp:exec --tmpfs /var/tmp:exec + strategy: + matrix: + php: + - '8.2' + - '8.3' + - '8.4' + - '8.5' + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install SQLite / GD / ZIP PHP extensions + run: | + apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev sqlite-dev + docker-php-ext-configure intl + docker-php-ext-configure gd + docker-php-ext-configure zip + docker-php-ext-install pdo pdo_sqlite intl gd zip + + - name: Install Composer + run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet + + - name: Install dependencies with Composer + run: composer install --no-progress --no-interaction --ansi + + - name: Run tests with PHPUnit + env: + DATABASE_DRIVER: sqlite + DATABASE_URL: sqlite:///%kernel.project_dir%/var/data_test.db + run: vendor/bin/phpunit --colors=always + + phpunit-mariadb: + name: PHPUnit / MariaDB (PHP 8.4) + runs-on: ubuntu-latest + container: + image: php:8.4-alpine + options: >- + --tmpfs /tmp:exec + --tmpfs /var/tmp:exec services: mysql: image: mariadb:10.11 env: - # Corresponds to what is in .env.test MYSQL_DATABASE: davis_test MYSQL_USER: davis MYSQL_PASSWORD: davis @@ -96,17 +136,6 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - ports: - - 3306:3306 - - strategy: - matrix: - php: - - '8.2' - - '8.3' - - '8.4' - - '8.5' - fail-fast: false steps: - name: Checkout @@ -128,8 +157,9 @@ jobs: - name: Run tests with PHPUnit env: + DATABASE_DRIVER: mysql DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test - run: vendor/bin/phpunit --process-isolation --colors=always + run: vendor/bin/phpunit --colors=always migrations: name: Migrations (${{ matrix.database }}) @@ -195,6 +225,7 @@ jobs: - name: Run migrations (MySQL) if: matrix.database == 'mysql' env: + DATABASE_DRIVER: mysql DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test run: | php bin/console doctrine:database:create --if-not-exists --env=test @@ -204,6 +235,7 @@ jobs: - name: Run migrations (PostgreSQL) if: matrix.database == 'postgresql' env: + DATABASE_DRIVER: pgsql DATABASE_URL: postgresql://davis:davis@postgres:5432/davis_test?serverVersion=15&charset=utf8 run: | php bin/console doctrine:database:create --if-not-exists --env=test @@ -213,33 +245,24 @@ jobs: - name: Run migrations (SQLite) if: matrix.database == 'sqlite' env: + DATABASE_DRIVER: sqlite DATABASE_URL: sqlite:///%kernel.project_dir%/var/data_test.db run: | php bin/console doctrine:migrations:migrate --no-interaction --env=test php bin/console doctrine:schema:validate --skip-sync --env=test smoke-test: - name: Application Smoke Test + name: SQLite DAV Smoke Test runs-on: ubuntu-latest container: image: php:8.4-alpine options: >- --tmpfs /tmp:exec --tmpfs /var/tmp:exec - - services: - mysql: - image: mariadb:10.11 - env: - MYSQL_DATABASE: davis_test - MYSQL_USER: davis - MYSQL_PASSWORD: davis - MYSQL_ROOT_PASSWORD: root - options: >- - --health-cmd "mysqladmin ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + env: + APP_ENV: test + DATABASE_DRIVER: sqlite + DATABASE_URL: sqlite:///%kernel.project_dir%/var/smoke_test.db steps: - name: Checkout @@ -247,11 +270,11 @@ jobs: - name: Install extensions and curl run: | - apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev curl + apk add $PHPIZE_DEPS icu-libs icu-dev libpng-dev libzip-dev sqlite-dev curl docker-php-ext-configure intl docker-php-ext-configure gd docker-php-ext-configure zip - docker-php-ext-install pdo pdo_mysql intl gd zip + docker-php-ext-install pdo pdo_sqlite intl gd zip - name: Install Composer run: wget -qO - https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet @@ -260,39 +283,23 @@ jobs: run: composer install --no-progress --no-interaction --ansi --optimize-autoloader - name: Prepare application - env: - DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test - APP_ENV: test run: | - php bin/console doctrine:database:create --if-not-exists --env=test php bin/console doctrine:migrations:migrate --no-interaction --env=test + php bin/console doctrine:fixtures:load --no-interaction --env=test php bin/console cache:clear --env=test - name: Start Symfony server in background - env: - DATABASE_URL: mysql://davis:davis@mysql:3306/davis_test - APP_ENV: test run: | - php -S 127.0.0.1:8000 -t public/ & + php -S 127.0.0.1:8000 -t public/ public/index.php & echo $! > server.pid - sleep 3 - - - name: Test application responds - run: | - # Test that the app responds with a successful HTTP status - RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/) - echo "HTTP Response code: $RESPONSE" - if [ "$RESPONSE" -ge 200 ] && [ "$RESPONSE" -lt 400 ]; then - echo "✅ Application is responding correctly" - else - echo "❌ Application returned unexpected status code: $RESPONSE" - exit 1 - fi - - - name: Test dashboard - continue-on-error: true - run: | - curl -f http://127.0.0.1:8000/dashboard || echo "No health endpoint available" + for attempt in 1 2 3 4 5 6 7 8 9 10; do + curl --silent --fail http://127.0.0.1:8000/ > /dev/null && exit 0 + sleep 1 + done + exit 1 + + - name: Exercise CalDAV and CardDAV + run: tests/smoke-dav.sh - name: Stop server if: always() diff --git a/README.md b/README.md index 01b825c..d67157b 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ Comes with already built Docker images in two flavours: [standalone](https://git # 🔩 Requirements - - PHP > 8.2 (with `pdo_mysql` [or `pdo_pgsql`, `pdo_sqlite`], `gd` and `intl` extensions), compatible up to PHP 8.5 (_See dependencies table below_) - - A compatible database layer, such as MySQL or MariaDB (recommended), PostgreSQL (not extensively tested yet) or SQLite (not extensively tested yet) + - PHP >= 8.2 (with `pdo_sqlite`, `pdo_mysql` or `pdo_pgsql`, plus the `gd` and `intl` extensions), compatible up to PHP 8.5 (_See dependencies table below_) + - SQLite, MySQL/MariaDB or PostgreSQL. SQLite is the simplest option for a standalone installation and is covered by the full test suite; PostgreSQL is not extensively tested yet. - Composer > 2 (_The last release compatible with Composer 1 is [v1.6.2](https://github.com/tchapi/davis/releases/tag/v1.6.2)_) - The [`imap`](https://www.php.net/manual/en/imap.installation.php) and [`ldap`](https://www.php.net/manual/en/ldap.installation.php) PHP extensions if you want to use either authentication methods (_these are not enabled / compiled by default except in the Docker image_) @@ -85,7 +85,7 @@ Dependencies And set `APP_ENV=prod` in your `.env.local` file (see below) -3. At least put the correct credentials to your database (driver and url) in your `.env.local` file so you can easily create the necessary tables. +3. Configure the database driver and URL in your `.env.local` file. 4. Run the migrations to create all the necessary tables: @@ -111,11 +111,20 @@ Create your own `.env.local` file to change the necessary variables, if you plan > > In a production environnement, the `APP_ENV` variable MUST be set to `prod` to prevent leaking sensitive data. -**a. The database driver and url** (_you should already have it configured since you created the database previously_) +**a. The database driver and URL** ```shell -DATABASE_DRIVER=mysql # or postgresql, or sqlite -DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4 +# SQLite +DATABASE_DRIVER=sqlite +DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db + +# MySQL/MariaDB +# DATABASE_DRIVER=mysql +# DATABASE_URL=mysql://db_user:db_pass@host:3306/db_name?serverVersion=10.9.3-MariaDB&charset=utf8mb4 + +# PostgreSQL +# DATABASE_DRIVER=pgsql +# DATABASE_URL=postgresql://db_user:db_pass@host:5432/db_name ``` **b. The admin password for the backend** diff --git a/tests/Fixtures/Dav/addressbook-query.xml b/tests/Fixtures/Dav/addressbook-query.xml new file mode 100644 index 0000000..d40337c --- /dev/null +++ b/tests/Fixtures/Dav/addressbook-query.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/Fixtures/Dav/calendar-query.xml b/tests/Fixtures/Dav/calendar-query.xml new file mode 100644 index 0000000..a01152d --- /dev/null +++ b/tests/Fixtures/Dav/calendar-query.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tests/Fixtures/Dav/contact.vcf b/tests/Fixtures/Dav/contact.vcf new file mode 100644 index 0000000..3795a3e --- /dev/null +++ b/tests/Fixtures/Dav/contact.vcf @@ -0,0 +1,7 @@ +BEGIN:VCARD +VERSION:3.0 +UID:davis-smoke-contact +FN:Davis CardDAV Smoke Test +N:Smoke Test;Davis;;; +EMAIL:davis@example.invalid +END:VCARD diff --git a/tests/Fixtures/Dav/event.ics b/tests/Fixtures/Dav/event.ics new file mode 100644 index 0000000..be577bd --- /dev/null +++ b/tests/Fixtures/Dav/event.ics @@ -0,0 +1,11 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Davis test suite//EN +BEGIN:VEVENT +UID:davis-smoke-event +DTSTAMP:20260101T000000Z +DTSTART:20260102T100000Z +DTEND:20260102T103000Z +SUMMARY:Davis CalDAV smoke test +END:VEVENT +END:VCALENDAR diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 60c4fa9..902e91a 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,6 +1,7 @@ bootEnv(dirname(__DIR__).'/.env'); } -// Create the test database, update the schema and resets the fixture before each test. -// Note: `--quiet` is needed here for each step so that PHPUnit doesn't fail. -$actions = [ - 'doctrine:database:create --if-not-exists ', - 'doctrine:schema:update --complete --force', - 'doctrine:fixtures:load --no-interaction', +// Reset the schema and fixtures before each isolated test process. Database +// provisioning belongs to the environment running the suite; SQLite creates +// its database file when Doctrine first connects. +$commands = [ + ['doctrine:schema:drop', '--full-database', '--force', '--quiet'], + ['doctrine:schema:create', '--quiet'], + ['doctrine:fixtures:load', '--no-interaction', '--quiet'], ]; -foreach ($actions as $action) { - passthru(sprintf( - 'APP_ENV=%s php "%s/../bin/console" %s --quiet', - $_ENV['APP_ENV'], - __DIR__, - $action, - )); +foreach ($commands as $command) { + $process = new Process([ + PHP_BINARY, + dirname(__DIR__).'/bin/console', + ...$command, + ], env: ['APP_ENV' => $_ENV['APP_ENV']]); + $process->mustRun(); } diff --git a/tests/smoke-dav.sh b/tests/smoke-dav.sh new file mode 100755 index 0000000..eafe821 --- /dev/null +++ b/tests/smoke-dav.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +set -eu + +base_url="${DAVIS_BASE_URL:-http://127.0.0.1:8000}" +base_url="${base_url%/}" +credentials="${DAVIS_TEST_CREDENTIALS:-test_user:password}" +script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" +fixtures="$script_dir/Fixtures/Dav" +temporary_directory="$(mktemp -d "${TMPDIR:-/tmp}/davis-smoke.XXXXXX")" +response="$temporary_directory/response" + +cleanup() { + rm -rf -- "$temporary_directory" +} +trap cleanup EXIT HUP INT TERM + +request() { + expected_status="$1" + description="$2" + output="$3" + shift 3 + + actual_status="$(curl --silent --show-error --output "$output" --write-out '%{http_code}' "$@")" + if [ "$actual_status" != "$expected_status" ]; then + printf '%s: expected HTTP %s, got %s\n' "$description" "$expected_status" "$actual_status" >&2 + sed -n '1,80p' "$output" >&2 + exit 1 + fi +} + +request 401 'unauthenticated DAV request' "$response" "$base_url/dav/" +request 207 'authenticated DAV discovery' "$response" \ + --user "$credentials" --request PROPFIND --header 'Depth: 1' "$base_url/dav/" + +calendar_url="$base_url/dav/calendars/test_user/default/davis-smoke-event.ics" +request 201 'CalDAV create' "$response" \ + --user "$credentials" --request PUT --header 'Content-Type: text/calendar; charset=utf-8' \ + --data-binary "@$fixtures/event.ics" "$calendar_url" +request 200 'CalDAV fetch' "$temporary_directory/event.ics" \ + --user "$credentials" "$calendar_url" +cmp "$fixtures/event.ics" "$temporary_directory/event.ics" +request 207 'CalDAV query' "$response" \ + --user "$credentials" --request REPORT --header 'Depth: 1' \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data-binary "@$fixtures/calendar-query.xml" "$base_url/dav/calendars/test_user/default/" +grep -q 'UID:davis-smoke-event' "$response" +request 204 'CalDAV delete' "$response" \ + --user "$credentials" --request DELETE "$calendar_url" +request 404 'CalDAV deleted resource' "$response" \ + --user "$credentials" "$calendar_url" + +contact_url="$base_url/dav/addressbooks/test_user/default/davis-smoke-contact.vcf" +request 201 'CardDAV create' "$response" \ + --user "$credentials" --request PUT --header 'Content-Type: text/vcard; charset=utf-8' \ + --data-binary "@$fixtures/contact.vcf" "$contact_url" +request 200 'CardDAV fetch' "$temporary_directory/contact.vcf" \ + --user "$credentials" "$contact_url" +cmp "$fixtures/contact.vcf" "$temporary_directory/contact.vcf" +request 207 'CardDAV query' "$response" \ + --user "$credentials" --request REPORT --header 'Depth: 1' \ + --header 'Content-Type: application/xml; charset=utf-8' \ + --data-binary "@$fixtures/addressbook-query.xml" "$base_url/dav/addressbooks/test_user/default/" +grep -q 'UID:davis-smoke-contact' "$response" +request 204 'CardDAV delete' "$response" \ + --user "$credentials" --request DELETE "$contact_url" +request 404 'CardDAV deleted resource' "$response" \ + --user "$credentials" "$contact_url"