forked from vhaudiquet/weekly-activity
- `weekly-activity config`: interactive wizard writing ~/.config/weekly-activity.toml (XDG-aware, --config override), one section per source, 0600 permissions, tokens never echoed; re-runs show current settings and pre-fill defaults - `weekly-activity report` (and bare invocation): loads enabled sources, collects each independently, joins blocks under one header; a failing source renders as [skipped: <source> — error] without aborting - config.py: typed schema, tomllib reader with strict validation, hand-formatted TOML writer (stdlib-only, no new dependency) - aggregate.py: source spec building + failure-isolated collection + combined rendering - README: document config/report usage and config schema - tests/: 16 stdlib-unittest cases (config round-trip/escaping/validation, aggregation mapping/isolation)
123 lines
4.4 KiB
Python
123 lines
4.4 KiB
Python
"""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()
|