"""Shared pytest fixtures and helpers.""" from __future__ import annotations import sys from dataclasses import dataclass from pathlib import Path import pytest # --- Isolate persistent state paths from the real user's home --------------- @pytest.fixture(autouse=True) def _isolate_user_state(tmp_path, monkeypatch): """Redirect all per-user state files into a tmp dir. Ensures tests never clobber real settings, tokens, or state DB on the developer's machine. """ fake_home = tmp_path / "home" fake_home.mkdir() monkeypatch.setattr(Path, "home", staticmethod(lambda: fake_home)) # Re-import config so its module-level paths re-evaluate against the # patched home. We clear and re-import to reset cached module constants. import importlib from tagsync import config importlib.reload(config) yield # Leave things as-is after the test; tmp_path cleanup handles disk. # --- Fake Dropbox API objects ------------------------------------------------ # # The Dropbox SDK may or may not be installed in the test env. We don't want # to require it, and the parts of core.py that need it are only reached via # helpers we can monkeypatch out. @dataclass class FakeFileMetadata: """Stand-in for dropbox.files.FileMetadata.""" name: str path_lower: str path_display: str @dataclass class FakeUserTag: tag_text: str class FakeTag: """Stand-in for dropbox.files.Tag (union type).""" def __init__(self, tag_text: str) -> None: self._user = FakeUserTag(tag_text) def get_user_generated_tag(self) -> FakeUserTag: return self._user @dataclass class FakePathToTags: path: str tags: list @dataclass class FakeTagsResult: paths_to_tags: list @pytest.fixture def patched_core(monkeypatch): """Monkeypatch core's Dropbox-dependent helpers to controllable fakes. Tests receive a `state` dict where they can configure: - `files`: list of FakeFileMetadata returned by _list_all_files - `tags_by_path`: dict[path_lower -> list[str]] returned by _fetch_tags """ from tagsync import core state = { "files": [], "tags_by_path": {}, "fetch_calls": 0, } def fake_client(_token): return object() # placeholder; never used because list/fetch are faked def fake_list(_dbx): return list(state["files"]) def fake_fetch(_dbx, files): state["fetch_calls"] += 1 for f in files: tag_names = sorted(state["tags_by_path"].get(f.path_lower, [])) yield f, tag_names monkeypatch.setattr(core, "_dropbox_client", fake_client) monkeypatch.setattr(core, "_list_all_files", fake_list) monkeypatch.setattr(core, "_fetch_tags", fake_fetch) return state # --- Marker-based skips ------------------------------------------------------ def pytest_collection_modifyitems(config, items): platform_skip = { "macos": sys.platform != "darwin", "windows": sys.platform != "win32", } for item in items: for mark_name, should_skip in platform_skip.items(): if mark_name in item.keywords and should_skip: item.add_marker( pytest.mark.skip(reason=f"requires {mark_name}") )