From d0e637813f35cadefce54cddc43a420312afce09 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 16 Jun 2026 05:12:07 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=AA=20Add=20tests=20for=20os.scandir?= =?UTF-8?q?=20OSError=20and=20PermissionError=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🎯 What: Added unit tests to tests/test_vibesec.py to cover the OSError and PermissionError exception handling logic in scanner/cli/vibesec.py during directory traversal. Specifically, we test when os.scandir fails (e.g. permission denied to open a directory) and when an individual file entry fails during stat checking (e.g. permission denied while checking is_symlink). 📊 Coverage: These tests use unittest.mock.patch to hit the except paths at lines 519/520 and at lines 522/523. Both test cases were added and verified to pass, along with adding the missing os import to the test file. ✨ Result: The codebase now has complete test coverage for edge-case directory and file permission errors during the _collect_files() process, making refactoring safer and ensuring robust error resilience for future modifications. --- tests/test_vibesec.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_vibesec.py b/tests/test_vibesec.py index a5a41f9..9a48053 100644 --- a/tests/test_vibesec.py +++ b/tests/test_vibesec.py @@ -1,3 +1,4 @@ +import os import re import tempfile from pathlib import Path @@ -212,6 +213,45 @@ def test_collect_files_handles_cyclic_symlink(tmp_path): assert collected_rel_paths == {"a/a.py", "b/b.py"} +def test_collect_files_handles_oserror_in_scandir(tmp_path): + (tmp_path / "a.py").touch() + with patch("os.scandir", side_effect=PermissionError): + assert list(_collect_files(tmp_path)) == [] + + +def test_collect_files_handles_oserror_in_entry(tmp_path): + (tmp_path / "a.py").touch() + (tmp_path / "b.py").touch() + + original_scandir = os.scandir + + def mock_scandir(path): + iterator = original_scandir(path) + class MockIterator: + def __enter__(self): + return self + def __exit__(self, *args): + iterator.close() + def __iter__(self): + return self + def __next__(self): + entry = next(iterator) + if entry.name == "a.py": + class MockEntry: + name = entry.name + path = entry.path + def is_symlink(self): + raise PermissionError("Access denied") + return MockEntry() + return entry + return MockIterator() + + with patch("os.scandir", side_effect=mock_scandir): + collected_rel_paths = {f.relative_to(tmp_path).as_posix() for f in _collect_files(tmp_path)} + assert collected_rel_paths == {"b.py"} + + + @patch("scanner.cli.vibesec.SCAN_RULES", MOCK_RULES) def test_scan_file_skips_symlink(tmp_path): target = tmp_path / "target.py"