Browse Source

Get unit tests passing again (#966).

Dan Helfman 3 months ago
parent
commit
775385e688

+ 1 - 1
borgmatic/borg/environment.py

@@ -40,7 +40,7 @@ def make_environment(config):
     for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
         value = config.get(option_name)
 
-        if option_name in CREDENTIAL_OPTIONS:
+        if option_name in CREDENTIAL_OPTIONS and value is not None:
             value = borgmatic.hooks.credential.tag.resolve_credential(value)
 
         if value is not None:

+ 2 - 1
borgmatic/config/schema.yaml

@@ -1646,7 +1646,8 @@ properties:
             token:
                 type: string
                 description: |
-                    Your application's API token. Supports the "!credential" tag.
+                    Your application's API token. Supports the "!credential"
+                    tag.
                 example: 7ms6TXHpTokTou2P6x4SodDeentHRa
             user:
                 type: string

+ 1 - 5
borgmatic/hooks/data_source/mariadb.py

@@ -289,11 +289,7 @@ def restore_data_source_dump(
         + (('--protocol', 'tcp') if hostname or port else ())
         + (('--user', username) if username else ())
     )
-    extra_environment = (
-        {'MYSQL_PWD': borgmatic.hooks.credential.tag.resolve_credential(password)}
-        if password
-        else None
-    )
+    extra_environment = {'MYSQL_PWD': password} if password else None
 
     logger.debug(f"Restoring MariaDB database {data_source['name']}{dry_run_label}")
     if dry_run:

+ 2 - 2
borgmatic/hooks/data_source/mongodb.py

@@ -236,9 +236,9 @@ def build_restore_command(extract_process, database, dump_filename, connection_p
     if port:
         command.extend(('--port', str(port)))
     if username:
-        command.extend(('--username', borgmatic.hooks.credential.tag.resolve_credential(username)))
+        command.extend(('--username', username))
     if password:
-        command.extend(('--password', borgmatic.hooks.credential.tag.resolve_credential(password)))
+        command.extend(('--password', password))
     if 'authentication_database' in database:
         command.extend(('--authenticationDatabase', database['authentication_database']))
     if 'restore_options' in database:

+ 1 - 5
borgmatic/hooks/data_source/mysql.py

@@ -288,11 +288,7 @@ def restore_data_source_dump(
         + (('--protocol', 'tcp') if hostname or port else ())
         + (('--user', username) if username else ())
     )
-    extra_environment = (
-        {'MYSQL_PWD': borgmatic.hooks.credential.tag.resolve_credential(password)}
-        if password
-        else None
-    )
+    extra_environment = {'MYSQL_PWD': password} if password else None
 
     logger.debug(f"Restoring MySQL database {data_source['name']}{dry_run_label}")
     if dry_run:

+ 1 - 1
borgmatic/hooks/monitoring/pagerduty.py

@@ -41,7 +41,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
         return
 
     try:
-        inegration_key = borgmatic.hooks.credential.tag.resolve_credential(
+        integration_key = borgmatic.hooks.credential.tag.resolve_credential(
             hook_config.get('integration_key')
         )
     except ValueError as error:

+ 0 - 30
tests/integration/config/test_validate.py

@@ -283,33 +283,3 @@ def test_parse_configuration_applies_normalization_after_environment_variable_in
     }
     assert config_paths == {'/tmp/config.yaml'}
     assert logs
-
-
-def test_parse_configuration_interpolates_credentials():
-    mock_config_and_schema(
-        '''
-        source_directories:
-            - /home
-
-        repositories:
-            - path: hostname.borg
-
-        encryption_passphrase: !credential systemd mycredential
-        '''
-    )
-    flexmock(os.environ).should_receive('get').replace_with(lambda variable_name: '/var')
-    credential_stream = io.StringIO('password')
-    credential_stream.name = '/var/mycredential'
-    builtins = flexmock(sys.modules['builtins'])
-    builtins.should_receive('open').with_args('/var/mycredential').and_return(credential_stream)
-
-    config, config_paths, logs = module.parse_configuration('/tmp/config.yaml', '/tmp/schema.yaml')
-
-    assert config == {
-        'source_directories': ['/home'],
-        'repositories': [{'path': 'hostname.borg'}],
-        'encryption_passphrase': 'password',
-        'bootstrap': {},
-    }
-    assert config_paths == {'/tmp/config.yaml'}
-    assert logs == []

+ 4 - 0
tests/unit/borg/test_environment.py

@@ -24,6 +24,10 @@ def test_make_environment_with_passphrase_should_set_environment():
     ).and_return(None)
     flexmock(module.os).should_receive('pipe').never()
     flexmock(module.os.environ).should_receive('get').and_return(None)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
+
     environment = module.make_environment({'encryption_passphrase': 'pass'})
 
     assert environment.get('BORG_PASSPHRASE') == 'pass'

+ 21 - 0
tests/unit/borg/test_passcommand.py

@@ -4,6 +4,7 @@ from borgmatic.borg import passcommand as module
 
 
 def test_run_passcommand_with_passphrase_configured_bails():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
 
     assert (
@@ -13,6 +14,7 @@ def test_run_passcommand_with_passphrase_configured_bails():
 
 
 def test_run_passcommand_without_passphrase_configured_executes_passcommand():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.execute).should_receive(
         'execute_command_and_capture_output'
     ).and_return('passphrase').once()
@@ -24,6 +26,7 @@ def test_run_passcommand_without_passphrase_configured_executes_passcommand():
 
 
 def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
     )
