1616# --------------------------------------------------------------------------- #
1717
1818
19- def test_validate_file_raises_on_empty_file_name ():
19+ def test_validate_file_raises_on_empty_file_name () -> None :
2020 """Rejects empty file_name with clear ValueError."""
2121 control = StorageControl ()
2222 with pytest .raises (ValueError , match = "file_name is required and must be non-empty" ):
@@ -28,7 +28,7 @@ def test_validate_file_raises_on_empty_file_name():
2828 )
2929
3030
31- def test_validate_file_raises_on_whitespace_only_file_name ():
31+ def test_validate_file_raises_on_whitespace_only_file_name () -> None :
3232 """Rejects whitespace-only file_name."""
3333 control = StorageControl ()
3434 with pytest .raises (ValueError , match = "file_name is required and must be non-empty" ):
@@ -40,7 +40,7 @@ def test_validate_file_raises_on_whitespace_only_file_name():
4040 )
4141
4242
43- def test_validate_file_raises_on_file_name_with_slash ():
43+ def test_validate_file_raises_on_file_name_with_slash () -> None :
4444 """Rejects file_name containing '/'."""
4545 control = StorageControl ()
4646 with pytest .raises (ValueError , match = "file_name must not contain" ):
@@ -52,7 +52,7 @@ def test_validate_file_raises_on_file_name_with_slash():
5252 )
5353
5454
55- def test_validate_file_raises_on_empty_allowed_schemas ():
55+ def test_validate_file_raises_on_empty_allowed_schemas () -> None :
5656 """Rejects empty allowed_schemas."""
5757 control = StorageControl ()
5858 with pytest .raises (ValueError , match = "allowed_schemas must not be empty" ):
@@ -69,7 +69,7 @@ def test_validate_file_raises_on_empty_allowed_schemas():
6969# --------------------------------------------------------------------------- #
7070
7171
72- def test_validate_file_raises_when_unvalidated_blob_not_found ():
72+ def test_validate_file_raises_when_unvalidated_blob_not_found () -> None :
7373 """Raises ValueError with clear message when file not in unvalidated/."""
7474 mock_bucket = MagicMock ()
7575 mock_blob = MagicMock ()
@@ -90,7 +90,7 @@ def test_validate_file_raises_when_unvalidated_blob_not_found():
9090 )
9191
9292
93- def test_validate_file_raises_when_normalized_df_none ():
93+ def test_validate_file_raises_when_normalized_df_none () -> None :
9494 """Raises ValueError when validation returns normalized_df None (e.g. empty schema)."""
9595 mock_bucket = MagicMock ()
9696 mock_blob = MagicMock ()
@@ -119,7 +119,7 @@ def test_validate_file_raises_when_normalized_df_none():
119119 )
120120
121121
122- def test_validate_file_raises_when_validated_blob_already_exists ():
122+ def test_validate_file_raises_when_validated_blob_already_exists () -> None :
123123 """Raises ValueError when validated/{file_name} already exists."""
124124 mock_bucket = MagicMock ()
125125 mock_unvalidated_blob = MagicMock ()
@@ -161,7 +161,9 @@ def blob_side_effect(name: str) -> Any:
161161# --------------------------------------------------------------------------- #
162162
163163
164- def test_validate_file_success_archives_raw_writes_validated_deletes_unvalidated ():
164+ def test_validate_file_success_archives_raw_writes_validated_deletes_unvalidated () -> (
165+ None
166+ ):
165167 """On success: copies to raw/, writes normalized CSV to validated/, deletes unvalidated/."""
166168 mock_bucket = MagicMock ()
167169 mock_unvalidated_blob = MagicMock ()
@@ -215,7 +217,7 @@ def blob_side_effect(name: str) -> Any:
215217# --------------------------------------------------------------------------- #
216218
217219
218- def test_validate_file_propagates_hard_validation_error ():
220+ def test_validate_file_propagates_hard_validation_error () -> None :
219221 """HardValidationError from validation is not wrapped and propagates."""
220222 mock_bucket = MagicMock ()
221223 mock_blob = MagicMock ()
@@ -246,7 +248,7 @@ def test_validate_file_propagates_hard_validation_error():
246248# --------------------------------------------------------------------------- #
247249
248250
249- def test_run_validation_and_get_normalized_df_returns_names_and_df ():
251+ def test_run_validation_and_get_normalized_df_returns_names_and_df () -> None :
250252 """Returns (inferred_schema_names, normalized_df) when validation succeeds."""
251253 mock_blob = MagicMock ()
252254 mock_file = io .StringIO ("foo_col,bar_col\n 1,a\n 2,b\n " )
@@ -274,7 +276,9 @@ def test_run_validation_and_get_normalized_df_returns_names_and_df():
274276 assert list (df .columns ) == ["x" ]
275277
276278
277- def test_run_validation_and_get_normalized_df_propagates_hard_validation_error ():
279+ def test_run_validation_and_get_normalized_df_propagates_hard_validation_error () -> (
280+ None
281+ ):
278282 """HardValidationError is re-raised without wrapping."""
279283 mock_blob = MagicMock ()
280284 mock_file = io .StringIO ("bad" )
@@ -291,12 +295,48 @@ def test_run_validation_and_get_normalized_df_propagates_hard_validation_error()
291295 )
292296
293297
298+ def test_run_validation_and_get_normalized_df_propagates_value_error () -> None :
299+ """ValueError from validate_file_reader (e.g. encoding) is re-raised."""
300+ mock_blob = MagicMock ()
301+ mock_file = io .StringIO ("data" )
302+ mock_blob .open .return_value .__enter__ = lambda self : mock_file
303+ mock_blob .open .return_value .__exit__ = lambda self , * args : None
304+
305+ control = StorageControl ()
306+ with patch (
307+ "src.webapp.gcsutil.validate_file_reader" ,
308+ side_effect = ValueError ("Invalid file format" ),
309+ ):
310+ with pytest .raises (ValueError , match = "Invalid file format" ):
311+ control ._run_validation_and_get_normalized_df (
312+ mock_blob , "f.csv" , ["STUDENT" ], {}, None , "pdp" , None
313+ )
314+
315+
316+ def test_run_validation_and_get_normalized_df_propagates_unicode_error () -> None :
317+ """UnicodeError from validate_file_reader (e.g. decode) is re-raised."""
318+ mock_blob = MagicMock ()
319+ mock_file = io .StringIO ("data" )
320+ mock_blob .open .return_value .__enter__ = lambda self : mock_file
321+ mock_blob .open .return_value .__exit__ = lambda self , * args : None
322+
323+ control = StorageControl ()
324+ with patch (
325+ "src.webapp.gcsutil.validate_file_reader" ,
326+ side_effect = UnicodeDecodeError ("utf-8" , b"x" , 0 , 1 , "invalid" ),
327+ ):
328+ with pytest .raises (UnicodeDecodeError ):
329+ control ._run_validation_and_get_normalized_df (
330+ mock_blob , "f.csv" , ["STUDENT" ], {}, None , "pdp" , None
331+ )
332+
333+
294334# --------------------------------------------------------------------------- #
295335# _write_dataframe_to_gcs_as_csv
296336# --------------------------------------------------------------------------- #
297337
298338
299- def test_write_dataframe_to_gcs_as_csv_uploads_utf8_csv ():
339+ def test_write_dataframe_to_gcs_as_csv_uploads_utf8_csv () -> None :
300340 """Writes DataFrame as UTF-8 CSV with correct content_type."""
301341 mock_blob = MagicMock ()
302342 mock_bucket = MagicMock ()
0 commit comments