from __future__ import annotations import os from pathlib import Path import pytest from tenbackward.config import ConfigError, load_config, validate_config def _populate(env_setup) -> dict[str, str]: return {k: os.environ[k] for k in os.environ if k.startswith(("MASTODON_", "VISIBILITY", "SITE_", "HASHTAGS", "THROWNBACK_", "MAX_RETRIES", "RUN_AT", "TZ"))} def _full_values() -> dict[str, str]: return { "MASTODON_BASE_URL": "https://mastodon.example", "MASTODON_ACCESS_TOKEN": "x", "VISIBILITY": "public", "SITE_URL": "https://blog.example.com", "HASHTAGS": "#throwback", "THROWNBACK_PREFIX": "Throwback:", "MAX_RETRIES": "3", "RUN_AT": "09:00", "TZ": "Europe/Berlin", } def test_load_config_succeeds_with_complete_env(env_setup) -> None: config = load_config() assert config.mastodon_base_url == "https://mastodon.example" assert config.run_at == "09:00" assert config.max_retries == 3 assert config.throwback_prefix == "Throwback:" assert config.visibility == "public" def test_load_config_blog_defaults(env_setup) -> None: config = load_config() assert config.blog_repo_url == "https://git.chaospott.de/Chaospott/site" assert config.blog_dir.name == "blog" def test_load_config_blog_repo_url_override(env_setup, monkeypatch) -> None: monkeypatch.setenv("BLOG_REPO_URL", "https://example.com/repo.git") config = load_config() assert config.blog_repo_url == "https://example.com/repo.git" def test_validate_config_rejects_bad_blog_repo_url(env_setup) -> None: values = { "MASTODON_BASE_URL": "https://mastodon.example", "MASTODON_ACCESS_TOKEN": "x", "VISIBILITY": "public", "SITE_URL": "https://blog.example.com", "HASHTAGS": "#throwback", "THROWNBACK_PREFIX": "Throwback:", "MAX_RETRIES": "3", "RUN_AT": "09:00", "TZ": "Europe/Berlin", "BLOG_REPO_URL": "ftp://bad", } with pytest.raises(ConfigError, match="BLOG_REPO_URL"): validate_config(values) def test_validate_config_lists_every_missing_key(env_setup, monkeypatch) -> None: monkeypatch.delenv("MASTODON_ACCESS_TOKEN") monkeypatch.delenv("SITE_URL") values = {k: os.environ.get(k, "") for k in [ "MASTODON_BASE_URL", "MASTODON_ACCESS_TOKEN", "VISIBILITY", "SITE_URL", "HASHTAGS", "THROWNBACK_PREFIX", "MAX_RETRIES", "RUN_AT", "TZ", ]} with pytest.raises(ConfigError) as excinfo: validate_config(values) message = str(excinfo.value) assert "MASTODON_ACCESS_TOKEN" in message assert "SITE_URL" in message def test_validate_config_rejects_bad_run_at() -> None: values = _full_values() values["RUN_AT"] = "25:99" with pytest.raises(ConfigError): validate_config(values) def test_validate_config_rejects_invalid_url() -> None: values = _full_values() values["SITE_URL"] = "not-a-url" with pytest.raises(ConfigError, match="SITE_URL"): validate_config(values) values = _full_values() values["MASTODON_BASE_URL"] = "ftp://mastodon.example" with pytest.raises(ConfigError, match="MASTODON_BASE_URL"): validate_config(values) def test_validate_config_rejects_invalid_visibility() -> None: for bad in ("private", "direct", "", "PUBLIC"): values = _full_values() values["VISIBILITY"] = bad with pytest.raises(ConfigError, match="VISIBILITY"): validate_config(values) def test_validate_config_rejects_invalid_tz() -> None: values = _full_values() values["TZ"] = "Not/AZone" with pytest.raises(ConfigError, match="TZ"): validate_config(values) def test_validate_config_rejects_negative_max_retries() -> None: values = _full_values() values["MAX_RETRIES"] = "-1" with pytest.raises(ConfigError, match="MAX_RETRIES"): validate_config(values) def test_validate_config_accepts_zero_max_retries() -> None: values = _full_values() values["MAX_RETRIES"] = "0" validate_config(values) def test_load_config_applies_optional_defaults(env_setup, monkeypatch) -> None: monkeypatch.delenv("VISIBILITY", raising=False) monkeypatch.delenv("THROWNBACK_PREFIX", raising=False) monkeypatch.delenv("MAX_RETRIES", raising=False) monkeypatch.delenv("TZ", raising=False) config = load_config() assert config.throwback_prefix == "Throwback:" assert config.max_retries == 3 assert config.tz == "Europe/Berlin" assert config.visibility == "public" def test_load_config_reads_dotenv_file(tmp_path: Path, monkeypatch) -> None: dotenv = tmp_path / ".env" dotenv.write_text( "MASTODON_BASE_URL=https://from-file.example\n" "MASTODON_ACCESS_TOKEN=file-token\n" "VISIBILITY=unlisted\n" "SITE_URL=https://blog.example.com\n" "HASHTAGS=#throwback\n" "THROWNBACK_PREFIX=Werferückblick:\n" "MAX_RETRIES=5\n" "RUN_AT=12:34\n" "TZ=Europe/Berlin\n" ) for key in ( "MASTODON_BASE_URL", "MASTODON_ACCESS_TOKEN", "VISIBILITY", "SITE_URL", "HASHTAGS", "THROWNBACK_PREFIX", "MAX_RETRIES", "RUN_AT", "TZ", ): monkeypatch.delenv(key, raising=False) config = load_config(dotenv_path=dotenv) assert config.mastodon_base_url == "https://from-file.example" assert config.mastodon_access_token == "file-token" assert config.run_at == "12:34" assert config.visibility == "unlisted" assert config.throwback_prefix == "Werferückblick:" assert config.max_retries == 5 def test_env_overrides_dotenv(tmp_path: Path, monkeypatch) -> None: dotenv = tmp_path / ".env" dotenv.write_text("RUN_AT=01:00\n") monkeypatch.setenv("RUN_AT", "23:00") for key in ( "MASTODON_BASE_URL", "MASTODON_ACCESS_TOKEN", "VISIBILITY", "SITE_URL", "HASHTAGS", "THROWNBACK_PREFIX", "MAX_RETRIES", "TZ", ): monkeypatch.setenv(key, "x") monkeypatch.setenv("MASTODON_BASE_URL", "https://mastodon.example") monkeypatch.setenv("SITE_URL", "https://blog.example.com") monkeypatch.setenv("VISIBILITY", "public") monkeypatch.setenv("TZ", "Europe/Berlin") monkeypatch.setenv("MAX_RETRIES", "3") config = load_config(dotenv_path=dotenv) assert config.run_at == "23:00"