@@ -40,6 +43,7 @@ def test_get_passphrase_from_passcommand_with_configured_passcommand_runs_it():
 
 
 def test_get_passphrase_from_passcommand_with_configured_passphrase_and_passcommand_detects_passphrase():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
     )
@@ -56,6 +60,7 @@ def test_get_passphrase_from_passcommand_with_configured_passphrase_and_passcomm
 
 
 def test_get_passphrase_from_passcommand_with_configured_blank_passphrase_and_passcommand_detects_passphrase():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
     )
@@ -69,3 +74,19 @@ def test_get_passphrase_from_passcommand_with_configured_blank_passphrase_and_pa
         )
         is None
     )
+
+
+def test_run_passcommand_caches_passcommand_after_first_call():
+    module.run_passcommand.cache_clear()
+    flexmock(module.borgmatic.execute).should_receive(
+        'execute_command_and_capture_output'
+    ).and_return('passphrase').once()
+
+    assert (
+        module.run_passcommand('passcommand', passphrase_configured=False, working_directory=None)
+        == 'passphrase'
+    )
+    assert (
+        module.run_passcommand('passcommand', passphrase_configured=False, working_directory=None)
+        == 'passphrase'
+    )

+ 3 - 10
tests/unit/commands/test_borgmatic.py

@@ -1115,16 +1115,10 @@ def test_run_actions_runs_multiple_actions_in_argument_order():
 
 
 @pytest.mark.parametrize(
-    'resolve_env,resolve_credentials',
-    (
-        (True, True),
-        (False, True),
-        (True, False),
-    ),
+    'resolve_env',
+    ((True, False),),
 )
-def test_load_configurations_collects_parsed_configurations_and_logs(
-    resolve_env, resolve_credentials
-):
+def test_load_configurations_collects_parsed_configurations_and_logs(resolve_env):
     configuration = flexmock()
     other_configuration = flexmock()
     test_expected_logs = [flexmock(), flexmock()]
@@ -1137,7 +1131,6 @@ def test_load_configurations_collects_parsed_configurations_and_logs(
         module.load_configurations(
             ('test.yaml', 'other.yaml'),
             resolve_env=resolve_env,
-            resolve_credentials=resolve_credentials,
         )
     )
 

+ 0 - 135
tests/unit/config/test_credential.py

