Explorar o código

Get unit tests passing again (#966).

Dan Helfman hai 3 meses
pai
achega
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():
     for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
         value = config.get(option_name)
         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)
             value = borgmatic.hooks.credential.tag.resolve_credential(value)
 
 
         if value is not None:
         if value is not None:

+ 2 - 1
borgmatic/config/schema.yaml

@@ -1646,7 +1646,8 @@ properties:
             token:
             token:
                 type: string
                 type: string
                 description: |
                 description: |
-                    Your application's API token. Supports the "!credential" tag.
+                    Your application's API token. Supports the "!credential"
+                    tag.
                 example: 7ms6TXHpTokTou2P6x4SodDeentHRa
                 example: 7ms6TXHpTokTou2P6x4SodDeentHRa
             user:
             user:
                 type: string
                 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 ())
         + (('--protocol', 'tcp') if hostname or port else ())
         + (('--user', username) if username 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}")
     logger.debug(f"Restoring MariaDB database {data_source['name']}{dry_run_label}")
     if dry_run:
     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:
     if port:
         command.extend(('--port', str(port)))
         command.extend(('--port', str(port)))
     if username:
     if username:
-        command.extend(('--username', borgmatic.hooks.credential.tag.resolve_credential(username)))
+        command.extend(('--username', username))
     if password:
     if password:
-        command.extend(('--password', borgmatic.hooks.credential.tag.resolve_credential(password)))
+        command.extend(('--password', password))
     if 'authentication_database' in database:
     if 'authentication_database' in database:
         command.extend(('--authenticationDatabase', database['authentication_database']))
         command.extend(('--authenticationDatabase', database['authentication_database']))
     if 'restore_options' in 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 ())
         + (('--protocol', 'tcp') if hostname or port else ())
         + (('--user', username) if username 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}")
     logger.debug(f"Restoring MySQL database {data_source['name']}{dry_run_label}")
     if dry_run:
     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
         return
 
 
     try:
     try:
-        inegration_key = borgmatic.hooks.credential.tag.resolve_credential(
+        integration_key = borgmatic.hooks.credential.tag.resolve_credential(
             hook_config.get('integration_key')
             hook_config.get('integration_key')
         )
         )
     except ValueError as error:
     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 config_paths == {'/tmp/config.yaml'}
     assert logs
     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)
     ).and_return(None)
     flexmock(module.os).should_receive('pipe').never()
     flexmock(module.os).should_receive('pipe').never()
     flexmock(module.os.environ).should_receive('get').and_return(None)
     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'})
     environment = module.make_environment({'encryption_passphrase': 'pass'})
 
 
     assert environment.get('BORG_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():
 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()
     flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
 
 
     assert (
     assert (
@@ -13,6 +14,7 @@ def test_run_passcommand_with_passphrase_configured_bails():
 
 
 
 
 def test_run_passcommand_without_passphrase_configured_executes_passcommand():
 def test_run_passcommand_without_passphrase_configured_executes_passcommand():
+    module.run_passcommand.cache_clear()
     flexmock(module.borgmatic.execute).should_receive(
     flexmock(module.borgmatic.execute).should_receive(
         'execute_command_and_capture_output'
         'execute_command_and_capture_output'
     ).and_return('passphrase').once()
     ).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():
 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(
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
         '/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():
 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(
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
         '/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():
 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(
     flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(
         '/working'
         '/working'
     )
     )
@@ -69,3 +74,19 @@ def test_get_passphrase_from_passcommand_with_configured_blank_passphrase_and_pa
         )
         )
         is None
         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(
 @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()
     configuration = flexmock()
     other_configuration = flexmock()
     other_configuration = flexmock()
     test_expected_logs = [flexmock(), flexmock()]
     test_expected_logs = [flexmock(), flexmock()]
@@ -1137,7 +1131,6 @@ def test_load_configurations_collects_parsed_configurations_and_logs(
         module.load_configurations(
         module.load_configurations(
             ('test.yaml', 'other.yaml'),
             ('test.yaml', 'other.yaml'),
             resolve_env=resolve_env,
             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():
 def test_database_names_to_dump_queries_mariadb_for_database_names():
     extra_environment = flexmock()
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         ('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
         extra_environment=extra_environment,
@@ -50,6 +53,9 @@ def test_dump_data_sources_dumps_each_database():
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     databases = [{'name': 'foo'}, {'name': 'bar'}]
     processes = [flexmock(), flexmock()]
     processes = [flexmock(), flexmock()]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
         ('bar',)
     )
     )
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     process = flexmock()
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
         ('bar',)
     )
     )
@@ -108,6 +117,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     process = flexmock()
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
     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('database_names_to_dump').and_return(('foo', 'bar'))
     flexmock(module).should_receive('execute_dump_command').with_args(
     flexmock(module).should_receive('execute_dump_command').with_args(
         database={'name': 'all'},
         database={'name': 'all'},
@@ -132,6 +144,9 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
     databases = [{'name': 'all', 'format': 'sql'}]
     databases = [{'name': 'all', 'format': 'sql'}]
     processes = [flexmock(), flexmock()]
     processes = [flexmock(), flexmock()]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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('database_names_to_dump').and_return(('foo', 'bar'))
 
 
     for name, process in zip(('foo', 'bar'), processes):
     for name, process in zip(('foo', 'bar'), processes):
@@ -199,6 +214,9 @@ def test_execute_dump_command_runs_mariadb_dump():
     process = flexmock()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
@@ -334,6 +361,9 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
     process = flexmock()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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():
 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.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').never()
     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():
 def test_dump_data_sources_errors_for_missing_all_databases():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
         '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():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
         'databases/localhost/all'
     )
     )
@@ -483,6 +525,9 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch'),
         ('mariadb', '--batch'),
         processes=[extract_process],
         processes=[extract_process],
@@ -511,6 +556,9 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--harder'),
         ('mariadb', '--batch', '--harder'),
         processes=[extract_process],
         processes=[extract_process],
@@ -541,6 +589,9 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mariadb', '--batch', '--harder'),
         ('custom_mariadb', '--batch', '--harder'),
         processes=[extract_process],
         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}]
     hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mariadb',
             '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'}]
     hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mariadb', '--batch', '--user', 'root'),
         ('mariadb', '--batch', '--user', 'root'),
         processes=[extract_process],
         processes=[extract_process],
