diff --git a/scripts/ci/opencode_review_normalize_output.py b/scripts/ci/opencode_review_normalize_output.py index 7d2a797..92174c1 100755 --- a/scripts/ci/opencode_review_normalize_output.py +++ b/scripts/ci/opencode_review_normalize_output.py @@ -121,6 +121,10 @@ def iter_json_objects(text: str) -> list[Any]: return values +def project_root() -> Path: + return Path(__file__).resolve().parents[2] + + def main(argv: list[str]) -> int: if len(argv) != 5: print( @@ -132,9 +136,9 @@ def main(argv: list[str]) -> int: expected_head_sha, expected_run_id, expected_run_attempt, output_file_arg = argv[1:] output_file = Path(output_file_arg) - project_root = Path.cwd().resolve() + root = project_root() - if not output_file.resolve().is_relative_to(project_root): + if not output_file.resolve().is_relative_to(root): print(f"error: output file path {output_file_arg!r} is outside the project root", file=sys.stderr) return 65 diff --git a/tests/scripts/ci/test_opencode_review_normalize_output.py b/tests/scripts/ci/test_opencode_review_normalize_output.py index 6926389..7fbc04f 100644 --- a/tests/scripts/ci/test_opencode_review_normalize_output.py +++ b/tests/scripts/ci/test_opencode_review_normalize_output.py @@ -1,6 +1,6 @@ import pytest -from scripts.ci.opencode_review_normalize_output import valid_control +from scripts.ci.opencode_review_normalize_output import main, valid_control def test_valid_control_approve(): value = { @@ -164,3 +164,14 @@ def test_valid_control_invalid_findings(): finding[field] = " " val = dict(base, findings=[finding]) assert valid_control(val, expected_head_sha="sha", expected_run_id="id", expected_run_attempt="1") is None + + +def test_main_rejects_output_file_outside_repo(monkeypatch, tmp_path, capsys): + monkeypatch.chdir(tmp_path) + output_file = tmp_path / "review.json" + output_file.write_text("{}", encoding="utf-8") + + exit_code = main(["prog", "sha123", "run123", "1", str(output_file)]) + + assert exit_code == 65 + assert "outside the project root" in capsys.readouterr().err diff --git a/tests/test_vibesec.py b/tests/test_vibesec.py index 17bdd6d..cfc60f0 100644 --- a/tests/test_vibesec.py +++ b/tests/test_vibesec.py @@ -7,7 +7,7 @@ import pytest -from scanner.cli.vibesec import _collect_files, _print_scan_results, _scan_file, cmd_init, cmd_scan, cmd_review, REVIEW_PROMPT_BASE, REVIEW_PROMPT_NEXTJS, REVIEW_PROMPT_SUPABASE, REVIEW_PROMPT_FIREBASE, REVIEW_PROMPT_STRIPE, REVIEW_PROMPT_FOOTER +from scanner.cli.vibesec import _collect_files, _print_scan_results, _print_supabase_reminder, _scan_file, cmd_init, cmd_review, cmd_scan, REVIEW_PROMPT_BASE, REVIEW_PROMPT_FIREBASE, REVIEW_PROMPT_FOOTER, REVIEW_PROMPT_NEXTJS, REVIEW_PROMPT_STRIPE, REVIEW_PROMPT_SUPABASE MOCK_RULES = [ { @@ -458,6 +458,16 @@ def test_sanitize_terminal_output(): # Test non-strings assert _sanitize_terminal_output(None) is None +def test_print_supabase_reminder(capsys): + _print_supabase_reminder() + captured = capsys.readouterr() + + assert "Supabase stack detected. Quick reminders:" in captured.out + assert "Enable RLS on every user-data table" in captured.out + assert "Use getUser() not getSession() on the server" in captured.out + assert "Keep SUPABASE_SERVICE_ROLE_KEY server-side only" in captured.out + + def test_collect_files_oserror_on_scandir(tmp_path): (tmp_path / "dir1").mkdir() (tmp_path / "dir1" / "file1.py").touch() @@ -506,7 +516,6 @@ def __iter__(self): yield MockEntry(entry) return MockIterator(original_scandir(path)) - with patch("os.scandir", side_effect=mock_scandir): files = list(_collect_files(tmp_path)) assert len(files) == 1 @@ -526,42 +535,49 @@ def test_cmd_review_base_prompt(capsys): assert REVIEW_PROMPT_FIREBASE not in captured.out assert REVIEW_PROMPT_STRIPE not in captured.out + def test_cmd_review_nextjs(capsys): args = Namespace(stack=["nextjs"], db=None, payments=None) cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_NEXTJS in captured.out + def test_cmd_review_supabase(capsys): args = Namespace(stack=None, db="supabase", payments=None) cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_SUPABASE in captured.out + def test_cmd_review_supabase_via_stack(capsys): args = Namespace(stack=["supabase"], db=None, payments=None) cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_SUPABASE in captured.out + def test_cmd_review_firebase(capsys): args = Namespace(stack=None, db="firebase", payments=None) cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_FIREBASE in captured.out + def test_cmd_review_firebase_via_stack(capsys): args = Namespace(stack=["firebase"], db=None, payments=None) cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_FIREBASE in captured.out + def test_cmd_review_stripe(capsys): args = Namespace(stack=None, db=None, payments="stripe") cmd_review(args) captured = capsys.readouterr() assert REVIEW_PROMPT_STRIPE in captured.out + def test_cmd_review_all_options(capsys): args = Namespace(stack=["nextjs"], db="supabase", payments="stripe") cmd_review(args)