Files
kandev/.github/scripts/claude-read-pr-file_test.py
2026-07-31 19:51:06 +01:00

127 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""Behavior tests for the constrained PR-head file reader."""
import base64
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import textwrap
import unittest
REPO_ROOT = Path(__file__).resolve().parents[2]
HELPER = REPO_ROOT / ".github" / "scripts" / "claude-read-pr-file.py"
class ClaudeReadPrFileTest(unittest.TestCase):
def run_helper(self, path: str) -> subprocess.CompletedProcess[str]:
with tempfile.TemporaryDirectory() as temp_dir:
fake_gh = Path(temp_dir) / "gh"
head_sha = "a" * 40
content = base64.b64encode(b"const reviewed = true;\n").decode("ascii")
binary_content = base64.b64encode(b"\xff").decode("ascii")
fake_gh.write_text(
textwrap.dedent(
f"""\
#!/usr/bin/env python3
import json
import sys
endpoint = sys.argv[-1]
if endpoint == "repos/kdlbs/kandev/pulls/2109":
print(json.dumps({{"head": {{"sha": "{head_sha}"}}}}))
elif endpoint == (
"repos/kdlbs/kandev/contents/apps/web/demo%20file.ts"
"?ref={head_sha}"
):
print(json.dumps({{
"type": "file",
"path": "apps/web/demo file.ts",
"size": 23,
"encoding": "base64",
"content": "{content}",
}}))
elif endpoint.endswith("/contents/directory?ref={head_sha}"):
print(json.dumps({{"type": "dir", "path": "directory"}}))
elif endpoint.endswith("/contents/large.txt?ref={head_sha}"):
print(json.dumps({{
"type": "file",
"path": "large.txt",
"size": 262145,
"encoding": "base64",
"content": "",
}}))
elif endpoint.endswith("/contents/binary.dat?ref={head_sha}"):
print(json.dumps({{
"type": "file",
"path": "binary.dat",
"size": 1,
"encoding": "base64",
"content": "{binary_content}",
}}))
else:
print(f"unexpected endpoint: {{endpoint}}", file=sys.stderr)
raise SystemExit(2)
"""
),
encoding="utf-8",
)
fake_gh.chmod(0o755)
env = os.environ.copy()
env.update(
{
"CLAUDE_REVIEW_PR_NUMBER": "2109",
"GITHUB_REPOSITORY": "kdlbs/kandev",
"PATH": f"{temp_dir}:{env['PATH']}",
}
)
return subprocess.run(
[sys.executable, str(HELPER), path],
check=False,
capture_output=True,
env=env,
text=True,
)
def test_reads_utf8_file_from_the_bound_pr_head(self) -> None:
result = self.run_helper("apps/web/demo file.ts")
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual("const reviewed = true;\n", result.stdout)
def test_rejects_paths_outside_the_repository(self) -> None:
result = self.run_helper("../runner-secret")
self.assertNotEqual(0, result.returncode)
self.assertIn("relative repository path", result.stderr)
def test_rejects_non_file_content(self) -> None:
result = self.run_helper("directory")
self.assertNotEqual(0, result.returncode)
self.assertIn("regular file", result.stderr)
def test_rejects_oversized_content(self) -> None:
result = self.run_helper("large.txt")
self.assertNotEqual(0, result.returncode)
self.assertIn("256 KiB", result.stderr)
def test_rejects_non_utf8_content(self) -> None:
result = self.run_helper("binary.dat")
self.assertNotEqual(0, result.returncode)
self.assertIn("UTF-8", result.stderr)
def test_rejects_non_normalized_paths(self) -> None:
result = self.run_helper("apps//web/file.ts")
self.assertNotEqual(0, result.returncode)
self.assertIn("relative repository path", result.stderr)
if __name__ == "__main__":
unittest.main()