"""Sandbox-only installer tests; curl never accesses the network.""" import os from pathlib import Path import subprocess import tempfile import unittest ROOT = Path(__file__).resolve().parent INSTALLER = ROOT / 'install-question-tools.sh' POLICY = (ROOT / 'grilling-question-tools.md').read_bytes() BEGIN = b'' END = b'' class InstallerTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory(prefix='question-tools-test-') self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name) self.home = self.root / 'home' self.home.mkdir() self.bin = self.root / 'bin' self.bin.mkdir() self.fixture = self.root / 'policy.md' self.fixture.write_bytes(POLICY) curl = self.bin / 'curl' curl.write_text( '#!/usr/bin/env python3\n' 'import os, pathlib, sys\n' 'pathlib.Path(os.environ["CURL_LOG"]).write_text(sys.argv[-1])\n' 'if os.environ.get("CURL_FAIL"): sys.exit(22)\n' 'sys.stdout.buffer.write(pathlib.Path(os.environ["POLICY_FIXTURE"]).read_bytes())\n' ) curl.chmod(0o700) self.env = {key: value for key, value in os.environ.items() if key not in ( 'HOME', 'XDG_CONFIG_HOME', 'CLAUDE_CONFIG_DIR', 'CODEX_HOME', 'OPENCODE_CONFIG_DIR', 'QUESTION_TOOLS_POLICY_DIR', 'QUESTION_TOOLS_REVISION')} self.env.update(HOME=str(self.home), PATH=str(self.bin) + ':' + os.environ['PATH'], POLICY_FIXTURE=str(self.fixture), CURL_LOG=str(self.root / 'curl.log')) self.hosts = [self.home / '.claude/CLAUDE.md', self.home / '.codex/AGENTS.md', self.home / '.config/opencode/AGENTS.md'] self.policy = self.home / '.config/ai-agents/grilling-question-tools.md' def run_install(self, success=True): result = subprocess.run(['bash', str(INSTALLER)], env=self.env, cwd=self.root, capture_output=True, text=True) self.assertEqual(result.returncode == 0, success, result.stdout + result.stderr) return result def write_hosts(self, data=b'Unrelated\x80 instructions\r\n'): for host in self.hosts: host.parent.mkdir(parents=True, exist_ok=True) host.write_bytes(data) def snapshot(self): return {str(p.relative_to(self.home)): ( ('link', os.readlink(p)) if p.is_symlink() else ('file', p.read_bytes(), p.stat().st_mode, p.stat().st_mtime_ns) if p.is_file() else ('directory',)) for p in self.home.rglob('*')} def test_regular_hosts_separate_policy_and_unchanged_rerun(self): original = b'Unrelated\x80 instructions\r\n' self.write_hosts(original) self.hosts[0].chmod(0o640) self.run_install() self.assertEqual(self.policy.read_bytes(), POLICY) for host in self.hosts: content = host.read_bytes() self.assertTrue(content.startswith(original)) self.assertEqual(content.count(BEGIN), 1) self.assertIn(str(self.policy).encode(), content) self.assertNotIn(POLICY, content) backups = list(host.parent.glob(host.name + '.question-tools-backup-*')) self.assertEqual(len(backups), 1) self.assertEqual(backups[0].read_bytes(), original) self.assertEqual(backups[0].stat().st_mode & 0o777, 0o600) self.assertEqual(self.hosts[0].stat().st_mode & 0o777, 0o640) before = self.snapshot() self.run_install() self.assertEqual(self.snapshot(), before) def test_shared_symlink_target_and_migration(self): target = self.home / 'shared.md' prefix, suffix = b'Existing\xff\r\n\n', b'\r\nAfter\x80' original = prefix + BEGIN + b'\n' + POLICY + END + suffix target.write_bytes(original) for host in self.hosts: host.parent.mkdir(parents=True, exist_ok=True) host.symlink_to(os.path.relpath(target, host.parent)) links = [os.readlink(p) for p in self.hosts] self.run_install() content = target.read_bytes() self.assertTrue(content.startswith(prefix + BEGIN)) self.assertTrue(content.endswith(END + suffix)) self.assertEqual(content.count(BEGIN), 1) self.assertNotIn(POLICY, content) self.assertEqual([os.readlink(p) for p in self.hosts], links) backups = list(self.home.rglob('*.question-tools-backup-*')) self.assertEqual(len(backups), 1) self.assertEqual(backups[0].read_bytes(), original) before = self.snapshot() self.run_install() self.assertEqual(self.snapshot(), before) def test_malformed_blocks_preflight_no_writes(self): for malformed in (BEGIN, END + b'\n' + BEGIN, BEGIN + b'\nx\n' + END + END, b'prefix' + BEGIN + b'\nx\n' + END, BEGIN + b'x\n' + END): with self.subTest(malformed=malformed): self.write_hosts() self.hosts[-1].write_bytes(malformed) before = self.snapshot() self.run_install(False) self.assertEqual(self.snapshot(), before) def test_download_failure_and_invalid_policy_no_writes(self): self.write_hosts() before = self.snapshot() self.env['CURL_FAIL'] = '1' self.run_install(False) self.assertEqual(self.snapshot(), before) del self.env['CURL_FAIL'] self.fixture.write_bytes(b'incomplete') self.run_install(False) self.assertEqual(self.snapshot(), before) def test_dangling_file_and_directory_symlinks(self): for path in (self.hosts[-1], self.hosts[-1].parent): with self.subTest(path=path): path.parent.mkdir(parents=True, exist_ok=True) path.symlink_to(self.home / 'missing') before = self.snapshot() self.run_install(False) self.assertEqual(self.snapshot(), before) path.unlink() if path == self.hosts[-1]: path.parent.rmdir() def test_nonregular_destination(self): self.write_hosts() self.hosts[-1].unlink() os.mkfifo(self.hosts[-1]) self.run_install(False) self.assertFalse(self.policy.exists()) self.assertEqual(list(self.home.rglob('*.question-tools-backup-*')), []) @unittest.skipIf(os.geteuid() == 0, 'root bypasses filesystem permissions') def test_permission_failure_preflight(self): self.write_hosts() self.hosts[-1].chmod(0o400) before = self.snapshot() self.run_install(False) self.assertEqual(self.snapshot(), before) def test_overrides_revision_and_directory_symlink(self): config = self.home / 'config space' config.mkdir() (self.home / 'linked').symlink_to(config, target_is_directory=True) self.env['XDG_CONFIG_HOME'] = str(self.home / 'linked') self.env['QUESTION_TOOLS_REVISION'] = 'a' * 40 self.run_install() policy = config / 'ai-agents/grilling-question-tools.md' self.assertEqual(policy.read_bytes(), POLICY) self.assertTrue((self.home / 'linked').is_symlink()) self.assertIn(str(policy).encode(), self.hosts[0].read_bytes()) self.assertTrue((self.root / 'curl.log').read_text().endswith('/raw/' + 'a' * 40 + '/grilling-question-tools.md')) self.env['QUESTION_TOOLS_POLICY_DIR'] = str(self.home / 'custom policy') self.run_install() self.assertEqual((self.home / 'custom policy/grilling-question-tools.md').read_bytes(), POLICY) def test_policy_cannot_alias_host(self): self.write_hosts() self.policy.parent.mkdir(parents=True, exist_ok=True) self.policy.symlink_to(self.hosts[0]) before = self.snapshot() self.run_install(False) self.assertEqual(self.snapshot(), before) def test_eli5_is_unchanged_and_not_an_input(self): eli5 = ROOT / 'ELI5.mdx' before = eli5.read_bytes() self.run_install() self.assertEqual(eli5.read_bytes(), before) self.assertNotIn('ELI5', INSTALLER.read_text()) self.assertEqual((self.root / 'curl.log').read_text(), 'https://opengist.resetrix.work/weehong/9e381dc89dff4670b7232b910454f056/raw/HEAD/grilling-question-tools.md') if __name__ == '__main__': unittest.main()