import re import subprocess import tempfile import unittest from pathlib import Path from scripts.build_tsl_playbook import build ROOT_CONFIG_CONTENTS = { "package.json": b'{"name":"fixture"}\n', "package-lock.json": b'{"lockfileVersion":3}\n', ".prettierrc.json": b'{"proseWrap":"preserve"}\n', ".prettierignore": b"skills/thirdparty/**\n", } ROOT = Path(__file__).resolve().parents[1] SYNC_WORKFLOW = ROOT / ".gitea" / "workflows" / "sync-tsl-playbook.yml" EXPECTED_MANAGED_PATHS = ( "AGENTS.md", "package.json", "package-lock.json", ".prettierrc.json", ".prettierignore", "docs/tsl", "skills/tsl-syntax-reference", "skills/tsl-api-reference", "tools/tsl-codegen", ) class TslPlaybookSyncTests(unittest.TestCase): def test_build_copies_root_config_files_verbatim(self): with tempfile.TemporaryDirectory() as temp_dir: repo_root = Path(temp_dir) / "repo" output = Path(temp_dir) / "bundle" write_minimal_sources(repo_root) build(output, repo_root) for filename, expected in ROOT_CONFIG_CONTENTS.items(): with self.subTest(filename=filename): self.assertEqual(expected, (output / filename).read_bytes()) def test_non_file_root_config_fails_before_output_cleanup(self): with tempfile.TemporaryDirectory() as temp_dir: repo_root = Path(temp_dir) / "repo" output = Path(temp_dir) / "bundle" write_minimal_sources(repo_root) invalid_config = repo_root / "package.json" invalid_config.unlink() invalid_config.mkdir() output.mkdir() sentinel = output / "keep.txt" sentinel.write_text("keep\n", encoding="utf-8", newline="\n") with self.assertRaisesRegex( FileNotFoundError, re.escape(str(invalid_config)), ): build(output, repo_root) self.assertEqual("keep\n", sentinel.read_text(encoding="utf-8")) def test_root_config_files_are_not_ignored(self): for filename in ROOT_CONFIG_CONTENTS: with self.subTest(filename=filename): self.assertTrue((ROOT / filename).is_file()) result = subprocess.run( ["git", "check-ignore", "--quiet", "--", filename], cwd=ROOT, check=False, ) self.assertEqual(1, result.returncode, f"ignored: {filename}") def test_workflow_owns_exact_bundle_paths(self): text = SYNC_WORKFLOW.read_text(encoding="utf-8") match = re.search( r"^ {10}managed_paths=\(\n" r'(?P(?:^ {12}"[^"]+"\n)+)' r"^ {10}\)$", text, flags=re.MULTILINE, ) self.assertIsNotNone(match) managed_paths = tuple( line.strip().strip('"') for line in match.group("body").splitlines() ) self.assertEqual(EXPECTED_MANAGED_PATHS, managed_paths) self.assertIn('rm -rf -- "${managed_paths[@]}"', text) self.assertIn('cp -R -- "$bundle/$path" "$path"', text) self.assertIn('git add -f -A -- "${managed_paths[@]}"', text) self.assertNotIn('cp -R "$bundle"/. "$REPO_DIR"/', text) def write_minimal_sources(repo_root: Path) -> None: for relative in ( "docs/tsl", "skills/tsl-syntax-reference", "skills/tsl-api-reference", "tools/tsl-codegen", ): (repo_root / relative).mkdir(parents=True) ruleset = repo_root / "rulesets" / "tsl" / "index.md" ruleset.parent.mkdir(parents=True) ruleset.write_text( "# TSL 智能体规则\n", encoding="utf-8", newline="\n", ) for filename, content in ROOT_CONFIG_CONTENTS.items(): (repo_root / filename).write_bytes(content) if __name__ == "__main__": unittest.main()