From cb5a2c49c8e6caa2de15857819fbb3f44a114500 Mon Sep 17 00:00:00 2001 From: Cyber MacGeddon Date: Wed, 3 Sep 2025 15:19:31 +0100 Subject: [PATCH] New tests (partly broken) --- .../test_cassandra_config_end_to_end.py | 440 ++++++++++++++++++ tests/unit/test_base/test_cassandra_config.py | 411 ++++++++++++++++ .../test_cassandra_config_integration.py | 417 +++++++++++++++++ .../test_triples_cassandra_storage.py | 109 +++-- 4 files changed, 1344 insertions(+), 33 deletions(-) create mode 100644 tests/integration/test_cassandra_config_end_to_end.py create mode 100644 tests/unit/test_base/test_cassandra_config.py create mode 100644 tests/unit/test_storage/test_cassandra_config_integration.py diff --git a/tests/integration/test_cassandra_config_end_to_end.py b/tests/integration/test_cassandra_config_end_to_end.py new file mode 100644 index 00000000..b71d1096 --- /dev/null +++ b/tests/integration/test_cassandra_config_end_to_end.py @@ -0,0 +1,440 @@ +""" +End-to-end integration tests for Cassandra configuration. + +Tests complete configuration flow from environment variables +through processors to Cassandra connections. +""" + +import os +import pytest +from unittest.mock import Mock, patch, MagicMock, call +from argparse import ArgumentParser + +# Import processors that use Cassandra configuration +from trustgraph.storage.triples.cassandra.write import Processor as TriplesWriter +from trustgraph.storage.objects.cassandra.write import Processor as ObjectsWriter +from trustgraph.query.triples.cassandra.service import Processor as TriplesQuery +from trustgraph.storage.knowledge.store import Processor as KgStore + + +class TestEndToEndConfigurationFlow: + """Test complete configuration flow from environment to processors.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_triples_writer_env_to_connection(self, mock_trust_graph): + """Test complete flow from environment variables to TrustGraph connection.""" + env_vars = { + 'CASSANDRA_HOST': 'integration-host1,integration-host2,integration-host3', + 'CASSANDRA_USERNAME': 'integration-user', + 'CASSANDRA_PASSWORD': 'integration-pass' + } + + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = TriplesWriter() + + # Create a mock message to trigger TrustGraph creation + mock_message = MagicMock() + mock_message.metadata.user = 'test_user' + mock_message.metadata.collection = 'test_collection' + mock_message.triples = [] + + # This should create TrustGraph with environment config + processor.store_triples(mock_message) + + # Verify TrustGraph was created with correct environment config + mock_trust_graph.assert_called_once_with( + hosts=['integration-host1', 'integration-host2', 'integration-host3'], + keyspace='test_user', + table='test_collection', + username='integration-user', + password='integration-pass' + ) + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + @patch('trustgraph.storage.objects.cassandra.write.PlainTextAuthProvider') + def test_objects_writer_env_to_cluster_connection(self, mock_auth_provider, mock_cluster): + """Test complete flow from environment variables to Cassandra Cluster connection.""" + env_vars = { + 'CASSANDRA_HOST': 'obj-host1,obj-host2', + 'CASSANDRA_USERNAME': 'obj-user', + 'CASSANDRA_PASSWORD': 'obj-pass' + } + + mock_auth_instance = MagicMock() + mock_auth_provider.return_value = mock_auth_instance + mock_cluster_instance = MagicMock() + mock_session = MagicMock() + mock_cluster_instance.connect.return_value = mock_session + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + + # Trigger Cassandra connection + processor.connect_cassandra() + + # Verify auth provider was created with env vars + mock_auth_provider.assert_called_once_with( + username='obj-user', + password='obj-pass' + ) + + # Verify cluster was created with hosts from env and auth + mock_cluster.assert_called_once() + call_args = mock_cluster.call_args + assert call_args.kwargs['contact_points'] == ['obj-host1', 'obj-host2'] + assert call_args.kwargs['auth_provider'] == mock_auth_instance + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_kg_store_env_to_table_store(self, mock_table_store): + """Test complete flow from environment variables to KnowledgeTableStore.""" + env_vars = { + 'CASSANDRA_HOST': 'kg-host1,kg-host2,kg-host3,kg-host4', + 'CASSANDRA_USERNAME': 'kg-user', + 'CASSANDRA_PASSWORD': 'kg-pass' + } + + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = KgStore() + + # Verify KnowledgeTableStore was created with env config + mock_table_store.assert_called_once_with( + cassandra_host=['kg-host1', 'kg-host2', 'kg-host3', 'kg-host4'], + cassandra_user='kg-user', + cassandra_password='kg-pass', + keyspace='knowledge' + ) + + +class TestConfigurationPriorityEndToEnd: + """Test configuration priority chains end-to-end.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_cli_override_env_end_to_end(self, mock_trust_graph): + """Test that CLI parameters override environment variables end-to-end.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + with patch.dict(os.environ, env_vars, clear=True): + # CLI parameters should override environment + processor = TriplesWriter( + cassandra_host='cli-host1,cli-host2', + cassandra_username='cli-user', + cassandra_password='cli-pass' + ) + + # Trigger TrustGraph creation + mock_message = MagicMock() + mock_message.metadata.user = 'test_user' + mock_message.metadata.collection = 'test_collection' + mock_message.triples = [] + + processor.store_triples(mock_message) + + # Should use CLI parameters, not environment + mock_trust_graph.assert_called_once_with( + hosts=['cli-host1', 'cli-host2'], # From CLI + keyspace='test_user', + table='test_collection', + username='cli-user', # From CLI + password='cli-pass' # From CLI + ) + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_partial_cli_with_env_fallback_end_to_end(self, mock_table_store): + """Test partial CLI parameters with environment fallback end-to-end.""" + env_vars = { + 'CASSANDRA_HOST': 'fallback-host1,fallback-host2', + 'CASSANDRA_USERNAME': 'fallback-user', + 'CASSANDRA_PASSWORD': 'fallback-pass' + } + + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + with patch.dict(os.environ, env_vars, clear=True): + # Only provide host via parameter, rest should fall back to env + processor = KgStore( + cassandra_host='partial-host' + # username and password not provided - should use env + ) + + # Verify mixed configuration + mock_table_store.assert_called_once_with( + cassandra_host=['partial-host'], # From parameter + cassandra_user='fallback-user', # From environment + cassandra_password='fallback-pass', # From environment + keyspace='knowledge' + ) + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_no_config_defaults_end_to_end(self, mock_trust_graph): + """Test that defaults are used when no configuration provided end-to-end.""" + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + with patch.dict(os.environ, {}, clear=True): + processor = TriplesQuery() + + # Mock query to trigger TrustGraph creation + mock_query = MagicMock() + mock_query.user = 'default_user' + mock_query.collection = 'default_collection' + mock_query.s = None + mock_query.p = None + mock_query.o = None + mock_query.limit = 100 + + # Mock the get_all method to return empty list + mock_tg_instance.get_all.return_value = [] + + processor.query_triples(mock_query) + + # Should use defaults + mock_trust_graph.assert_called_once_with( + hosts=['cassandra'], # Default host + keyspace='default_user', + table='default_collection' + # No username/password (defaults to None) + ) + + +class TestBackwardCompatibilityEndToEnd: + """Test backward compatibility with old parameter names end-to-end.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_old_graph_params_still_work_end_to_end(self, mock_trust_graph): + """Test that old graph_* parameters still work end-to-end.""" + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + # Use old parameter names + processor = TriplesWriter( + graph_host='legacy-host', + graph_username='legacy-user', + graph_password='legacy-pass' + ) + + # Trigger TrustGraph creation + mock_message = MagicMock() + mock_message.metadata.user = 'legacy_user' + mock_message.metadata.collection = 'legacy_collection' + mock_message.triples = [] + + processor.store_triples(mock_message) + + # Should work with legacy parameters + mock_trust_graph.assert_called_once_with( + hosts=['legacy-host'], + keyspace='legacy_user', + table='legacy_collection', + username='legacy-user', + password='legacy-pass' + ) + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_old_cassandra_user_param_still_works_end_to_end(self, mock_table_store): + """Test that old cassandra_user parameter still works end-to-end.""" + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + # Use old cassandra_user parameter + processor = KgStore( + cassandra_host='legacy-kg-host', + cassandra_user='legacy-kg-user', # Old parameter name + cassandra_password='legacy-kg-pass' + ) + + # Should work with old parameter name + mock_table_store.assert_called_once_with( + cassandra_host=['legacy-kg-host'], + cassandra_user='legacy-kg-user', + cassandra_password='legacy-kg-pass', + keyspace='knowledge' + ) + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_new_params_override_old_params_end_to_end(self, mock_trust_graph): + """Test that new parameters override old ones when both are present end-to-end.""" + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + # Provide both old and new parameters + processor = TriplesWriter( + cassandra_host='new-host', + graph_host='old-host', # Should be ignored + cassandra_username='new-user', + graph_username='old-user', # Should be ignored + cassandra_password='new-pass', + graph_password='old-pass' # Should be ignored + ) + + # Trigger TrustGraph creation + mock_message = MagicMock() + mock_message.metadata.user = 'precedence_user' + mock_message.metadata.collection = 'precedence_collection' + mock_message.triples = [] + + processor.store_triples(mock_message) + + # Should use new parameters, not old ones + mock_trust_graph.assert_called_once_with( + hosts=['new-host'], # New parameter wins + keyspace='precedence_user', + table='precedence_collection', + username='new-user', # New parameter wins + password='new-pass' # New parameter wins + ) + + +class TestMultipleHostsHandling: + """Test multiple Cassandra hosts handling end-to-end.""" + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + def test_multiple_hosts_passed_to_cluster(self, mock_cluster): + """Test that multiple hosts are correctly passed to Cassandra cluster.""" + env_vars = { + 'CASSANDRA_HOST': 'host1,host2,host3,host4,host5' + } + + mock_cluster_instance = MagicMock() + mock_session = MagicMock() + mock_cluster_instance.connect.return_value = mock_session + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + processor.connect_cassandra() + + # Verify all hosts were passed to Cluster + mock_cluster.assert_called_once() + call_args = mock_cluster.call_args + assert call_args.kwargs['contact_points'] == ['host1', 'host2', 'host3', 'host4', 'host5'] + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_single_host_converted_to_list(self, mock_trust_graph): + """Test that single host is converted to list for TrustGraph.""" + mock_tg_instance = MagicMock() + mock_trust_graph.return_value = mock_tg_instance + + processor = TriplesWriter(cassandra_host='single-host') + + # Trigger TrustGraph creation + mock_message = MagicMock() + mock_message.metadata.user = 'single_user' + mock_message.metadata.collection = 'single_collection' + mock_message.triples = [] + + processor.store_triples(mock_message) + + # Single host should be converted to list + mock_trust_graph.assert_called_once_with( + hosts=['single-host'], # Converted to list + keyspace='single_user', + table='single_collection' + ) + + def test_whitespace_handling_in_host_list(self): + """Test that whitespace in host lists is handled correctly.""" + from trustgraph.base.cassandra_config import resolve_cassandra_config + + # Test various whitespace scenarios + hosts1, _, _ = resolve_cassandra_config(host='host1, host2 , host3') + assert hosts1 == ['host1', 'host2', 'host3'] + + hosts2, _, _ = resolve_cassandra_config(host='host1,host2,host3,') + assert hosts2 == ['host1', 'host2', 'host3'] + + hosts3, _, _ = resolve_cassandra_config(host=' host1 , host2 ') + assert hosts3 == ['host1', 'host2'] + + +class TestAuthenticationFlow: + """Test authentication configuration flow end-to-end.""" + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + @patch('trustgraph.storage.objects.cassandra.write.PlainTextAuthProvider') + def test_authentication_enabled_when_both_credentials_provided(self, mock_auth_provider, mock_cluster): + """Test that authentication is enabled when both username and password are provided.""" + env_vars = { + 'CASSANDRA_HOST': 'auth-host', + 'CASSANDRA_USERNAME': 'auth-user', + 'CASSANDRA_PASSWORD': 'auth-secret' + } + + mock_auth_instance = MagicMock() + mock_auth_provider.return_value = mock_auth_instance + mock_cluster_instance = MagicMock() + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + processor.connect_cassandra() + + # Auth provider should be created + mock_auth_provider.assert_called_once_with( + username='auth-user', + password='auth-secret' + ) + + # Cluster should be created with auth provider + call_args = mock_cluster.call_args + assert 'auth_provider' in call_args.kwargs + assert call_args.kwargs['auth_provider'] == mock_auth_instance + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + @patch('trustgraph.storage.objects.cassandra.write.PlainTextAuthProvider') + def test_no_authentication_when_credentials_missing(self, mock_auth_provider, mock_cluster): + """Test that authentication is not used when credentials are missing.""" + env_vars = { + 'CASSANDRA_HOST': 'no-auth-host' + # No username/password + } + + mock_cluster_instance = MagicMock() + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + processor.connect_cassandra() + + # Auth provider should not be created + mock_auth_provider.assert_not_called() + + # Cluster should be created without auth provider + call_args = mock_cluster.call_args + assert 'auth_provider' not in call_args.kwargs + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + @patch('trustgraph.storage.objects.cassandra.write.PlainTextAuthProvider') + def test_no_authentication_when_only_username_provided(self, mock_auth_provider, mock_cluster): + """Test that authentication is not used when only username is provided.""" + processor = ObjectsWriter( + cassandra_host='partial-auth-host', + cassandra_username='partial-user' + # No password + ) + + mock_cluster_instance = MagicMock() + mock_cluster.return_value = mock_cluster_instance + + processor.connect_cassandra() + + # Auth provider should not be created (needs both username AND password) + mock_auth_provider.assert_not_called() + + # Cluster should be created without auth provider + call_args = mock_cluster.call_args + assert 'auth_provider' not in call_args.kwargs \ No newline at end of file diff --git a/tests/unit/test_base/test_cassandra_config.py b/tests/unit/test_base/test_cassandra_config.py new file mode 100644 index 00000000..a3579462 --- /dev/null +++ b/tests/unit/test_base/test_cassandra_config.py @@ -0,0 +1,411 @@ +""" +Unit tests for Cassandra configuration helper module. + +Tests configuration resolution, environment variable handling, +command-line argument parsing, and backward compatibility. +""" + +import argparse +import os +import pytest +from unittest.mock import patch + +from trustgraph.base.cassandra_config import ( + get_cassandra_defaults, + add_cassandra_args, + resolve_cassandra_config, + get_cassandra_config_from_params +) + + +class TestGetCassandraDefaults: + """Test the get_cassandra_defaults function.""" + + def test_defaults_with_no_env_vars(self): + """Test defaults when no environment variables are set.""" + with patch.dict(os.environ, {}, clear=True): + defaults = get_cassandra_defaults() + + assert defaults['host'] == 'cassandra' + assert defaults['username'] is None + assert defaults['password'] is None + + def test_defaults_with_env_vars(self): + """Test defaults when environment variables are set.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host1,env-host2', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + defaults = get_cassandra_defaults() + + assert defaults['host'] == 'env-host1,env-host2' + assert defaults['username'] == 'env-user' + assert defaults['password'] == 'env-pass' + + def test_partial_env_vars(self): + """Test defaults when only some environment variables are set.""" + env_vars = { + 'CASSANDRA_HOST': 'partial-host', + 'CASSANDRA_USERNAME': 'partial-user' + # CASSANDRA_PASSWORD not set + } + + with patch.dict(os.environ, env_vars, clear=True): + defaults = get_cassandra_defaults() + + assert defaults['host'] == 'partial-host' + assert defaults['username'] == 'partial-user' + assert defaults['password'] is None + + +class TestAddCassandraArgs: + """Test the add_cassandra_args function.""" + + def test_basic_args_added(self): + """Test that all three arguments are added to parser.""" + parser = argparse.ArgumentParser() + add_cassandra_args(parser) + + # Parse empty args to check defaults + args = parser.parse_args([]) + + assert hasattr(args, 'cassandra_host') + assert hasattr(args, 'cassandra_username') + assert hasattr(args, 'cassandra_password') + + def test_help_text_no_env_vars(self): + """Test help text when no environment variables are set.""" + with patch.dict(os.environ, {}, clear=True): + parser = argparse.ArgumentParser() + add_cassandra_args(parser) + + help_text = parser.format_help() + + assert 'Cassandra host list, comma-separated (default:' in help_text + assert 'cassandra)' in help_text + assert 'Cassandra username' in help_text + assert 'Cassandra password' in help_text + assert '[from CASSANDRA_HOST]' not in help_text + + def test_help_text_with_env_vars(self): + """Test help text when environment variables are set.""" + env_vars = { + 'CASSANDRA_HOST': 'help-host1,help-host2', + 'CASSANDRA_USERNAME': 'help-user', + 'CASSANDRA_PASSWORD': 'help-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + parser = argparse.ArgumentParser() + add_cassandra_args(parser) + + help_text = parser.format_help() + + # Help text may have line breaks - argparse breaks long lines + # So check for the components that should be there + assert 'help-' in help_text and 'host1' in help_text + assert 'help-host2' in help_text + # Check key components (may be split across lines by argparse) + assert '[from CASSANDRA_HOST]' in help_text + assert '(default: help-user)' in help_text + assert '[from' in help_text and 'CASSANDRA_USERNAME]' in help_text + assert '(default: )' in help_text # Password hidden + assert '[from' in help_text and 'CASSANDRA_PASSWORD]' in help_text + assert 'help-pass' not in help_text # Password value not shown + + def test_command_line_override(self): + """Test that command-line arguments override environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + parser = argparse.ArgumentParser() + add_cassandra_args(parser) + + args = parser.parse_args([ + '--cassandra-host', 'cli-host', + '--cassandra-username', 'cli-user', + '--cassandra-password', 'cli-pass' + ]) + + assert args.cassandra_host == 'cli-host' + assert args.cassandra_username == 'cli-user' + assert args.cassandra_password == 'cli-pass' + + +class TestResolveCassandraConfig: + """Test the resolve_cassandra_config function.""" + + def test_default_configuration(self): + """Test resolution with no parameters or environment variables.""" + with patch.dict(os.environ, {}, clear=True): + hosts, username, password = resolve_cassandra_config() + + assert hosts == ['cassandra'] + assert username is None + assert password is None + + def test_environment_variable_resolution(self): + """Test resolution from environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'env1,env2,env3', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + hosts, username, password = resolve_cassandra_config() + + assert hosts == ['env1', 'env2', 'env3'] + assert username == 'env-user' + assert password == 'env-pass' + + def test_explicit_parameter_override(self): + """Test that explicit parameters override environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + hosts, username, password = resolve_cassandra_config( + host='explicit-host', + username='explicit-user', + password='explicit-pass' + ) + + assert hosts == ['explicit-host'] + assert username == 'explicit-user' + assert password == 'explicit-pass' + + def test_host_list_parsing(self): + """Test different host list formats.""" + # Single host + hosts, _, _ = resolve_cassandra_config(host='single-host') + assert hosts == ['single-host'] + + # Multiple hosts with spaces + hosts, _, _ = resolve_cassandra_config(host='host1, host2 ,host3') + assert hosts == ['host1', 'host2', 'host3'] + + # Empty elements filtered out + hosts, _, _ = resolve_cassandra_config(host='host1,,host2,') + assert hosts == ['host1', 'host2'] + + # Already a list + hosts, _, _ = resolve_cassandra_config(host=['list-host1', 'list-host2']) + assert hosts == ['list-host1', 'list-host2'] + + def test_args_object_resolution(self): + """Test resolution from argparse args object.""" + # Mock args object + class MockArgs: + cassandra_host = 'args-host1,args-host2' + cassandra_username = 'args-user' + cassandra_password = 'args-pass' + + args = MockArgs() + hosts, username, password = resolve_cassandra_config(args) + + assert hosts == ['args-host1', 'args-host2'] + assert username == 'args-user' + assert password == 'args-pass' + + def test_partial_args_with_env_fallback(self): + """Test args object with missing attributes falls back to environment.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + # Args object with only some attributes + class PartialArgs: + cassandra_host = 'args-host' + # Missing cassandra_username and cassandra_password + + with patch.dict(os.environ, env_vars, clear=True): + args = PartialArgs() + hosts, username, password = resolve_cassandra_config(args) + + assert hosts == ['args-host'] # From args + assert username == 'env-user' # From env + assert password == 'env-pass' # From env + + +class TestGetCassandraConfigFromParams: + """Test the get_cassandra_config_from_params function.""" + + def test_new_parameter_names(self): + """Test with new cassandra_* parameter names.""" + params = { + 'cassandra_host': 'new-host1,new-host2', + 'cassandra_username': 'new-user', + 'cassandra_password': 'new-pass' + } + + hosts, username, password = get_cassandra_config_from_params(params) + + assert hosts == ['new-host1', 'new-host2'] + assert username == 'new-user' + assert password == 'new-pass' + + def test_backward_compatibility_graph_params(self): + """Test backward compatibility with old graph_* parameter names.""" + params = { + 'graph_host': 'old-host', + 'graph_username': 'old-user', + 'graph_password': 'old-pass' + } + + hosts, username, password = get_cassandra_config_from_params(params) + + assert hosts == ['old-host'] + assert username == 'old-user' + assert password == 'old-pass' + + def test_old_cassandra_user_compatibility(self): + """Test backward compatibility with cassandra_user (vs cassandra_username).""" + params = { + 'cassandra_host': 'compat-host', + 'cassandra_user': 'compat-user', # Old name + 'cassandra_password': 'compat-pass' + } + + hosts, username, password = get_cassandra_config_from_params(params) + + assert hosts == ['compat-host'] + assert username == 'compat-user' + assert password == 'compat-pass' + + def test_parameter_precedence(self): + """Test that new parameter names take precedence over old ones.""" + params = { + 'cassandra_host': 'new-host', + 'graph_host': 'old-host', + 'cassandra_username': 'new-user', + 'graph_username': 'old-user', + 'cassandra_user': 'older-user', + 'cassandra_password': 'new-pass', + 'graph_password': 'old-pass' + } + + hosts, username, password = get_cassandra_config_from_params(params) + + assert hosts == ['new-host'] # New takes precedence + assert username == 'new-user' # New takes precedence + assert password == 'new-pass' # New takes precedence + + def test_empty_params_with_env_fallback(self): + """Test that empty params falls back to environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'fallback-host1,fallback-host2', + 'CASSANDRA_USERNAME': 'fallback-user', + 'CASSANDRA_PASSWORD': 'fallback-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + params = {} + hosts, username, password = get_cassandra_config_from_params(params) + + assert hosts == ['fallback-host1', 'fallback-host2'] + assert username == 'fallback-user' + assert password == 'fallback-pass' + + +class TestConfigurationPriority: + """Test the overall configuration priority: CLI > env vars > defaults.""" + + def test_full_priority_chain(self): + """Test complete priority chain with all sources present.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + # CLI args should override everything + hosts, username, password = resolve_cassandra_config( + host='cli-host', + username='cli-user', + password='cli-pass' + ) + + assert hosts == ['cli-host'] + assert username == 'cli-user' + assert password == 'cli-pass' + + def test_partial_cli_with_env_fallback(self): + """Test partial CLI args with environment variable fallback.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + # Only provide host via CLI + hosts, username, password = resolve_cassandra_config( + host='cli-host' + # username and password not provided + ) + + assert hosts == ['cli-host'] # From CLI + assert username == 'env-user' # From env + assert password == 'env-pass' # From env + + def test_no_config_defaults(self): + """Test that defaults are used when no configuration is provided.""" + with patch.dict(os.environ, {}, clear=True): + hosts, username, password = resolve_cassandra_config() + + assert hosts == ['cassandra'] # Default + assert username is None # Default + assert password is None # Default + + +class TestEdgeCases: + """Test edge cases and error conditions.""" + + def test_empty_host_string(self): + """Test handling of empty host string falls back to default.""" + hosts, _, _ = resolve_cassandra_config(host='') + assert hosts == ['cassandra'] # Falls back to default + + def test_whitespace_only_host(self): + """Test handling of whitespace-only host string.""" + hosts, _, _ = resolve_cassandra_config(host=' ') + assert hosts == [] # Empty after stripping whitespace + + def test_none_values_preserved(self): + """Test that None values are preserved correctly.""" + hosts, username, password = resolve_cassandra_config( + host=None, + username=None, + password=None + ) + + # Should fall back to defaults + assert hosts == ['cassandra'] + assert username is None + assert password is None + + def test_mixed_none_and_values(self): + """Test mixing None and actual values.""" + hosts, username, password = resolve_cassandra_config( + host='mixed-host', + username=None, + password='mixed-pass' + ) + + assert hosts == ['mixed-host'] + assert username is None # Stays None + assert password == 'mixed-pass' \ No newline at end of file diff --git a/tests/unit/test_storage/test_cassandra_config_integration.py b/tests/unit/test_storage/test_cassandra_config_integration.py new file mode 100644 index 00000000..0d6149b4 --- /dev/null +++ b/tests/unit/test_storage/test_cassandra_config_integration.py @@ -0,0 +1,417 @@ +""" +Integration tests for Cassandra configuration in processors. + +Tests that processors correctly use the configuration helper +and handle environment variables, CLI args, and backward compatibility. +""" + +import os +import pytest +from unittest.mock import Mock, patch, MagicMock + +from trustgraph.storage.triples.cassandra.write import Processor as TriplesWriter +from trustgraph.storage.objects.cassandra.write import Processor as ObjectsWriter +from trustgraph.query.triples.cassandra.service import Processor as TriplesQuery +from trustgraph.storage.knowledge.store import Processor as KgStore + + +class TestTriplesWriterConfiguration: + """Test Cassandra configuration in triples writer processor.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_environment_variable_configuration(self, mock_trust_graph): + """Test processor picks up configuration from environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host1,env-host2', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + processor = TriplesWriter() + + assert processor.graph_host == ['env-host1', 'env-host2'] + assert processor.username == 'env-user' + assert processor.password == 'env-pass' + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_parameter_override_environment(self, mock_trust_graph): + """Test explicit parameters override environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + processor = TriplesWriter( + cassandra_host='param-host1,param-host2', + cassandra_username='param-user', + cassandra_password='param-pass' + ) + + assert processor.graph_host == ['param-host1', 'param-host2'] + assert processor.username == 'param-user' + assert processor.password == 'param-pass' + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_backward_compatibility_graph_params(self, mock_trust_graph): + """Test backward compatibility with old graph_* parameter names.""" + processor = TriplesWriter( + graph_host='compat-host', + graph_username='compat-user', + graph_password='compat-pass' + ) + + assert processor.graph_host == ['compat-host'] + assert processor.username == 'compat-user' + assert processor.password == 'compat-pass' + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_default_configuration(self, mock_trust_graph): + """Test default configuration when no params or env vars provided.""" + with patch.dict(os.environ, {}, clear=True): + processor = TriplesWriter() + + assert processor.graph_host == ['cassandra'] + assert processor.username is None + assert processor.password is None + + +class TestObjectsWriterConfiguration: + """Test Cassandra configuration in objects writer processor.""" + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + def test_environment_variable_configuration(self, mock_cluster): + """Test processor picks up configuration from environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'obj-env-host1,obj-env-host2', + 'CASSANDRA_USERNAME': 'obj-env-user', + 'CASSANDRA_PASSWORD': 'obj-env-pass' + } + + mock_cluster_instance = MagicMock() + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + + assert processor.graph_host == ['obj-env-host1', 'obj-env-host2'] + assert processor.graph_username == 'obj-env-user' + assert processor.graph_password == 'obj-env-pass' + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + def test_cassandra_connection_with_hosts_list(self, mock_cluster): + """Test that Cassandra connection uses hosts list correctly.""" + env_vars = { + 'CASSANDRA_HOST': 'conn-host1,conn-host2,conn-host3', + 'CASSANDRA_USERNAME': 'conn-user', + 'CASSANDRA_PASSWORD': 'conn-pass' + } + + mock_cluster_instance = MagicMock() + mock_session = MagicMock() + mock_cluster_instance.connect.return_value = mock_session + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + processor.connect_cassandra() + + # Verify cluster was called with hosts list + mock_cluster.assert_called_once() + call_args = mock_cluster.call_args + + # Check that contact_points was passed the hosts list + assert 'contact_points' in call_args.kwargs + assert call_args.kwargs['contact_points'] == ['conn-host1', 'conn-host2', 'conn-host3'] + + @patch('trustgraph.storage.objects.cassandra.write.Cluster') + @patch('trustgraph.storage.objects.cassandra.write.PlainTextAuthProvider') + def test_authentication_configuration(self, mock_auth_provider, mock_cluster): + """Test authentication is configured when credentials are provided.""" + env_vars = { + 'CASSANDRA_HOST': 'auth-host', + 'CASSANDRA_USERNAME': 'auth-user', + 'CASSANDRA_PASSWORD': 'auth-pass' + } + + mock_auth_instance = MagicMock() + mock_auth_provider.return_value = mock_auth_instance + mock_cluster_instance = MagicMock() + mock_cluster.return_value = mock_cluster_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = ObjectsWriter() + processor.connect_cassandra() + + # Verify auth provider was created with correct credentials + mock_auth_provider.assert_called_once_with( + username='auth-user', + password='auth-pass' + ) + + # Verify cluster was configured with auth provider + call_args = mock_cluster.call_args + assert 'auth_provider' in call_args.kwargs + assert call_args.kwargs['auth_provider'] == mock_auth_instance + + +class TestTriplesQueryConfiguration: + """Test Cassandra configuration in triples query processor.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_environment_variable_configuration(self, mock_trust_graph): + """Test processor picks up configuration from environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'query-env-host1,query-env-host2', + 'CASSANDRA_USERNAME': 'query-env-user', + 'CASSANDRA_PASSWORD': 'query-env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + processor = TriplesQuery() + + assert processor.graph_host == ['query-env-host1', 'query-env-host2'] + assert processor.username == 'query-env-user' + assert processor.password == 'query-env-pass' + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_mixed_old_new_parameters(self, mock_trust_graph): + """Test mixing old and new parameter names (new should win).""" + processor = TriplesQuery( + cassandra_host='new-host', + graph_host='old-host', + cassandra_username='new-user', + graph_username='old-user' + ) + + # New parameters should take precedence + assert processor.graph_host == ['new-host'] + assert processor.username == 'new-user' + + +class TestKgStoreConfiguration: + """Test Cassandra configuration in knowledge store processor.""" + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_environment_variable_configuration(self, mock_table_store): + """Test kg-store picks up configuration from environment variables.""" + env_vars = { + 'CASSANDRA_HOST': 'kg-env-host1,kg-env-host2,kg-env-host3', + 'CASSANDRA_USERNAME': 'kg-env-user', + 'CASSANDRA_PASSWORD': 'kg-env-pass' + } + + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + with patch.dict(os.environ, env_vars, clear=True): + processor = KgStore() + + # Verify KnowledgeTableStore was called with resolved config + mock_table_store.assert_called_once_with( + cassandra_host=['kg-env-host1', 'kg-env-host2', 'kg-env-host3'], + cassandra_user='kg-env-user', + cassandra_password='kg-env-pass', + keyspace='knowledge' + ) + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_explicit_parameters(self, mock_table_store): + """Test kg-store with explicit parameters.""" + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + processor = KgStore( + cassandra_host='explicit-host', + cassandra_username='explicit-user', + cassandra_password='explicit-pass' + ) + + # Verify KnowledgeTableStore was called with explicit config + mock_table_store.assert_called_once_with( + cassandra_host=['explicit-host'], + cassandra_user='explicit-user', + cassandra_password='explicit-pass', + keyspace='knowledge' + ) + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_backward_compatibility_cassandra_user(self, mock_table_store): + """Test backward compatibility with cassandra_user parameter.""" + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + processor = KgStore( + cassandra_host='compat-host', + cassandra_user='compat-user', # Old parameter name + cassandra_password='compat-pass' + ) + + # Should still work with old parameter name + mock_table_store.assert_called_once_with( + cassandra_host=['compat-host'], + cassandra_user='compat-user', + cassandra_password='compat-pass', + keyspace='knowledge' + ) + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_default_configuration(self, mock_table_store): + """Test kg-store default configuration.""" + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + with patch.dict(os.environ, {}, clear=True): + processor = KgStore() + + # Should use defaults + mock_table_store.assert_called_once_with( + cassandra_host=['cassandra'], + cassandra_user=None, + cassandra_password=None, + keyspace='knowledge' + ) + + +class TestCommandLineArgumentHandling: + """Test command-line argument parsing in processors.""" + + def test_triples_writer_add_args(self): + """Test that triples writer adds standard Cassandra arguments.""" + import argparse + from trustgraph.storage.triples.cassandra.write import Processor as TriplesWriter + + parser = argparse.ArgumentParser() + TriplesWriter.add_args(parser) + + # Parse empty args to check that arguments exist + args = parser.parse_args([]) + + assert hasattr(args, 'cassandra_host') + assert hasattr(args, 'cassandra_username') + assert hasattr(args, 'cassandra_password') + + def test_objects_writer_add_args(self): + """Test that objects writer adds standard Cassandra arguments.""" + import argparse + from trustgraph.storage.objects.cassandra.write import Processor as ObjectsWriter + + parser = argparse.ArgumentParser() + ObjectsWriter.add_args(parser) + + # Parse empty args to check that arguments exist + args = parser.parse_args([]) + + assert hasattr(args, 'cassandra_host') + assert hasattr(args, 'cassandra_username') + assert hasattr(args, 'cassandra_password') + assert hasattr(args, 'config_type') # Objects writer specific arg + + def test_triples_query_add_args(self): + """Test that triples query adds standard Cassandra arguments.""" + import argparse + from trustgraph.query.triples.cassandra.service import Processor as TriplesQuery + + parser = argparse.ArgumentParser() + TriplesQuery.add_args(parser) + + # Parse empty args to check that arguments exist + args = parser.parse_args([]) + + assert hasattr(args, 'cassandra_host') + assert hasattr(args, 'cassandra_username') + assert hasattr(args, 'cassandra_password') + + def test_kg_store_add_args(self): + """Test that kg-store now adds Cassandra arguments (previously missing).""" + import argparse + from trustgraph.storage.knowledge.store import Processor as KgStore + + parser = argparse.ArgumentParser() + KgStore.add_args(parser) + + # Parse empty args to check that arguments exist + args = parser.parse_args([]) + + assert hasattr(args, 'cassandra_host') + assert hasattr(args, 'cassandra_username') + assert hasattr(args, 'cassandra_password') + + def test_help_text_with_environment_variables(self): + """Test that help text shows environment variable values.""" + import argparse + from trustgraph.storage.triples.cassandra.write import Processor as TriplesWriter + + env_vars = { + 'CASSANDRA_HOST': 'help-host1,help-host2', + 'CASSANDRA_USERNAME': 'help-user', + 'CASSANDRA_PASSWORD': 'help-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + parser = argparse.ArgumentParser() + TriplesWriter.add_args(parser) + + help_text = parser.format_help() + + # Should show environment variable values (except password) + assert 'help-host1,help-host2' in help_text + assert 'help-user' in help_text + assert '' in help_text # Password should be hidden + assert 'help-pass' not in help_text # Password value not shown + assert '[from CASSANDRA_HOST]' in help_text + assert '[from CASSANDRA_USERNAME]' in help_text + assert '[from CASSANDRA_PASSWORD]' in help_text + + +class TestConfigurationPriorityIntegration: + """Test complete configuration priority chain in processors.""" + + @patch('trustgraph.direct.cassandra.TrustGraph') + def test_complete_priority_chain(self, mock_trust_graph): + """Test CLI params > env vars > defaults priority in actual processor.""" + env_vars = { + 'CASSANDRA_HOST': 'env-host', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + # Explicit parameters should override environment + processor = TriplesWriter( + cassandra_host='cli-host1,cli-host2', + cassandra_username='cli-user' + # Password not provided - should fall back to env + ) + + assert processor.graph_host == ['cli-host1', 'cli-host2'] # From CLI + assert processor.username == 'cli-user' # From CLI + assert processor.password == 'env-pass' # From env + + @patch('trustgraph.tables.knowledge.KnowledgeTableStore') + def test_kg_store_priority_chain(self, mock_table_store): + """Test configuration priority chain in kg-store processor.""" + mock_store_instance = MagicMock() + mock_table_store.return_value = mock_store_instance + + env_vars = { + 'CASSANDRA_HOST': 'env-host1,env-host2', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + + with patch.dict(os.environ, env_vars, clear=True): + processor = KgStore( + cassandra_host='param-host' + # username and password not provided - should use env + ) + + # Verify correct priority resolution + mock_table_store.assert_called_once_with( + cassandra_host=['param-host'], # From parameter + cassandra_user='env-user', # From environment + cassandra_password='env-pass', # From environment + keyspace='knowledge' + ) \ No newline at end of file diff --git a/tests/unit/test_storage/test_triples_cassandra_storage.py b/tests/unit/test_storage/test_triples_cassandra_storage.py index 9fbeb187..a4af0408 100644 --- a/tests/unit/test_storage/test_triples_cassandra_storage.py +++ b/tests/unit/test_storage/test_triples_cassandra_storage.py @@ -16,23 +16,25 @@ class TestCassandraStorageProcessor: """Test processor initialization with default parameters""" taskgroup_mock = MagicMock() - processor = Processor(taskgroup=taskgroup_mock) + # Patch environment to ensure clean defaults + with patch.dict('os.environ', {}, clear=True): + processor = Processor(taskgroup=taskgroup_mock) - assert processor.graph_host == ['localhost'] + assert processor.graph_host == ['cassandra'] # Updated default assert processor.username is None assert processor.password is None assert processor.table is None def test_processor_initialization_with_custom_params(self): - """Test processor initialization with custom parameters""" + """Test processor initialization with custom parameters (new cassandra_* names)""" taskgroup_mock = MagicMock() processor = Processor( taskgroup=taskgroup_mock, id='custom-storage', - graph_host='cassandra.example.com', - graph_username='testuser', - graph_password='testpass' + cassandra_host='cassandra.example.com', + cassandra_username='testuser', + cassandra_password='testpass' ) assert processor.graph_host == ['cassandra.example.com'] @@ -46,11 +48,41 @@ class TestCassandraStorageProcessor: processor = Processor( taskgroup=taskgroup_mock, - graph_username='testuser' + cassandra_username='testuser' ) assert processor.username == 'testuser' assert processor.password is None + + def test_processor_initialization_backward_compatibility(self): + \"\"\"Test processor initialization with old graph_* parameters (backward compatibility)\"\"\" + taskgroup_mock = MagicMock() + + processor = Processor( + taskgroup=taskgroup_mock, + graph_host='old-host', + graph_username='old-user', + graph_password='old-pass' + ) + + assert processor.graph_host == ['old-host'] + assert processor.username == 'old-user' + assert processor.password == 'old-pass' + + def test_processor_parameter_precedence(self): + \"\"\"Test that new cassandra_* parameters take precedence over old graph_* parameters\"\"\" + taskgroup_mock = MagicMock() + + processor = Processor( + taskgroup=taskgroup_mock, + cassandra_host='new-host', + graph_host='old-host', # Should be ignored + cassandra_username='new-user', + graph_username='old-user' # Should be ignored + ) + + assert processor.graph_host == ['new-host'] # New parameter wins + assert processor.username == 'new-user' # New parameter wins @pytest.mark.asyncio @patch('trustgraph.storage.triples.cassandra.write.TrustGraph') @@ -62,8 +94,8 @@ class TestCassandraStorageProcessor: processor = Processor( taskgroup=taskgroup_mock, - graph_username='testuser', - graph_password='testpass' + cassandra_username='testuser', + cassandra_password='testpass' ) # Create mock message @@ -76,7 +108,7 @@ class TestCassandraStorageProcessor: # Verify TrustGraph was called with auth parameters mock_trustgraph.assert_called_once_with( - hosts=['localhost'], + hosts=['cassandra'], # Updated default keyspace='user1', table='collection1', username='testuser', @@ -104,7 +136,7 @@ class TestCassandraStorageProcessor: # Verify TrustGraph was called without auth parameters mock_trustgraph.assert_called_once_with( - hosts=['localhost'], + hosts=['cassandra'], # Updated default keyspace='user2', table='collection2' ) @@ -225,16 +257,16 @@ class TestCassandraStorageProcessor: # Verify parent add_args was called mock_parent_add_args.assert_called_once_with(parser) - # Verify our specific arguments were added + # Verify our specific arguments were added (now using cassandra_* names) # Parse empty args to check defaults args = parser.parse_args([]) - assert hasattr(args, 'graph_host') - assert args.graph_host == 'localhost' - assert hasattr(args, 'graph_username') - assert args.graph_username is None - assert hasattr(args, 'graph_password') - assert args.graph_password is None + assert hasattr(args, 'cassandra_host') + assert args.cassandra_host == 'cassandra' # Updated default + assert hasattr(args, 'cassandra_username') + assert args.cassandra_username is None + assert hasattr(args, 'cassandra_password') + assert args.cassandra_password is None def test_add_args_with_custom_values(self): """Test add_args with custom command line values""" @@ -246,31 +278,42 @@ class TestCassandraStorageProcessor: with patch('trustgraph.storage.triples.cassandra.write.TriplesStoreService.add_args'): Processor.add_args(parser) - # Test parsing with custom values + # Test parsing with custom values (new cassandra_* arguments) args = parser.parse_args([ - '--graph-host', 'cassandra.example.com', - '--graph-username', 'testuser', - '--graph-password', 'testpass' + '--cassandra-host', 'cassandra.example.com', + '--cassandra-username', 'testuser', + '--cassandra-password', 'testpass' ]) - assert args.graph_host == 'cassandra.example.com' - assert args.graph_username == 'testuser' - assert args.graph_password == 'testpass' + assert args.cassandra_host == 'cassandra.example.com' + assert args.cassandra_username == 'testuser' + assert args.cassandra_password == 'testpass' - def test_add_args_short_form(self): - """Test add_args with short form arguments""" + def test_add_args_with_env_vars(self): + """Test add_args shows environment variables in help text""" from argparse import ArgumentParser from unittest.mock import patch + import os parser = ArgumentParser() + # Set environment variables + env_vars = { + 'CASSANDRA_HOST': 'env-host1,env-host2', + 'CASSANDRA_USERNAME': 'env-user', + 'CASSANDRA_PASSWORD': 'env-pass' + } + with patch('trustgraph.storage.triples.cassandra.write.TriplesStoreService.add_args'): - Processor.add_args(parser) - - # Test parsing with short form - args = parser.parse_args(['-g', 'short.example.com']) - - assert args.graph_host == 'short.example.com' + with patch.dict(os.environ, env_vars, clear=True): + Processor.add_args(parser) + + # Check that help text includes environment variable info + help_text = parser.format_help() + assert 'env-host1,env-host2' in help_text + assert 'env-user' in help_text + assert '' in help_text # Password should be hidden + assert 'env-pass' not in help_text # Password value not shown @patch('trustgraph.storage.triples.cassandra.write.Processor.launch') def test_run_function(self, mock_launch):