@@ -1,135 +0,0 @@
-import pytest
-from flexmock import flexmock
-
-from borgmatic.config import credential as module
-
-
-def test_resolve_credentials_passes_through_string_without_credential_tag():
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
-
-    assert (
-        module.resolve_credentials(config=flexmock(), item='!no credentials here')
-        == '!no credentials here'
-    )
-
-
-def test_resolve_credentials_passes_through_none():
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
-
-    assert module.resolve_credentials(config=flexmock(), item=None) is None
-
-
-def test_resolve_credentials_with_invalid_credential_tag_raises():
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
-
-    with pytest.raises(ValueError):
-        module.resolve_credentials(config=flexmock(), item='!credential systemd')
-
-
-def test_resolve_credentials_with_valid_credential_tag_loads_credential():
-    config = flexmock()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'mycredential',
-    ).and_return('result').once()
-
-    assert (
-        module.resolve_credentials(config=config, item='!credential systemd mycredential')
-        == 'result'
-    )
-
-
-def test_resolve_credentials_with_list_recurses_and_loads_credentials():
-    config = flexmock()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'mycredential',
-    ).and_return('result1').once()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'othercredential',
-    ).and_return('result2').once()
-
-    assert module.resolve_credentials(
-        config=config,
-        item=['!credential systemd mycredential', 'nope', '!credential systemd othercredential'],
-    ) == ['result1', 'nope', 'result2']
-
-
-def test_resolve_credentials_with_dict_recurses_and_loads_credentials():
-    config = flexmock()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'mycredential',
-    ).and_return('result1').once()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'othercredential',
-    ).and_return('result2').once()
-
-    assert module.resolve_credentials(
-        config=config,
-        item={
-            'a': '!credential systemd mycredential',
-            'b': 'nope',
-            'c': '!credential systemd othercredential',
-        },
-    ) == {'a': 'result1', 'b': 'nope', 'c': 'result2'}
-
-
-def test_resolve_credentials_with_list_of_dicts_recurses_and_loads_credentials():
-    config = flexmock()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'mycredential',
-    ).and_return('result1').once()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'othercredential',
-    ).and_return('result2').once()
-
-    assert module.resolve_credentials(
-        config=config,
-        item=[
-            {'a': '!credential systemd mycredential', 'b': 'nope'},
-            {'c': '!credential systemd othercredential'},
-        ],
-    ) == [{'a': 'result1', 'b': 'nope'}, {'c': 'result2'}]
-
-
-def test_resolve_credentials_with_dict_of_lists_recurses_and_loads_credentials():
-    config = flexmock()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'mycredential',
-    ).and_return('result1').once()
-    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
-        'load_credential',
-        config,
-        'systemd',
-        'othercredential',
-    ).and_return('result2').once()
-
-    assert module.resolve_credentials(
-        config=config,
-        item={
-            'a': ['!credential systemd mycredential', 'nope'],
-            'b': ['!credential systemd othercredential'],
-        },
-    ) == {'a': ['result1', 'nope'], 'b': ['result2']}

+ 51 - 0
tests/unit/hooks/credential/test_tag.py

@@ -0,0 +1,51 @@
+import pytest
+from flexmock import flexmock
+
+from borgmatic.hooks.credential import tag as module
+
+
+def test_resolve_credential_passes_through_string_without_credential_tag():
+    module.resolve_credential.cache_clear()
+    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
+
+    assert module.resolve_credential('!no credentials here') == '!no credentials here'
+
+
+def test_resolve_credential_passes_through_none():
+    module.resolve_credential.cache_clear()
+    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
+
+    assert module.resolve_credential(None) is None
+
+
+def test_resolve_credential_with_invalid_credential_tag_raises():
+    module.resolve_credential.cache_clear()
+    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
+
+    with pytest.raises(ValueError):
+        module.resolve_credential('!credential systemd')
+
+
+def test_resolve_credential_with_valid_credential_tag_loads_credential():
+    module.resolve_credential.cache_clear()
+    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
+        'load_credential',
+        {},
+        'systemd',
+        'mycredential',
+    ).and_return('result').once()
+
+    assert module.resolve_credential('!credential systemd mycredential') == 'result'
+
+
+def test_resolve_credential_caches_credential_after_first_call():
+    module.resolve_credential.cache_clear()
+    flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
+        'load_credential',
+        {},
+        'systemd',
+        'mycredential',
+    ).and_return('result').once()
+
+    assert module.resolve_credential('!credential systemd mycredential') == 'result'
+    assert module.resolve_credential('!credential systemd mycredential') == 'result'

