"""Behavioral checks for sync ownership, read-only modes, and delivery commands.""" from pathlib import Path import importlib.machinery import importlib.util import subprocess import tempfile import unittest REPO = Path(__file__).resolve().parents[1] loader = importlib.machinery.SourceFileLoader('catalog_validator', str(REPO / 'bin/validate-skills')) spec = importlib.util.spec_from_loader(loader.name, loader) validator = importlib.util.module_from_spec(spec) loader.exec_module(validator) class ToolTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory(prefix='global-skills-test-') self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name) self.global_dir = self.root / 'global' self.jev_dir = self.root / 'jev' self.target = self.root / 'target' self.global_dir.mkdir() self.jev_dir.mkdir() def skill(self, root, name): path = root / name path.mkdir() (path / 'SKILL.md').write_text(f'---\nname: {name}\ndescription: fixture\nscope: global\n---\n') return path def sync(self, *flags): return subprocess.run(['bash', str(REPO / 'sync-skills.sh'), '--global-dir', str(self.global_dir), '--jev-dir', str(self.jev_dir), '--target', str(self.target), *flags], text=True, capture_output=True) def test_sync_rejects_target_overlapping_catalog(self): source = self.skill(self.global_dir, 'example') self.target = self.global_dir self.assertEqual(self.sync().returncode, 2) self.assertTrue((source / 'SKILL.md').is_file()) def test_metadata_rejects_invalid_yaml_scalar(self): for field in ['name: "unterminated', 'description: bad: scalar']: with self.assertRaises(ValueError): validator.metadata('---\n' + field + '\n---\n') def test_install_and_idempotence(self): source = self.skill(self.global_dir, 'example') self.assertEqual(self.sync().returncode, 0) self.assertEqual((self.target / 'example').resolve(), source) self.assertIn('0 changes', self.sync().stdout) self.assertEqual(self.sync('--check').returncode, 0) def test_external_symlink_collision_preserves_user_link(self): self.skill(self.global_dir, 'example') self.target.mkdir() external = self.root / 'user-skill' external.mkdir() dest = self.target / 'example' dest.symlink_to(external) self.assertEqual(self.sync().returncode, 1) self.assertEqual(dest.resolve(), external) def test_dangling_external_symlink_is_preserved(self): self.skill(self.global_dir, 'example') self.target.mkdir() dest = self.target / 'example' dest.symlink_to(self.root / 'missing-user-skill') self.assertEqual(self.sync().returncode, 1) self.assertTrue(dest.is_symlink()) self.assertEqual(dest.readlink(), self.root / 'missing-user-skill') def test_unrelated_directory_is_preserved(self): self.skill(self.global_dir, 'example') (self.target / 'example').mkdir(parents=True) file = self.target / 'example' / 'user.txt' file.write_text('user work') self.assertEqual(self.sync().returncode, 1) self.assertEqual(file.read_text(), 'user work') def test_deleted_skill_link_is_pruned(self): source = self.skill(self.global_dir, 'example') self.sync() (source / 'SKILL.md').unlink() source.rmdir() self.assertEqual(self.sync().returncode, 0) self.assertFalse((self.target / 'example').is_symlink()) def test_relative_managed_link_is_pruned(self): self.target.mkdir() dest = self.target / 'gone' dest.symlink_to('../global/gone') self.assertEqual(self.sync().returncode, 0) self.assertFalse(dest.is_symlink()) def test_move_between_managed_sources(self): old = self.skill(self.jev_dir, 'example') self.sync() (old / 'SKILL.md').unlink() old.rmdir() new = self.skill(self.global_dir, 'example') self.assertEqual(self.sync().returncode, 0) self.assertEqual((self.target / 'example').resolve(), new) def test_unavailable_jev_does_not_prune_its_links(self): self.target.mkdir() dest = self.target / 'example' dest.symlink_to(self.jev_dir / 'example') self.jev_dir.rmdir() self.assertEqual(self.sync().returncode, 0) self.assertTrue(dest.is_symlink()) def test_dry_run_and_check_do_not_create_targets(self): self.skill(self.global_dir, 'example') self.assertEqual(self.sync('--dry-run').returncode, 0) self.assertFalse(self.target.exists()) self.assertEqual(self.sync('--check').returncode, 1) self.assertFalse(self.target.exists()) def test_read_only_modes_do_not_remove_stale_links(self): self.target.mkdir() dest = self.target / 'gone' dest.symlink_to(self.global_dir / 'gone') self.assertEqual(self.sync('--check').returncode, 1) self.assertEqual(self.sync('--dry-run').returncode, 0) self.assertTrue(dest.is_symlink()) def test_source_collision_fails_before_writing(self): self.skill(self.global_dir, 'example') self.skill(self.jev_dir, 'example') self.assertEqual(self.sync().returncode, 2) self.assertFalse(self.target.exists()) def test_invalid_source_fails_before_writing(self): (self.global_dir / 'empty').mkdir() self.assertEqual(self.sync().returncode, 2) self.assertFalse(self.target.exists()) def test_file_and_directory_delivery_support_spaces(self): file = self.root / 'report with spaces.md' file.write_text('report') result = subprocess.run(['bash', str(REPO / 'bin/t3-deliver'), str(file)], text=True, capture_output=True) self.assertEqual(result.returncode, 0) self.assertEqual(result.stdout.splitlines()[-1], f'scp "vasily@msi.tail0b014a.ts.net:{file}" .') result = subprocess.run(['bash', str(REPO / 'bin/t3-deliver'), str(self.root)], text=True, capture_output=True) self.assertEqual(result.returncode, 0) self.assertEqual(result.stdout.splitlines()[-1], f'scp -r "vasily@msi.tail0b014a.ts.net:{self.root}" .') def test_delivery_rejects_shell_expansion_in_names(self): for name in ['cash$report', 'percent%report', 'tick`report', 'quote"report', 'bang!report', 'newline\nreport']: path = self.root / name path.write_text('fixture') result = subprocess.run(['bash', str(REPO / 'bin/t3-deliver'), str(path)], text=True, capture_output=True) self.assertNotEqual(result.returncode, 0, name) self.assertNotIn('scp ', result.stdout) def test_validator_reports_missing_links_and_collision(self): source = self.skill(self.global_dir, 'example') self.skill(self.jev_dir, 'example') (source / 'SKILL.md').write_text((source / 'SKILL.md').read_text() + '\n[missing](no-such-file.md)\n') result = subprocess.run(['python3', str(REPO / 'bin/validate-skills'), '--global-dir', str(self.global_dir), '--jev-dir', str(self.jev_dir)], text=True, capture_output=True) self.assertEqual(result.returncode, 1) self.assertIn('collides', result.stderr) self.assertIn('missing link target', result.stderr) def test_metadata_rejects_duplicate_keys(self): with self.assertRaises(ValueError): validator.metadata('---\nname: example\nname: other\n---\n') def test_validator_ignores_example_links_inside_fences(self): path = self.root / 'reference.md' path.write_text('```markdown\n[example](not-a-real-file.md)\n```\n') self.assertEqual(list(validator.local_links(path)), []) if __name__ == '__main__': unittest.main()