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"