+ 66 - 0
tests/unit/hooks/data_source/test_mariadb.py

@@ -25,6 +25,9 @@ def test_database_names_to_dump_bails_for_dry_run():
 
 def test_database_names_to_dump_queries_mariadb_for_database_names():
     extra_environment = flexmock()
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
@@ -50,6 +53,9 @@ def test_dump_data_sources_dumps_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     processes = [flexmock(), flexmock()]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
@@ -108,6 +117,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
     databases = [{'name': 'all'}]
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
     flexmock(module).should_receive('execute_dump_command').with_args(
         database={'name': 'all'},
@@ -132,6 +144,9 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
     databases = [{'name': 'all', 'format': 'sql'}]
     processes = [flexmock(), flexmock()]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
 
     for name, process in zip(('foo', 'bar'), processes):
@@ -199,6 +214,9 @@ def test_execute_dump_command_runs_mariadb_dump():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -231,6 +249,9 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -262,6 +283,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -300,6 +324,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -334,6 +361,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -367,6 +397,9 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -422,6 +455,9 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
 def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').never()
@@ -442,6 +478,9 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
 def test_dump_data_sources_errors_for_missing_all_databases():
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -461,6 +500,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -483,6 +525,9 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch'),
         processes=[extract_process],
@@ -511,6 +556,9 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--harder'),
         processes=[extract_process],
@@ -541,6 +589,9 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mariadb', '--batch', '--harder'),
         processes=[extract_process],
@@ -569,6 +620,9 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
     hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -606,6 +660,9 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
     hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--user', 'root'),
         processes=[extract_process],
@@ -644,6 +701,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -695,6 +755,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mariadb',
@@ -733,6 +796,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo'}]
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(

+ 36 - 0
tests/unit/hooks/data_source/test_mongodb.py

@@ -124,6 +124,9 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/foo'
     )
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -242,6 +245,9 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
 
 def test_build_dump_command_with_username_injection_attack_gets_escaped():
     database = {'name': 'test', 'username': 'bob; naughty-command'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
 
     command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
 
@@ -254,6 +260,9 @@ def test_restore_data_source_dump_runs_mongorestore():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop'],
         processes=[extract_process],
@@ -285,6 +294,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -330,6 +342,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -381,6 +396,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -436,6 +454,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -479,6 +500,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop', '--harder'],
         processes=[extract_process],
@@ -508,6 +532,9 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
             'mongorestore',
@@ -545,6 +572,9 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive'],
         processes=[extract_process],
@@ -573,6 +603,9 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(
@@ -596,6 +629,9 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--dir', '/dump/path', '--drop'],
         processes=[],

+ 60 - 0
tests/unit/hooks/data_source/test_mysql.py

@@ -16,6 +16,9 @@ def test_database_names_to_dump_passes_through_name():
 
 def test_database_names_to_dump_bails_for_dry_run():
     extra_environment = flexmock()
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
     names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
@@ -25,6 +28,9 @@ def test_database_names_to_dump_bails_for_dry_run():
 
 def test_database_names_to_dump_queries_mysql_for_database_names():
     extra_environment = flexmock()
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
     )
@@ -199,6 +208,9 @@ def test_execute_dump_command_runs_mysqldump():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -231,6 +243,9 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -262,6 +277,9 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -300,6 +318,9 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -334,6 +355,9 @@ def test_execute_dump_command_runs_mysqldump_with_options():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -367,6 +391,9 @@ def test_execute_dump_command_runs_non_default_mysqldump():
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -420,6 +447,9 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
 def test_execute_dump_command_with_dry_run_skips_mysqldump():
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').never()
@@ -440,6 +470,9 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
 def test_dump_data_sources_errors_for_missing_all_databases():
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -459,6 +492,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
     )
@@ -481,6 +517,9 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch'),
         processes=[extract_process],
@@ -509,6 +548,9 @@ def test_restore_data_source_dump_runs_mysql_with_options():
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--harder'),
         processes=[extract_process],
@@ -537,6 +579,9 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
     hook_config = [{'name': 'foo', 'mysql_command': 'custom_mysql', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mysql', '--batch', '--harder'),
         processes=[extract_process],
@@ -565,6 +610,9 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
     hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -602,6 +650,9 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
     hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--user', 'root'),
         processes=[extract_process],
@@ -640,6 +691,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -691,6 +745,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
             'mysql',
@@ -729,6 +786,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo'}]
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_with_processes').never()
 
     module.restore_data_source_dump(

+ 90 - 0
tests/unit/hooks/data_source/test_postgresql.py

@@ -24,6 +24,9 @@ def test_make_extra_environment_maps_options_to_environment():
         'PGSSLROOTCERT': 'root.crt',
         'PGSSLCRL': 'crl.crl',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
 
     extra_env = module.make_extra_environment(database)
 
@@ -32,6 +35,9 @@ def test_make_extra_environment_maps_options_to_environment():
 
 def test_make_extra_environment_with_cli_password_sets_correct_password():
     database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
 
     extra = module.make_extra_environment(
         database, restore_connection_params={'password': 'clipassword'}
@@ -78,6 +84,9 @@ def test_database_names_to_dump_passes_through_all_without_format():
 
 def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
     database = {'name': 'all', 'format': 'custom'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
     assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
@@ -85,6 +94,9 @@ def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
 
 def test_database_names_to_dump_with_all_and_format_lists_databases():
     database = {'name': 'all', 'format': 'custom'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\nbar,test,"stuff and such"'
     )
@@ -97,6 +109,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases():
 
 def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostname_and_port():
     database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'psql',
@@ -121,6 +136,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
 
 def test_database_names_to_dump_with_all_and_format_lists_databases_with_username():
     database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'psql',
@@ -143,6 +161,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
 
 def test_database_names_to_dump_with_all_and_format_lists_databases_with_options():
     database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
         extra_environment=object,
@@ -156,6 +177,9 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_options
 
 def test_database_names_to_dump_with_all_and_format_excludes_particular_databases():
     database = {'name': 'all', 'format': 'custom'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\ntemplate0,test,blah'
     )
@@ -169,6 +193,9 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
         'format': 'custom',
         'psql_command': 'docker exec --workdir * mycontainer psql',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
             'docker',
@@ -219,6 +246,9 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     for name, process in zip(('foo', 'bar'), processes):
@@ -323,6 +353,9 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').never()
 
@@ -349,6 +382,9 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
         'databases/database.example.org/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -394,6 +430,9 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
         'databases/localhost/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -437,6 +476,9 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
         'databases/localhost/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -477,6 +519,9 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
         'databases/localhost/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_parent_directory_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
 
@@ -519,6 +564,9 @@ def test_dump_data_sources_runs_pg_dump_with_options():
         'databases/localhost/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -559,6 +607,9 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
         'databases/localhost/all'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -588,6 +639,9 @@ def test_dump_data_sources_runs_non_default_pg_dump():
         'databases/localhost/foo'
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.dump).should_receive('create_named_pipe_for_dump')
 
     flexmock(module).should_receive('execute_command').with_args(
@@ -623,6 +677,9 @@ def test_restore_data_source_dump_runs_pg_restore():
     hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -677,6 +734,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -739,6 +799,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
     )
@@ -810,6 +873,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
     )
@@ -889,6 +955,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
     )
@@ -962,6 +1031,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1016,6 +1088,9 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
     hook_config = [{'name': 'all', 'schemas': None}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1055,6 +1130,9 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
     hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1106,6 +1184,9 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
     ]
     extract_process = flexmock(stdout=flexmock())
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1167,6 +1248,9 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo', 'schemas': None}]
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
@@ -1191,6 +1275,9 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
 def test_restore_data_source_dump_without_extract_process_restores_from_disk():
     hook_config = [{'name': 'foo', 'schemas': None}]
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
@@ -1243,6 +1330,9 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
 def test_restore_data_source_dump_with_schemas_restores_schemas():
     hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')

+ 42 - 0
tests/unit/hooks/monitoring/test_ntfy.py

@@ -36,6 +36,9 @@ def return_default_message_headers(state=Enum):
 
 def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -57,6 +60,9 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail():
         'topic': topic,
         'access_token': 'abc123',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -80,6 +86,9 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p
         'password': 'fakepassword',
         'access_token': 'abc123',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -103,6 +112,9 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
         'username': 'testuser',
         'password': 'fakepassword',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -121,6 +133,9 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
 
 def test_ping_monitor_with_password_but_no_username_warns():
     hook_config = {'topic': topic, 'password': 'fakepassword'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -140,6 +155,9 @@ def test_ping_monitor_with_password_but_no_username_warns():
 
 def test_ping_monitor_with_username_but_no_password_warns():
     hook_config = {'topic': topic, 'username': 'testuser'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -159,6 +177,9 @@ def test_ping_monitor_with_username_but_no_password_warns():
 
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -173,6 +194,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
 
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -187,6 +211,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
 
 def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'server': custom_base_url}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{custom_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -205,6 +232,9 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
 
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -219,6 +249,9 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
 
 def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'fail': custom_message_config}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None
     ).and_return(flexmock(ok=True)).once()
@@ -235,6 +268,9 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
 
 def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
     hook_config = {'topic': topic, 'states': ['start', 'fail']}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
@@ -253,6 +289,9 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
 
 def test_ping_monitor_with_connection_error_logs_warning():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
@@ -272,6 +311,9 @@ def test_ping_monitor_with_connection_error_logs_warning():
 
 def test_ping_monitor_with_other_error_logs_warning():
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     response = flexmock(ok=False)
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException

+ 18 - 0
tests/unit/hooks/monitoring/test_pagerduty.py

@@ -4,6 +4,9 @@ from borgmatic.hooks.monitoring import pagerduty as module
 
 
 def test_ping_monitor_ignores_start_state():
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -17,6 +20,9 @@ def test_ping_monitor_ignores_start_state():
 
 
 def test_ping_monitor_ignores_finish_state():
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -30,6 +36,9 @@ def test_ping_monitor_ignores_finish_state():
 
 
 def test_ping_monitor_calls_api_for_fail_state():
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
 
     module.ping_monitor(
@@ -43,6 +52,9 @@ def test_ping_monitor_calls_api_for_fail_state():
 
 
 def test_ping_monitor_dry_run_does_not_call_api():
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').never()
 
     module.ping_monitor(
@@ -56,6 +68,9 @@ def test_ping_monitor_dry_run_does_not_call_api():
 
 
 def test_ping_monitor_with_connection_error_logs_warning():
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').and_raise(
         module.requests.exceptions.ConnectionError
     )
@@ -73,6 +88,9 @@ def test_ping_monitor_with_connection_error_logs_warning():
 
 def test_ping_monitor_with_other_error_logs_warning():
     response = flexmock(ok=False)
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException
     )

+ 44 - 2
tests/unit/hooks/monitoring/test_pushover.py

@@ -11,6 +11,9 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
     should be auto populated with the default value which is the state name.
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -38,6 +41,9 @@ def test_ping_monitor_config_with_minimum_config_start_state_backup_not_send_to_
     'start' state. Only the 'fail' state is enabled by default.
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').never()
 
@@ -63,6 +69,9 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu
         'user': '983hfe0of902lkjfa2amanfgui',
         'states': {'start', 'fail', 'finish'},
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -96,6 +105,9 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus
         'states': {'start', 'fail', 'finish'},
         'start': {'message': 'custom start message'},
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -128,6 +140,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2},
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -163,6 +178,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2, 'expire': 600},
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -198,6 +216,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2, 'retry': 30},
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -236,6 +257,9 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_high_decl
         'start': {'priority': 1, 'expire': 30, 'retry': 30},
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').never()
 
