From 0d54fda27a58006866d00e31d3b293e6ccf599aa Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Fri, 27 Jun 2025 22:30:00 -0700 Subject: [PATCH] Use the Bandit security analysis tool when running tests. SECURITY: Add timeouts to all monitoring hooks to prevent hangs on network requests. SECURITY: For the "spot" check, use a more secure source of randomness when selecting paths to check. --- NEWS | 4 +++ borgmatic/actions/check.py | 2 +- borgmatic/borg/borg.py | 2 +- borgmatic/commands/borgmatic.py | 2 +- borgmatic/config/paths.py | 4 ++- borgmatic/execute.py | 6 ++-- borgmatic/hooks/command.py | 2 +- borgmatic/hooks/data_source/mongodb.py | 7 ++-- borgmatic/hooks/data_source/postgresql.py | 4 +-- borgmatic/hooks/data_source/sqlite.py | 4 ++- borgmatic/hooks/monitoring/cronhub.py | 3 +- borgmatic/hooks/monitoring/cronitor.py | 3 +- borgmatic/hooks/monitoring/healthchecks.py | 6 +++- borgmatic/hooks/monitoring/loki.py | 5 ++- borgmatic/hooks/monitoring/ntfy.py | 7 +++- borgmatic/hooks/monitoring/pagerduty.py | 5 ++- borgmatic/hooks/monitoring/pushover.py | 2 ++ borgmatic/hooks/monitoring/sentry.py | 5 ++- borgmatic/hooks/monitoring/uptime_kuma.py | 9 ++++- borgmatic/hooks/monitoring/zabbix.py | 5 ++- test_requirements.txt | 3 ++ tests/unit/actions/test_check.py | 36 +++++++++---------- tests/unit/hooks/monitoring/test_cronhub.py | 10 +++--- tests/unit/hooks/monitoring/test_cronitor.py | 16 ++++----- .../hooks/monitoring/test_healthchecks.py | 24 +++++++------ tests/unit/hooks/monitoring/test_ntfy.py | 12 ++++++- tests/unit/hooks/monitoring/test_pushover.py | 10 ++++++ tests/unit/hooks/monitoring/test_sentry.py | 6 ++-- .../unit/hooks/monitoring/test_uptimekuma.py | 16 ++++----- tests/unit/hooks/monitoring/test_zabbix.py | 10 +++--- 30 files changed, 149 insertions(+), 81 deletions(-) diff --git a/NEWS b/NEWS index 78a1a5c4..81f27d9d 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ * #1099: Clarify documentation on command hooks order of execution. * #1100: Fix a bug in which "borg --version" failing isn't considered a "fail" state in a command hook. + * Use the Bandit security analysis tool when running tests. + * SECURITY: Add timeouts to all monitoring hooks to prevent hangs on network requests. + * SECURITY: For the "spot" check, use a more secure source of randomness when selecting paths to + check. 2.0.6 * #1068: Fix a warning from LVM about leaked file descriptors. diff --git a/borgmatic/actions/check.py b/borgmatic/actions/check.py index 055eb672..edfe2c78 100644 --- a/borgmatic/actions/check.py +++ b/borgmatic/actions/check.py @@ -486,7 +486,7 @@ def compare_spot_check_hashes( sample_count = max( int(len(source_paths) * (min(spot_check_config['data_sample_percentage'], 100) / 100)), 1 ) - source_sample_paths = tuple(random.sample(source_paths, sample_count)) + source_sample_paths = tuple(random.SystemRandom().sample(source_paths, sample_count)) working_directory = borgmatic.config.paths.get_working_directory(config) hashable_source_sample_path = { source_path diff --git a/borgmatic/borg/borg.py b/borgmatic/borg/borg.py index a2afd430..3c8408bf 100644 --- a/borgmatic/borg/borg.py +++ b/borgmatic/borg/borg.py @@ -60,7 +60,7 @@ def run_arbitrary_borg( return execute_command( tuple(shlex.quote(part) for part in full_command), output_file=DO_NOT_CAPTURE, - shell=True, + shell=True, # noqa: S604 environment=dict( (environment.make_environment(config) or {}), **{ diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 1e3b7c2d..8fcab132 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -750,7 +750,7 @@ def log_error_records( except (ValueError, OSError) as error: yield log_record(levelno=levelno, levelname=level_name, msg=str(message)) yield log_record(levelno=levelno, levelname=level_name, msg=str(error)) - except: # noqa: E722 + except: # noqa: E722, S110 # Raising above only as a means of determining the error type. Swallow the exception here # because we don't want the exception to propagate out of this function. pass diff --git a/borgmatic/config/paths.py b/borgmatic/config/paths.py index 48dff228..fd15cdcd 100644 --- a/borgmatic/config/paths.py +++ b/borgmatic/config/paths.py @@ -103,7 +103,9 @@ class Runtime_directory: self.temporary_directory = None else: - base_directory = os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' + base_directory = ( + os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' # noqa: S108 + ) if not base_directory.startswith(os.path.sep): raise ValueError('The temporary directory must be an absolute path') diff --git a/borgmatic/execute.py b/borgmatic/execute.py index 6216d383..4376217c 100644 --- a/borgmatic/execute.py +++ b/borgmatic/execute.py @@ -313,7 +313,7 @@ def execute_command( stdin=input_file, stdout=None if do_not_capture else (output_file or subprocess.PIPE), stderr=None if do_not_capture else (subprocess.PIPE if output_file else subprocess.STDOUT), - shell=shell, + shell=shell, # noqa: S602 env=environment, cwd=working_directory, close_fds=close_fds, @@ -363,7 +363,7 @@ def execute_command_and_capture_output( command, stdin=input_file, stderr=subprocess.STDOUT if capture_stderr else None, - shell=shell, + shell=shell, # noqa: S602 env=environment, cwd=working_directory, close_fds=close_fds, @@ -423,7 +423,7 @@ def execute_command_with_processes( stderr=( None if do_not_capture else (subprocess.PIPE if output_file else subprocess.STDOUT) ), - shell=shell, + shell=shell, # noqa: S602 env=environment, cwd=working_directory, close_fds=close_fds, diff --git a/borgmatic/hooks/command.py b/borgmatic/hooks/command.py index 3195a4a5..080724bc 100644 --- a/borgmatic/hooks/command.py +++ b/borgmatic/hooks/command.py @@ -144,7 +144,7 @@ def execute_hooks(command_hooks, umask, working_directory, dry_run, **context): output_log_level=( logging.ERROR if hook_config.get('after') == 'error' else logging.ANSWER ), - shell=True, + shell=True, # noqa: S604 environment=make_environment(os.environ), working_directory=working_directory, ) diff --git a/borgmatic/hooks/data_source/mongodb.py b/borgmatic/hooks/data_source/mongodb.py index 74ae15f3..23782369 100644 --- a/borgmatic/hooks/data_source/mongodb.py +++ b/borgmatic/hooks/data_source/mongodb.py @@ -74,11 +74,12 @@ def dump_data_sources( if dump_format == 'directory': dump.create_parent_directory_for_dump(dump_filename) - execute_command(command, shell=True) + execute_command(command, shell=True) # noqa: S604 else: dump.create_named_pipe_for_dump(dump_filename) - processes.append(execute_command(command, shell=True, run_to_completion=False)) - + processes.append( + execute_command(command, shell=True, run_to_completion=False) # noqa: S604 + ) if not dry_run: patterns.append( borgmatic.borg.pattern.Pattern( diff --git a/borgmatic/hooks/data_source/postgresql.py b/borgmatic/hooks/data_source/postgresql.py index 4f470c52..d4445d74 100644 --- a/borgmatic/hooks/data_source/postgresql.py +++ b/borgmatic/hooks/data_source/postgresql.py @@ -224,7 +224,7 @@ def dump_data_sources( dump.create_parent_directory_for_dump(dump_filename) execute_command( command, - shell=True, + shell=True, # noqa: S604 environment=environment, ) else: @@ -232,7 +232,7 @@ def dump_data_sources( processes.append( execute_command( command, - shell=True, + shell=True, # noqa: S604 environment=environment, run_to_completion=False, ) diff --git a/borgmatic/hooks/data_source/sqlite.py b/borgmatic/hooks/data_source/sqlite.py index 347ca5dc..8bf6edfe 100644 --- a/borgmatic/hooks/data_source/sqlite.py +++ b/borgmatic/hooks/data_source/sqlite.py @@ -88,7 +88,9 @@ def dump_data_sources( continue dump.create_named_pipe_for_dump(dump_filename) - processes.append(execute_command(command, shell=True, run_to_completion=False)) + processes.append( + execute_command(command, shell=True, run_to_completion=False) # noqa: S604 + ) if not dry_run: patterns.append( diff --git a/borgmatic/hooks/monitoring/cronhub.py b/borgmatic/hooks/monitoring/cronhub.py index 8d64957d..d2c2d79c 100644 --- a/borgmatic/hooks/monitoring/cronhub.py +++ b/borgmatic/hooks/monitoring/cronhub.py @@ -11,6 +11,7 @@ MONITOR_STATE_TO_CRONHUB = { monitor.State.FINISH: 'finish', monitor.State.FAIL: 'fail', } +TIMEOUT_SECONDS = 10 def initialize_monitor( @@ -45,7 +46,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.get(ping_url) + response = requests.get(ping_url, timeout=TIMEOUT_SECONDS) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/cronitor.py b/borgmatic/hooks/monitoring/cronitor.py index 2a176dc1..bb94fa31 100644 --- a/borgmatic/hooks/monitoring/cronitor.py +++ b/borgmatic/hooks/monitoring/cronitor.py @@ -11,6 +11,7 @@ MONITOR_STATE_TO_CRONITOR = { monitor.State.FINISH: 'complete', monitor.State.FAIL: 'fail', } +TIMEOUT_SECONDS = 10 def initialize_monitor( @@ -40,7 +41,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.get(ping_url) + response = requests.get(ping_url, timeout=TIMEOUT_SECONDS) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/healthchecks.py b/borgmatic/hooks/monitoring/healthchecks.py index 0b30d923..ec7c529d 100644 --- a/borgmatic/hooks/monitoring/healthchecks.py +++ b/borgmatic/hooks/monitoring/healthchecks.py @@ -17,6 +17,7 @@ MONITOR_STATE_TO_HEALTHCHECKS = { DEFAULT_PING_BODY_LIMIT_BYTES = 100000 HANDLER_IDENTIFIER = 'healthchecks' +TIMEOUT_SECONDS = 10 def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run): @@ -84,7 +85,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logging.getLogger('urllib3').setLevel(logging.ERROR) try: response = requests.post( - ping_url, data=payload.encode('utf-8'), verify=hook_config.get('verify_tls', True) + ping_url, + data=payload.encode('utf-8'), + verify=hook_config.get('verify_tls', True), + timeout=TIMEOUT_SECONDS, ) if not response.ok: response.raise_for_status() diff --git a/borgmatic/hooks/monitoring/loki.py b/borgmatic/hooks/monitoring/loki.py index 2c0405b1..7029d940 100644 --- a/borgmatic/hooks/monitoring/loki.py +++ b/borgmatic/hooks/monitoring/loki.py @@ -15,6 +15,7 @@ MONITOR_STATE_TO_LOKI = { monitor.State.FINISH: 'Finished', monitor.State.FAIL: 'Failed', } +TIMEOUT_SECONDS = 10 # Threshold at which logs get flushed to loki MAX_BUFFER_LINES = 100 @@ -69,7 +70,9 @@ class Loki_log_buffer: request_header = {'Content-Type': 'application/json'} try: - result = requests.post(self.url, headers=request_header, data=request_body, timeout=5) + result = requests.post( + self.url, headers=request_header, data=request_body, timeout=TIMEOUT_SECONDS + ) result.raise_for_status() except requests.RequestException: logger.warning('Failed to upload logs to loki') diff --git a/borgmatic/hooks/monitoring/ntfy.py b/borgmatic/hooks/monitoring/ntfy.py index 9c479cdd..ce17004a 100644 --- a/borgmatic/hooks/monitoring/ntfy.py +++ b/borgmatic/hooks/monitoring/ntfy.py @@ -7,6 +7,9 @@ import borgmatic.hooks.credential.parse logger = logging.getLogger(__name__) +TIMEOUT_SECONDS = 10 + + def initialize_monitor( ping_url, config, config_filename, monitoring_log_level, dry_run ): # pragma: no cover @@ -82,7 +85,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.post(f'{base_url}/{topic}', headers=headers, auth=auth) + response = requests.post( + f'{base_url}/{topic}', headers=headers, auth=auth, timeout=TIMEOUT_SECONDS + ) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/pagerduty.py b/borgmatic/hooks/monitoring/pagerduty.py index cd63f633..eb58aec0 100644 --- a/borgmatic/hooks/monitoring/pagerduty.py +++ b/borgmatic/hooks/monitoring/pagerduty.py @@ -14,6 +14,7 @@ logger = logging.getLogger(__name__) EVENTS_API_URL = 'https://events.pagerduty.com/v2/enqueue' DEFAULT_LOGS_PAYLOAD_LIMIT_BYTES = 10000 HANDLER_IDENTIFIER = 'pagerduty' +TIMEOUT_SECONDS = 10 def initialize_monitor(hook_config, config, config_filename, monitoring_log_level, dry_run): @@ -94,7 +95,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.post(EVENTS_API_URL, data=payload.encode('utf-8')) + response = requests.post( + EVENTS_API_URL, data=payload.encode('utf-8'), timeout=TIMEOUT_SECONDS + ) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/pushover.py b/borgmatic/hooks/monitoring/pushover.py index 86a1ac26..1d2711f7 100644 --- a/borgmatic/hooks/monitoring/pushover.py +++ b/borgmatic/hooks/monitoring/pushover.py @@ -8,6 +8,7 @@ logger = logging.getLogger(__name__) EMERGENCY_PRIORITY = 2 +TIMEOUT_SECONDS = 10 def initialize_monitor( @@ -79,6 +80,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev 'https://api.pushover.net/1/messages.json', headers={'Content-type': 'application/x-www-form-urlencoded'}, data=data, + timeout=TIMEOUT_SECONDS, ) if not response.ok: response.raise_for_status() diff --git a/borgmatic/hooks/monitoring/sentry.py b/borgmatic/hooks/monitoring/sentry.py index 857f85bc..e31dfe12 100644 --- a/borgmatic/hooks/monitoring/sentry.py +++ b/borgmatic/hooks/monitoring/sentry.py @@ -6,6 +6,9 @@ import requests logger = logging.getLogger(__name__) +TIMEOUT_SECONDS = 10 + + def initialize_monitor( ping_url, config, config_filename, monitoring_log_level, dry_run ): # pragma: no cover @@ -61,7 +64,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.post(f'{cron_url}?status={status}') + response = requests.post(f'{cron_url}?status={status}', timeout=TIMEOUT_SECONDS) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/uptime_kuma.py b/borgmatic/hooks/monitoring/uptime_kuma.py index 68d5f29b..98c89ec4 100644 --- a/borgmatic/hooks/monitoring/uptime_kuma.py +++ b/borgmatic/hooks/monitoring/uptime_kuma.py @@ -5,6 +5,9 @@ import requests logger = logging.getLogger(__name__) +TIMEOUT_SECONDS = 10 + + def initialize_monitor( push_url, config, config_filename, monitoring_log_level, dry_run ): # pragma: no cover @@ -37,7 +40,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.get(f'{push_url}?{query}', verify=hook_config.get('verify_tls', True)) + response = requests.get( + f'{push_url}?{query}', + verify=hook_config.get('verify_tls', True), + timeout=TIMEOUT_SECONDS, + ) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/borgmatic/hooks/monitoring/zabbix.py b/borgmatic/hooks/monitoring/zabbix.py index 9235147f..b514f003 100644 --- a/borgmatic/hooks/monitoring/zabbix.py +++ b/borgmatic/hooks/monitoring/zabbix.py @@ -7,6 +7,9 @@ import borgmatic.hooks.credential.parse logger = logging.getLogger(__name__) +TIMEOUT_SECONDS = 10 + + def initialize_monitor( ping_url, config, config_filename, monitoring_log_level, dry_run ): # pragma: no cover @@ -28,7 +31,7 @@ def send_zabbix_request(server, headers, data): logger.debug(f'Sending a "{data["method"]}" request to the Zabbix server') try: - response = requests.post(server, headers=headers, json=data) + response = requests.post(server, headers=headers, json=data, timeout=TIMEOUT_SECONDS) if not response.ok: response.raise_for_status() diff --git a/test_requirements.txt b/test_requirements.txt index 64bca041..efd1f828 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,6 +1,7 @@ appdirs==1.4.4 apprise==1.9.3 attrs==25.3.0 +bandit==1.8.5 black==25.1.0 certifi==2025.6.15 chardet==5.2.0 @@ -8,6 +9,7 @@ click==8.2.1 codespell==2.4.1 coverage==7.9.1 flake8==7.3.0 +flake8-bandit==4.1.1 flake8-quotes==3.4.0 flake8-use-fstring==1.4 flake8-variables-names==0.0.6 @@ -19,6 +21,7 @@ Markdown==3.8.2 mccabe==0.7.0 packaging==25.0 pathspec==0.12.1 +pbr==6.1.1 pluggy==1.6.0 py==1.11.0 pycodestyle==2.14.0 diff --git a/tests/unit/actions/test_check.py b/tests/unit/actions/test_check.py index 6c49b782..e63bfdab 100644 --- a/tests/unit/actions/test_check.py +++ b/tests/unit/actions/test_check.py @@ -938,8 +938,8 @@ def test_collect_spot_check_source_paths_uses_working_directory(): def test_compare_spot_check_hashes_returns_paths_having_failing_hashes(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -979,8 +979,8 @@ def test_compare_spot_check_hashes_returns_paths_having_failing_hashes(): def test_compare_spot_check_hashes_returns_relative_paths_having_failing_hashes(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1020,8 +1020,8 @@ def test_compare_spot_check_hashes_returns_relative_paths_having_failing_hashes( def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1061,8 +1061,8 @@ def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100(): def test_compare_spot_check_hashes_uses_xxh64sum_command_option(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1101,8 +1101,8 @@ def test_compare_spot_check_hashes_uses_xxh64sum_command_option(): def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_matching(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1138,8 +1138,8 @@ def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_ma def test_compare_spot_check_hashes_considers_symlink_path_as_not_matching(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1174,8 +1174,8 @@ def test_compare_spot_check_hashes_considers_symlink_path_as_not_matching(): def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1211,8 +1211,8 @@ def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching() def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in_chunks(): flexmock(module).SAMPLE_PATHS_SUBSET_COUNT = 2 - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( None, @@ -1257,8 +1257,8 @@ def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in def test_compare_spot_check_hashes_uses_working_directory_to_access_source_paths(): - flexmock(module.random).should_receive('sample').replace_with( - lambda population, count: population[:count] + flexmock(module.random).should_receive('SystemRandom').and_return( + flexmock(sample=lambda population, count: population[:count]) ) flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return( '/working/dir', diff --git a/tests/unit/hooks/monitoring/test_cronhub.py b/tests/unit/hooks/monitoring/test_cronhub.py index 5a9dd882..95edb021 100644 --- a/tests/unit/hooks/monitoring/test_cronhub.py +++ b/tests/unit/hooks/monitoring/test_cronhub.py @@ -6,7 +6,7 @@ from borgmatic.hooks.monitoring import cronhub as module def test_ping_monitor_rewrites_ping_url_for_start_state(): hook_config = {'ping_url': 'https://example.com/start/abcdef'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/start/abcdef' + 'https://example.com/start/abcdef', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -22,7 +22,7 @@ def test_ping_monitor_rewrites_ping_url_for_start_state(): def test_ping_monitor_rewrites_ping_url_and_state_for_start_state(): hook_config = {'ping_url': 'https://example.com/ping/abcdef'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/start/abcdef' + 'https://example.com/start/abcdef', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -38,7 +38,7 @@ def test_ping_monitor_rewrites_ping_url_and_state_for_start_state(): def test_ping_monitor_rewrites_ping_url_for_finish_state(): hook_config = {'ping_url': 'https://example.com/start/abcdef'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/finish/abcdef' + 'https://example.com/finish/abcdef', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -54,7 +54,7 @@ def test_ping_monitor_rewrites_ping_url_for_finish_state(): def test_ping_monitor_rewrites_ping_url_for_fail_state(): hook_config = {'ping_url': 'https://example.com/start/abcdef'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/fail/abcdef' + 'https://example.com/fail/abcdef', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -105,7 +105,7 @@ def test_ping_monitor_with_other_error_logs_warning(): module.requests.exceptions.RequestException ) flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/start/abcdef' + 'https://example.com/start/abcdef', timeout=int ).and_return(response) flexmock(module.logger).should_receive('warning').once() diff --git a/tests/unit/hooks/monitoring/test_cronitor.py b/tests/unit/hooks/monitoring/test_cronitor.py index ebf3b569..0fd81740 100644 --- a/tests/unit/hooks/monitoring/test_cronitor.py +++ b/tests/unit/hooks/monitoring/test_cronitor.py @@ -5,9 +5,9 @@ from borgmatic.hooks.monitoring import cronitor as module def test_ping_monitor_hits_ping_url_for_start_state(): hook_config = {'ping_url': 'https://example.com'} - flexmock(module.requests).should_receive('get').with_args('https://example.com/run').and_return( - flexmock(ok=True) - ) + flexmock(module.requests).should_receive('get').with_args( + 'https://example.com/run', timeout=int + ).and_return(flexmock(ok=True)) module.ping_monitor( hook_config, @@ -22,7 +22,7 @@ def test_ping_monitor_hits_ping_url_for_start_state(): def test_ping_monitor_hits_ping_url_for_finish_state(): hook_config = {'ping_url': 'https://example.com'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/complete' + 'https://example.com/complete', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -38,7 +38,7 @@ def test_ping_monitor_hits_ping_url_for_finish_state(): def test_ping_monitor_hits_ping_url_for_fail_state(): hook_config = {'ping_url': 'https://example.com'} flexmock(module.requests).should_receive('get').with_args( - 'https://example.com/fail' + 'https://example.com/fail', timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -88,9 +88,9 @@ def test_ping_monitor_with_other_error_logs_warning(): response.should_receive('raise_for_status').and_raise( module.requests.exceptions.RequestException ) - flexmock(module.requests).should_receive('get').with_args('https://example.com/run').and_return( - response - ) + flexmock(module.requests).should_receive('get').with_args( + 'https://example.com/run', timeout=int + ).and_return(response) flexmock(module.logger).should_receive('warning').once() module.ping_monitor( diff --git a/tests/unit/hooks/monitoring/test_healthchecks.py b/tests/unit/hooks/monitoring/test_healthchecks.py index 1ae02d56..2031a661 100644 --- a/tests/unit/hooks/monitoring/test_healthchecks.py +++ b/tests/unit/hooks/monitoring/test_healthchecks.py @@ -79,7 +79,7 @@ def test_ping_monitor_hits_ping_url_for_start_state(): ).never() hook_config = {'ping_url': 'https://example.com'} flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start', data=''.encode('utf-8'), verify=True + 'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -100,7 +100,7 @@ def test_ping_monitor_hits_ping_url_for_finish_state(): 'format_buffered_logs_for_payload' ).and_return(payload) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com', data=payload.encode('utf-8'), verify=True + 'https://example.com', data=payload.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -121,7 +121,7 @@ def test_ping_monitor_hits_ping_url_for_fail_state(): 'format_buffered_logs_for_payload' ).and_return(payload) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/fail', data=payload.encode('utf'), verify=True + 'https://example.com/fail', data=payload.encode('utf'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -142,7 +142,7 @@ def test_ping_monitor_hits_ping_url_for_log_state(): 'format_buffered_logs_for_payload' ).and_return(payload) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/log', data=payload.encode('utf'), verify=True + 'https://example.com/log', data=payload.encode('utf'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -166,6 +166,7 @@ def test_ping_monitor_with_ping_uuid_hits_corresponding_url(): f"https://hc-ping.com/{hook_config['ping_url']}", data=payload.encode('utf-8'), verify=True, + timeout=int, ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -186,7 +187,7 @@ def test_ping_monitor_skips_ssl_verification_when_verify_tls_false(): 'format_buffered_logs_for_payload' ).and_return(payload) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com', data=payload.encode('utf-8'), verify=False + 'https://example.com', data=payload.encode('utf-8'), verify=False, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -207,7 +208,7 @@ def test_ping_monitor_executes_ssl_verification_when_verify_tls_true(): 'format_buffered_logs_for_payload' ).and_return(payload) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com', data=payload.encode('utf-8'), verify=True + 'https://example.com', data=payload.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -260,7 +261,7 @@ def test_ping_monitor_hits_ping_url_when_states_matching(): ).never() hook_config = {'ping_url': 'https://example.com', 'states': ['start', 'finish']} flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start', data=''.encode('utf-8'), verify=True + 'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -279,7 +280,7 @@ def test_ping_monitor_adds_create_query_parameter_when_create_slug_true(): ).never() hook_config = {'ping_url': 'https://example.com', 'create_slug': True} flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True + 'https://example.com/start?create=1', data=''.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -298,7 +299,7 @@ def test_ping_monitor_does_not_add_create_query_parameter_when_create_slug_false ).never() hook_config = {'ping_url': 'https://example.com', 'create_slug': False} flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start', data=''.encode('utf-8'), verify=True + 'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -317,6 +318,7 @@ def test_ping_monitor_does_not_add_create_query_parameter_when_ping_url_is_uuid( f"https://hc-ping.com/{hook_config['ping_url']}", data=''.encode('utf-8'), verify=True, + timeout=int, ).and_return(flexmock(ok=True)) module.ping_monitor( @@ -352,7 +354,7 @@ def test_ping_monitor_with_connection_error_logs_warning(): ).never() hook_config = {'ping_url': 'https://example.com'} flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start', data=''.encode('utf-8'), verify=True + 'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int ).and_raise(module.requests.exceptions.ConnectionError) flexmock(module.logger).should_receive('warning').once() @@ -376,7 +378,7 @@ def test_ping_monitor_with_other_error_logs_warning(): module.requests.exceptions.RequestException ) flexmock(module.requests).should_receive('post').with_args( - 'https://example.com/start', data=''.encode('utf-8'), verify=True + 'https://example.com/start', data=''.encode('utf-8'), verify=True, timeout=int ).and_return(response) flexmock(module.logger).should_receive('warning').once() diff --git a/tests/unit/hooks/monitoring/test_ntfy.py b/tests/unit/hooks/monitoring/test_ntfy.py index d1a8c198..3e749e4b 100644 --- a/tests/unit/hooks/monitoring/test_ntfy.py +++ b/tests/unit/hooks/monitoring/test_ntfy.py @@ -43,6 +43,7 @@ def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -67,6 +68,7 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=module.requests.auth.HTTPBasicAuth('', 'abc123'), + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -93,6 +95,7 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=module.requests.auth.HTTPBasicAuth('', 'abc123'), + timeout=int, ).and_return(flexmock(ok=True)).once() flexmock(module.logger).should_receive('warning').once() @@ -119,6 +122,7 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=module.requests.auth.HTTPBasicAuth('testuser', 'fakepassword'), + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -140,6 +144,7 @@ def test_ping_monitor_with_password_but_no_username_warns(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_return(flexmock(ok=True)).once() flexmock(module.logger).should_receive('warning').once() @@ -162,6 +167,7 @@ def test_ping_monitor_with_username_but_no_password_warns(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_return(flexmock(ok=True)).once() flexmock(module.logger).should_receive('warning').once() @@ -218,6 +224,7 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail(): f'{custom_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -253,7 +260,7 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail(): 'resolve_credential' ).replace_with(lambda value, config: value) flexmock(module.requests).should_receive('post').with_args( - f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None + f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -275,6 +282,7 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START), auth=None, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -296,6 +304,7 @@ def test_ping_monitor_with_connection_error_logs_warning(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_raise(module.requests.exceptions.ConnectionError) flexmock(module.logger).should_receive('warning').once() @@ -340,6 +349,7 @@ def test_ping_monitor_with_other_error_logs_warning(): f'{default_base_url}/{topic}', headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL), auth=None, + timeout=int, ).and_return(response) flexmock(module.logger).should_receive('warning').once() diff --git a/tests/unit/hooks/monitoring/test_pushover.py b/tests/unit/hooks/monitoring/test_pushover.py index be97994b..139467b0 100644 --- a/tests/unit/hooks/monitoring/test_pushover.py +++ b/tests/unit/hooks/monitoring/test_pushover.py @@ -23,6 +23,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_ 'user': '983hfe0of902lkjfa2amanfgui', 'message': 'fail', }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -81,6 +82,7 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu 'user': '983hfe0of902lkjfa2amanfgui', 'message': 'start', }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -117,6 +119,7 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus 'user': '983hfe0of902lkjfa2amanfgui', 'message': 'custom start message', }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -155,6 +158,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency 'retry': 30, 'expire': 600, }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -193,6 +197,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency 'retry': 30, 'expire': 600, }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -231,6 +236,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency 'retry': 30, 'expire': 600, }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -328,6 +334,7 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example 'html': 1, 'ttl': 10, }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -399,6 +406,7 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_ 'url': 'https://ticketing-system.example.com/login', 'url_title': 'Login to ticketing system', }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -467,6 +475,7 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl 'url': 'https://ticketing-system.example.com/login', 'url_title': 'Login to ticketing system', }, + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -550,6 +559,7 @@ def test_ping_monitor_push_post_error_bails(): 'user': '983hfe0of902lkjfa2amanfgui', 'message': 'fail', }, + timeout=int, ).and_return(push_response).once() flexmock(module.logger).should_receive('warning').once() diff --git a/tests/unit/hooks/monitoring/test_sentry.py b/tests/unit/hooks/monitoring/test_sentry.py index 3d3b3dc1..bd959de1 100644 --- a/tests/unit/hooks/monitoring/test_sentry.py +++ b/tests/unit/hooks/monitoring/test_sentry.py @@ -29,7 +29,8 @@ def test_ping_monitor_constructs_cron_url_and_pings_it(state, configured_states, hook_config['states'] = configured_states flexmock(module.requests).should_receive('post').with_args( - f'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status={expected_status}' + f'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status={expected_status}', + timeout=int, ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -134,7 +135,8 @@ def test_ping_monitor_with_network_error_does_not_raise(): module.requests.exceptions.ConnectionError ) flexmock(module.requests).should_receive('post').with_args( - 'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status=in_progress' + 'https://o294220.ingest.us.sentry.io/api/203069/cron/test/5f80ec/?status=in_progress', + timeout=int, ).and_return(response).once() module.ping_monitor( diff --git a/tests/unit/hooks/monitoring/test_uptimekuma.py b/tests/unit/hooks/monitoring/test_uptimekuma.py index a9d00bee..adef5db7 100644 --- a/tests/unit/hooks/monitoring/test_uptimekuma.py +++ b/tests/unit/hooks/monitoring/test_uptimekuma.py @@ -10,7 +10,7 @@ CUSTOM_PUSH_URL = 'https://uptime.example.com/api/push/efgh5678' def test_ping_monitor_hits_default_uptimekuma_on_fail(): hook_config = {} flexmock(module.requests).should_receive('get').with_args( - f'{DEFAULT_PUSH_URL}?status=down&msg=fail', verify=True + f'{DEFAULT_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -26,7 +26,7 @@ def test_ping_monitor_hits_default_uptimekuma_on_fail(): def test_ping_monitor_hits_custom_uptimekuma_on_fail(): hook_config = {'push_url': CUSTOM_PUSH_URL} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True + f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -42,7 +42,7 @@ def test_ping_monitor_hits_custom_uptimekuma_on_fail(): def test_ping_monitor_custom_uptimekuma_on_start(): hook_config = {'push_url': CUSTOM_PUSH_URL} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=up&msg=start', verify=True + f'{CUSTOM_PUSH_URL}?status=up&msg=start', verify=True, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -58,7 +58,7 @@ def test_ping_monitor_custom_uptimekuma_on_start(): def test_ping_monitor_custom_uptimekuma_on_finish(): hook_config = {'push_url': CUSTOM_PUSH_URL} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=up&msg=finish', verify=True + f'{CUSTOM_PUSH_URL}?status=up&msg=finish', verify=True, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -116,7 +116,7 @@ def test_ping_monitor_does_not_hit_custom_uptimekuma_on_finish_dry_run(): def test_ping_monitor_with_connection_error_logs_warning(): hook_config = {'push_url': CUSTOM_PUSH_URL} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True + f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int ).and_raise(module.requests.exceptions.ConnectionError) flexmock(module.logger).should_receive('warning').once() @@ -137,7 +137,7 @@ def test_ping_monitor_with_other_error_logs_warning(): module.requests.exceptions.RequestException ) flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True + f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int ).and_return(response) flexmock(module.logger).should_receive('warning').once() @@ -168,7 +168,7 @@ def test_ping_monitor_with_invalid_run_state(): def test_ping_monitor_skips_ssl_verification_when_verify_tls_false(): hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': False} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=False + f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=False, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( @@ -184,7 +184,7 @@ def test_ping_monitor_skips_ssl_verification_when_verify_tls_false(): def test_ping_monitor_executes_ssl_verification_when_verify_tls_true(): hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': True} flexmock(module.requests).should_receive('get').with_args( - f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True + f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True, timeout=int ).and_return(flexmock(ok=True)).once() module.ping_monitor( diff --git a/tests/unit/hooks/monitoring/test_zabbix.py b/tests/unit/hooks/monitoring/test_zabbix.py index 0c2ea577..90627191 100644 --- a/tests/unit/hooks/monitoring/test_zabbix.py +++ b/tests/unit/hooks/monitoring/test_zabbix.py @@ -74,7 +74,7 @@ def test_send_zabbix_request_with_post_error_bails(): ) flexmock(module.requests).should_receive('post').with_args( - server, headers=headers, json=data + server, headers=headers, json=data, timeout=int ).and_return(response) assert module.send_zabbix_request(server, headers, data) is None @@ -89,7 +89,7 @@ def test_send_zabbix_request_with_invalid_json_response_bails(): response.should_receive('json').and_raise(module.requests.exceptions.JSONDecodeError) flexmock(module.requests).should_receive('post').with_args( - server, headers=headers, json=data + server, headers=headers, json=data, timeout=int ).and_return(response) assert module.send_zabbix_request(server, headers, data) is None @@ -103,7 +103,7 @@ def test_send_zabbix_request_with_success_returns_response_result(): response.should_receive('json').and_return({'result': {'foo': 'bar'}}) flexmock(module.requests).should_receive('post').with_args( - server, headers=headers, json=data + server, headers=headers, json=data, timeout=int ).and_return(response) assert module.send_zabbix_request(server, headers, data) == {'foo': 'bar'} @@ -117,7 +117,7 @@ def test_send_zabbix_request_with_success_passes_through_missing_result(): response.should_receive('json').and_return({}) flexmock(module.requests).should_receive('post').with_args( - server, headers=headers, json=data + server, headers=headers, json=data, timeout=int ).and_return(response) assert module.send_zabbix_request(server, headers, data) is None @@ -131,7 +131,7 @@ def test_send_zabbix_request_with_error_bails(): response.should_receive('json').and_return({'result': {'data': [{'error': 'oops'}]}}) flexmock(module.requests).should_receive('post').with_args( - server, headers=headers, json=data + server, headers=headers, json=data, timeout=int ).and_return(response) assert module.send_zabbix_request(server, headers, data) is None