diff --git a/docs/strategy_plugin_runtime_contract.md b/docs/strategy_plugin_runtime_contract.md index 09cefdf..d57c50a 100644 --- a/docs/strategy_plugin_runtime_contract.md +++ b/docs/strategy_plugin_runtime_contract.md @@ -68,7 +68,7 @@ The default registry currently defines: | Plugin | Supported strategies | Supported mode | Escalated alert channel | | --- | --- | --- | --- | -| `crisis_response_shadow` | `tqqq_growth_income`, `soxl_soxx_trend_income` | `shadow` | `google_voice` | +| `crisis_response_shadow` | `tqqq_growth_income`, `soxl_soxx_trend_income` | `shadow` | `email` | To expand a plugin later, update the shared definition or pass an explicit definition registry into the parser/loader. This keeps future plugin eligibility @@ -124,33 +124,30 @@ when any of the following is true: - `suggested_action` is `defend` or `blocked` - `would_trade_if_enabled` is `true` -Platforms may still choose their delivery sinks, but Google Voice escalation via -the Gmail-to-Google-Voice SMS gateway should use -`quant_platform_kit.notifications.strategy_plugin_google_voice.publish_strategy_plugin_google_voice_alerts()`. +Platforms may still choose their delivery sinks, but email escalation should use +`quant_platform_kit.notifications.strategy_plugin_email.publish_strategy_plugin_email_alerts()`. The publisher builds the shared subject/body, prefixes platform context, returns structured sent/skipped/failed diagnostics, and can use -`StrategyPluginGoogleVoiceAlertMarkerStore` to skip alert keys that were already +`StrategyPluginEmailAlertMarkerStore` to skip alert keys that were already sent. -Platforms should expose this as Google Voice notification config, not as a -generic email alert surface. The recipient value is still an email-form address: -a normal mailbox receives an email, while a Google Voice mailbox/address can -also surface the Google Voice prompt. The public configuration names should be -channel specific: +Platforms should expose this as crisis email notification config. The recipient +value is an email address list: a normal mailbox receives an email, while a +Google Voice-associated mailbox/address can also surface a Google Voice prompt +through Google's own forwarding behavior. The public configuration names should +be channel-neutral: -- `CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS` -- `CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL` -- `CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD` +- `CRISIS_ALERT_EMAIL_RECIPIENTS` +- `CRISIS_ALERT_EMAIL_SENDER_EMAIL` +- `CRISIS_ALERT_EMAIL_SENDER_PASSWORD` By default the transport uses Gmail SMTP (`smtp.gmail.com`, port `465`, SSL), -but the sender is not part of the Google Voice channel contract. Non-Gmail -senders can override: +but the sender provider is not part of the email channel contract. Non-Gmail +senders can override the transport: -- `CRISIS_ALERT_GOOGLE_VOICE_SMTP_HOST` -- `CRISIS_ALERT_GOOGLE_VOICE_SMTP_PORT` -- `CRISIS_ALERT_GOOGLE_VOICE_SMTP_SECURITY` (`ssl`, `starttls`, or `none`) +- `CRISIS_ALERT_EMAIL_SMTP_HOST` +- `CRISIS_ALERT_EMAIL_SMTP_PORT` +- `CRISIS_ALERT_EMAIL_SMTP_SECURITY` (`ssl`, `starttls`, or `none`) -Future direct email notifications should use a separate namespace such as -`CRISIS_ALERT_EMAIL_*`. This keeps the Crisis Response plugin behavior consistent across IBKR, Schwab, LongBridge, Firstrade, and future platform runtimes. diff --git a/src/quant_platform_kit/common/__init__.py b/src/quant_platform_kit/common/__init__.py index 8ba3f38..6bdcf9c 100644 --- a/src/quant_platform_kit/common/__init__.py +++ b/src/quant_platform_kit/common/__init__.py @@ -43,7 +43,7 @@ DEFAULT_STRATEGY_PLUGIN_DEFINITIONS, PLUGIN_CRISIS_RESPONSE_SHADOW, PLUGIN_MODE_SHADOW, - STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE, + STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL, STRATEGY_PLUGIN_ALERT_ACTIONS, STRATEGY_PLUGIN_NON_ALERT_ROUTES, SUPPORTED_STRATEGY_PLUGIN_MODES, @@ -84,7 +84,7 @@ "STAGE_PARTIAL_SUBMITTED", "STAGE_RECONCILED", "STAGE_SUBMITTED", - "STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE", + "STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL", "STRATEGY_PLUGIN_ALERT_ACTIONS", "STRATEGY_PLUGIN_NON_ALERT_ROUTES", "SUPPORTED_STRATEGY_PLUGIN_MODES", diff --git a/src/quant_platform_kit/common/strategy_plugins.py b/src/quant_platform_kit/common/strategy_plugins.py index 4913e20..071c891 100644 --- a/src/quant_platform_kit/common/strategy_plugins.py +++ b/src/quant_platform_kit/common/strategy_plugins.py @@ -12,7 +12,7 @@ PLUGIN_CRISIS_RESPONSE_SHADOW = "crisis_response_shadow" PLUGIN_MODE_SHADOW = "shadow" -STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE = "google_voice" +STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL = "email" SUPPORTED_STRATEGY_PLUGIN_MODES = frozenset({PLUGIN_MODE_SHADOW}) DEFAULT_PLUGIN_ARTIFACT_CACHE_DIR = Path(tempfile.gettempdir()) / "quant_strategy_plugin_artifacts" STRATEGY_PLUGIN_NON_ALERT_ROUTES = frozenset({"no_action"}) @@ -67,7 +67,7 @@ def supports_strategy(self, strategy: str) -> bool: plugin=PLUGIN_CRISIS_RESPONSE_SHADOW, supported_strategies=CRISIS_RESPONSE_SHADOW_SUPPORTED_STRATEGIES, supported_modes=SUPPORTED_STRATEGY_PLUGIN_MODES, - alert_channels=(STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE,), + alert_channels=(STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL,), ) } diff --git a/src/quant_platform_kit/notifications/__init__.py b/src/quant_platform_kit/notifications/__init__.py index 4be0424..2e9bd09 100644 --- a/src/quant_platform_kit/notifications/__init__.py +++ b/src/quant_platform_kit/notifications/__init__.py @@ -2,25 +2,25 @@ from .email import parse_email_recipients, send_smtp_email from .events import NotificationPublisher, RenderedNotification, publish_rendered_notification -from .strategy_plugin_google_voice import ( - StrategyPluginGoogleVoiceAlertDelivery, - StrategyPluginGoogleVoiceAlertMarkerStore, - StrategyPluginGoogleVoiceAlertPublishResult, - StrategyPluginGoogleVoiceSettings, +from .strategy_plugin_email import ( + StrategyPluginEmailAlertDelivery, + StrategyPluginEmailAlertMarkerStore, + StrategyPluginEmailAlertPublishResult, + StrategyPluginEmailSettings, build_strategy_plugin_alert_context_label, - publish_strategy_plugin_google_voice_alerts, + publish_strategy_plugin_email_alerts, ) __all__ = [ "NotificationPublisher", "RenderedNotification", - "StrategyPluginGoogleVoiceAlertDelivery", - "StrategyPluginGoogleVoiceAlertMarkerStore", - "StrategyPluginGoogleVoiceAlertPublishResult", - "StrategyPluginGoogleVoiceSettings", + "StrategyPluginEmailAlertDelivery", + "StrategyPluginEmailAlertMarkerStore", + "StrategyPluginEmailAlertPublishResult", + "StrategyPluginEmailSettings", "build_strategy_plugin_alert_context_label", "parse_email_recipients", "publish_rendered_notification", - "publish_strategy_plugin_google_voice_alerts", + "publish_strategy_plugin_email_alerts", "send_smtp_email", ] diff --git a/src/quant_platform_kit/notifications/strategy_plugin_google_voice.py b/src/quant_platform_kit/notifications/strategy_plugin_email.py similarity index 82% rename from src/quant_platform_kit/notifications/strategy_plugin_google_voice.py rename to src/quant_platform_kit/notifications/strategy_plugin_email.py index 41f2de4..df0cc22 100644 --- a/src/quant_platform_kit/notifications/strategy_plugin_google_voice.py +++ b/src/quant_platform_kit/notifications/strategy_plugin_email.py @@ -1,4 +1,4 @@ -"""Google Voice notification helpers for strategy plugin alerts.""" +"""Email notification helpers for strategy plugin alerts.""" from __future__ import annotations @@ -18,9 +18,9 @@ from .email import parse_email_recipients, send_smtp_email -_DEFAULT_GOOGLE_VOICE_SMTP_HOST = "smtp.gmail.com" -_DEFAULT_GOOGLE_VOICE_SMTP_PORT = 465 -_DEFAULT_GOOGLE_VOICE_SMTP_SECURITY = "ssl" +_DEFAULT_EMAIL_SMTP_HOST = "smtp.gmail.com" +_DEFAULT_EMAIL_SMTP_PORT = 465 +_DEFAULT_EMAIL_SMTP_SECURITY = "ssl" _SMTP_SECURITY_NONE = "none" _SMTP_SECURITY_SSL = "ssl" _SMTP_SECURITY_STARTTLS = "starttls" @@ -32,46 +32,46 @@ @dataclass(frozen=True) -class StrategyPluginGoogleVoiceSettings: +class StrategyPluginEmailSettings: recipients: tuple[str, ...] = () sender_email: str | None = None sender_password: str | None = field(default=None, repr=False) - smtp_host: str = _DEFAULT_GOOGLE_VOICE_SMTP_HOST - smtp_port: int = _DEFAULT_GOOGLE_VOICE_SMTP_PORT - smtp_security: str = _DEFAULT_GOOGLE_VOICE_SMTP_SECURITY + smtp_host: str = _DEFAULT_EMAIL_SMTP_HOST + smtp_port: int = _DEFAULT_EMAIL_SMTP_PORT + smtp_security: str = _DEFAULT_EMAIL_SMTP_SECURITY timeout: float = 10.0 @classmethod - def from_object(cls, value: object) -> "StrategyPluginGoogleVoiceSettings": + def from_object(cls, value: object) -> "StrategyPluginEmailSettings": if isinstance(value, cls): return value return cls( recipients=tuple( - parse_email_recipients(_get_value(value, "crisis_alert_google_voice_recipients", ())) + parse_email_recipients(_get_value(value, "crisis_alert_email_recipients", ())) ), - sender_email=_first_non_empty(_get_value(value, "crisis_alert_google_voice_sender_email")), - sender_password=_get_value(value, "crisis_alert_google_voice_sender_password"), + sender_email=_first_non_empty(_get_value(value, "crisis_alert_email_sender_email")), + sender_password=_get_value(value, "crisis_alert_email_sender_password"), smtp_host=_first_non_empty( - _get_value(value, "crisis_alert_google_voice_smtp_host") + _get_value(value, "crisis_alert_email_smtp_host") ) - or _DEFAULT_GOOGLE_VOICE_SMTP_HOST, + or _DEFAULT_EMAIL_SMTP_HOST, smtp_port=_coerce_int( - _get_value(value, "crisis_alert_google_voice_smtp_port"), - _DEFAULT_GOOGLE_VOICE_SMTP_PORT, + _get_value(value, "crisis_alert_email_smtp_port"), + _DEFAULT_EMAIL_SMTP_PORT, ), smtp_security=_coerce_smtp_security( - _get_value(value, "crisis_alert_google_voice_smtp_security") + _get_value(value, "crisis_alert_email_smtp_security") ), ) def missing_fields(self) -> tuple[str, ...]: missing: list[str] = [] if not parse_email_recipients(self.recipients): - missing.append("CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS") + missing.append("CRISIS_ALERT_EMAIL_RECIPIENTS") if not str(self.sender_email or "").strip(): - missing.append("CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL") + missing.append("CRISIS_ALERT_EMAIL_SENDER_EMAIL") if not str(self.sender_password or "").strip(): - missing.append("CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD") + missing.append("CRISIS_ALERT_EMAIL_SENDER_PASSWORD") return tuple(missing) @property @@ -80,7 +80,7 @@ def is_configured(self) -> bool: @dataclass(frozen=True) -class StrategyPluginGoogleVoiceAlertDelivery: +class StrategyPluginEmailAlertDelivery: alert_key: str subject: str status: str @@ -101,8 +101,8 @@ def to_dict(self) -> dict[str, Any]: @dataclass(frozen=True) -class StrategyPluginGoogleVoiceAlertPublishResult: - deliveries: tuple[StrategyPluginGoogleVoiceAlertDelivery, ...] = () +class StrategyPluginEmailAlertPublishResult: + deliveries: tuple[StrategyPluginEmailAlertDelivery, ...] = () @property def attempted_count(self) -> int: @@ -120,7 +120,7 @@ def skipped_count(self) -> int: def failed_count(self) -> int: return sum(1 for delivery in self.deliveries if delivery.status == "failed") - def to_report_fields(self, *, prefix: str = "strategy_plugin_alert_google_voice") -> dict[str, Any]: + def to_report_fields(self, *, prefix: str = "strategy_plugin_alert_email") -> dict[str, Any]: return { f"{prefix}_attempted_count": self.attempted_count, f"{prefix}_sent_count": self.sent_count, @@ -131,11 +131,11 @@ def to_report_fields(self, *, prefix: str = "strategy_plugin_alert_google_voice" @dataclass(frozen=True) -class StrategyPluginGoogleVoiceAlertMarkerStore: +class StrategyPluginEmailAlertMarkerStore: local_dir: str | Path | None = None gcs_prefix_uri: str | None = None gcp_project_id: str | None = None - namespace: str = "strategy_plugin_google_voice_alerts" + namespace: str = "strategy_plugin_email_alerts" client_factory: Any = None def has_alert(self, alert_key: str) -> bool: @@ -152,7 +152,7 @@ def record_alert( metadata: Mapping[str, Any] | None = None, ) -> None: payload = { - "schema_version": "strategy_plugin_google_voice_alert_marker.v1", + "schema_version": "strategy_plugin_email_alert_marker.v1", "alert_key": str(alert_key), "recorded_at": datetime.now(timezone.utc).isoformat(), "metadata": dict(metadata or {}), @@ -215,26 +215,26 @@ def build_strategy_plugin_alert_context_label( return " / ".join(str(part).strip() for part in parts if str(part or "").strip()) -def publish_strategy_plugin_google_voice_alerts( +def publish_strategy_plugin_email_alerts( signals: Sequence[object], *, - google_voice_settings: StrategyPluginGoogleVoiceSettings | object, + email_settings: StrategyPluginEmailSettings | object, translator: Callable[..., str] | None = None, strategy_label: str | None = None, context_label: str | None = None, - alert_store: StrategyPluginGoogleVoiceAlertMarkerStore | object | None = None, + alert_store: StrategyPluginEmailAlertMarkerStore | object | None = None, send_notification: Callable[..., bool] = send_smtp_email, log_message: Callable[..., Any] = print, -) -> StrategyPluginGoogleVoiceAlertPublishResult: - settings = StrategyPluginGoogleVoiceSettings.from_object(google_voice_settings) +) -> StrategyPluginEmailAlertPublishResult: + settings = StrategyPluginEmailSettings.from_object(email_settings) messages = build_strategy_plugin_alert_messages( signals, translator=translator, strategy_label=strategy_label, context_label=context_label, - alert_namespace="strategy_plugin_google_voice_alert", + alert_namespace="strategy_plugin_email_alert", ) - deliveries: list[StrategyPluginGoogleVoiceAlertDelivery] = [] + deliveries: list[StrategyPluginEmailAlertDelivery] = [] missing_fields = settings.missing_fields() if missing_fields: for message in messages: @@ -242,11 +242,11 @@ def publish_strategy_plugin_google_voice_alerts( _delivery( message, status="skipped", - reason="missing_google_voice_config", + reason="missing_email_config", error=",".join(missing_fields), ) ) - result = StrategyPluginGoogleVoiceAlertPublishResult(tuple(deliveries)) + result = StrategyPluginEmailAlertPublishResult(tuple(deliveries)) _log_publish_result(result, log_message=log_message) return result @@ -268,7 +268,7 @@ def publish_strategy_plugin_google_voice_alerts( record_error = _store_record_error(alert_store, alert_key, message) combined_error = "; ".join(error for error in (store_error, record_error) if error) deliveries.append(_delivery(message, status="sent", error=combined_error or None)) - result = StrategyPluginGoogleVoiceAlertPublishResult(tuple(deliveries)) + result = StrategyPluginEmailAlertPublishResult(tuple(deliveries)) _log_publish_result(result, log_message=log_message) return result @@ -279,8 +279,8 @@ def _delivery( status: str, reason: str | None = None, error: str | None = None, -) -> StrategyPluginGoogleVoiceAlertDelivery: - return StrategyPluginGoogleVoiceAlertDelivery( +) -> StrategyPluginEmailAlertDelivery: + return StrategyPluginEmailAlertDelivery( alert_key=message.alert_key or _fallback_alert_key(message), subject=message.subject, status=status, @@ -293,7 +293,7 @@ def _delivery( def _send_message( send_notification: Callable[..., bool], message: StrategyPluginAlertMessage, - settings: StrategyPluginGoogleVoiceSettings, + settings: StrategyPluginEmailSettings, ) -> tuple[bool, str | None]: try: sent = send_notification( @@ -347,7 +347,7 @@ def _store_record_error( def _log_publish_result( - result: StrategyPluginGoogleVoiceAlertPublishResult, + result: StrategyPluginEmailAlertPublishResult, *, log_message: Callable[..., Any], ) -> None: @@ -356,7 +356,7 @@ def _log_publish_result( _call_log_message( log_message, ( - "strategy_plugin_alert_google_voice_result " + "strategy_plugin_alert_email_result " f"attempted={result.attempted_count} " f"sent={result.sent_count} " f"skipped={result.skipped_count} " @@ -400,11 +400,11 @@ def _coerce_smtp_security(value: Any) -> str: security = str(value or "").strip().lower() if security in _SMTP_SECURITY_VALUES: return security - return _DEFAULT_GOOGLE_VOICE_SMTP_SECURITY + return _DEFAULT_EMAIL_SMTP_SECURITY def _fallback_alert_key(message: StrategyPluginAlertMessage) -> str: - return "strategy_plugin_google_voice_alert/" + _clean_relative_key(message.subject or "unknown") + return "strategy_plugin_email_alert/" + _clean_relative_key(message.subject or "unknown") def _clean_relative_key(value: str) -> str: diff --git a/tests/test_google_voice_notifications.py b/tests/test_strategy_plugin_email_notifications.py similarity index 68% rename from tests/test_google_voice_notifications.py rename to tests/test_strategy_plugin_email_notifications.py index ac83267..2323874 100644 --- a/tests/test_google_voice_notifications.py +++ b/tests/test_strategy_plugin_email_notifications.py @@ -1,10 +1,10 @@ from types import SimpleNamespace from quant_platform_kit.notifications.email import parse_email_recipients, send_smtp_email -from quant_platform_kit.notifications.strategy_plugin_google_voice import ( - StrategyPluginGoogleVoiceAlertMarkerStore, - StrategyPluginGoogleVoiceSettings, - publish_strategy_plugin_google_voice_alerts, +from quant_platform_kit.notifications.strategy_plugin_email import ( + StrategyPluginEmailAlertMarkerStore, + StrategyPluginEmailSettings, + publish_strategy_plugin_email_alerts, ) @@ -71,12 +71,12 @@ def _alert_signal(): ) -def test_publish_strategy_plugin_google_voice_alerts_skips_missing_config(): +def test_publish_strategy_plugin_email_alerts_skips_missing_config(): observed = [] - result = publish_strategy_plugin_google_voice_alerts( + result = publish_strategy_plugin_email_alerts( [_alert_signal()], - google_voice_settings=StrategyPluginGoogleVoiceSettings(), + email_settings=StrategyPluginEmailSettings(), strategy_label="TQQQ", context_label="ibkr / paper / tqqq", send_notification=lambda **_kwargs: observed.append(_kwargs) or True, @@ -85,20 +85,20 @@ def test_publish_strategy_plugin_google_voice_alerts_skips_missing_config(): assert result.sent_count == 0 assert result.skipped_count == 1 - assert result.deliveries[0].reason == "missing_google_voice_config" - assert "CRISIS_ALERT_GOOGLE_VOICE_RECIPIENTS" in result.deliveries[0].error - assert "CRISIS_ALERT_GOOGLE_VOICE_SENDER_EMAIL" in result.deliveries[0].error - assert "CRISIS_ALERT_GOOGLE_VOICE_SENDER_PASSWORD" in result.deliveries[0].error + assert result.deliveries[0].reason == "missing_email_config" + assert "CRISIS_ALERT_EMAIL_RECIPIENTS" in result.deliveries[0].error + assert "CRISIS_ALERT_EMAIL_SENDER_EMAIL" in result.deliveries[0].error + assert "CRISIS_ALERT_EMAIL_SENDER_PASSWORD" in result.deliveries[0].error assert observed == [] -def test_publish_strategy_plugin_google_voice_alerts_sends_and_records_marker(tmp_path): +def test_publish_strategy_plugin_email_alerts_sends_and_records_marker(tmp_path): observed = [] - store = StrategyPluginGoogleVoiceAlertMarkerStore(local_dir=tmp_path) + store = StrategyPluginEmailAlertMarkerStore(local_dir=tmp_path) - result = publish_strategy_plugin_google_voice_alerts( + result = publish_strategy_plugin_email_alerts( [_alert_signal()], - google_voice_settings=StrategyPluginGoogleVoiceSettings( + email_settings=StrategyPluginEmailSettings( recipients=("risk@example.com",), sender_email="bot@example.com", sender_password="app-password", @@ -125,16 +125,16 @@ def test_publish_strategy_plugin_google_voice_alerts_sends_and_records_marker(tm assert store.has_alert(result.deliveries[0].alert_key) -def test_publish_strategy_plugin_google_voice_alerts_skips_duplicate_marker(tmp_path): - store = StrategyPluginGoogleVoiceAlertMarkerStore(local_dir=tmp_path) - settings = StrategyPluginGoogleVoiceSettings( +def test_publish_strategy_plugin_email_alerts_skips_duplicate_marker(tmp_path): + store = StrategyPluginEmailAlertMarkerStore(local_dir=tmp_path) + settings = StrategyPluginEmailSettings( recipients=("risk@example.com",), sender_email="bot@example.com", sender_password="app-password", ) - first = publish_strategy_plugin_google_voice_alerts( + first = publish_strategy_plugin_email_alerts( [_alert_signal()], - google_voice_settings=settings, + email_settings=settings, strategy_label="TQQQ", context_label="ibkr / paper / tqqq", alert_store=store, @@ -142,9 +142,9 @@ def test_publish_strategy_plugin_google_voice_alerts_skips_duplicate_marker(tmp_ log_message=lambda *_args, **_kwargs: None, ) - second = publish_strategy_plugin_google_voice_alerts( + second = publish_strategy_plugin_email_alerts( [_alert_signal()], - google_voice_settings=settings, + email_settings=settings, strategy_label="TQQQ", context_label="ibkr / paper / tqqq", alert_store=store, @@ -158,12 +158,12 @@ def test_publish_strategy_plugin_google_voice_alerts_skips_duplicate_marker(tmp_ assert second.deliveries[0].reason == "duplicate_alert" -def test_publish_strategy_plugin_google_voice_alerts_uses_transport_overrides(): +def test_publish_strategy_plugin_email_alerts_uses_transport_overrides(): observed = [] - result = publish_strategy_plugin_google_voice_alerts( + result = publish_strategy_plugin_email_alerts( [_alert_signal()], - google_voice_settings=StrategyPluginGoogleVoiceSettings( + email_settings=StrategyPluginEmailSettings( recipients=("voice@example.com",), sender_email="bot@example.com", sender_password="secret", @@ -184,12 +184,12 @@ def test_publish_strategy_plugin_google_voice_alerts_uses_transport_overrides(): assert observed[0]["use_ssl"] is False -def test_google_voice_settings_reads_sender_and_default_transport_names_only(): - settings = StrategyPluginGoogleVoiceSettings.from_object( +def test_email_settings_reads_sender_and_default_transport_names_only(): + settings = StrategyPluginEmailSettings.from_object( SimpleNamespace( - crisis_alert_google_voice_recipients="alerts@example.com; voice@example.com", - crisis_alert_google_voice_sender_email="sender@example.com", - crisis_alert_google_voice_sender_password="app-password", + crisis_alert_email_recipients="alerts@example.com; voice@example.com", + crisis_alert_email_sender_email="sender@example.com", + crisis_alert_email_sender_password="app-password", ) ) @@ -202,15 +202,15 @@ def test_google_voice_settings_reads_sender_and_default_transport_names_only(): assert settings.missing_fields() == () -def test_google_voice_settings_reads_optional_smtp_transport_overrides(): - settings = StrategyPluginGoogleVoiceSettings.from_object( +def test_email_settings_reads_optional_smtp_transport_overrides(): + settings = StrategyPluginEmailSettings.from_object( SimpleNamespace( - crisis_alert_google_voice_recipients="voice@example.com", - crisis_alert_google_voice_sender_email="sender@example.com", - crisis_alert_google_voice_sender_password="secret", - crisis_alert_google_voice_smtp_host="smtp.example.com", - crisis_alert_google_voice_smtp_port="587", - crisis_alert_google_voice_smtp_security="starttls", + crisis_alert_email_recipients="voice@example.com", + crisis_alert_email_sender_email="sender@example.com", + crisis_alert_email_sender_password="secret", + crisis_alert_email_smtp_host="smtp.example.com", + crisis_alert_email_smtp_port="587", + crisis_alert_email_smtp_security="starttls", ) ) diff --git a/tests/test_strategy_plugins.py b/tests/test_strategy_plugins.py index 43a6fed..7c8d2e2 100644 --- a/tests/test_strategy_plugins.py +++ b/tests/test_strategy_plugins.py @@ -8,7 +8,7 @@ DEFAULT_STRATEGY_PLUGIN_DEFINITIONS, PLUGIN_CRISIS_RESPONSE_SHADOW, PLUGIN_MODE_SHADOW, - STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE, + STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL, StrategyPluginDefinition, build_strategy_plugin_alert_messages, build_strategy_plugin_notification_lines, @@ -88,7 +88,7 @@ def test_default_plugin_definition_limits_crisis_response_to_supported_strategie definition = DEFAULT_STRATEGY_PLUGIN_DEFINITIONS[PLUGIN_CRISIS_RESPONSE_SHADOW] self.assertEqual(definition.supported_strategies, CRISIS_RESPONSE_SHADOW_SUPPORTED_STRATEGIES) - self.assertEqual(definition.alert_channels, (STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE,)) + self.assertEqual(definition.alert_channels, (STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL,)) validate_strategy_plugin_compatibility( strategy="tqqq_growth_income", plugin=PLUGIN_CRISIS_RESPONSE_SHADOW, @@ -128,7 +128,7 @@ def test_plugin_definition_can_extend_future_strategy_support(self): plugin=PLUGIN_CRISIS_RESPONSE_SHADOW, supported_strategies=frozenset({"global_etf_rotation"}), supported_modes=frozenset({PLUGIN_MODE_SHADOW}), - alert_channels=(STRATEGY_PLUGIN_ALERT_CHANNEL_GOOGLE_VOICE,), + alert_channels=(STRATEGY_PLUGIN_ALERT_CHANNEL_EMAIL,), ) }