"""Shared test fixtures. Fixtures here construct a *synthetic* app bundle on disk that mimics the structural shape of a real Raycast install: same directory layout, a Mach-O- sized binary containing a fake `window.signatureSecret = '...'` line, and a JS bundle whose AST shape matches what the extractors look for. None of this uses real Raycast secrets — the synthetic secret is `"DEAD" + "BEEF" * 15`. """ from __future__ import annotations import os from pathlib import Path from typing import Iterator import pytest FIXTURES = Path(__file__).parent / "fixtures" FAKE_SECRET = ( "DEAD" + "BEEF" * 15 ) def pytest_addoption(parser: pytest.Parser) -> None: """Register the opt-in `--live` flag that enables real-backend tests.""" parser.addoption( "--live", action="store_true", default=False, help=( "run tests marked `live` (hits backend.raycast.com — requires " "RAYCAST_BEARER + RAYCAST_DEVICE_ID env vars)" ), ) def pytest_collection_modifyitems( config: pytest.Config, items: list[pytest.Item] ) -> None: """Skip `live`-marked tests unless `--live` was passed.""" if config.getoption("--live"): return skip = pytest.mark.skip( reason="needs --live (and RAYCAST_BEARER / RAYCAST_DEVICE_ID)" ) for item in items: if "live" in item.keywords: item.add_marker(skip) @pytest.fixture(scope="session") def fixtures_dir() -> Path: return FIXTURES @pytest.fixture def mock_bundle_source() -> str: """Synthetic JS source containing all three structurally-matched functions. Mirrors the real bundle shape: a rot fn `Roto` (1 param, the three required numeric triplets), a duplicate `Roto2` (so dedup logic gets exercised), an async 4-param signing fn `SigF` that calls .map(Roto), and a confusingly- named decoy with a similar shape but missing one of the literals — the extractor should ignore it. """ return (FIXTURES / "mock_bundle.mjs").read_text() @pytest.fixture def mock_binary_bytes() -> bytes: """A blob that looks like a Mach-O binary far enough to fool the extractor.""" return (FIXTURES / "mock_binary.bin").read_bytes() @pytest.fixture def mock_app(tmp_path: Path, mock_bundle_source: str, mock_binary_bytes: bytes) -> Path: """Build a fake `Foo.app` bundle on disk and return its path.""" app = tmp_path / "Foo.app" (app / "Contents" / "MacOS").mkdir(parents=True) sub_bundle = app / "Contents" / "Resources" / "test_RaycastDesktopApp.bundle" (sub_bundle / "Contents" / "Resources" / "backend").mkdir(parents=True) (app / "Contents" / "MacOS" / "Foo").write_bytes(mock_binary_bytes) (sub_bundle / "Contents" / "Resources" / "backend" / "index.mjs").write_text( mock_bundle_source ) plist = ( '' '' '' "CFBundleShortVersionString9.9.9.0" "" ) (app / "Contents" / "Info.plist").write_text(plist) return app @pytest.fixture def isolated_cache(tmp_path: Path) -> Iterator[Path]: """Point XDG_CACHE_HOME at a temp dir so DiscoveryCache doesn't touch ~/.cache.""" prev = os.environ.get("XDG_CACHE_HOME") cache = tmp_path / "cache" os.environ["XDG_CACHE_HOME"] = str(cache) try: yield cache finally: if prev is None: os.environ.pop("XDG_CACHE_HOME", None) else: os.environ["XDG_CACHE_HOME"] = prev