@@ -288,6 +312,9 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example
             'url_title': 'Login to ticketing system',
         },
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -351,6 +378,9 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_
             'url_title': 'Login to ticketing system',
         },
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -419,6 +449,9 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
             'url_title': 'Login to ticketing system',
         },
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
@@ -446,12 +479,15 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
     )
 
 
-def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_send_to_pushover_dryrun():
+def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_send_to_pushover_dry_run():
     '''
     This test should be the minimum working configuration. The "message"
     should be auto populated with the default value which is the state name.
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
 
@@ -473,6 +509,9 @@ def test_ping_monitor_config_incorrect_state_exit_early():
         'token': 'ksdjfwoweijfvwoeifvjmwghagy92',
         'user': '983hfe0of902lkjfa2amanfgui',
     }
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
 
@@ -486,7 +525,7 @@ def test_ping_monitor_config_incorrect_state_exit_early():
     )
 
 
-def test_ping_monitor_push_post_error_exits_early():
+def test_ping_monitor_push_post_error_bails():
     '''
     This test simulates the Pushover servers not responding with a 200 OK. We
     should raise for status and warn then exit.
@@ -496,6 +535,9 @@ def test_ping_monitor_push_post_error_exits_early():
         'user': '983hfe0of902lkjfa2amanfgui',
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     push_response = flexmock(ok=False)
     push_response.should_receive('raise_for_status').and_raise(
         module.requests.ConnectionError

+ 69 - 15
tests/unit/hooks/monitoring/test_zabbix.py

@@ -57,7 +57,7 @@ AUTH_HEADERS_API_KEY = {
 AUTH_HEADERS_USERNAME_PASSWORD = {'Content-Type': 'application/json-rpc'}
 
 
-def test_ping_monitor_with_non_matching_state_exits_early():
+def test_ping_monitor_with_non_matching_state_bails():
     hook_config = {'api_key': API_KEY}
     flexmock(module.requests).should_receive('post').never()
 
@@ -71,10 +71,13 @@ def test_ping_monitor_with_non_matching_state_exits_early():
     )
 
 
-def test_ping_monitor_config_with_api_key_only_exit_early():
+def test_ping_monitor_config_with_api_key_only_bails():
     # This test should exit early since only providing an API KEY is not enough
     # for the hook to work
     hook_config = {'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -88,10 +91,13 @@ def test_ping_monitor_config_with_api_key_only_exit_early():
     )
 
 
-def test_ping_monitor_config_with_host_only_exit_early():
+def test_ping_monitor_config_with_host_only_bails():
     # This test should exit early since only providing a HOST is not enough
     # for the hook to work
     hook_config = {'host': HOST}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -105,10 +111,13 @@ def test_ping_monitor_config_with_host_only_exit_early():
     )
 
 
