"""
test_code_review.py

scripts/code-review.py 단위 테스트 (TDD)

테스트 항목:
1. check_hardcoded_secrets - 시크릿 탐지
2. check_todos - TODO/FIXME 탐지
3. check_unused_imports - 미사용 import 탐지
4. check_function_length - 과도한 함수 길이 탐지
5. review_file - 파일 종합 리뷰
6. review_all - 전체 리뷰 결과 집계
7. get_changed_files - git diff 기반 파일 목록
8. 에지 케이스: 빈 파일, 바이너리 파일, git 불가 환경
9. CLI 출력 형식
"""

import importlib.util
import json
import subprocess
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# scripts 디렉토리를 import path에 추가
_SCRIPTS_DIR = Path(__file__).parent.parent
sys.path.insert(0, str(_SCRIPTS_DIR))

# code-review.py는 하이픈이 있으므로 importlib으로 임포트
_MODULE_PATH = _SCRIPTS_DIR / "code-review.py"
spec = importlib.util.spec_from_file_location("code_review", _MODULE_PATH)
assert spec is not None
code_review = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(code_review)


# ---------------------------------------------------------------------------
# 1. check_hardcoded_secrets 테스트
# ---------------------------------------------------------------------------


class TestCheckHardcodedSecrets:
    """하드코딩된 시크릿 탐지 테스트"""

    def test_detects_aws_access_key(self):
        """AWS Access Key ID 탐지"""
        content = 'AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1
        assert any(f["severity"] == "critical" for f in findings)
        assert any(f["category"] == "hardcoded_secret" for f in findings)

    def test_detects_api_key_assignment(self):
        """api_key = '...' 패턴 탐지"""
        content = "api_key = 'sk-1234567890abcdef'"
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1
        assert findings[0]["severity"] == "critical"

    def test_detects_password_assignment(self):
        """password = '...' 패턴 탐지"""
        content = 'password = "super_secret_password_123"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_detects_token_assignment(self):
        """token = '...' 패턴 탐지"""
        content = 'token = "ghp_1234567890abcdefghij"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_detects_secret_key_assignment(self):
        """secret_key = '...' 패턴 탐지"""
        content = 'secret_key = "my-super-secret-key"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_no_false_positive_env_var(self):
        """환경변수 참조는 탐지 안 함"""
        content = 'api_key = os.environ.get("API_KEY")'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) == 0

    def test_no_false_positive_empty_string(self):
        """빈 문자열 할당은 탐지 안 함"""
        content = 'password = ""'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) == 0

    def test_finding_has_line_number(self):
        """발견 항목에 줄 번호 포함"""
        content = "# line 1\n# line 2\napi_key = 'secret123'"
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1
        assert findings[0]["line"] == 3

    def test_finding_has_file_path(self):
        """발견 항목에 파일 경로 포함"""
        content = 'password = "secret"'
        findings = code_review.check_hardcoded_secrets("scripts/test.py", content)
        assert findings[0]["file"] == "scripts/test.py"

    def test_empty_content_returns_no_findings(self):
        """빈 파일에서 탐지 없음"""
        findings = code_review.check_hardcoded_secrets("test.py", "")
        assert findings == []

    def test_detects_private_key_header(self):
        """-----BEGIN PRIVATE KEY----- 헤더 탐지"""
        content = "key = '-----BEGIN PRIVATE KEY-----'"
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_multiline_detection(self):
        """여러 줄에 걸쳐 있는 시크릿 탐지"""
        content = "x = 1\n" "y = 2\n" 'api_key = "AKIAIOSFODNN7EXAMPLE"\n' "z = 3\n" 'password = "hunter2"\n'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 2


# ---------------------------------------------------------------------------
# 2. check_todos 테스트
# ---------------------------------------------------------------------------


