V 3.0-dev - async support and misc #1240
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: tests | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| branches: | |
| - master | |
| jobs: | |
| tests: | |
| name: ${{ matrix.python }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python: ${{ github.event_name == 'pull_request' && fromJSON('["3.10", "3.14"]') || fromJSON('["3.10", "3.11", "3.12", "3.13", "3.14"]') }} | |
| services: | |
| baikal: | |
| image: ckulka/baikal:nginx | |
| ports: | |
| - 8800:80 | |
| options: >- | |
| --health-cmd "curl -f http://localhost/ || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| --health-start-period 30s | |
| nextcloud: | |
| image: nextcloud:latest | |
| ports: | |
| - 8801:80 | |
| env: | |
| NEXTCLOUD_ADMIN_USER: admin | |
| NEXTCLOUD_ADMIN_PASSWORD: admin | |
| options: >- | |
| --health-cmd "curl -f http://localhost/status.php || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| --health-start-period 60s | |
| cyrus: | |
| image: ghcr.io/cyrusimap/cyrus-docker-test-server:latest | |
| ports: | |
| - 8802:8080 | |
| - 8001:8001 | |
| env: | |
| DEFAULTDOMAIN: example.com | |
| SERVERNAME: cyrus-test | |
| options: >- | |
| --health-cmd "curl -s http://localhost:8080/ || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| --health-start-period 60s | |
| sogo-db: | |
| image: mariadb:11 | |
| env: | |
| MYSQL_DATABASE: sogo | |
| MYSQL_USER: sogo | |
| MYSQL_PASSWORD: sogo | |
| MYSQL_ROOT_PASSWORD: sogo | |
| options: >- | |
| --health-cmd "healthcheck.sh --connect --innodb_initialized" | |
| --health-interval 5s | |
| --health-timeout 5s | |
| --health-retries 20 | |
| --health-start-period 10s | |
| --network-alias db | |
| sogo: | |
| image: japoch/sogo:latest | |
| ports: | |
| - 8803:80 | |
| env: | |
| sogo_user: testuser | |
| sogo_pass: testpass | |
| sogo_name: Test User | |
| sogo_fqhn: example.com | |
| bedework: | |
| image: ioggstream/bedework:latest | |
| ports: | |
| - 8804:8080 | |
| options: >- | |
| --health-cmd "curl -f http://localhost:8080/bedework/ || exit 1" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 15 | |
| --health-start-period 120s | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} | |
| - run: pip install tox | |
| - name: Configure Baikal with pre-seeded database | |
| run: | | |
| # Copy pre-configured database and config to Baikal container | |
| docker cp tests/docker-test-servers/baikal/Specific/. ${{ job.services.baikal.id }}:/var/www/baikal/Specific/ | |
| docker cp tests/docker-test-servers/baikal/config/. ${{ job.services.baikal.id }}:/var/www/baikal/config/ | |
| # Fix permissions for SQLite | |
| docker exec ${{ job.services.baikal.id }} chown -R nginx:nginx /var/www/baikal/Specific /var/www/baikal/config | |
| docker exec ${{ job.services.baikal.id }} chmod -R 770 /var/www/baikal/Specific | |
| # Restart to pick up configuration | |
| docker restart ${{ job.services.baikal.id }} | |
| - name: Wait for Baikal to be ready | |
| run: | | |
| sleep 5 | |
| if timeout 60 bash -c 'until curl -f http://localhost:8800/ 2>/dev/null; do echo "Waiting..."; sleep 2; done'; then | |
| echo "✓ Baikal is ready!" | |
| else | |
| echo "✗ Error: Baikal did not become ready within 60 seconds" | |
| exit 1 | |
| fi | |
| - name: Configure Nextcloud | |
| run: | | |
| # Wait for Nextcloud web server to be up | |
| echo "Waiting for Nextcloud web server..." | |
| if timeout 60 bash -c 'until curl -f http://localhost:8801/status.php 2>/dev/null; do echo -n "."; sleep 2; done'; then | |
| echo "" | |
| echo "✓ Web server is up" | |
| else | |
| echo "" | |
| echo "✗ Error: Nextcloud web server did not become ready within 60 seconds" | |
| exit 1 | |
| fi | |
| # Install Nextcloud if not already installed | |
| if ! docker exec ${{ job.services.nextcloud.id }} php occ status 2>/dev/null | grep -q "installed: true"; then | |
| echo "Installing Nextcloud..." | |
| docker exec ${{ job.services.nextcloud.id }} php occ maintenance:install \ | |
| --database=sqlite \ | |
| --admin-user=admin \ | |
| --admin-pass=admin | |
| echo "✓ Nextcloud installed" | |
| else | |
| echo "✓ Nextcloud is already installed" | |
| fi | |
| # Disable password policy | |
| docker exec ${{ job.services.nextcloud.id }} php occ app:disable password_policy || true | |
| # Create test user | |
| docker exec -e OC_PASS="testpass" ${{ job.services.nextcloud.id }} php occ user:add --password-from-env --display-name="Test User" testuser || echo "User may already exist" | |
| # Enable calendar and contacts apps | |
| docker exec ${{ job.services.nextcloud.id }} php occ app:enable calendar || true | |
| docker exec ${{ job.services.nextcloud.id }} php occ app:enable contacts || true | |
| # Disable rate limiting and bruteforce protection | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:system:set ratelimit.enabled --value=false --type=boolean || true | |
| docker exec ${{ job.services.nextcloud.id }} php occ app:disable bruteforcesettings || true | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:system:set auth.bruteforce.protection.enabled --value=false --type=boolean || true | |
| # Configure CalDAV rate limits | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:app:set dav rateLimitCalendarCreation --value=99999 || true | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:app:set dav maximumCalendarsSubscriptions --value=-1 || true | |
| # Add IP whitelist for rate limiting | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:system:set ratelimit.whitelist.0 --value='172.17.0.0/16' || true | |
| docker exec ${{ job.services.nextcloud.id }} php occ config:system:set ratelimit.whitelist.1 --value='127.0.0.1' || true | |
| # Clear rate limit cache | |
| docker exec ${{ job.services.nextcloud.id }} php -r " | |
| \$db = new PDO('sqlite:/var/www/html/data/nextcloud.db'); | |
| \$db->exec('DELETE FROM oc_ratelimit_entries'); | |
| \$db->exec('DELETE FROM oc_bruteforce_attempts'); | |
| echo 'Cleared rate limit and bruteforce caches\n'; | |
| " || true | |
| echo "Nextcloud is configured!" | |
| - name: Wait for Cyrus to be ready | |
| run: | | |
| echo "Waiting for Cyrus server..." | |
| # Cyrus takes a bit longer to initialize | |
| sleep 10 | |
| if timeout 60 bash -c 'until curl -s http://localhost:8802/ 2>/dev/null | grep -q .; do echo -n "."; sleep 2; done'; then | |
| echo "" | |
| echo "✓ Cyrus HTTP server is ready" | |
| else | |
| echo "" | |
| echo "✗ Error: Cyrus HTTP server did not become ready within 60 seconds" | |
| exit 1 | |
| fi | |
| # Verify CalDAV access with pre-created user | |
| # Cyrus CalDAV can take significant time to initialize, especially in CI | |
| echo "Waiting for CalDAV access..." | |
| if timeout 120 bash -c 'until curl -s -X PROPFIND -H "Depth: 0" -u user1:x http://localhost:8802/dav/calendars/user/user1/ 2>/dev/null | grep -qi "multistatus\|collection" ; do echo -n "."; sleep 2; done'; then | |
| echo "" | |
| echo "✓ Cyrus CalDAV with pre-created user ready" | |
| else | |
| echo "" | |
| echo "✗ Error: Cyrus CalDAV did not become accessible within 120 seconds" | |
| echo "Attempting to debug..." | |
| echo "" | |
| echo "=== Container logs ===" | |
| docker logs ${{ job.services.cyrus.id }} 2>&1 | tail -100 || true | |
| echo "" | |
| echo "=== Listening ports inside container ===" | |
| docker exec ${{ job.services.cyrus.id }} netstat -tlnp 2>&1 || true | |
| echo "" | |
| echo "=== Running processes ===" | |
| docker exec ${{ job.services.cyrus.id }} ps aux 2>&1 || true | |
| echo "" | |
| echo "=== HTTP response from host ===" | |
| curl -v http://localhost:8802/ 2>&1 || true | |
| echo "" | |
| echo "=== CalDAV PROPFIND response from host ===" | |
| curl -v -X PROPFIND -H "Depth: 0" -u user1:x http://localhost:8802/dav/calendars/user/user1/ 2>&1 || true | |
| echo "" | |
| echo "=== HTTP response from inside container ===" | |
| docker exec ${{ job.services.cyrus.id }} curl -v http://localhost:8080/ 2>&1 || true | |
| exit 1 | |
| fi | |
| - name: Configure SOGo | |
| run: | | |
| echo "Configuring SOGo..." | |
| # Wait for database to be ready | |
| echo "Waiting for database..." | |
| sleep 5 | |
| # Initialize database with test user | |
| docker cp tests/docker-test-servers/sogo/init-sogo-users.sql ${{ job.services.sogo-db.id }}:/tmp/init-sogo-users.sql | |
| docker exec -i ${{ job.services.sogo-db.id }} mariadb -usogo -psogo sogo < tests/docker-test-servers/sogo/init-sogo-users.sql | |
| echo "✓ Database initialized with test user" | |
| # Copy SOGo configuration (database is now reachable via network alias 'db') | |
| docker cp tests/docker-test-servers/sogo/sogo.conf ${{ job.services.sogo.id }}:/etc/sogo/sogo.conf | |
| echo "✓ SOGo configuration copied" | |
| # Restart SOGo to pick up configuration | |
| docker restart ${{ job.services.sogo.id }} | |
| # Wait for SOGo to be ready | |
| echo "Waiting for SOGo to start..." | |
| if timeout 60 bash -c 'until curl -f http://localhost:8803/SOGo/ 2>/dev/null; do echo -n "."; sleep 2; done'; then | |
| echo "" | |
| echo "✓ SOGo is ready" | |
| else | |
| echo "" | |
| echo "✗ Error: SOGo did not become ready within 60 seconds" | |
| exit 1 | |
| fi | |
| # Verify CalDAV access | |
| echo "Verifying CalDAV access..." | |
| if curl -s -X PROPFIND -H "Depth: 0" -u testuser:testpass http://localhost:8803/SOGo/dav/testuser/Calendar/ 2>/dev/null | grep -qi "multistatus"; then | |
| echo "✓ SOGo CalDAV access verified" | |
| else | |
| echo "✗ Error: SOGo CalDAV access failed" | |
| exit 1 | |
| fi | |
| - name: Configure Bedework | |
| run: | | |
| echo "Waiting for Bedework..." | |
| # Bedework/JBoss takes longer to start up | |
| if timeout 180 bash -c 'until curl -f http://localhost:8804/bedework/ 2>/dev/null; do echo -n "."; sleep 5; done'; then | |
| echo "" | |
| echo "✓ Bedework web interface is ready" | |
| else | |
| echo "" | |
| echo "✗ Error: Bedework did not become ready within 180 seconds" | |
| exit 1 | |
| fi | |
| # Verify CalDAV access with default user (vbede:bedework) | |
| echo "Verifying CalDAV access..." | |
| if curl -s -X PROPFIND -H "Depth: 0" -u vbede:bedework http://localhost:8804/ucaldav/user/vbede/ 2>/dev/null | grep -qi "multistatus"; then | |
| echo "✓ Bedework CalDAV access verified" | |
| else | |
| echo "✗ Error: Bedework CalDAV access failed" | |
| exit 1 | |
| fi | |
| - run: tox -e py | |
| env: | |
| NEXTCLOUD_URL: http://localhost:8801 | |
| BAIKAL_URL: http://localhost:8800 | |
| CYRUS_URL: http://localhost:8802 | |
| SOGO_URL: http://localhost:8803 | |
| BEDEWORK_URL: http://localhost:8804 | |
| docs: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} | |
| - run: pip install tox | |
| - run: tox -e docs | |
| style: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pre-commit | |
| key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} | |
| - run: pip install tox | |
| - run: tox -e style | |
| deptry: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: pip|${{ hashFiles('setup.py') }}|${{ hashFiles('tox.ini') }} | |
| - run: pip install tox | |
| - run: tox -e deptry | |
| async-niquests: | |
| # Test that async code works with niquests when httpx is not installed | |
| name: async (niquests fallback) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies without httpx | |
| run: | | |
| pip install --editable .[test] | |
| pip uninstall -y httpx | |
| - name: Verify niquests is used | |
| run: | | |
| python -c " | |
| from caldav.async_davclient import _USE_HTTPX, _USE_NIQUESTS | |
| assert not _USE_HTTPX, 'httpx should not be available' | |
| assert _USE_NIQUESTS, 'niquests should be used' | |
| print('✓ Using niquests for async HTTP') | |
| " | |
| - name: Run async tests with niquests | |
| run: pytest tests/test_async_davclient.py -v | |
| sync-requests: | |
| # Test that sync code works with requests when niquests is not installed | |
| name: sync (requests fallback) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install dependencies with requests instead of niquests | |
| run: | | |
| pip install --editable .[test] | |
| pip uninstall -y niquests | |
| pip install requests | |
| - name: Verify requests is used | |
| run: | | |
| python -c " | |
| from caldav.davclient import _USE_REQUESTS, _USE_NIQUESTS | |
| assert _USE_REQUESTS, 'requests should be available' | |
| assert not _USE_NIQUESTS, 'niquests should not be available' | |
| print('✓ Using requests for sync HTTP') | |
| " | |
| - name: Run sync tests with requests | |
| run: pytest tests/test_caldav.py -v -k "Radicale" --ignore=tests/test_async_integration.py |