from __future__ import annotations import subprocess import sys import tempfile import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] CREATE = REPO_ROOT / "skills" / "ship" / "scripts" / "create_ship.py" VALIDATE = REPO_ROOT / "skills" / "ship" / "scripts" / "validate_ship.py" APPEND = REPO_ROOT / "skills" / "ship" / "scripts" / "append_evidence.py" def _run(script: Path, *args: str, cwd: Path | None = None) -> subprocess.CompletedProcess[str]: return subprocess.run( [sys.executable, str(script), *args], check=False, capture_output=True, text=True, cwd=cwd, ) class YamlEmptyCollectionsTests(unittest.TestCase): def test_roundtrip_empty_list_and_dict(self) -> None: from pouch.yaml_io import safe_dump, safe_load data = {"items": [], "meta": {}} loaded = safe_load(safe_dump(data)) self.assertEqual(loaded, data) class CreateAndValidateTests(unittest.TestCase): def test_create_short_ship_validates(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) result = _run( CREATE, "--project-root", str(root), "--ship-id", "add-export-button", "--kind", "short", "--title", "列表页增加导出按钮", ) self.assertEqual(result.returncode, 0, result.stderr) ship_dir = root / ".pouch" / "ship" / "add-export-button" self.assertTrue((ship_dir / "current.yaml").is_file()) self.assertTrue((ship_dir / "spec.md").is_file()) self.assertFalse((ship_dir / "spec").is_dir()) check = _run(VALIDATE, str(ship_dir)) self.assertEqual(check.returncode, 0, check.stderr) def test_create_long_ship_has_spec_dir(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) result = _run( CREATE, "--project-root", str(root), "--ship-id", "tenant-permissions", "--kind", "long", "--title", "多租户权限", ) self.assertEqual(result.returncode, 0, result.stderr) ship_dir = root / ".pouch" / "ship" / "tenant-permissions" self.assertTrue((ship_dir / "spec").is_dir()) check = _run(VALIDATE, str(ship_dir)) self.assertEqual(check.returncode, 0, check.stderr) def test_rejects_date_id(self) -> None: with tempfile.TemporaryDirectory() as tmp: result = _run( CREATE, "--project-root", tmp, "--ship-id", "20261013", "--kind", "short", "--title", "nope", ) self.assertNotEqual(result.returncode, 0) self.assertIn("日期", result.stderr) def test_append_test_run_pointer(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) created = _run( CREATE, "--project-root", str(root), "--ship-id", "add-export-button", "--kind", "short", "--title", "列表页增加导出按钮", ) self.assertEqual(created.returncode, 0, created.stderr) run_dir = root / ".pouch" / "tester" / "runs" run_dir.mkdir(parents=True) run_path = run_dir / "20261013T010000Z-smoke-ab12.yaml" run_path.write_text("runId: demo\n", encoding="utf-8") ship_dir = root / ".pouch" / "ship" / "add-export-button" appended = _run( APPEND, str(ship_dir), "--kind", "testRun", "--path", ".pouch/tester/runs/20261013T010000Z-smoke-ab12.yaml", "--result", "pass", "--suite", "smoke", ) self.assertEqual(appended.returncode, 0, appended.stderr) text = (ship_dir / "current.yaml").read_text(encoding="utf-8") self.assertIn("20261013T010000Z-smoke-ab12.yaml", text) def test_missing_pointer_file_fails(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) _run( CREATE, "--project-root", str(root), "--ship-id", "add-export-button", "--kind", "short", "--title", "列表页增加导出按钮", ) ship_dir = root / ".pouch" / "ship" / "add-export-button" appended = _run( APPEND, str(ship_dir), "--kind", "testRun", "--path", ".pouch/tester/runs/missing.yaml", "--result", "pass", ) self.assertNotEqual(appended.returncode, 0) self.assertIn("不存在", appended.stderr) if __name__ == "__main__": unittest.main()