- src/dist 산출물 분리 원칙 정리(.gitignore, .gitattributes) - 루트 및 주요 폴더(config/scripts/prompts/tests/src, 런타임 폴더 5종)에 안내용 README.md 추가 - CHANGELOG.md, LICENSE, docs/ops/05-release-and-versioning.md 추가 - docs/README.md 문서 지도 갱신
111 lines
3.5 KiB
Python
111 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
"""프로젝트 R0 계약 RED.
|
|
|
|
이 파일은 `docs/design/07-tdd-red-system.md` 의 7.1 backlog 를 실행 가능한
|
|
테스트로 만든다. 목적은 기능 세부가 아니라 "프로젝트가 에이전트/사용자 관점에서
|
|
부팅 가능한가"를 보장하는 것이다.
|
|
"""
|
|
|
|
import importlib
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PYTHON = ROOT / ".venv" / "Scripts" / "python.exe"
|
|
|
|
|
|
def _python() -> str:
|
|
return str(PYTHON if PYTHON.exists() else Path(sys.executable))
|
|
|
|
|
|
def _env() -> dict[str, str]:
|
|
env = os.environ.copy()
|
|
env.setdefault("PYTHONIOENCODING", "utf-8")
|
|
env["DMF_CRAWLER_HOME"] = str(ROOT)
|
|
return env
|
|
|
|
|
|
def test_all_expected_runtime_entry_modules_import() -> None:
|
|
"""CLI/저장소/리포트/GUI/알림 진입 모듈은 모두 import 가능해야 한다."""
|
|
modules = [
|
|
"dmf_crawler.__main__",
|
|
"dmf_crawler.cli",
|
|
"dmf_crawler.storage.repo",
|
|
"dmf_crawler.report",
|
|
"dmf_crawler.gui.app",
|
|
"dmf_crawler.notify.pump",
|
|
]
|
|
failed: list[str] = []
|
|
for name in modules:
|
|
try:
|
|
importlib.import_module(name)
|
|
except Exception as exc: # noqa: BLE001 - 모든 실패를 한 번에 보여준다
|
|
failed.append(f"{name}: {type(exc).__name__}: {exc}")
|
|
assert not failed, "\n".join(failed)
|
|
|
|
|
|
def test_cli_help_exits_zero() -> None:
|
|
proc = subprocess.run(
|
|
[_python(), "-m", "dmf_crawler", "--help"],
|
|
cwd=ROOT,
|
|
env=_env(),
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
capture_output=True,
|
|
timeout=30,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
assert "doctor" in proc.stdout
|
|
assert "run" in proc.stdout
|
|
|
|
|
|
def test_doctor_json_never_crashes_without_key() -> None:
|
|
"""인증키가 없어도 doctor 는 JSON 진단 결과를 출력하고 종료 코드 2로 끝나야 한다."""
|
|
proc = subprocess.run(
|
|
[_python(), "-m", "dmf_crawler", "doctor", "--json"],
|
|
cwd=ROOT,
|
|
env=_env(),
|
|
text=True,
|
|
encoding="utf-8",
|
|
errors="replace",
|
|
capture_output=True,
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode in {0, 1, 2}, proc.stderr
|
|
payload = json.loads(proc.stdout)
|
|
assert isinstance(payload, list)
|
|
keys = {row["key"] for row in payload}
|
|
assert "api_key_present" in keys
|
|
assert "database" in keys
|
|
|
|
|
|
def test_powershell_scripts_have_utf8_bom() -> None:
|
|
scripts = sorted((ROOT / "scripts").glob("*.ps1"))
|
|
assert scripts, "scripts/*.ps1 이 없다"
|
|
offenders = [p.name for p in scripts if not p.read_bytes().startswith(b"\xef\xbb\xbf")]
|
|
assert not offenders, "UTF-8 BOM 누락: " + ", ".join(offenders)
|
|
|
|
|
|
def test_cmd_files_are_ascii_only() -> None:
|
|
scripts = [ROOT / "bootstrap.cmd"]
|
|
offenders: list[str] = []
|
|
for path in scripts:
|
|
data = path.read_bytes()
|
|
if any(byte >= 128 for byte in data):
|
|
offenders.append(path.name)
|
|
assert not offenders, "CMD 파일은 ASCII 만 허용: " + ", ".join(offenders)
|
|
|
|
|
|
def test_agent_docs_link_tdd_ssot() -> None:
|
|
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
|
|
claude = (ROOT / "CLAUDE.md").read_text(encoding="utf-8")
|
|
required = "docs/design/07-tdd-red-system.md"
|
|
assert required in agents
|
|
assert required in claude
|
|
assert "No RED, No Code" in agents
|
|
assert "테스트를 약하게" in agents
|