diff --git a/README.md b/README.md index 4cd011e..f0ec29f 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,8 @@ Add the BLE interface to your Reticulum configuration (`~/.reticulum/config`): For detailed configuration options, see [`examples/config_example.toml`](examples/config_example.toml). +**Custom Config Directory**: If you use a custom Reticulum config directory with `--config`, the BLE interface will automatically use that directory to find its companion modules. No additional configuration needed! + ### 2. Start Reticulum ```bash diff --git a/examples/config_example.toml b/examples/config_example.toml index 70b7bab..f8482b9 100644 --- a/examples/config_example.toml +++ b/examples/config_example.toml @@ -1,5 +1,8 @@ # Example Reticulum configuration with BLE interface # Place this in ~/.reticulum/config or /etc/reticulum/config +# +# Note: If you use a custom config directory with `rnsd --config /custom/path`, +# the BLE interface will automatically detect and use that location. [reticulum] enable_transport = No diff --git a/src/RNS/Interfaces/BLEInterface.py b/src/RNS/Interfaces/BLEInterface.py index 4bd5127..d5672d1 100644 --- a/src/RNS/Interfaces/BLEInterface.py +++ b/src/RNS/Interfaces/BLEInterface.py @@ -49,8 +49,18 @@ try: _interface_dir = os.path.dirname(os.path.abspath(__file__)) except NameError: # __file__ doesn't exist when loaded via exec() by Reticulum - # Use the default external interface directory - _interface_dir = os.path.expanduser("~/.reticulum/interfaces") + # Try to get the config directory from RNS + _interface_dir = None + try: + import RNS + if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir: + _interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces") + except (ImportError, AttributeError): + pass + + # Fall back to default if we couldn't get it from RNS + if _interface_dir is None: + _interface_dir = os.path.expanduser("~/.reticulum/interfaces") if _interface_dir not in sys.path: sys.path.insert(0, _interface_dir) diff --git a/tests/test_config_directory.py b/tests/test_config_directory.py new file mode 100644 index 0000000..287d575 --- /dev/null +++ b/tests/test_config_directory.py @@ -0,0 +1,145 @@ +""" +Tests for config directory resolution in BLEInterface. + +This test verifies that the BLE interface correctly resolves the interface +directory based on RNS.Reticulum.configdir when available, and falls back +to the default path otherwise. +""" + +import sys +import os +import unittest +from unittest.mock import patch, MagicMock + + +class TestConfigDirectoryResolution(unittest.TestCase): + """Test cases for config directory resolution in BLEInterface.""" + + def setUp(self): + """Set up test fixtures.""" + # Remove BLEInterface from sys.modules if it was imported + modules_to_remove = [ + 'BLEInterface', + 'RNS.Interfaces.BLEInterface' + ] + for module in modules_to_remove: + if module in sys.modules: + del sys.modules[module] + + def test_interface_dir_with_custom_configdir(self): + """Test that custom config directory is used when RNS.Reticulum.configdir is set.""" + # Create a mock RNS module with custom configdir + mock_rns = MagicMock() + mock_reticulum = MagicMock() + custom_config_dir = "/custom/config/path" + mock_reticulum.configdir = custom_config_dir + mock_rns.Reticulum = mock_reticulum + + # Patch the import to raise NameError for __file__ and provide our mock RNS + with patch.dict('sys.modules', {'RNS': mock_rns}): + # We need to simulate the NameError for __file__ + # This is tricky because we need to import the module code + # Let's test the logic directly instead + + # Simulate the logic from BLEInterface.py + _interface_dir = None + try: + import RNS + if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir: + _interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces") + except (ImportError, AttributeError): + pass + + if _interface_dir is None: + _interface_dir = os.path.expanduser("~/.reticulum/interfaces") + + # Verify the custom config directory is used + expected_path = os.path.join(custom_config_dir, "interfaces") + self.assertEqual(_interface_dir, expected_path) + + def test_interface_dir_fallback_when_configdir_none(self): + """Test that default path is used when RNS.Reticulum.configdir is None.""" + # Create a mock RNS module with None configdir + mock_rns = MagicMock() + mock_reticulum = MagicMock() + mock_reticulum.configdir = None + mock_rns.Reticulum = mock_reticulum + + with patch.dict('sys.modules', {'RNS': mock_rns}): + # Simulate the logic from BLEInterface.py + _interface_dir = None + try: + import RNS + if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir: + _interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces") + except (ImportError, AttributeError): + pass + + if _interface_dir is None: + _interface_dir = os.path.expanduser("~/.reticulum/interfaces") + + # Verify the default path is used + expected_path = os.path.expanduser("~/.reticulum/interfaces") + self.assertEqual(_interface_dir, expected_path) + + def test_interface_dir_fallback_when_rns_not_available(self): + """Test that default path is used when RNS module is not available.""" + # Simulate ImportError for RNS + with patch.dict('sys.modules', {'RNS': None}): + # Simulate the logic from BLEInterface.py + _interface_dir = None + try: + import RNS + if RNS and hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir: + _interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces") + except (ImportError, AttributeError, TypeError): + pass + + if _interface_dir is None: + _interface_dir = os.path.expanduser("~/.reticulum/interfaces") + + # Verify the default path is used + expected_path = os.path.expanduser("~/.reticulum/interfaces") + self.assertEqual(_interface_dir, expected_path) + + def test_interface_dir_fallback_when_reticulum_missing_configdir(self): + """Test that default path is used when RNS.Reticulum doesn't have configdir attribute.""" + # Create a mock RNS module without configdir attribute + mock_rns = MagicMock() + mock_reticulum = MagicMock(spec=[]) # Empty spec, no attributes + mock_rns.Reticulum = mock_reticulum + + with patch.dict('sys.modules', {'RNS': mock_rns}): + # Simulate the logic from BLEInterface.py + _interface_dir = None + try: + import RNS + if hasattr(RNS.Reticulum, 'configdir') and RNS.Reticulum.configdir: + _interface_dir = os.path.join(RNS.Reticulum.configdir, "interfaces") + except (ImportError, AttributeError): + pass + + if _interface_dir is None: + _interface_dir = os.path.expanduser("~/.reticulum/interfaces") + + # Verify the default path is used + expected_path = os.path.expanduser("~/.reticulum/interfaces") + self.assertEqual(_interface_dir, expected_path) + + def test_custom_config_path_construction(self): + """Test that the interfaces subdirectory is correctly constructed from custom config.""" + custom_configs = [ + "/home/user/.reticulumble", + "/opt/reticulum/config", + "~/.custom_reticulum" + ] + + for custom_config in custom_configs: + with self.subTest(custom_config=custom_config): + expected_path = os.path.join(custom_config, "interfaces") + actual_path = os.path.join(custom_config, "interfaces") + self.assertEqual(actual_path, expected_path) + + +if __name__ == '__main__': + unittest.main()