Skip to content

Commit 80c7938

Browse files
authored
Merge pull request #602 from tisnik/lcore-625-check-if-transcript-directory-is-writable
LCORE-625: Check if transcript directory is writable
2 parents 761bb29 + 3388ab8 commit 80c7938

File tree

4 files changed

+113
-5
lines changed

4 files changed

+113
-5
lines changed

src/models/config.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,27 @@ class UserDataCollection(ConfigurationBase):
229229
@model_validator(mode="after")
230230
def check_storage_location_is_set_when_needed(self) -> Self:
231231
"""Check that storage_location is set when enabled."""
232-
if self.feedback_enabled and self.feedback_storage is None:
233-
raise ValueError("feedback_storage is required when feedback is enabled")
234-
if self.transcripts_enabled and self.transcripts_storage is None:
235-
raise ValueError(
236-
"transcripts_storage is required when transcripts is enabled"
232+
if self.feedback_enabled:
233+
if self.feedback_storage is None:
234+
raise ValueError(
235+
"feedback_storage is required when feedback is enabled"
236+
)
237+
checks.directory_check(
238+
Path(self.feedback_storage),
239+
desc="Check directory to store feedback",
240+
must_exists=False,
241+
must_be_writable=True,
242+
)
243+
if self.transcripts_enabled:
244+
if self.transcripts_storage is None:
245+
raise ValueError(
246+
"transcripts_storage is required when transcripts is enabled"
247+
)
248+
checks.directory_check(
249+
Path(self.transcripts_storage),
250+
desc="Check directory to store transcripts",
251+
must_exists=False,
252+
must_be_writable=True,
237253
)
238254
return self
239255

src/utils/checks.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,36 @@ def file_check(path: FilePath, desc: str) -> None:
5757
raise InvalidConfigurationError(f"{desc} '{path}' is not readable")
5858

5959

60+
def directory_check(
61+
path: FilePath, must_exists: bool, must_be_writable: bool, desc: str
62+
) -> None:
63+
"""
64+
Ensure the given path is an existing directory.
65+
66+
If the path is not a directory, raises InvalidConfigurationError.
67+
68+
Parameters:
69+
path (FilePath): Filesystem path to validate.
70+
must_exists (bool): Should the directory exists?
71+
must_be_writable (bool): Should the check test if directory is writable?
72+
desc (str): Short description of the value being checked; used in error
73+
messages.
74+
75+
Raises:
76+
InvalidConfigurationError: If `path` does not point to a directory or
77+
is not writable when required.
78+
"""
79+
if not os.path.exists(path):
80+
if must_exists:
81+
raise InvalidConfigurationError(f"{desc} '{path}' does not exist")
82+
return
83+
if not os.path.isdir(path):
84+
raise InvalidConfigurationError(f"{desc} '{path}' is not a directory")
85+
if must_be_writable:
86+
if not os.access(path, os.W_OK):
87+
raise InvalidConfigurationError(f"{desc} '{path}' is not writable")
88+
89+
6090
def import_python_module(profile_name: str, profile_path: str) -> ModuleType | None:
6191
"""Import a Python module from a file path."""
6292
if not profile_path.endswith(".py"):

tests/unit/models/config/test_user_data_collection.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
from models.config import UserDataCollection
6+
from utils.checks import InvalidConfigurationError
67

78

89
def test_user_data_collection_feedback_enabled() -> None:
@@ -39,3 +40,18 @@ def test_user_data_collection_transcripts_disabled() -> None:
3940
match="transcripts_storage is required when transcripts is enabled",
4041
):
4142
UserDataCollection(transcripts_enabled=True, transcripts_storage=None)
43+
44+
45+
def test_user_data_collection_wrong_directory_path() -> None:
46+
"""Test the UserDataCollection constructor for wrong directory path."""
47+
with pytest.raises(
48+
InvalidConfigurationError,
49+
match="Check directory to store feedback '/root' is not writable",
50+
):
51+
_ = UserDataCollection(feedback_enabled=True, feedback_storage="/root")
52+
53+
with pytest.raises(
54+
InvalidConfigurationError,
55+
match="Check directory to store transcripts '/root' is not writable",
56+
):
57+
_ = UserDataCollection(transcripts_enabled=True, transcripts_storage="/root")

tests/unit/utils/test_checks.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ def input_file_fixture(tmp_path):
1919
return filename
2020

2121

22+
@pytest.fixture(name="input_directory")
23+
def input_directory_fixture(tmp_path):
24+
"""Create directory manually using the tmp_path fixture."""
25+
dirname = os.path.join(tmp_path, "mydir")
26+
os.mkdir(dirname)
27+
return dirname
28+
29+
2230
def test_get_attribute_from_file_no_record():
2331
"""Test the get_attribute_from_file function when record is not in dictionary."""
2432
# no data
@@ -81,6 +89,44 @@ def test_file_check_not_readable_file(input_file):
8189
checks.file_check(input_file, "description")
8290

8391

92+
def test_directory_check_non_existing_directory():
93+
"""Test the function directory_check skips non-existing directory."""
94+
# just call the function, it should not raise an exception
95+
checks.directory_check(
96+
"/foo/bar/baz", must_exists=False, must_be_writable=False, desc="foobar"
97+
)
98+
with pytest.raises(checks.InvalidConfigurationError):
99+
checks.directory_check(
100+
"/foo/bar/baz", must_exists=True, must_be_writable=False, desc="foobar"
101+
)
102+
103+
104+
def test_directory_check_existing_writable_directory(input_directory):
105+
"""Test the function directory_check checks directory."""
106+
# just call the function, it should not raise an exception
107+
checks.directory_check(
108+
input_directory, must_exists=True, must_be_writable=True, desc="foobar"
109+
)
110+
111+
112+
def test_directory_check_non_a_directory(input_file):
113+
"""Test the function directory_check checks directory."""
114+
# pass a filename not a directory name
115+
with pytest.raises(checks.InvalidConfigurationError):
116+
checks.directory_check(
117+
input_file, must_exists=True, must_be_writable=True, desc="foobar"
118+
)
119+
120+
121+
def test_directory_check_existing_non_writable_directory(input_directory):
122+
"""Test the function directory_check checks directory."""
123+
with patch("os.access", return_value=False):
124+
with pytest.raises(checks.InvalidConfigurationError):
125+
checks.directory_check(
126+
input_directory, must_exists=True, must_be_writable=True, desc="foobar"
127+
)
128+
129+
84130
def test_import_python_module_success():
85131
"""Test importing a Python module."""
86132
module_path = "tests/profiles/test/profile.py"

0 commit comments

Comments
 (0)