1+ #!/usr/bin/env python3
2+ # -*- coding: utf-8 -*- #
3+ #
4+ # Copyright 2025 Wrike Inc.
5+ #
6+
7+ import pytest
8+ import yaml
9+ import json
10+ import sys
11+ import tempfile
12+ from pathlib import Path
13+ from unittest .mock import patch , mock_open , MagicMock
14+ from io import StringIO
15+
16+ # Add src to path for imports
17+ sys .path .insert (0 , str (Path (__file__ ).parent .parent / "src" ))
18+
19+ from promabbix .core .fs_utils import DataLoader , DataSaver
20+
21+
22+ class TestDataLoaderCoverageImprovements :
23+ """Test additional scenarios for DataLoader to improve coverage."""
24+
25+ def test_load_from_file_import_error_fallback (self , temp_directory ):
26+ """Test YAML loading with CLoader import error fallback."""
27+ test_file = temp_directory / "test.yaml"
28+ test_data = {"test" : "data" , "number" : 123 }
29+ test_file .write_text (yaml .dump (test_data ))
30+
31+ # Mock the import to simulate CLoader not available
32+ with patch ('promabbix.core.fs_utils.CLoader' , side_effect = ImportError ):
33+ # Reload the module to test the import fallback
34+ import importlib
35+ import promabbix .core .fs_utils
36+ importlib .reload (promabbix .core .fs_utils )
37+
38+ loader = DataLoader ()
39+ result = loader .load_from_file (str (test_file ))
40+ assert result == test_data
41+
42+ def test_load_from_stdin_with_yaml_data (self ):
43+ """Test loading YAML data from stdin."""
44+ yaml_data = {"test" : "data" , "yaml" : True }
45+ yaml_content = yaml .dump (yaml_data )
46+
47+ with patch ('sys.stdin' , StringIO (yaml_content )):
48+ loader = DataLoader ()
49+ result = loader .load_from_stdin ()
50+ assert result == yaml_data
51+
52+ def test_load_from_stdin_with_json_data (self ):
53+ """Test loading JSON data from stdin."""
54+ json_data = {"test" : "data" , "json" : True }
55+ json_content = json .dumps (json_data )
56+
57+ with patch ('sys.stdin' , StringIO (json_content )):
58+ loader = DataLoader ()
59+ result = loader .load_from_stdin ()
60+ assert result == json_data
61+
62+ def test_load_from_stdin_yaml_error_fallback_to_json (self ):
63+ """Test stdin loading with YAML error falling back to JSON."""
64+ # This is valid JSON but invalid YAML due to quoted keys
65+ json_content = '{"test": "data", "json": true}'
66+
67+ with patch ('sys.stdin' , StringIO (json_content )):
68+ loader = DataLoader ()
69+ result = loader .load_from_stdin ()
70+ assert result == {"test" : "data" , "json" : True }
71+
72+ def test_load_from_stdin_both_yaml_and_json_error (self ):
73+ """Test stdin loading when both YAML and JSON parsing fail."""
74+ invalid_content = "invalid: yaml: and json content: [unclosed"
75+
76+ with patch ('sys.stdin' , StringIO (invalid_content )):
77+ loader = DataLoader ()
78+ with pytest .raises (ValueError ) as excinfo :
79+ loader .load_from_stdin ()
80+ assert "Could not parse data from STDIN" in str (excinfo .value )
81+
82+ def test_load_from_file_permission_error_different_exception (self , temp_directory ):
83+ """Test file loading with different permission error types."""
84+ test_file = temp_directory / "test.yaml"
85+ test_file .write_text ("test: data" )
86+
87+ # Mock to raise PermissionError
88+ with patch ('builtins.open' , side_effect = PermissionError ("Access denied" )):
89+ loader = DataLoader ()
90+ with pytest .raises (ValueError ) as excinfo :
91+ loader .load_from_file (str (test_file ))
92+ assert "Permission denied to read file" in str (excinfo .value )
93+
94+
95+ class TestDataSaverCoverageImprovements :
96+ """Test additional scenarios for DataSaver to improve coverage."""
97+
98+ def test_save_to_stdout_string_content (self ):
99+ """Test saving string content to stdout."""
100+ test_content = "This is a test string"
101+
102+ with patch ('sys.stdout' , new_callable = StringIO ) as mock_stdout :
103+ saver = DataSaver ()
104+ saver .save_to_stdout (test_content )
105+ assert mock_stdout .getvalue () == test_content
106+
107+ def test_save_to_stdout_dict_content (self ):
108+ """Test saving dict content to stdout (should serialize to JSON)."""
109+ test_data = {"test" : "data" , "number" : 123 }
110+
111+ with patch ('sys.stdout' , new_callable = StringIO ) as mock_stdout :
112+ saver = DataSaver ()
113+ saver .save_to_stdout (test_data )
114+ output = mock_stdout .getvalue ()
115+ # Should be valid JSON
116+ parsed = json .loads (output )
117+ assert parsed == test_data
118+
119+ def test_save_to_file_directory_creation_failure (self , temp_directory ):
120+ """Test save to file when directory creation fails."""
121+ test_file = temp_directory / "subdir" / "test.json"
122+ test_data = {"test" : "data" }
123+
124+ # Mock Path.mkdir to raise PermissionError
125+ with patch .object (Path , 'mkdir' , side_effect = PermissionError ("Permission denied" )):
126+ saver = DataSaver ()
127+ with pytest .raises (ValueError ) as excinfo :
128+ saver .save_to_file (test_data , str (test_file ))
129+ assert "Failed to create directory" in str (excinfo .value )
130+
131+ def test_save_to_file_write_permission_error (self , temp_directory ):
132+ """Test save to file when write operation fails due to permissions."""
133+ test_file = temp_directory / "test.json"
134+ test_data = {"test" : "data" }
135+
136+ # Mock open to raise PermissionError during write
137+ with patch ('builtins.open' , side_effect = PermissionError ("Write permission denied" )):
138+ saver = DataSaver ()
139+ with pytest .raises (ValueError ) as excinfo :
140+ saver .save_to_file (test_data , str (test_file ))
141+ assert "Failed to write to file" in str (excinfo .value )
142+
143+ def test_save_to_file_json_encoding_error (self , temp_directory ):
144+ """Test save to file when JSON encoding fails."""
145+ test_file = temp_directory / "test.json"
146+ # Create an object that can't be JSON serialized
147+ test_data = {"test" : set ([1 , 2 , 3 ])} # sets are not JSON serializable
148+
149+ saver = DataSaver ()
150+ with pytest .raises (ValueError ) as excinfo :
151+ saver .save_to_file (test_data , str (test_file ))
152+ assert "Failed to serialize data to JSON" in str (excinfo .value )
153+
154+ def test_save_to_file_yaml_encoding_error (self , temp_directory ):
155+ """Test save to file when YAML encoding fails."""
156+ test_file = temp_directory / "test.yaml"
157+
158+ # Mock yaml.dump to raise an exception
159+ with patch ('yaml.dump' , side_effect = yaml .YAMLError ("YAML encoding failed" )):
160+ saver = DataSaver ()
161+ test_data = {"test" : "data" }
162+ with pytest .raises (ValueError ) as excinfo :
163+ saver .save_to_file (test_data , str (test_file ))
164+ assert "Failed to serialize data to YAML" in str (excinfo .value )
165+
166+ def test_save_text_to_file_encoding_error (self , temp_directory ):
167+ """Test save text to file when encoding fails."""
168+ test_file = temp_directory / "test.txt"
169+ test_content = "test content"
170+
171+ # Mock open to raise UnicodeEncodeError
172+ with patch ('builtins.open' , side_effect = UnicodeEncodeError ('utf-8' , b'' , 0 , 1 , "encoding error" )):
173+ saver = DataSaver ()
174+ with pytest .raises (ValueError ) as excinfo :
175+ saver .save_text_to_file (test_content , str (test_file ))
176+ assert "Failed to write text to file" in str (excinfo .value )
177+
178+ def test_save_to_file_dict_unknown_extension (self , temp_directory ):
179+ """Test saving dict to file with unknown extension defaults to YAML."""
180+ test_file = temp_directory / "test.unknown"
181+ test_data = {"test" : "data" , "number" : 123 }
182+
183+ saver = DataSaver ()
184+ saver .save_to_file (test_data , str (test_file ))
185+
186+ # Should default to YAML format
187+ with open (test_file , 'r' ) as f :
188+ content = f .read ()
189+ # Check if it looks like YAML (contains : but not {})
190+ assert "test: data" in content
191+ assert "{" not in content
192+
193+ def test_save_to_file_list_unknown_extension (self , temp_directory ):
194+ """Test saving list to file with unknown extension defaults to YAML."""
195+ test_file = temp_directory / "test.unknown"
196+ test_data = [{"test" : "data" }, {"number" : 123 }]
197+
198+ saver = DataSaver ()
199+ saver .save_to_file (test_data , str (test_file ))
200+
201+ # Should default to YAML format
202+ with open (test_file , 'r' ) as f :
203+ content = f .read ()
204+ # Check if it looks like YAML (contains - for list items)
205+ assert "- test: data" in content or "test: data" in content
206+
207+ def test_save_to_file_string_unknown_extension (self , temp_directory ):
208+ """Test saving string to file with unknown extension saves as text."""
209+ test_file = temp_directory / "test.unknown"
210+ test_content = "This is plain text content"
211+
212+ saver = DataSaver ()
213+ saver .save_to_file (test_content , str (test_file ))
214+
215+ with open (test_file , 'r' ) as f :
216+ content = f .read ()
217+ assert content == test_content
218+
219+ def test_save_to_file_unsupported_type (self , temp_directory ):
220+ """Test saving unsupported data type to file."""
221+ test_file = temp_directory / "test.json"
222+ # Use a custom class that's not serializable
223+ class CustomClass :
224+ def __init__ (self ):
225+ self .value = "test"
226+
227+ test_data = CustomClass ()
228+
229+ saver = DataSaver ()
230+ with pytest .raises (ValueError ) as excinfo :
231+ saver .save_to_file (test_data , str (test_file ))
232+ assert "Unsupported data type" in str (excinfo .value )
233+
234+ def test_determine_format_from_extension_edge_cases (self ):
235+ """Test format determination with various file extensions."""
236+ saver = DataSaver ()
237+
238+ # Test case sensitivity
239+ assert saver ._determine_format_from_extension ("test.JSON" ) == "json"
240+ assert saver ._determine_format_from_extension ("test.YAML" ) == "yaml"
241+ assert saver ._determine_format_from_extension ("test.YML" ) == "yaml"
242+
243+ # Test with multiple dots
244+ assert saver ._determine_format_from_extension ("test.backup.json" ) == "json"
245+ assert saver ._determine_format_from_extension ("test.old.yaml" ) == "yaml"
246+
247+ # Test with no extension
248+ assert saver ._determine_format_from_extension ("test" ) == "yaml" # default
249+
250+ # Test with unknown extension
251+ assert saver ._determine_format_from_extension ("test.txt" ) == "yaml" # default
252+
253+ def test_save_to_file_complex_yaml_data (self , temp_directory ):
254+ """Test saving complex YAML data structures."""
255+ test_file = temp_directory / "complex.yaml"
256+ complex_data = {
257+ "simple_string" : "value" ,
258+ "multiline_string" : "line1\n line2\n line3" ,
259+ "list_of_dicts" : [
260+ {"name" : "item1" , "value" : 1 },
261+ {"name" : "item2" , "value" : 2 }
262+ ],
263+ "nested_dict" : {
264+ "level1" : {
265+ "level2" : {
266+ "level3" : "deep_value"
267+ }
268+ }
269+ },
270+ "special_chars" : "Special chars: åäö, 中文, 🚀"
271+ }
272+
273+ saver = DataSaver ()
274+ saver .save_to_file (complex_data , str (test_file ))
275+
276+ # Verify the data can be loaded back correctly
277+ loader = DataLoader ()
278+ loaded_data = loader .load_from_file (str (test_file ))
279+ assert loaded_data == complex_data
280+
281+ def test_save_to_file_complex_json_data (self , temp_directory ):
282+ """Test saving complex JSON data structures."""
283+ test_file = temp_directory / "complex.json"
284+ complex_data = {
285+ "string" : "value" ,
286+ "number" : 42 ,
287+ "float" : 3.14159 ,
288+ "boolean" : True ,
289+ "null_value" : None ,
290+ "list" : [1 , 2 , 3 , "four" , 5.0 ],
291+ "nested" : {
292+ "inner_list" : [{"a" : 1 }, {"b" : 2 }],
293+ "inner_dict" : {"x" : "y" }
294+ }
295+ }
296+
297+ saver = DataSaver ()
298+ saver .save_to_file (complex_data , str (test_file ))
299+
300+ # Verify the data can be loaded back correctly
301+ loader = DataLoader ()
302+ loaded_data = loader .load_from_file (str (test_file ))
303+ assert loaded_data == complex_data
304+
305+
306+ @pytest .fixture
307+ def temp_directory ():
308+ """Create a temporary directory for test files."""
309+ with tempfile .TemporaryDirectory () as tmpdir :
310+ yield Path (tmpdir )
0 commit comments