Files
raycast-api/tests/conftest.py
T

113 lines
3.6 KiB
Python

"""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 = (
'<?xml version="1.0" encoding="UTF-8"?>'
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
'<plist version="1.0"><dict>'
"<key>CFBundleShortVersionString</key><string>9.9.9.0</string>"
"</dict></plist>"
)
(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