# Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. """Tests for reading and validating ~/.config/abusectl/config.toml.""" import os import pathlib import tempfile import unittest from unittest import mock from abusectl import config class TestConfig(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.root = pathlib.Path(self._tmp.name) def tearDown(self): self._tmp.cleanup() def _write(self, text: str) -> pathlib.Path: path = self.root / "config.toml" path.write_text(text, encoding="utf-8") return path def test_trusted_relays_and_cases_are_read(self): path = self._write( "[general]\n" 'cases = "~/cases"\n' 'trusted_relays = ["192.0.2.0/24"]\n' ) loaded = config.load(path) self.assertEqual(loaded.trusted_relays, ["192.0.2.0/24"]) self.assertEqual(loaded.cases, pathlib.Path.home() / "cases") def test_a_missing_file_is_reported_as_not_configured(self): with self.assertRaises(config.NotConfigured): config.load(self.root / "absent.toml") def test_an_empty_relay_list_is_not_configured(self): # Present but empty is the same as absent: parse must refuse either # way rather than guess, so they are one error. path = self._write("[general]\ntrusted_relays = []\n") with self.assertRaises(config.NotConfigured): config.load(path) def test_a_malformed_cidr_is_rejected_at_load(self): # Reported against the file that holds the typo, not later against a # message that did nothing wrong. path = self._write('[general]\ntrusted_relays = ["not-a-network"]\n') with self.assertRaises(ValueError): config.load(path) def test_the_cases_path_has_a_default(self): path = self._write('[general]\ntrusted_relays = ["192.0.2.0/24"]\n') self.assertEqual(config.load(path).cases, config.DEFAULT_CASES) def test_malformed_toml_is_not_reported_as_not_configured(self): # A syntax error is a broken file, which is a different problem from # an absent one and must not be answered with "run abusectl init". path = self._write("[general\ntrusted_relays = [") with self.assertRaises(Exception) as caught: config.load(path) self.assertNotIsInstance(caught.exception, config.NotConfigured) def test_an_empty_cases_value_falls_back_to_the_default(self): # Empty is the same as absent, per this module's own rule: writing # Path("") would put evidence in whatever directory the command # happened to run from. path = self._write( '[general]\ntrusted_relays = ["192.0.2.0/24"]\ncases = ""\n' ) self.assertEqual(config.load(path).cases, config.DEFAULT_CASES) def test_a_whitespace_cases_value_falls_back_to_the_default(self): path = self._write( '[general]\ntrusted_relays = ["192.0.2.0/24"]\ncases = " "\n' ) self.assertEqual(config.load(path).cases, config.DEFAULT_CASES) def test_a_string_trusted_relays_is_rejected_clearly(self): # Easy to write by hand without the brackets. Iterating the string # validates single characters and reports an error naming nothing # the user can find in their file. path = self._write('[general]\ntrusted_relays = "192.0.2.0/24"\n') with self.assertRaises(ValueError) as caught: config.load(path) self.assertIn("must be a list", str(caught.exception)) def test_a_relay_entry_that_is_not_a_string_is_rejected_clearly(self): path = self._write("[general]\ntrusted_relays = [42]\n") with self.assertRaises(ValueError): config.load(path) def test_the_config_path_follows_xdg_config_home(self): with mock.patch.dict(os.environ, {"XDG_CONFIG_HOME": "/tmp/xdg-probe"}): self.assertEqual( config.path(), pathlib.Path("/tmp/xdg-probe/abusectl/config.toml"), ) if __name__ == "__main__": unittest.main()