class TestCheckTodos:
    """TODO/FIXME 잔존 탐지 테스트"""

    def test_detects_todo_comment(self):
        """# TODO: 패턴 탐지"""
        content = "# TODO: fix this later"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) == 1
        assert findings[0]["category"] == "todo"
        assert findings[0]["severity"] == "info"

    def test_detects_fixme_comment(self):
        """# FIXME: 패턴 탐지"""
        content = "# FIXME: broken logic here"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) == 1
        assert findings[0]["category"] == "todo"

    def test_detects_todo_inline(self):
        """인라인 TODO 탐지"""
        content = "x = 1  # TODO: remove this"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) == 1

    def test_detects_hack_comment(self):
        """# HACK: 패턴 탐지"""
        content = "# HACK: temporary workaround"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) >= 1

    def test_detects_xxx_comment(self):
        """# XXX: 패턴 탐지"""
        content = "# XXX: this needs attention"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) >= 1

    def test_case_insensitive_todo(self):
        """대소문자 무관 탐지"""
        content = "# todo: lowercase todo"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) >= 1

    def test_finding_message_contains_todo_text(self):
        """발견 메시지에 TODO 내용 포함"""
        content = "# TODO: fix this later"
        findings = code_review.check_todos("test.py", content)
        assert "fix this later" in findings[0]["message"]

    def test_finding_line_number_correct(self):
        """줄 번호 정확성"""
        content = "x = 1\n# TODO: check\ny = 2"
        findings = code_review.check_todos("test.py", content)
        assert findings[0]["line"] == 2

    def test_multiple_todos_detected(self):
        """여러 TODO 탐지"""
        content = "# TODO: first\n# FIXME: second\n# TODO: third"
        findings = code_review.check_todos("test.py", content)
        assert len(findings) == 3

    def test_empty_content_returns_no_findings(self):
        """빈 파일에서 탐지 없음"""
        findings = code_review.check_todos("test.py", "")
        assert findings == []

    def test_no_false_positive_in_string(self):
        """일반 코드에서 오탐 없음"""
        content = "message = 'This is working correctly'\nresult = compute()"
        findings = code_review.check_todos("test.py", content)
        assert findings == []


# ---------------------------------------------------------------------------
# 3. check_unused_imports 테스트
# ---------------------------------------------------------------------------


class TestCheckUnusedImports:
    """미사용 import 탐지 테스트"""

    def test_detects_unused_import(self):
        """미사용 import 탐지"""
        content = "import os\nimport sys\n\nprint(sys.argv)"
        findings = code_review.check_unused_imports("test.py", content)
        # os는 사용되지 않음
        assert any("os" in f["message"] for f in findings)

    def test_no_finding_for_used_import(self):
        """사용된 import는 탐지 안 함"""
        content = "import os\n\nresult = os.path.join('a', 'b')"
        findings = code_review.check_unused_imports("test.py", content)
        assert len(findings) == 0

    def test_detects_multiple_unused_imports(self):
        """여러 미사용 import 탐지"""
        content = "import os\nimport sys\nimport json\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert len(findings) >= 2

    def test_finding_severity_is_warning(self):
        """미사용 import 심각도는 warning"""
        content = "import os\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert all(f["severity"] == "warning" for f in findings)

    def test_finding_category_is_unused_import(self):
        """발견 카테고리 확인"""
        content = "import os\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert all(f["category"] == "unused_import" for f in findings)

    def test_import_as_alias_used(self):
        """import as 별칭 사용 케이스"""
        content = "import numpy as np\n\narr = np.array([1, 2, 3])"
        findings = code_review.check_unused_imports("test.py", content)
        assert len(findings) == 0

    def test_import_as_alias_unused(self):
        """import as 별칭 미사용"""
        content = "import numpy as np\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert len(findings) >= 1

    def test_from_import_used(self):
        """from X import Y 사용 케이스"""
        content = "from os.path import join\n\nresult = join('a', 'b')"
        findings = code_review.check_unused_imports("test.py", content)
        assert len(findings) == 0

    def test_from_import_unused(self):
        """from X import Y 미사용 케이스"""
        content = "from os.path import join\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert any("join" in f["message"] for f in findings)

    def test_empty_content_returns_no_findings(self):
        """빈 파일에서 탐지 없음"""
        findings = code_review.check_unused_imports("test.py", "")
        assert findings == []

    def test_finding_has_line_number(self):
        """발견 항목에 줄 번호 포함"""
        content = "import os\n\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert findings[0]["line"] == 1

    def test_non_python_file_returns_empty(self):
        """Python 파일 아닌 경우 탐지 없음"""
        content = "import something"
        findings = code_review.check_unused_imports("test.js", content)
        assert findings == []


# ---------------------------------------------------------------------------
# 4. check_function_length 테스트
# ---------------------------------------------------------------------------


