Skip to content

Commit f88de06

Browse files
committed
Add tests/relink/test_handle_non_dir.py
1 parent 13d455d commit f88de06

1 file changed

Lines changed: 222 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
"""
2+
Tests of handle_non_dir() and _handle_non_dir_entry() in relink.py
3+
"""
4+
5+
# pylint: disable=protected-access
6+
7+
import os
8+
import sys
9+
import tempfile
10+
import logging
11+
from unittest.mock import Mock, patch
12+
13+
import pytest
14+
15+
# Add parent directory to path to import relink module
16+
sys.path.insert(
17+
0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18+
)
19+
# pylint: disable=wrong-import-position
20+
import relink # noqa: E402
21+
22+
23+
@pytest.fixture(name="mock_direntry")
24+
def fixture_mock_direntry():
25+
"""
26+
Factory fixture to create mock DirEntry objects.
27+
28+
Returns:
29+
callable: A function that creates a mock DirEntry with specified properties.
30+
"""
31+
32+
def _create_mock(name, path, uid, is_file=True, is_symlink=False):
33+
"""
34+
Create a mock DirEntry object.
35+
36+
Args:
37+
name (str): The name of the file/directory.
38+
path (str): The full path to the file/directory.
39+
uid (int): The UID of the owner.
40+
is_file (bool): Whether this is a file.
41+
is_symlink (bool): Whether this is a symlink.
42+
43+
Returns:
44+
Mock: A mock DirEntry object.
45+
"""
46+
mock_entry = Mock(spec=os.DirEntry)
47+
mock_entry.name = name
48+
mock_entry.path = path
49+
50+
mock_stat = Mock()
51+
mock_stat.st_uid = uid
52+
mock_entry.stat.return_value = mock_stat
53+
54+
mock_entry.is_file.return_value = is_file
55+
mock_entry.is_symlink.return_value = is_symlink
56+
57+
return mock_entry
58+
59+
return _create_mock
60+
61+
62+
class TestHandleNonDirEntry:
63+
"""
64+
Tests for _handle_non_dir_entry() function.
65+
66+
Logging tests are in test_verbosity.py.
67+
"""
68+
69+
def test_returns_path_for_owned_regular_file(self, temp_dirs):
70+
"""Test that owned regular files return their path."""
71+
source_dir, _ = temp_dirs
72+
user_uid = os.stat(source_dir).st_uid
73+
74+
# Create a regular file
75+
test_file = os.path.join(source_dir, "test.txt")
76+
with open(test_file, "w", encoding="utf-8") as f:
77+
f.write("content")
78+
79+
# Get DirEntry for the file
80+
with os.scandir(source_dir) as entries:
81+
entry = next(e for e in entries if e.name == "test.txt")
82+
result = relink._handle_non_dir_entry(entry, user_uid)
83+
84+
assert result == test_file
85+
86+
def test_returns_none_for_file_owned_by_different_user(
87+
self, temp_dirs, mock_direntry
88+
):
89+
"""Test that files owned by different users return None."""
90+
source_dir, _ = temp_dirs
91+
user_uid = os.stat(source_dir).st_uid
92+
different_uid = user_uid + 1000
93+
94+
# Create a file
95+
test_file = os.path.join(source_dir, "test.txt")
96+
with open(test_file, "w", encoding="utf-8") as f:
97+
f.write("content")
98+
99+
# Create mock entry with different UID
100+
mock_entry = mock_direntry(
101+
"test.txt", test_file, different_uid, is_file=True, is_symlink=False
102+
)
103+
104+
result = relink._handle_non_dir_entry(mock_entry, user_uid)
105+
106+
assert result is None
107+
108+
def test_returns_none_and_logs_for_owned_symlink(self, temp_dirs, caplog):
109+
"""Test that owned symlinks return None and are logged."""
110+
source_dir, _ = temp_dirs
111+
user_uid = os.stat(source_dir).st_uid
112+
113+
# Create a symlink
114+
symlink_path = os.path.join(source_dir, "link.txt")
115+
dummy_target = os.path.join(tempfile.gettempdir(), "somewhere")
116+
os.symlink(dummy_target, symlink_path)
117+
118+
# Get DirEntry for the symlink
119+
with os.scandir(source_dir) as entries:
120+
entry = next(e for e in entries if e.name == "link.txt")
121+
122+
with caplog.at_level(logging.DEBUG):
123+
result = relink._handle_non_dir_entry(entry, user_uid)
124+
125+
assert result is None
126+
assert "Skipping symlink:" in caplog.text
127+
assert symlink_path in caplog.text
128+
129+
def test_returns_none_for_symlink_owned_by_different_user(
130+
self, temp_dirs, caplog, mock_direntry
131+
):
132+
"""Test that symlinks owned by different users return None without logging."""
133+
source_dir, _ = temp_dirs
134+
user_uid = os.stat(source_dir).st_uid
135+
different_uid = user_uid + 1000
136+
137+
# Create a symlink
138+
symlink_path = os.path.join(source_dir, "link.txt")
139+
dummy_target = os.path.join(tempfile.gettempdir(), "somewhere")
140+
os.symlink(dummy_target, symlink_path)
141+
142+
# Create mock entry with different UID
143+
mock_entry = mock_direntry(
144+
"link.txt", symlink_path, different_uid, is_file=False, is_symlink=True
145+
)
146+
147+
with caplog.at_level(logging.DEBUG):
148+
result = relink._handle_non_dir_entry(mock_entry, user_uid)
149+
150+
assert result is None
151+
# Should NOT log because it's not owned by the user
152+
assert "Skipping symlink:" not in caplog.text
153+
154+
def test_handles_file_with_spaces(self, temp_dirs):
155+
"""Test that files with spaces in names are handled correctly."""
156+
source_dir, _ = temp_dirs
157+
user_uid = os.stat(source_dir).st_uid
158+
159+
# Create a file with spaces
160+
test_file = os.path.join(source_dir, "file with spaces.txt")
161+
with open(test_file, "w", encoding="utf-8") as f:
162+
f.write("content")
163+
164+
# Get DirEntry for the file
165+
with os.scandir(source_dir) as entries:
166+
entry = next(e for e in entries if e.name == "file with spaces.txt")
167+
result = relink._handle_non_dir_entry(entry, user_uid)
168+
169+
assert result == test_file
170+
171+
def test_handles_file_with_special_characters(self, temp_dirs):
172+
"""Test that files with special characters are handled correctly."""
173+
source_dir, _ = temp_dirs
174+
user_uid = os.stat(source_dir).st_uid
175+
176+
# Create a file with special characters
177+
filename = "file-with_special.chars@123.txt"
178+
test_file = os.path.join(source_dir, filename)
179+
with open(test_file, "w", encoding="utf-8") as f:
180+
f.write("content")
181+
182+
# Get DirEntry for the file
183+
with os.scandir(source_dir) as entries:
184+
entry = next(e for e in entries if e.name == filename)
185+
result = relink._handle_non_dir_entry(entry, user_uid)
186+
187+
assert result == test_file
188+
189+
190+
class TestHandleNonDir:
191+
"""Tests for handle_non_dir() function."""
192+
193+
def test_works_with_direntry(self, temp_dirs):
194+
"""Test that handle_non_dir works with os.DirEntry objects."""
195+
source_dir, _ = temp_dirs
196+
user_uid = os.stat(source_dir).st_uid
197+
198+
# Create a regular file
199+
test_file = os.path.join(source_dir, "test.txt")
200+
with open(test_file, "w", encoding="utf-8") as f:
201+
f.write("content")
202+
203+
# Get DirEntry for the file
204+
with os.scandir(source_dir) as entries:
205+
entry = next(e for e in entries if e.name == "test.txt")
206+
result = relink.handle_non_dir(entry, user_uid, source_dir)
207+
208+
assert result == test_file
209+
210+
def test_raises_typeerror_for_int(self, temp_dirs):
211+
"""Test that handle_non_dir raises TypeError for an integer."""
212+
source_dir, _ = temp_dirs
213+
user_uid = os.stat(source_dir).st_uid
214+
215+
invalid_input = 12345
216+
expected_type = type(invalid_input)
217+
218+
with pytest.raises(
219+
TypeError,
220+
match=f"Unsure how to handle non-directory variable of type.*{expected_type}",
221+
):
222+
relink.handle_non_dir(invalid_input, user_uid, source_dir)

0 commit comments

Comments
 (0)