| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- import dataclasses
- import os
- import socket
- import subprocess
- import sys
- import time
- import httpx
- import pytest
- def pytest_collection_modifyitems(config, items):
- """Skip tests marked 'live' unless ROOM_CAL_LIVE_TESTS=1.
- Live tests hit real service APIs and need credentials that dev systems
- only sometimes have (especially for 2FA-gated backends).
- """
- if os.environ.get('ROOM_CAL_LIVE_TESTS'):
- return
- skip_live = pytest.mark.skip(reason='live service-api test; set ROOM_CAL_LIVE_TESTS=1 to run')
- for item in items:
- if 'live' in item.keywords:
- item.add_marker(skip_live)
- 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(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()
- 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)
- # reset the memoized user_cal Calendar so it's rebuilt from the patched
- # config (and restored after the test)
- monkeypatch.setattr('room_cal.user_cal._user_calendar', None)
|