class TestCheckFunctionLength:
    """과도한 함수 길이 탐지 테스트"""

    def _make_long_function(self, lines: int, name: str = "long_func") -> str:
        """지정된 줄 수의 함수 생성"""
        body = "\n".join(f"    x_{i} = {i}" for i in range(lines - 1))
        return f"def {name}():\n{body}\n    return x_0\n"

    def test_detects_long_function(self):
        """50줄 초과 함수 탐지"""
        content = self._make_long_function(55)
        findings = code_review.check_function_length("test.py", content)
        assert len(findings) >= 1
        assert findings[0]["category"] == "long_function"

    def test_no_finding_for_short_function(self):
        """50줄 이하 함수는 탐지 안 함"""
        content = self._make_long_function(30)
        findings = code_review.check_function_length("test.py", content)
        assert len(findings) == 0

    def test_exact_50_lines_not_flagged(self):
        """정확히 50줄은 탐지 안 함 (>50 조건).

        _make_long_function(n)은 AST에서 n+1줄 함수를 생성하므로
        정확히 50줄 함수를 만들려면 n=49를 사용한다.
        """
        content = self._make_long_function(49)
        findings = code_review.check_function_length("test.py", content)
        assert len(findings) == 0

    def test_51_lines_flagged(self):
        """51줄 함수는 탐지됨.

        _make_long_function(n)은 AST에서 n+1줄 함수를 생성하므로
        51줄 함수를 만들려면 n=50을 사용한다.
        """
        content = self._make_long_function(50)
        findings = code_review.check_function_length("test.py", content)
        assert len(findings) == 1

    def test_finding_severity_is_warning(self):
        """과도한 함수 길이 심각도는 warning"""
        content = self._make_long_function(60)
        findings = code_review.check_function_length("test.py", content)
        assert findings[0]["severity"] == "warning"

    def test_custom_max_lines(self):
        """사용자 정의 max_lines 사용"""
        content = self._make_long_function(25)
        findings = code_review.check_function_length("test.py", content, max_lines=20)
        assert len(findings) >= 1

    def test_multiple_long_functions(self):
        """여러 개의 긴 함수 탐지"""
        func1 = self._make_long_function(55, "func_a")
        func2 = self._make_long_function(60, "func_b")
        content = func1 + "\n" + func2
        findings = code_review.check_function_length("test.py", content)
        assert len(findings) == 2

    def test_finding_contains_function_name(self):
        """발견 메시지에 함수명 포함"""
        content = self._make_long_function(55, "my_long_function")
        findings = code_review.check_function_length("test.py", content)
        assert "my_long_function" in findings[0]["message"]

    def test_finding_has_start_line(self):
        """발견 항목에 시작 줄 번호 포함"""
        content = "# header\n" + self._make_long_function(55)
        findings = code_review.check_function_length("test.py", content)
        assert findings[0]["line"] == 2

    def test_empty_content_returns_no_findings(self):
        """빈 파일에서 탐지 없음"""
        findings = code_review.check_function_length("test.py", "")
        assert findings == []

    def test_non_python_file_returns_empty(self):
        """Python 파일 아닌 경우 탐지 없음"""
        content = self._make_long_function(55)
        findings = code_review.check_function_length("test.js", content)
        assert findings == []


# ---------------------------------------------------------------------------
# 5. review_file 테스트
# ---------------------------------------------------------------------------


class TestReviewFile:
    """파일 종합 리뷰 테스트"""

    def test_review_nonexistent_file(self, tmp_path):
        """존재하지 않는 파일은 빈 결과 반환"""
        findings = code_review.review_file(str(tmp_path / "nonexistent.py"))
        assert findings == []

    def test_review_empty_file(self, tmp_path):
        """빈 파일은 탐지 없음"""
        f = tmp_path / "empty.py"
        f.write_text("", encoding="utf-8")
        findings = code_review.review_file(str(f))
        assert findings == []

    def test_review_file_with_all_issues(self, tmp_path):
        """모든 이슈 포함 파일 리뷰"""
        content = "import os\n" "import sys\n" "# TODO: fix this\n" 'api_key = "AKIAIOSFODNN7EXAMPLE"\n'
        f = tmp_path / "problem.py"
        f.write_text(content, encoding="utf-8")
        findings = code_review.review_file(str(f))
        categories = {fi["category"] for fi in findings}
        assert "hardcoded_secret" in categories
        assert "todo" in categories

    def test_review_binary_file_graceful(self, tmp_path):
        """바이너리 파일은 graceful 처리"""
        f = tmp_path / "binary.bin"
        f.write_bytes(bytes(range(256)))
        findings = code_review.review_file(str(f))
        assert isinstance(findings, list)

    def test_review_file_returns_list(self, tmp_path):
        """review_file은 항상 리스트 반환"""
        f = tmp_path / "test.py"
        f.write_text("x = 1\n", encoding="utf-8")
        result = code_review.review_file(str(f))
        assert isinstance(result, list)


# ---------------------------------------------------------------------------
# 6. review_all 테스트
# ---------------------------------------------------------------------------


