"""Unit tests for weekly_activity.config — pure logic, no network.""" from __future__ import annotations import os import tempfile import unittest from pathlib import Path from weekly_activity.config import ( ActivityConfig, BtsSettings, ConfigError, GithubSettings, GitlabSettings, LaunchpadSettings, default_config_path, format_toml, load_config, save_config, ) class ConfigRoundTripTest(unittest.TestCase): def test_round_trip_preserves_all_sections(self) -> None: config = ActivityConfig( launchpad=LaunchpadSettings( username="lp-user", mode="credentials", credentials_file="/tmp/lp-creds.json", service="staging", ), github=GithubSettings(username='gh"user', token="tok\\en"), gitlab=GitlabSettings( username="gl-user", url="https://salsa.debian.org", token_env="SALSA_TOKEN", ), bts=BtsSettings(email="dev@example.org"), ) with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "weekly-activity.toml" save_config(config, path) self.assertEqual(load_config(path), config) def test_save_creates_owner_only_file(self) -> None: config = ActivityConfig(bts=BtsSettings(email="x@example.org")) with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "nested" / "cfg.toml" save_config(config, path) self.assertEqual(os.stat(path).st_mode & 0o777, 0o600) def test_empty_optionals_round_trip_to_defaults(self) -> None: config = ActivityConfig( github=GithubSettings(username="octocat"), bts=BtsSettings(email="a@b.c"), ) text = format_toml(config) reparsed = load_config_from_text(text) github = reparsed.github assert github is not None self.assertEqual(github.username, "octocat") self.assertEqual(github.token, "") self.assertIsNone(reparsed.launchpad) def test_escapes_quotes_backslashes_and_newlines(self) -> None: config = ActivityConfig(github=GithubSettings(username='a"b\\c\nd')) reparsed = load_config_from_text(format_toml(config)) github = reparsed.github assert github is not None self.assertEqual(github.username, 'a"b\\c\nd') class ConfigLoadingTest(unittest.TestCase): def test_missing_file_raises_filenotfound(self) -> None: with self.assertRaises(FileNotFoundError): load_config(Path("/nonexistent/weekly-activity.toml")) def test_unknown_section_rejected(self) -> None: with self.assertRaises(ConfigError): load_config_from_text("[gitea]\nusername = 'x'\n") def test_unknown_key_rejected(self) -> None: with self.assertRaises(ConfigError): load_config_from_text('[github]\nusername = "x"\ntokken = "t"\n') def test_non_string_value_rejected(self) -> None: with self.assertRaises(ConfigError): load_config_from_text("[github]\nusername = 42\n") def test_bad_launchpad_mode_rejected(self) -> None: with self.assertRaises(ConfigError): load_config_from_text('[launchpad]\nusername = "u"\nmode = "oauth2"\n') def test_enabled_sources_stable_order(self) -> None: config = load_config_from_text( '[bts]\nemail = "e"\n\n[github]\nusername = "g"\n' ) self.assertEqual(config.enabled_sources(), ["github", "bts"]) def test_default_path_uses_xdg_or_home(self) -> None: old = os.environ.get("XDG_CONFIG_HOME") try: os.environ["XDG_CONFIG_HOME"] = "/custom/cfg" self.assertEqual(default_config_path(), Path("/custom/cfg/weekly-activity.toml")) del os.environ["XDG_CONFIG_HOME"] self.assertTrue(str(default_config_path()).endswith("/.config/weekly-activity.toml")) finally: if old is not None: os.environ["XDG_CONFIG_HOME"] = old def load_config_from_text(text: str) -> ActivityConfig: """Helper: parse TOML through the public loader without touching disk.""" with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) / "weekly-activity.toml" path.write_text(text, encoding="utf-8") return load_config(path) if __name__ == "__main__": unittest.main()