forked from vhaudiquet/weekly-activity
Add config onboarding wizard and aggregated report
- `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)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
"""Unit tests for weekly_activity.aggregate — stubbed sources, no network."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from unittest import mock
|
||||
|
||||
from weekly_activity.aggregate import (
|
||||
SourceSpec,
|
||||
build_specs,
|
||||
collect_specs,
|
||||
format_combined,
|
||||
)
|
||||
from weekly_activity.config import (
|
||||
ActivityConfig,
|
||||
BtsSettings,
|
||||
GithubSettings,
|
||||
GitlabSettings,
|
||||
LaunchpadSettings,
|
||||
)
|
||||
from weekly_activity.model import ActivityReport, Section
|
||||
|
||||
|
||||
class StubSource:
|
||||
def __init__(self, name: str = "stub", *, fail: bool = False) -> None:
|
||||
self.name = name
|
||||
self._fail = fail
|
||||
|
||||
def collect(self, username: str, since: datetime, until: datetime) -> ActivityReport:
|
||||
if self._fail:
|
||||
raise RuntimeError("kaput")
|
||||
return ActivityReport(
|
||||
source=self.name,
|
||||
username=username,
|
||||
since=since,
|
||||
until=until,
|
||||
sections=[Section("Things", ["did a thing"])],
|
||||
warnings=["some query failed"],
|
||||
)
|
||||
|
||||
|
||||
def window() -> tuple[datetime, datetime]:
|
||||
return datetime(2026, 8, 19), datetime(2026, 8, 26)
|
||||
|
||||
|
||||
class BuildSpecsTest(unittest.TestCase):
|
||||
def test_maps_every_section_in_order(self) -> None:
|
||||
config = ActivityConfig(
|
||||
launchpad=LaunchpadSettings(username="lp"),
|
||||
github=GithubSettings(username="gh"),
|
||||
gitlab=GitlabSettings(username="gl"),
|
||||
bts=BtsSettings(email="bts@mail"),
|
||||
)
|
||||
specs = build_specs(config)
|
||||
self.assertEqual([s.name for s in specs], ["launchpad", "github", "gitlab", "bts"])
|
||||
self.assertEqual([s.username for s in specs], ["lp", "gh", "gl", "bts@mail"])
|
||||
|
||||
def test_disabled_sections_are_skipped(self) -> None:
|
||||
config = ActivityConfig(github=GithubSettings(username="only-one"))
|
||||
self.assertEqual([s.name for s in build_specs(config)], ["github"])
|
||||
|
||||
def test_launchpad_spec_expands_tilde_credentials_file(self) -> None:
|
||||
settings = LaunchpadSettings(
|
||||
username="u", mode="credentials", credentials_file="~/creds.json"
|
||||
)
|
||||
spec = build_specs(ActivityConfig(launchpad=settings))[0]
|
||||
with mock.patch("weekly_activity.aggregate.LaunchpadSource") as fake_source:
|
||||
spec.make()
|
||||
kwargs = fake_source.call_args.kwargs
|
||||
self.assertEqual(kwargs["credentials_file"], os.path.expanduser("~") + "/creds.json")
|
||||
self.assertFalse(kwargs["anonymous"])
|
||||
|
||||
|
||||
class CollectAndFormatTest(unittest.TestCase):
|
||||
def test_failure_isolated_per_source(self) -> None:
|
||||
since, until = window()
|
||||
specs = [
|
||||
SourceSpec(name="alpha", username="a", make=lambda: StubSource("alpha")),
|
||||
SourceSpec(name="beta", username="b", make=lambda: StubSource("beta", fail=True)),
|
||||
]
|
||||
collected = collect_specs(specs, since, until)
|
||||
self.assertIsNotNone(collected[0].report)
|
||||
self.assertIn("RuntimeError: kaput", collected[1].error or "")
|
||||
|
||||
text = format_combined(collected, since, until)
|
||||
self.assertIn("Weekly activity report — 2 source(s)", text)
|
||||
self.assertIn("Activity report — alpha / ~a", text)
|
||||
self.assertIn("[skipped: beta — RuntimeError: kaput]", text)
|
||||
|
||||
def test_warning_surfaced_inside_block(self) -> None:
|
||||
since, until = window()
|
||||
specs = [SourceSpec(name="gamma", username="g", make=lambda: StubSource("gamma"))]
|
||||
text = format_combined(collect_specs(specs, since, until), since, until)
|
||||
self.assertIn("Warnings (partial data — some queries failed):", text)
|
||||
self.assertIn("some query failed", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user