class TestReviewAll:
    """전체 리뷰 결과 집계 테스트"""

    def test_review_all_structure(self, tmp_path):
        """review_all 결과 구조 검증"""
        f = tmp_path / "test.py"
        f.write_text("x = 1\n", encoding="utf-8")
        result = code_review.review_all([str(f)])
        assert "files_analyzed" in result
        assert "findings" in result
        assert "summary" in result
        assert "critical" in result["summary"]
        assert "warning" in result["summary"]
        assert "info" in result["summary"]

    def test_review_all_files_analyzed_count(self, tmp_path):
        """분석된 파일 수 정확성"""
        files = []
        for i in range(3):
            f = tmp_path / f"test_{i}.py"
            f.write_text("x = 1\n", encoding="utf-8")
            files.append(str(f))
        result = code_review.review_all(files)
        assert result["files_analyzed"] == 3

    def test_review_all_summary_counts(self, tmp_path):
        """심각도별 요약 카운트 정확성"""
        f = tmp_path / "test.py"
        content = 'api_key = "AKIAIOSFODNN7EXAMPLE"\n' "import os\n" "# TODO: fix\n"
        f.write_text(content, encoding="utf-8")
        result = code_review.review_all([str(f)])
        assert result["summary"]["critical"] >= 1
        assert result["summary"]["info"] >= 1

    def test_review_all_empty_list(self):
        """빈 파일 목록 처리"""
        result = code_review.review_all([])
        assert result["files_analyzed"] == 0
        assert result["findings"] == []
        assert result["summary"]["critical"] == 0
        assert result["summary"]["warning"] == 0
        assert result["summary"]["info"] == 0

    def test_review_all_findings_list(self, tmp_path):
        """findings가 리스트인지 확인"""
        f = tmp_path / "test.py"
        f.write_text("# TODO: test\n", encoding="utf-8")
        result = code_review.review_all([str(f)])
        assert isinstance(result["findings"], list)

    def test_review_all_json_serializable(self, tmp_path):
        """결과가 JSON 직렬화 가능한지 확인"""
        f = tmp_path / "test.py"
        f.write_text('api_key = "AKIAIOSFODNN7EXAMPLE"\n', encoding="utf-8")
        result = code_review.review_all([str(f)])
        json_str = json.dumps(result)
        parsed = json.loads(json_str)
        assert parsed["files_analyzed"] == 1


# ---------------------------------------------------------------------------
# 7. get_changed_files 테스트 (git mock)
# ---------------------------------------------------------------------------


class TestGetChangedFiles:
    """get_changed_files 함수 테스트"""

    def test_returns_list_of_files(self):
        """파일 목록 반환 확인"""
        mock_output = "scripts/foo.py\nscripts/bar.py\n"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout=mock_output,
                returncode=0,
            )
            files = code_review.get_changed_files("HEAD~1..HEAD")
        assert files == ["scripts/foo.py", "scripts/bar.py"]

    def test_empty_diff_returns_empty_list(self):
        """변경 파일 없으면 빈 리스트 반환"""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout="",
                returncode=0,
            )
            files = code_review.get_changed_files("HEAD~1..HEAD")
        assert files == []

    def test_git_failure_returns_empty_list(self):
        """git 실패 시 빈 리스트 반환 (graceful)"""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.CalledProcessError(1, "git", stderr="not a git repository")
            files = code_review.get_changed_files("HEAD~1..HEAD")
        assert files == []

    def test_git_not_found_returns_empty_list(self):
        """git 명령 없을 때 빈 리스트 반환"""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = FileNotFoundError("git not found")
            files = code_review.get_changed_files("HEAD~1..HEAD")
        assert files == []

    def test_filters_non_python_files(self):
        """Python 파일만 필터링 (선택적)"""
        mock_output = "scripts/foo.py\nREADME.md\nscripts/bar.py\n"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout=mock_output,
                returncode=0,
            )
            files = code_review.get_changed_files("HEAD~1..HEAD")
        # 적어도 py 파일은 포함돼야 함
        assert any(f.endswith(".py") for f in files)

    def test_default_diff_range(self):
        """기본 diff range (HEAD) 사용"""
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(
                stdout="scripts/foo.py\n",
                returncode=0,
            )
            files = code_review.get_changed_files()
        assert isinstance(files, list)


# ---------------------------------------------------------------------------
# 8. 에지 케이스 테스트
# ---------------------------------------------------------------------------


