Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python: ${{ github.event_name == 'pull_request' && fromJSON('["3.9", "3.14"]') || fromJSON('["3.9", "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
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -25,7 +36,24 @@ jobs:
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
timeout 60 bash -c 'until curl -f http://localhost:8800/ 2>/dev/null; do echo "Waiting..."; sleep 2; done'
echo "Baikal is ready!"
- run: tox -e py
env:
BAIKAL_URL: http://localhost:8800
docs:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ tests/conf_private.py
.eggs
.venv
caldav/_version.py
tests/docker-test-servers/baikal/baikal-backup/
tests/docker-test-servers/*/baikal-backup/
# But keep the pre-configured Specific directory for Baikal
!tests/docker-test-servers/baikal/Specific/
21 changes: 12 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Also, the RFC6764 discovery may not always be robust, causing fallbacks and henc
### Deprecations

* `Event.expand_rrule` will be removed in some future release, unless someone protests.
* `Event.split_expanded` too. Both of them were used internally, now it's not. It's dead code, most lkely nobody and nothing is using them.
* `Event.split_expanded` too. Both of them were used internally, now it's not. It's dead code, most likely nobody and nothing is using them.

### Changed

Expand All @@ -50,22 +50,25 @@ Also, the RFC6764 discovery may not always be robust, causing fallbacks and henc

### Added

* **RFC 6764 DNS-based service discovery**: Automatic CalDAV/CardDAV service discovery using DNS SRV/TXT records and well-known URIs. Users can now provide just a domain name or email address (e.g., `DAVClient(username='user@example.com')`) and the library will automatically discover the CalDAV service endpoint. The discovery process follows RFC 6764 specification. This involves a new required dependency: `dnspython` for DNS queries. DNS-based discovery can be disabled in the davclient connection settings, but I've opted against implementing a fallback if the dns library is not installed.
- **SECURITY**: DNS-based discovery has security implications. By default, `require_tls=True` prevents downgrade attacks by only accepting HTTPS connections. See security documentation for details.
* **New ways to configure the client connection, new parameters**
- **RFC 6764 DNS-based service discovery**: Automatic CalDAV/CardDAV service discovery using DNS SRV/TXT records and well-known URIs. Users can now provide just a domain name or email address (e.g., `DAVClient(username='user@example.com')`) and the library will automatically discover the CalDAV service endpoint. The discovery process follows RFC 6764 specification. This involves a new required dependency: `dnspython` for DNS queries. DNS-based discovery can be disabled in the davclient connection settings, but I've opted against implementing a fallback if the dns library is not installed.
- New `require_tls` parameter (default: `True`) prevents DNS-based downgrade attacks
- Username extraction from email addresses (`user@example.com` → username: `user`)
- Discovery from username parameter when URL is omitted
* The client connection parameter `features` may now simply be a string label referencing a well-known server or cloud solution - like `features: posteo`. https://github.com/python-caldav/caldav/pull/561
* The client connection parameter `url` is no longer needed when referencing a well-known cloud solution. https://github.com/python-caldav/caldav/pull/561
* The client connection parameter `url` may contain just the domain name (without any slashes) and the URL will be constructed, if referencing a well-known caldav server implementation. https://github.com/python-caldav/caldav/pull/561
* New interface for searches. `mysearcher = caldav.CalDAVSearcher(...) ; mysearcher.add_property_filter(...) ; mysearcher.search(calendar)`. May be useful for complicated searches.
- The client connection parameter `features` may now simply be a string label referencing a well-known server or cloud solution - like `features: posteo`. https://github.com/python-caldav/caldav/pull/561
- The client connection parameter `url` is no longer needed when referencing a well-known cloud solution. https://github.com/python-caldav/caldav/pull/561
* The client connection parameter `url` may contain just the domain name (without any slashes). It may then either look up the URL path in the known caldav server database, or through RFC6764
* **New interface for searches** `mysearcher = caldav.CalDAVSearcher(...) ; mysearcher.add_property_filter(...) ; mysearcher.search(calendar)`. It's a bit harder to use, but opens up the possibility to do more complicated searches.
* **Collation support for CalDAV text-match queries (RFC 4791 § 9.7.5)**: CalDAV searches now properly pass collation attributes to the server, enabling case-insensitive searches and Unicode-aware text matching. The `CalDAVSearcher.add_property_filter()` method now accepts `case_sensitive` and `collation` parameters. Supported collations include:
- `i;octet` (case-sensitive, binary comparison) - default
- `i;ascii-casemap` (case-insensitive for ASCII characters, RFC 4790)
- `i;unicode-casemap` (Unicode case-insensitive, RFC 5051 - server support may vary)
* Client-side filtering method: `CalDAVSearcher.filter()` provides comprehensive client-side filtering, expansion, and sorting of calendar objects with full timezone preservation support.
* Example code: New `examples/collation_usage.py` demonstrates case-sensitive and case-insensitive calendar searches.

### Test suite

* **Automated Baikal Docker testing framework** using Docker containers. Will only run if docker is available.
* Since the new search code now can work around different server quirks, quite some of the test code has been simplified. Many cases of "make a search, if server supports this, then assert correct number of events returned" could be collapsed to "make a search, then assert correct number of events returned" - meaning that **the library is tested rather than the server**.

## [2.1.2] - [2025-11-08]

Version 2.1.0 comes without niquests in the dependency file. Version 2.1.2 come with niquests in the dependency file. Also fixed up some minor mistakes in the CHANGELOG.
Expand Down
12 changes: 8 additions & 4 deletions caldav/compatibility_hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,10 +812,7 @@ def dotted_feature_set_list(self, compact=False):
'duplicate_in_other_calendar_with_same_uid_is_lost'
]

baikal = {
'create-calendar': {'support': 'quirk', 'behaviour': 'mkcol-required'},
'create-calendar.auto': {'support': 'unsupported'}, ## this is the default, but the "quirk" from create-calendar overwrites it. Hm.

baikal = { ## version 0.10.1
#'search.comp-type-optional': {'support': 'ungraceful'}, ## Possibly this has been fixed?
'search.recurrences.expanded.todo': {'support': 'unsupported'},
'search.recurrences.expanded.exception': {'support': 'unsupported'},
Expand All @@ -833,6 +830,13 @@ def dotted_feature_set_list(self, compact=False):
]
} ## TODO: testPrincipals, testWrongAuthType, testTodoDatesearch fails

## Some unknown version of baikal has this
baikal_old = baikal | {
'create-calendar': {'support': 'quirk', 'behaviour': 'mkcol-required'},
'create-calendar.auto': {'support': 'unsupported'}, ## this is the default, but the "quirk" from create-calendar overwrites it. Hm.

}

## See comments on https://github.com/python-caldav/caldav/issues/3
#icloud = [
# 'unique_calendar_ids',
Expand Down
139 changes: 139 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# CalDAV Library Tests

This directory contains the test suite for the caldav Python library.

## Running Tests

### Quick Start

Run all tests using pytest:

```bash
pytest
```

Or using tox (recommended):

```bash
tox -e py
```

### Running Specific Tests

```bash
# Run a specific test file
pytest tests/test_cdav.py

# Run a specific test function
pytest tests/test_cdav.py::test_element

# Run tests matching a pattern
pytest -k "test_to_utc"
```

## Test Configuration

Test configuration is managed through several files:

- `conf.py` - Main test configuration, includes setup for Xandikos and Radicale servers
- `conf_private.py.EXAMPLE` - Example private configuration for custom CalDAV servers
- `conf_private.py` - Your personal test server configuration (gitignored)
- `conf_baikal.py` - Configuration for Baikal Docker test server

### Testing Against Your Own Server

1. Copy the example configuration:
```bash
cp tests/conf_private.py.EXAMPLE tests/conf_private.py
```

2. Edit `conf_private.py` and add your server details:
```python
caldav_servers = [
{
'name': 'MyServer',
'url': 'https://caldav.example.com',
'username': 'testuser',
'password': 'password',
'features': [],
}
]
```

3. Run tests:
```bash
pytest
```

## Docker Test Servers

The `docker-test-servers/` directory contains Docker configurations for running tests against various CalDAV server implementations:

- **Baikal** - Lightweight CalDAV/CardDAV server

See [docker-test-servers/README.md](docker-test-servers/README.md) for details.

### Quick Start with Baikal

```bash
cd tests/docker-test-servers/baikal
docker-compose up -d
# Configure Baikal through web interface at http://localhost:8800
# Then run tests from project root
cd ../../..
pytest
```

## Test Structure

- `test_cdav.py` - Tests for CalDAV elements
- `test_vcal.py` - Tests for calendar/event handling
- `test_utils.py` - Utility function tests
- `test_docs.py` - Documentation tests
- `test_caldav.py` - Main CalDAV client tests
- `test_caldav_unit.py` - Unit tests for CalDAV client
- `test_search.py` - Search functionality tests

## Continuous Integration

Tests run automatically on GitHub Actions for:
- Python versions: 3.9, 3.10, 3.11, 3.12, 3.13, 3.14
- With Baikal CalDAV server as a service container

See `.github/workflows/tests.yaml` for the full CI configuration.

## Coverage

Generate a coverage report:

```bash
coverage run -m pytest
coverage report
coverage html # Generate HTML report
```

## Troubleshooting

### No test servers configured

If you see warnings about no test servers being configured:
1. Either set up `conf_private.py` with your server details
2. Or use the Docker test servers (recommended)
3. Tests will still run against embedded servers (Xandikos, Radicale) if available

### Docker test server won't start

```bash
cd tests/docker-test-servers/baikal
docker-compose logs
docker-compose down -v # Reset everything
docker-compose up -d
```

### Tests timing out

Some CalDAV servers may be slow to respond. You can increase timeouts in your test configuration or skip slow tests:

```bash
pytest -m "not slow"
```
Loading