From 7ad812694ef47ba931c930754f1057304adba0c2 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Sat, 23 Aug 2025 12:11:35 -0400 Subject: [PATCH] fixed test case --- apprise/config/base.py | 10 ++--- apprise/plugins/base.py | 20 ++++------ tests/test_config_base.py | 78 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/apprise/config/base.py b/apprise/config/base.py index 59698de5..a57ceef0 100644 --- a/apprise/config/base.py +++ b/apprise/config/base.py @@ -898,19 +898,15 @@ class ConfigBase(URLBase): # Acquire our asset tokens tokens = result.get("asset", None) if tokens and isinstance(tokens, dict): - - # Prepare our default timezone (if specified) - timezone = str(tokens.get("timezone", tokens.get("tz", ""))) + raw_tz = tokens.get("timezone", tokens.get("tz")) + timezone = raw_tz.strip() if isinstance(raw_tz, str) else "" if timezone: default_timezone = zoneinfo(re.sub(r"[^\w/-]+", "", timezone)) if not default_timezone: ConfigBase.logger.warning( - 'Ignored invalid timezone "%s"', timezone) - # Restore our timezone back to what was found in the - # asset object + 'Ignored invalid timezone "%s"', raw_tz) default_timezone = asset.tzinfo else: - # Set our newly specified timezone asset._tzinfo = default_timezone # Iterate over remaining tokens diff --git a/apprise/plugins/base.py b/apprise/plugins/base.py index bb0455b5..0738a326 100644 --- a/apprise/plugins/base.py +++ b/apprise/plugins/base.py @@ -31,7 +31,6 @@ from datetime import tzinfo from functools import partial import re from typing import Any, ClassVar, Optional, TypedDict, Union -from zoneinfo import ZoneInfo from ..apprise_attachment import AppriseAttachment from ..common import ( @@ -298,7 +297,6 @@ class NotifyBase(URLBase): # automatically initialized by specifying ?tz= on the Apprise URLs __tzinfo = None - def __init__(self, **kwargs): """Initialize some general configuration that will keep things consistent when working with the notifiers that will inherit this @@ -350,17 +348,13 @@ class NotifyBase(URLBase): if "tz" in kwargs: value = kwargs["tz"] - if isinstance(value, ZoneInfo): - self.__tzinfo = kwargs["tz"] - - else: - self.__tzinfo = zoneinfo(value) - if not self.__tzinfo: - err = ( - f"An invalid notification timezone ({value}) was " - "specified.") - self.logger.warning(err) - raise TypeError(err) from None + self.__tzinfo = zoneinfo(value) + if not self.__tzinfo: + err = ( + f"An invalid notification timezone ({value}) was " + "specified.") + self.logger.warning(err) + raise TypeError(err) from None if "overflow" in kwargs: value = kwargs["overflow"] diff --git a/tests/test_config_base.py b/tests/test_config_base.py index dc29f838..061d234e 100644 --- a/tests/test_config_base.py +++ b/tests/test_config_base.py @@ -25,7 +25,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from datetime import tzinfo +from datetime import datetime, timezone as _tz, tzinfo from inspect import cleandoc # Disable logging for a cleaner testing output @@ -34,7 +34,7 @@ import logging import pytest import yaml -from apprise import Apprise, AppriseAsset, ConfigFormat +from apprise import Apprise, AppriseAsset, AppriseConfig, ConfigFormat from apprise.config import ConfigBase from apprise.plugins.email import NotifyEmail from apprise.utils.time import zoneinfo @@ -1592,3 +1592,77 @@ include: [file:///absolute/path/, relative/path, http://test.com] assert "file:///absolute/path/" in config assert "relative/path" in config assert "http://test.com" in config + + +def test_yaml_asset_timezone_and_asset_tokens(tmpdir): + """ + Covers: valid tz, reserved keys, invalid key, bool coercion, None->"", + invalid type for string, and %z formatting path used later by plugins. + """ + cfg = tmpdir.join("asset-tz.yml") + cfg.write( + """ +version: 1 +asset: + tz: " america/toronto " # case-insensitive + whitespace cleanup + _private: "ignored" # reserved (starts with _) + name_: "ignored" # reserved (ends with _) + not_a_field: "ignored" # invalid asset key + secure_logging: "yes" # string -> bool via parse_bool + app_id: null # None becomes empty string + app_desc: [ "list" ] # invalid type for string -> warning path +urls: + - json://localhost +""" + ) + + ac = AppriseConfig(paths=str(cfg)) + # Force a fresh parse and get the loaded plugin + servers = ac.servers() + assert len(servers) == 1 + + plugin = servers[0] + asset = plugin.asset + + # tz was accepted and normalised + assert getattr(asset.tzinfo, "key", None) == "America/Toronto" + # boolean coercion applied + assert asset.secure_logging is True + # None -> "" + assert asset.app_id == "" + + +def test_yaml_asset_timezone_invalid_and_precedence(tmpdir): + """ + If 'timezone' is present but invalid, it takes precedence over 'tz' + and MUST NOT set the asset to the 'tz' value. We assert that London + was not applied. We deliberately avoid asserting the exact fallback, + since environments may surface a system tz (datetime.timezone) that + lacks a `.key` attribute. + """ + cfg = tmpdir.join("asset-tz-invalid.yml") + cfg.write( + """ +version: 1 +asset: + timezone: null # invalid (will be seen as "None") + tz: Europe/London # would be valid, but 'timezone' wins +urls: + - json://localhost +""" + ) + + base_asset = AppriseAsset(timezone="UTC") + ac = AppriseConfig(paths=str(cfg)) + servers = ac.servers(asset=base_asset) + assert len(servers) == 1 + + tzinfo = servers[0].asset.tzinfo + + # The key assertion: 'tz' MUST NOT have been applied + assert getattr(tzinfo, "key", "").lower() != "europe/london" + + # Sanity check that something sensible is set + # Compare offsets at a fixed instant instead of object identity + dt = datetime(2024, 1, 1, 12, 0, tzinfo=_tz.utc) + assert tzinfo.utcoffset(dt) is not None