88from pyntc .devices import IOSDevice
99from pyntc .devices import ios_device as ios_module
1010from pyntc .devices .base_device import RollbackError
11+ from pyntc .errors import NotEnoughFreeSpaceError
1112from pyntc .utils .models import FileCopyModel
1213
1314from .device_mocks .ios import send_command , send_command_expect
@@ -140,9 +141,13 @@ def test_file_copy_remote_exists_not(self, mock_ft):
140141
141142 @mock .patch .object (IOSDevice , "get_local_checksum" )
142143 @mock .patch .object (IOSDevice , "verify_file" )
144+ @mock .patch .object (IOSDevice , "_check_free_space" )
145+ @mock .patch ("pyntc.devices.ios_device.os.path.getsize" , return_value = 1024 )
143146 @mock .patch ("pyntc.devices.ios_device.FileTransfer" , autospec = True )
144147 @mock .patch .object (IOSDevice , "open" )
145- def test_file_copy (self , mock_open , mock_ft , mock_verify_file , mock_get_local_checksum ):
148+ def test_file_copy (
149+ self , mock_open , mock_ft , _mock_getsize , _mock_check_space , mock_verify_file , mock_get_local_checksum
150+ ):
146151 self .device .native .send_command .side_effect = None
147152 self .device .native .send_command .return_value = "flash: /dev/null"
148153
@@ -159,9 +164,13 @@ def test_file_copy(self, mock_open, mock_ft, mock_verify_file, mock_get_local_ch
159164
160165 @mock .patch .object (IOSDevice , "get_local_checksum" )
161166 @mock .patch .object (IOSDevice , "verify_file" )
167+ @mock .patch .object (IOSDevice , "_check_free_space" )
168+ @mock .patch ("pyntc.devices.ios_device.os.path.getsize" , return_value = 1024 )
162169 @mock .patch ("pyntc.devices.ios_device.FileTransfer" , autospec = True )
163170 @mock .patch .object (IOSDevice , "open" )
164- def test_file_copy_different_dest (self , mock_open , mock_ft , mock_verify_file , mock_get_local_checksum ):
171+ def test_file_copy_different_dest (
172+ self , mock_open , mock_ft , _mock_getsize , _mock_check_space , mock_verify_file , mock_get_local_checksum
173+ ):
165174 self .device .native .send_command_timing .side_effect = None
166175 self .device .native .send_command .return_value = "flash: /dev/null"
167176 mock_ft_instance = mock_ft .return_value
@@ -177,9 +186,13 @@ def test_file_copy_different_dest(self, mock_open, mock_ft, mock_verify_file, mo
177186
178187 @mock .patch .object (IOSDevice , "get_local_checksum" )
179188 @mock .patch .object (IOSDevice , "verify_file" )
189+ @mock .patch .object (IOSDevice , "_check_free_space" )
190+ @mock .patch ("pyntc.devices.ios_device.os.path.getsize" , return_value = 1024 )
180191 @mock .patch ("pyntc.devices.ios_device.FileTransfer" , autospec = True )
181192 @mock .patch .object (IOSDevice , "open" )
182- def test_file_copy_fail (self , mock_open , mock_ft , mock_verify_file , mock_get_local_checksum ):
193+ def test_file_copy_fail (
194+ self , mock_open , mock_ft , _mock_getsize , _mock_check_space , mock_verify_file , mock_get_local_checksum
195+ ):
183196 self .device .native .send_command_timing .side_effect = None
184197 self .device .native .send_command .return_value = "flash: /dev/null"
185198 mock_ft_instance = mock_ft .return_value
@@ -193,9 +206,13 @@ def test_file_copy_fail(self, mock_open, mock_ft, mock_verify_file, mock_get_loc
193206
194207 @mock .patch .object (IOSDevice , "get_local_checksum" )
195208 @mock .patch .object (IOSDevice , "verify_file" )
209+ @mock .patch .object (IOSDevice , "_check_free_space" )
210+ @mock .patch ("pyntc.devices.ios_device.os.path.getsize" , return_value = 1024 )
196211 @mock .patch ("pyntc.devices.ios_device.FileTransfer" , autospec = True )
197212 @mock .patch .object (IOSDevice , "open" )
198- def test_file_copy_socket_closed_good_md5 (self , mock_open , mock_ft , mock_verify_file , mock_get_local_checksum ):
213+ def test_file_copy_socket_closed_good_md5 (
214+ self , mock_open , mock_ft , _mock_getsize , _mock_check_space , mock_verify_file , mock_get_local_checksum
215+ ):
199216 self .device .native .send_command_timing .side_effect = None
200217 self .device .native .send_command .return_value = "flash: /dev/null"
201218 mock_ft_instance = mock_ft .return_value
@@ -214,9 +231,13 @@ def test_file_copy_socket_closed_good_md5(self, mock_open, mock_ft, mock_verify_
214231
215232 @mock .patch .object (IOSDevice , "get_local_checksum" )
216233 @mock .patch .object (IOSDevice , "verify_file" )
234+ @mock .patch .object (IOSDevice , "_check_free_space" )
235+ @mock .patch ("pyntc.devices.ios_device.os.path.getsize" , return_value = 1024 )
217236 @mock .patch ("pyntc.devices.ios_device.FileTransfer" , autospec = True )
218237 @mock .patch .object (IOSDevice , "open" )
219- def test_file_copy_fail_socket_closed_bad_md5 (self , mock_open , mock_ft , mock_verify_file , mock_get_local_checksum ):
238+ def test_file_copy_fail_socket_closed_bad_md5 (
239+ self , mock_open , mock_ft , _mock_getsize , _mock_check_space , mock_verify_file , mock_get_local_checksum
240+ ):
220241 self .device .native .send_command_timing .side_effect = None
221242 self .device .native .send_command .return_value = "flash: /dev/null"
222243 mock_ft_instance = mock_ft .return_value
@@ -479,6 +500,15 @@ def test_get_remote_checksum(self):
479500 with self .assertRaises (ValueError ):
480501 self .device .get_remote_checksum ("file.txt" , hashing_algorithm = "invalid_algo" , file_system = "flash:" )
481502
503+ def test_get_free_space (self ):
504+ self .device .native .send_command .return_value = "16777216 bytes total (1592488 bytes free)"
505+ self .assertEqual (self .device ._get_free_space (file_system = "flash:" ), 1592488 )
506+
507+ def test_get_free_space_raises_when_unparsable (self ):
508+ self .device .native .send_command .return_value = "Directory of flash:/\n Unable to read totals"
509+ with self .assertRaises (ios_module .CommandError ):
510+ self .device ._get_free_space (file_system = "flash:" )
511+
482512 @mock .patch .object (IOSDevice , "verify_file" )
483513 def test_remote_file_copy_success (self , mock_verify ):
484514 # Setup file model
@@ -594,6 +624,49 @@ def test_remote_file_copy_failure_on_error_output(self, mock_verify):
594624 with self .assertRaises (FileTransferError ):
595625 self .device .remote_file_copy (src )
596626
627+ @mock .patch .object (IOSDevice , "verify_file" )
628+ def test_remote_file_copy_raises_not_enough_free_space (self , mock_verify ):
629+ src = FileCopyModel (
630+ download_url = "http://1.1.1.1/test.bin" ,
631+ checksum = "12345" ,
632+ file_name = "test.bin" ,
633+ hashing_algorithm = "md5" ,
634+ file_size = 2 ,
635+ file_size_unit = "gigabytes" ,
636+ )
637+ mock_verify .return_value = False
638+ self .device .native .send_command .return_value = "16777216 bytes total (1592488 bytes free)"
639+
640+ with self .assertRaises (NotEnoughFreeSpaceError ):
641+ self .device .remote_file_copy (src , file_system = "flash:" )
642+
643+ assert not any (
644+ "copy " in str (call .kwargs .get ("command_string" , call .args [0 ] if call .args else "" ))
645+ for call in self .device .native .send_command .call_args_list
646+ )
647+
648+ @mock .patch .object (IOSDevice , "verify_file" )
649+ @mock .patch .object (IOSDevice , "_check_free_space" )
650+ def test_remote_file_copy_skips_space_check_when_file_size_omitted (self , mock_check_free_space , mock_verify ):
651+ src = FileCopyModel (
652+ download_url = "sftp://1.1.1.1/test.bin" ,
653+ checksum = "12345" ,
654+ file_name = "test.bin" ,
655+ hashing_algorithm = "md5" ,
656+ timeout = 300 ,
657+ )
658+ mock_verify .side_effect = [False , True ]
659+ self .device .native .send_command .side_effect = [
660+ "Address or name of remote host [1.1.1.1]?" ,
661+ "123456 bytes copied in 10.2 secs. Copy complete." ,
662+ ]
663+ self .device .native .find_prompt .return_value = "Router#"
664+
665+ self .device .remote_file_copy (src , file_system = "flash:" )
666+
667+ mock_check_free_space .assert_not_called ()
668+ self .device .native .send_command .assert_called ()
669+
597670
598671if __name__ == "__main__" :
599672 unittest .main ()
0 commit comments