@@ -644,6 +701,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mariadb',
             'mariadb',
@@ -695,6 +755,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mariadb',
             '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():
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo'}]
     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()
     flexmock(module).should_receive('execute_command_with_processes').never()
 
 
     module.restore_data_source_dump(
     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(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/foo'
         '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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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():
 def test_build_dump_command_with_username_injection_attack_gets_escaped():
     database = {'name': 'test', 'username': 'bob; naughty-command'}
     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')
     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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop'],
         ['mongorestore', '--archive', '--drop'],
         processes=[extract_process],
         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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
         [
             'mongorestore',
             '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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
         [
             'mongorestore',
             '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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
         [
             'mongorestore',
             '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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
         [
             'mongorestore',
             'mongorestore',
@@ -479,6 +500,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
 
 
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive', '--drop', '--harder'],
         ['mongorestore', '--archive', '--drop', '--harder'],
         processes=[extract_process],
         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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         [
         [
             'mongorestore',
             '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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--archive'],
         ['mongorestore', '--archive'],
         processes=[extract_process],
         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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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()
     flexmock(module).should_receive('execute_command_with_processes').never()
 
 
     module.restore_data_source_dump(
     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).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ['mongorestore', '--dir', '/dump/path', '--drop'],
         ['mongorestore', '--dir', '/dump/path', '--drop'],
         processes=[],
         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():
 def test_database_names_to_dump_bails_for_dry_run():
     extra_environment = flexmock()
     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()
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
 
     names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
     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():
 def test_database_names_to_dump_queries_mysql_for_database_names():
     extra_environment = flexmock()
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         ('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
         extra_environment=extra_environment,
         extra_environment=extra_environment,
@@ -81,6 +87,9 @@ def test_dump_data_sources_dumps_with_password():
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
     process = flexmock()
     process = flexmock()
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
         ('bar',)
         ('bar',)
     )
     )
@@ -199,6 +208,9 @@ def test_execute_dump_command_runs_mysqldump():
     process = flexmock()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
@@ -334,6 +355,9 @@ def test_execute_dump_command_runs_mysqldump_with_options():
     process = flexmock()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     flexmock(module).should_receive('execute_command').with_args(
@@ -367,6 +391,9 @@ def test_execute_dump_command_runs_non_default_mysqldump():
     process = flexmock()
     process = flexmock()
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('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.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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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():
 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.dump).should_receive('make_data_source_dump_filename').and_return('dump')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').never()
     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():
 def test_dump_data_sources_errors_for_missing_all_databases():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
         '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():
 def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
     databases = [{'name': 'all'}]
     databases = [{'name': 'all'}]
     flexmock(module).should_receive('make_dump_path').and_return('')
     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(
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
         'databases/localhost/all'
         'databases/localhost/all'
     )
     )
@@ -481,6 +517,9 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     hook_config = [{'name': 'foo'}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch'),
         ('mysql', '--batch'),
         processes=[extract_process],
         processes=[extract_process],
@@ -509,6 +548,9 @@ def test_restore_data_source_dump_runs_mysql_with_options():
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     hook_config = [{'name': 'foo', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--harder'),
         ('mysql', '--batch', '--harder'),
         processes=[extract_process],
         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'}]
     hook_config = [{'name': 'foo', 'mysql_command': 'custom_mysql', 'restore_options': '--harder'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('custom_mysql', '--batch', '--harder'),
         ('custom_mysql', '--batch', '--harder'),
         processes=[extract_process],
         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}]
     hook_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mysql',
             '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'}]
     hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         ('mysql', '--batch', '--user', 'root'),
         ('mysql', '--batch', '--user', 'root'),
         processes=[extract_process],
         processes=[extract_process],
@@ -640,6 +691,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mysql',
             'mysql',
@@ -691,6 +745,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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(
     flexmock(module).should_receive('execute_command_with_processes').with_args(
         (
         (
             'mysql',
             '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():
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo'}]
     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()
     flexmock(module).should_receive('execute_command_with_processes').never()
 
 
     module.restore_data_source_dump(
     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',
         'PGSSLROOTCERT': 'root.crt',
         'PGSSLCRL': 'crl.crl',
         '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)
     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():
 def test_make_extra_environment_with_cli_password_sets_correct_password():
     database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
     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(
     extra = module.make_extra_environment(
         database, restore_connection_params={'password': 'clipassword'}
         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():
 def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
     database = {'name': 'all', 'format': 'custom'}
     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()
     flexmock(module).should_receive('execute_command_and_capture_output').never()
 
 
     assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
     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():
 def test_database_names_to_dump_with_all_and_format_lists_databases():
     database = {'name': 'all', 'format': 'custom'}
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\nbar,test,"stuff and such"'
         '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():
 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}
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
         (
             'psql',
             '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():
 def test_database_names_to_dump_with_all_and_format_lists_databases_with_username():
     database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
         (
             'psql',
             '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():
 def test_database_names_to_dump_with_all_and_format_lists_databases_with_options():
     database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
         ('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
         extra_environment=object,
         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():
 def test_database_names_to_dump_with_all_and_format_excludes_particular_databases():
     database = {'name': 'all', 'format': 'custom'}
     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(
     flexmock(module).should_receive('execute_command_and_capture_output').and_return(
         'foo,test,\ntemplate0,test,blah'
         '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',
         'format': 'custom',
         'psql_command': 'docker exec --workdir * mycontainer psql',
         '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(
     flexmock(module).should_receive('execute_command_and_capture_output').with_args(
         (
         (
             'docker',
             'docker',
@@ -219,6 +246,9 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
         'databases/localhost/foo'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     for name, process in zip(('foo', 'bar'), processes):
     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'
         'databases/localhost/foo'
     ).and_return('databases/localhost/bar')
     ).and_return('databases/localhost/bar')
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump').never()
     flexmock(module).should_receive('execute_command').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'
         'databases/database.example.org/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'
         'databases/localhost/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'
         'databases/localhost/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'
         'databases/localhost/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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_parent_directory_for_dump')
     flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
     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'
         'databases/localhost/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'
         'databases/localhost/all'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'
         'databases/localhost/foo'
     )
     )
     flexmock(module.os.path).should_receive('exists').and_return(False)
     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.dump).should_receive('create_named_pipe_for_dump')
 
 
     flexmock(module).should_receive('execute_command').with_args(
     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'}]
     hook_config = [{'name': 'foo', 'schemas': None}, {'name': 'bar'}]
     extract_process = flexmock(stdout=flexmock())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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())
     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(
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
         {'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())
     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(
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
         {'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())
     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(
     flexmock(module).should_receive('make_extra_environment').and_return(
         {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
         {'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
     )
     )
@@ -962,6 +1031,9 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
     ]
     ]
     extract_process = flexmock(stdout=flexmock())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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}]
     hook_config = [{'name': 'all', 'schemas': None}]
     extract_process = flexmock(stdout=flexmock())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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}]
     hook_config = [{'name': 'foo', 'format': 'plain', 'schemas': None}]
     extract_process = flexmock(stdout=flexmock())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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())
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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():
 def test_restore_data_source_dump_with_dry_run_skips_restore():
     hook_config = [{'name': 'foo', 'schemas': None}]
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename')
     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():
 def test_restore_data_source_dump_without_extract_process_restores_from_disk():
     hook_config = [{'name': 'foo', 'schemas': None}]
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/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():
 def test_restore_data_source_dump_with_schemas_restores_schemas():
     hook_config = [{'name': 'foo', 'schemas': ['bar', 'baz']}]
     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_extra_environment').and_return({'PGSSLMODE': 'disable'})
     flexmock(module).should_receive('make_dump_path')
     flexmock(module).should_receive('make_dump_path')
     flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/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():
 def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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,
         'topic': topic,
         'access_token': 'abc123',
         '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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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',
         'password': 'fakepassword',
         'access_token': 'abc123',
         '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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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',
         'username': 'testuser',
         'password': 'fakepassword',
         '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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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():
 def test_ping_monitor_with_password_but_no_username_warns():
     hook_config = {'topic': topic, 'password': 'fakepassword'}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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():
 def test_ping_monitor_with_username_but_no_password_warns():
     hook_config = {'topic': topic, 'username': 'testuser'}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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():
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
     hook_config = {'topic': topic}
     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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     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():
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
     hook_config = {'topic': topic}
     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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     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():
 def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'server': custom_base_url}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{custom_base_url}/{topic}',
         f'{custom_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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():
 def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
     hook_config = {'topic': topic}
     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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     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():
 def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
     hook_config = {'topic': topic, 'fail': custom_message_config}
     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(
     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
     ).and_return(flexmock(ok=True)).once()
     ).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():
 def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
     hook_config = {'topic': topic, 'states': ['start', 'fail']}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
         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():
 def test_ping_monitor_with_connection_error_logs_warning():
     hook_config = {'topic': topic}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{default_base_url}/{topic}',
         f'{default_base_url}/{topic}',
         headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
         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():
 def test_ping_monitor_with_other_error_logs_warning():
     hook_config = {'topic': topic}
     hook_config = {'topic': topic}
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     response = flexmock(ok=False)
     response = flexmock(ok=False)
     response.should_receive('raise_for_status').and_raise(
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException
         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():
 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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     module.ping_monitor(
@@ -17,6 +20,9 @@ def test_ping_monitor_ignores_start_state():
 
 
 
 
 def test_ping_monitor_ignores_finish_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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     module.ping_monitor(
@@ -30,6 +36,9 @@ def test_ping_monitor_ignores_finish_state():
 
 
 
 
 def test_ping_monitor_calls_api_for_fail_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))
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
 
 
     module.ping_monitor(
     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():
 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()
     flexmock(module.requests).should_receive('post').never()
 
 
     module.ping_monitor(
     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():
 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(
     flexmock(module.requests).should_receive('post').and_raise(
         module.requests.exceptions.ConnectionError
         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():
 def test_ping_monitor_with_other_error_logs_warning():
     response = flexmock(ok=False)
     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(
     response.should_receive('raise_for_status').and_raise(
         module.requests.exceptions.RequestException
         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.
     should be auto populated with the default value which is the state name.
     '''
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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.
     'start' state. Only the 'fail' state is enabled by default.
     '''
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').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',
         'user': '983hfe0of902lkjfa2amanfgui',
         'states': {'start', 'fail', 'finish'},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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'},
         'states': {'start', 'fail', 'finish'},
         'start': {'message': 'custom start message'},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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'},
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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'},
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2, 'expire': 600},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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'},
         'states': {'start', 'fail', 'finish'},
         'start': {'priority': 2, 'retry': 30},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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},
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').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',
             '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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',
             '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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',
             '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').with_args(
     flexmock(module.requests).should_receive('post').with_args(
         'https://api.pushover.net/1/messages.json',
         '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"
     This test should be the minimum working configuration. The "message"
     should be auto populated with the default value which is the state name.
     should be auto populated with the default value which is the state name.
     '''
     '''
     hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
     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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).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',
         'token': 'ksdjfwoweijfvwoeifvjmwghagy92',
         'user': '983hfe0of902lkjfa2amanfgui',
         '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.logger).should_receive('warning').never()
     flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).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
     This test simulates the Pushover servers not responding with a 200 OK. We
     should raise for status and warn then exit.
     should raise for status and warn then exit.
@@ -496,6 +535,9 @@ def test_ping_monitor_push_post_error_exits_early():
         'user': '983hfe0of902lkjfa2amanfgui',
         'user': '983hfe0of902lkjfa2amanfgui',
     }
     }
 
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     push_response = flexmock(ok=False)
     push_response = flexmock(ok=False)
     push_response.should_receive('raise_for_status').and_raise(
     push_response.should_receive('raise_for_status').and_raise(
         module.requests.ConnectionError
         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'}
 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}
     hook_config = {'api_key': API_KEY}
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since only providing an API KEY is not enough
     # for the hook to work
     # for the hook to work
     hook_config = {'api_key': API_KEY}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since only providing a HOST is not enough
     # for the hook to work
     # for the hook to work
     hook_config = {'host': HOST}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since only providing a KEY is not enough
     # for the hook to work
     # for the hook to work
     hook_config = {'key': KEY}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since only providing a SERVER is not enough
     # for the hook to work
     # for the hook to work
     hook_config = {'server': SERVER}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # 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}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # 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}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since there is no authentication provided
     # and Zabbix requires authentication to use it's API
     # and Zabbix requires authentication to use it's API
     hook_config = {'server': SERVER, 'itemid': ITEMID}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # This test should exit early since there is no authentication provided
     # and Zabbix requires authentication to use it's API
     # and Zabbix requires authentication to use it's API
     hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # 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.
     # 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}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
         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}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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}
     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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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,
         'password': PASSWORD,
     }
     }
 
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=True)
     auth_response = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
         {'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 = {
     hook_config = {
         'server': SERVER,
         'server': SERVER,
         'host': HOST,
         'host': HOST,
@@ -305,6 +341,9 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
         'password': PASSWORD,
         'password': PASSWORD,
     }
     }
 
 
+    flexmock(module.borgmatic.hooks.credential.tag).should_receive(
+        'resolve_credential'
+    ).replace_with(lambda value: value)
     auth_response = flexmock(ok=False)
     auth_response = flexmock(ok=False)
     auth_response.should_receive('json').and_return(
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
         {'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 = {
     hook_config = {
         'server': SERVER,
         'server': SERVER,
         'host': HOST,
         'host': HOST,
@@ -343,6 +382,9 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_exi
         'username': USERNAME,
         '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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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 = {
     hook_config = {
         'server': SERVER,
         'server': SERVER,
         'host': HOST,
         'host': HOST,
@@ -364,6 +406,9 @@ def test_ping_monitor_config_host_and_key_with_passing_and_missing_username_exit
         'password': PASSWORD,
         '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.logger).should_receive('warning').once()
     flexmock(module.requests).should_receive('post').never()
     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
     # 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.
     # to authenticate and HOST/KEY to know which item to populate in Zabbix.
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
     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(
     flexmock(module.requests).should_receive('post').with_args(
         f'{SERVER}',
         f'{SERVER}',
         headers=AUTH_HEADERS_API_KEY,
         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.
     # to authenticate and HOST/KEY to know which item to populate in Zabbix.
     hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
     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 = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
         {'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}
     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 = flexmock(ok=True)
     auth_response.should_receive('json').and_return(
     auth_response.should_receive('json').and_return(
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
         {'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}