class TestEdgeCases:
    """에지 케이스 처리 테스트"""

    def test_very_large_file(self, tmp_path):
        """대용량 파일 처리"""
        lines = ["x = 1\n"] * 10000
        f = tmp_path / "large.py"
        f.write_text("".join(lines), encoding="utf-8")
        findings = code_review.review_file(str(f))
        assert isinstance(findings, list)

    def test_file_with_only_comments(self, tmp_path):
        """주석만 있는 파일"""
        content = "# This is a comment\n# Another comment\n"
        f = tmp_path / "comments.py"
        f.write_text(content, encoding="utf-8")
        findings = code_review.review_file(str(f))
        assert isinstance(findings, list)

    def test_unicode_content(self, tmp_path):
        """유니코드 내용 처리"""
        content = "# 한글 주석\n# TODO: 한글 TODO\nx = '안녕하세요'\n"
        f = tmp_path / "unicode.py"
        f.write_text(content, encoding="utf-8")
        findings = code_review.review_file(str(f))
        assert isinstance(findings, list)

    def test_windows_line_endings(self, tmp_path):
        """Windows 줄 끝 처리"""
        content = "import os\r\n# TODO: fix\r\nx = 1\r\n"
        f = tmp_path / "windows.py"
        f.write_bytes(content.encode("utf-8"))
        findings = code_review.review_file(str(f))
        assert isinstance(findings, list)

    def test_finding_structure_complete(self, tmp_path):
        """Finding 구조 완전성 검증"""
        content = "# TODO: test finding structure"
        f = tmp_path / "test.py"
        f.write_text(content, encoding="utf-8")
        findings = code_review.review_file(str(f))
        assert len(findings) >= 1
        for finding in findings:
            assert "file" in finding
            assert "line" in finding
            assert "severity" in finding
            assert "category" in finding
            assert "message" in finding

    def test_severity_values_valid(self, tmp_path):
        """심각도 값이 유효한 범위 내인지 확인"""
        content = 'api_key = "AKIAIOSFODNN7EXAMPLE"\n' "import os\n" "# TODO: fix\n"
        f = tmp_path / "test.py"
        f.write_text(content, encoding="utf-8")
        findings = code_review.review_file(str(f))
        valid_severities = {"critical", "warning", "info"}
        for finding in findings:
            assert finding["severity"] in valid_severities


# ---------------------------------------------------------------------------
# 9. Finding 타입 구조 테스트
# ---------------------------------------------------------------------------


class TestFindingStructure:
    """Finding 데이터 구조 테스트"""

    def test_check_hardcoded_secrets_returns_dict_list(self):
        """check_hardcoded_secrets가 dict 리스트 반환"""
        content = 'api_key = "secret123"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert isinstance(findings, list)
        if findings:
            assert isinstance(findings[0], dict)

    def test_check_todos_returns_dict_list(self):
        """check_todos가 dict 리스트 반환"""
        content = "# TODO: test"
        findings = code_review.check_todos("test.py", content)
        assert isinstance(findings, list)
        if findings:
            assert isinstance(findings[0], dict)

    def test_check_unused_imports_returns_dict_list(self):
        """check_unused_imports가 dict 리스트 반환"""
        content = "import os\nx = 1"
        findings = code_review.check_unused_imports("test.py", content)
        assert isinstance(findings, list)
        if findings:
            assert isinstance(findings[0], dict)

    def test_check_function_length_returns_dict_list(self):
        """check_function_length가 dict 리스트 반환"""
        body = "\n".join(f"    x_{i} = {i}" for i in range(52))
        content = f"def func():\n{body}\n    return x_0\n"
        findings = code_review.check_function_length("test.py", content)
        assert isinstance(findings, list)
        if findings:
            assert isinstance(findings[0], dict)


# ---------------------------------------------------------------------------
# 10. 추가 시크릿 패턴 테스트
# ---------------------------------------------------------------------------


class TestAdditionalSecretPatterns:
    """추가 시크릿 패턴 탐지 테스트"""

    def test_detects_database_url_with_credentials(self):
        """DB URL에 자격증명 포함 패턴 탐지"""
        content = 'DATABASE_URL = "postgresql://user:password@host/db"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_no_false_positive_localhost(self):
        """localhost URL은 탐지 안 함 (자격증명 없는 경우)"""
        content = 'DATABASE_URL = "postgresql://localhost/mydb"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        # localhost + 비밀번호 없는 경우는 탐지 안 함 (구현에 따라 다를 수 있음)
        # 최소한 크래시 없이 실행돼야 함
        assert isinstance(findings, list)

    def test_detects_bearer_token(self):
        """Bearer 토큰 패턴 탐지"""
        content = 'headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"}'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        assert len(findings) >= 1

    def test_no_false_positive_placeholder(self):
        """플레이스홀더는 탐지 안 함"""
        content = 'api_key = "<YOUR_API_KEY>"'
        findings = code_review.check_hardcoded_secrets("test.py", content)
        # 플레이스홀더 형태는 탐지 안 함 (구현에 따라)
        assert isinstance(findings, list)
