113 lines
3.6 KiB
Python
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
|