|
1 | | -"""Tests for bundled assets and install source resolution.""" |
| 1 | +"""PyPI wheel must ship ``.cursor`` workflow files inside ``graphstack/assets``.""" |
2 | 2 |
|
3 | 3 | from __future__ import annotations |
4 | 4 |
|
| 5 | +import zipfile |
5 | 6 | from pathlib import Path |
6 | 7 |
|
7 | | -from graphstack.installer import PACKAGE_ROOT, install_source_root |
8 | | - |
9 | | - |
10 | | -def test_install_source_root_from_dev_repo() -> None: |
11 | | - root = install_source_root() |
12 | | - assert (root / "orchestrator" / "ORCHESTRATOR.md").is_file() |
13 | | - |
14 | | - |
15 | | -def test_bundled_assets_exist_in_package() -> None: |
16 | | - assets = PACKAGE_ROOT / "assets" |
17 | | - assert (assets / "orchestrator" / "ORCHESTRATOR.md").is_file() |
18 | | - assert (assets / ".cursor" / "rules" / "graphstack.mdc").is_file() |
19 | | - |
20 | | - |
21 | | -def test_install_from_bundled_assets_only(tmp_path: Path, monkeypatch) -> None: |
22 | | - from graphstack import installer |
23 | | - |
24 | | - assets = PACKAGE_ROOT / "assets" |
25 | | - monkeypatch.setattr( |
26 | | - installer, |
27 | | - "_source_root", |
28 | | - lambda: assets, |
29 | | - ) |
30 | | - target = tmp_path / "consumer" |
31 | | - target.mkdir() |
32 | | - assert installer.install(target, non_interactive=True) == 0 |
33 | | - assert (target / "orchestrator" / "ORCHESTRATOR.md").is_file() |
34 | | - assert (target / ".cursor" / "rules" / "graphstack.mdc").is_file() |
35 | | - assert (target / "handoff" / "BRIEF.md").is_file() |
| 8 | +import pytest |
| 9 | + |
| 10 | +REPO_ROOT = Path(__file__).resolve().parents[3] |
| 11 | +WHEEL_GLOB = "mertcapkin_graphstack-*.whl" |
| 12 | + |
| 13 | +REQUIRED_ASSET_PATHS = ( |
| 14 | + "graphstack/assets/.cursor/rules/graphstack.mdc", |
| 15 | + "graphstack/assets/.cursor/commands/graphstack.md", |
| 16 | + "graphstack/assets/.cursor/skills/architect/ARCHITECT.md", |
| 17 | + "graphstack/assets/.cursor/skills/builder/BUILDER.md", |
| 18 | +) |
| 19 | + |
| 20 | + |
| 21 | +def _latest_wheel(dist_dir: Path) -> Path | None: |
| 22 | + wheels = sorted(dist_dir.glob(WHEEL_GLOB), key=lambda p: p.stat().st_mtime) |
| 23 | + return wheels[-1] if wheels else None |
| 24 | + |
| 25 | + |
| 26 | +@pytest.mark.parametrize("member", REQUIRED_ASSET_PATHS) |
| 27 | +def test_wheel_includes_cursor_assets(member: str) -> None: |
| 28 | + for dist_name in ("dist", "dist_test"): |
| 29 | + dist_dir = REPO_ROOT / dist_name |
| 30 | + wheel = _latest_wheel(dist_dir) |
| 31 | + if wheel is None: |
| 32 | + continue |
| 33 | + with zipfile.ZipFile(wheel) as archive: |
| 34 | + names = set(archive.namelist()) |
| 35 | + assert member in names, ( |
| 36 | + f"{member} missing from {wheel.name}; " |
| 37 | + "dot-directories under assets/ need explicit package-data" |
| 38 | + ) |
| 39 | + return |
| 40 | + pytest.skip("no built wheel in dist/ or dist_test/ — run python -m build first") |
0 commit comments