|
|
@@ -1,9 +1,89 @@
|
|
|
+import dataclasses
|
|
|
+import socket
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+import time
|
|
|
+
|
|
|
+import httpx
|
|
|
import pytest
|
|
|
-from room_cal._utils import get_config
|
|
|
+
|
|
|
+
|
|
|
+def _free_port() -> int:
|
|
|
+ with socket.socket() as s:
|
|
|
+ s.bind(('127.0.0.1', 0))
|
|
|
+ return s.getsockname()[1]
|
|
|
+
|
|
|
+
|
|
|
+@pytest.fixture(scope='session')
|
|
|
+def radicale_server(tmp_path_factory):
|
|
|
+ """Start a local Radicale CalDAV server for the test session.
|
|
|
+
|
|
|
+ auth.type = none accepts any credentials; the username in the HTTP auth
|
|
|
+ still determines the path prefix (http://host/{username}/), so we always
|
|
|
+ use username='test'. The 'House' calendar is created on first use; any
|
|
|
+ test that writes to it cleans up in its own finally block.
|
|
|
+ """
|
|
|
+ port = _free_port()
|
|
|
+ data_dir = tmp_path_factory.mktemp('radicale')
|
|
|
+ config_ini = data_dir / 'config.ini'
|
|
|
+ config_ini.write_text('\n'.join([
|
|
|
+ '[server]',
|
|
|
+ f'hosts = 127.0.0.1:{port}',
|
|
|
+ '[auth]',
|
|
|
+ 'type = none',
|
|
|
+ '[storage]',
|
|
|
+ f'filesystem_folder = {data_dir / "collections"}',
|
|
|
+ ]))
|
|
|
+
|
|
|
+ proc = subprocess.Popen(
|
|
|
+ [sys.executable, '-m', 'radicale', '--config', str(config_ini)],
|
|
|
+ stdout=subprocess.DEVNULL,
|
|
|
+ stderr=subprocess.DEVNULL,
|
|
|
+ )
|
|
|
+
|
|
|
+ url = f'http://127.0.0.1:{port}/'
|
|
|
+ for _ in range(50):
|
|
|
+ try:
|
|
|
+ httpx.get(url, timeout=1.0)
|
|
|
+ break
|
|
|
+ except Exception:
|
|
|
+ time.sleep(0.1)
|
|
|
+ else:
|
|
|
+ proc.kill()
|
|
|
+ pytest.fail('Radicale did not start within 5 seconds')
|
|
|
+
|
|
|
+ import caldav
|
|
|
+ client = caldav.DAVClient(url=url, username='test', password='test')
|
|
|
+ principal = client.principal()
|
|
|
+ principal.make_calendar(name='House', cal_id='house')
|
|
|
+
|
|
|
+ yield {'url': url, 'username': 'test', 'password': 'test', 'calendar_name': 'House'}
|
|
|
+
|
|
|
+ proc.terminate()
|
|
|
+ try:
|
|
|
+ proc.wait(timeout=5)
|
|
|
+ except subprocess.TimeoutExpired:
|
|
|
+ proc.kill()
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
-def require_user_cal():
|
|
|
+def require_user_cal(radicale_server, monkeypatch):
|
|
|
+ """Redirect user_cal to the local Radicale server instead of skipping.
|
|
|
+
|
|
|
+ Patches get_config in the two modules that call it on the user_cal path:
|
|
|
+ - room_cal.user_cal (UserCal.__init__)
|
|
|
+ - room_cal.actions.fetch_user_cal (calendar_name lookup)
|
|
|
+ """
|
|
|
+ from room_cal._utils import get_config
|
|
|
cfg = get_config()
|
|
|
- if not getattr(cfg, 'user_cal', None) or not cfg.user_cal.url:
|
|
|
- pytest.skip('user_cal CalDAV not configured')
|
|
|
+ test_user_cal_cfg = dataclasses.replace(
|
|
|
+ cfg.user_cal,
|
|
|
+ url=radicale_server['url'],
|
|
|
+ username=radicale_server['username'],
|
|
|
+ password=radicale_server['password'],
|
|
|
+ calendar_name=radicale_server['calendar_name'],
|
|
|
+ )
|
|
|
+ test_cfg = dataclasses.replace(cfg, user_cal=test_user_cal_cfg)
|
|
|
+ mock = lambda: test_cfg
|
|
|
+ monkeypatch.setattr('room_cal.user_cal.get_config', mock)
|
|
|
+ monkeypatch.setattr('room_cal.actions.fetch_user_cal.get_config', mock)
|