#!/usr/bin/env python3
"""파일 뷰어 API 보안 및 기능 테스트"""
import os
import sys
import urllib.parse
from pathlib import Path

# workspace 루트
WORKSPACE_DIR = Path("/home/jay/workspace")

# 서버의 /api/file 엔드포인트 로직을 함수로 추출
def validate_file_request(rel_path_raw: str) -> dict:
    """서버의 /api/file 검증 로직 시뮬레이션. 성공 시 {"ok": True, "path": ...}, 실패 시 {"ok": False, "code": ..., "error": ...}"""
    if not rel_path_raw:
        return {"ok": False, "code": 400, "error": "Missing 'path' parameter"}

    rel_path = urllib.parse.unquote(rel_path_raw)

    # path traversal 방지
    if ".." in rel_path:
        return {"ok": False, "code": 403, "error": "Path traversal not allowed"}

    # 화이트리스트 경로 확인
    ALLOWED_PREFIXES = ("memory/reports/", "memory/tasks/", "memory/specs/", "memory/meetings/")
    if not any(rel_path.startswith(prefix) for prefix in ALLOWED_PREFIXES):
        return {"ok": False, "code": 403, "error": "Access denied: path not in allowed directories"}

    # 허용 확장자 확인
    ALLOWED_EXTENSIONS = (".md", ".txt", ".json", ".yaml", ".yml", ".py")
    ext = os.path.splitext(rel_path)[1].lower()
    if ext not in ALLOWED_EXTENSIONS:
        return {"ok": False, "code": 403, "error": f"File type not allowed: {ext}"}

    # 민감 파일 차단
    BLOCKED_EXTENSIONS = (".env", ".credentials", ".keys")
    filename = os.path.basename(rel_path)
    if any(filename.endswith(blocked) for blocked in BLOCKED_EXTENSIONS) or filename.startswith("."):
        return {"ok": False, "code": 403, "error": "Access denied: sensitive file"}

    # 절대 경로 계산 및 workspace 범위 확인
    file_path = (WORKSPACE_DIR / rel_path).resolve()
    workspace_resolved = WORKSPACE_DIR.resolve()
    if not str(file_path).startswith(str(workspace_resolved)):
        return {"ok": False, "code": 403, "error": "Access denied: outside workspace"}

    if not file_path.exists():
        return {"ok": False, "code": 404, "error": "File not found"}

    if not file_path.is_file():
        return {"ok": False, "code": 400, "error": "Not a file"}

    return {"ok": True, "path": rel_path, "file_path": str(file_path)}


def test_all():
    results = []
    passed = 0
    failed = 0

    def check(name, result, expected_ok, expected_code=None):
        nonlocal passed, failed
        ok = result["ok"] == expected_ok
        if expected_code and not expected_ok:
            ok = ok and result.get("code") == expected_code
        status = "PASS" if ok else "FAIL"
        if ok:
            passed += 1
        else:
            failed += 1
        results.append(f"  [{status}] {name}")
        if not ok:
            results.append(f"         Expected: ok={expected_ok}, code={expected_code}")
            results.append(f"         Got: {result}")

    # === 정상 케이스 ===
    print("=== 기능 테스트 ===")

    # 실제 존재하는 파일 테스트 (task 파일은 거의 항상 존재)
    # memory/tasks 디렉토리의 파일 확인
    tasks_dir = WORKSPACE_DIR / "memory" / "tasks"
    if tasks_dir.exists():
        task_files = list(tasks_dir.glob("*.md"))
        if task_files:
            rel = f"memory/tasks/{task_files[0].name}"
            check("정상 task 파일 접근", validate_file_request(rel), True)

    reports_dir = WORKSPACE_DIR / "memory" / "reports"
    if reports_dir.exists():
        report_files = list(reports_dir.glob("*.md"))
        if report_files:
            rel = f"memory/reports/{report_files[0].name}"
            check("정상 report 파일 접근", validate_file_request(rel), True)

    specs_dir = WORKSPACE_DIR / "memory" / "specs"
    if specs_dir.exists():
        spec_files = list(specs_dir.glob("*.md"))
        if spec_files:
            rel = f"memory/specs/{spec_files[0].name}"
            check("정상 specs 파일 접근", validate_file_request(rel), True)

    # === 보안 테스트: path traversal ===
    print("\n=== 보안 테스트: Path Traversal ===")
    check(".. 포함 경로 차단", validate_file_request("memory/reports/../../../etc/passwd"), False, 403)
    check("../ 시작 차단", validate_file_request("../etc/passwd"), False, 403)
    check("인코딩된 .. 차단", validate_file_request("memory/reports/..%2F..%2Fetc%2Fpasswd"), False, 403)

    # === 보안 테스트: 화이트리스트 ===
    print("\n=== 보안 테스트: 화이트리스트 ===")
    check("허용되지 않은 경로 차단", validate_file_request("teams/dev1/secret.py"), False, 403)
    check("루트 파일 접근 차단", validate_file_request("CLAUDE.md"), False, 403)
    check("scripts 접근 차단", validate_file_request("scripts/deploy.sh"), False, 403)
    check("memory/ 직접 접근 차단", validate_file_request("memory/task-timers.json"), False, 403)
    check("memory/logs 접근 차단", validate_file_request("memory/logs/ci-latest.json"), False, 403)

    # === 보안 테스트: 확장자 ===
    print("\n=== 보안 테스트: 확장자 ===")
    check(".exe 차단", validate_file_request("memory/reports/test.exe"), False, 403)
    check(".sh 차단", validate_file_request("memory/reports/test.sh"), False, 403)
    check("확장자 없는 파일 차단", validate_file_request("memory/reports/test"), False, 403)

    # === 보안 테스트: 민감 파일 ===
    print("\n=== 보안 테스트: 민감 파일 ===")
    check(".env 파일 차단", validate_file_request("memory/reports/.env"), False, 403)
    check("dot 파일 차단", validate_file_request("memory/reports/.gitignore"), False, 403)

    # === 에러 케이스 ===
    print("\n=== 에러 케이스 ===")
    check("빈 경로", validate_file_request(""), False, 400)
    check("존재하지 않는 파일", validate_file_request("memory/reports/nonexistent-task-99999.md"), False, 404)

    # === 결과 요약 ===
    print("\n".join(results))
    print(f"\n{'='*40}")
    print(f"결과: {passed} PASS / {failed} FAIL / {passed + failed} TOTAL")

    return failed == 0

if __name__ == "__main__":
    success = test_all()
    sys.exit(0 if success else 1)