-def test_ping_monitor_config_with_key_only_exit_early():
+def test_ping_monitor_config_with_key_only_bails():
     # This test should exit early since only providing a KEY is not enough
     # for the hook to work
     hook_config = {'key': KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -122,10 +131,13 @@ def test_ping_monitor_config_with_key_only_exit_early():
     )
 
 
-def test_ping_monitor_config_with_server_only_exit_early():
+def test_ping_monitor_config_with_server_only_bails():
     # This test should exit early since only providing a SERVER is not enough
     # for the hook to work
     hook_config = {'server': SERVER}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -139,9 +151,12 @@ def test_ping_monitor_config_with_server_only_exit_early():
     )
 
 
-def test_ping_monitor_config_user_password_no_zabbix_data_exit_early():
+def test_ping_monitor_config_user_password_no_zabbix_data_bails():
     # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
     hook_config = {'server': SERVER, 'username': USERNAME, 'password': PASSWORD}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -155,9 +170,12 @@ def test_ping_monitor_config_user_password_no_zabbix_data_exit_early():
     )
 
 
-def test_ping_monitor_config_api_key_no_zabbix_data_exit_early():
+def test_ping_monitor_config_api_key_no_zabbix_data_bails():
     # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
     hook_config = {'server': SERVER, 'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -171,10 +189,13 @@ def test_ping_monitor_config_api_key_no_zabbix_data_exit_early():
     )
 
 
