"""A14 backup.py:暫存路徑 → 確認 tar.gz 產出且內容齊全。""" from __future__ import annotations import importlib.util import tarfile from pathlib import Path SCRIPT = Path(__file__).resolve().parents[2] / "scripts" / "backup.py" def _load_module(): spec = importlib.util.spec_from_file_location("backup_script", SCRIPT) assert spec and spec.loader mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod def test_backup_produces_tar_with_db_and_vault(tmp_path, monkeypatch): db = tmp_path / "kbv5.db" db.write_bytes(b"SQLite format 3\x00") # 假 SQLite header vault = tmp_path / "vault" (vault / "知識" / "筆記").mkdir(parents=True) (vault / "知識" / "筆記" / "demo-1.md").write_text("# demo", encoding="utf-8") history = tmp_path / "history" history.mkdir() (history / "v1.md").write_text("# v1", encoding="utf-8") monkeypatch.setenv("KBV5_DB", str(db)) monkeypatch.setenv("KBV5_VAULT_DIR", str(vault)) monkeypatch.setenv("KBV5_HISTORY_DIR", str(history)) out_dir = tmp_path / "out" mod = _load_module() rc = mod.main([str(out_dir)]) assert rc == 0 backups = list(out_dir.glob("kbv5-*.tar.gz")) assert len(backups) == 1 with tarfile.open(backups[0], "r:gz") as tar: names = tar.getnames() # 三項都應在 tar 內(檔名透過 arcname 處理) assert any(n.endswith("kbv5.db") for n in names) assert any("demo-1.md" in n for n in names) assert any("v1.md" in n for n in names) def test_backup_missing_all_returns_error(tmp_path, monkeypatch): monkeypatch.setenv("KBV5_DB", str(tmp_path / "nope.db")) monkeypatch.setenv("KBV5_VAULT_DIR", str(tmp_path / "nope_vault")) monkeypatch.setenv("KBV5_HISTORY_DIR", str(tmp_path / "nope_history")) mod = _load_module() rc = mod.main([str(tmp_path / "out")]) assert rc == 1 def test_dockerfile_and_compose_exist(): """A14 必要部署檔存在。""" root = Path(__file__).resolve().parents[2] for f in ("Dockerfile", "docker-compose.yml", ".env.example", ".dockerignore"): assert (root / f).exists(), f"missing {f}" def test_env_example_lists_critical_vars(): """.env.example 必含關鍵 env 提示,避免新人漏設。""" root = Path(__file__).resolve().parents[2] text = (root / ".env.example").read_text(encoding="utf-8") for var in ("JWT_SECRET", "KBV5_DB", "KBV5_VAULT_DIR", "ALLOWED_EMAIL_DOMAINS", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY"): assert var in text, f".env.example missing {var}"