-def test_ping_monitor_config_itemid_no_auth_data_exit_early():
+def test_ping_monitor_config_itemid_no_auth_data_bails():
     # This test should exit early since there is no authentication provided
     # and Zabbix requires authentication to use it's API
     hook_config = {'server': SERVER, 'itemid': ITEMID}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -188,10 +209,13 @@ def test_ping_monitor_config_itemid_no_auth_data_exit_early():
     )
 
 
-def test_ping_monitor_config_host_and_key_no_auth_data_exit_early():
+def test_ping_monitor_config_host_and_key_no_auth_data_bails():
     # This test should exit early since there is no authentication provided
     # and Zabbix requires authentication to use it's API
     hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -209,6 +233,9 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
     # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
     # to authenticate and HOST/KEY to know which item to populate in Zabbix.
     hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
@@ -226,8 +253,11 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
     )
 
 
-def test_ping_monitor_config_host_and_missing_key_exits_early():
+def test_ping_monitor_config_host_and_missing_key_bails():
     hook_config = {'server': SERVER, 'host': HOST, 'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -241,8 +271,11 @@ def test_ping_monitor_config_host_and_missing_key_exits_early():
     )
 
 
-def test_ping_monitor_config_key_and_missing_host_exits_early():
+def test_ping_monitor_config_key_and_missing_host_bails():
     hook_config = {'server': SERVER, 'key': KEY, 'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -267,6 +300,9 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
         'password': PASSWORD,
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -296,7 +332,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
     )
 
 
-def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_auth_post_error_exits_early():
+def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_auth_post_error_bails():
     hook_config = {
         'server': SERVER,
         'host': HOST,
@@ -305,6 +341,9 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
         'password': PASSWORD,
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=False)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -335,7 +374,7 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
     )
 
 
-def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exits_early():
+def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bails():
     hook_config = {
         'server': SERVER,
         'host': HOST,
@@ -343,6 +382,9 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exi
         'username': USERNAME,
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -356,7 +398,7 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exi
     )
 
 
-def test_ping_monitor_config_host_and_key_with_passing_and_missing_username_exits_early():
+def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bails():
     hook_config = {
         'server': SERVER,
         'host': HOST,
@@ -364,6 +406,9 @@ def test_ping_monitor_config_host_and_key_with_passing_and_missing_username_exit
         'password': PASSWORD,
     }
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
 
@@ -381,6 +426,9 @@ def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
     # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
     # to authenticate and HOST/KEY to know which item to populate in Zabbix.
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
@@ -403,6 +451,9 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
     # to authenticate and HOST/KEY to know which item to populate in Zabbix.
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
@@ -432,9 +483,12 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
     )
 
 
-def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_post_error_exits_early():
+def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_post_error_bails():
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}