diff --git a/examples/local_service_run.py b/examples/local_service_run.py index babbb11..2c51452 100644 --- a/examples/local_service_run.py +++ b/examples/local_service_run.py @@ -97,7 +97,7 @@ def upsert_sandbox_listener( updated = client.update_listener( current.id, webhook_url=public_webhook_url, - email=current.email, + emails=current.emails, filters=current.filters, name=current.name or preferred_listener_name, active=True, diff --git a/notamify_sdk/client.py b/notamify_sdk/client.py index 0260472..0abeb76 100644 --- a/notamify_sdk/client.py +++ b/notamify_sdk/client.py @@ -164,7 +164,7 @@ def list_listeners(self) -> list[Listener]: def create_listener( self, webhook_url: str | None = None, - email: str = "", + emails: list[str] | None = None, filters: ListenerFilters | Mapping[str, Any] | None = None, name: str = "", active: bool | None = None, @@ -175,7 +175,7 @@ def create_listener( body = self._prepare_body( self._build_listener_request_body( webhook_url=webhook_url, - email=email, + emails=emails, filters=filters, name=name, active=active, @@ -192,7 +192,7 @@ def update_listener( self, listener_id: str, webhook_url: str | None = None, - email: str = "", + emails: list[str] | None = None, filters: ListenerFilters | Mapping[str, Any] | None = None, name: str = "", active: bool | None = None, @@ -203,7 +203,7 @@ def update_listener( body = self._prepare_body( self._build_listener_request_body( webhook_url=webhook_url, - email=email, + emails=emails, filters=filters, name=name, active=active, @@ -428,7 +428,7 @@ def _iterate_notam_pages( def _build_listener_request_body( self, webhook_url: str | None, - email: str, + emails: list[str] | None, filters: ListenerFilters | Mapping[str, Any] | None, name: str, active: bool | None, @@ -438,7 +438,7 @@ def _build_listener_request_body( ) -> dict[str, Any]: return { "webhook_url": self._normalize_listener_text(webhook_url), - "email": self._normalize_listener_text(email), + "emails": emails, "filters": filters or {}, "name": self._normalize_listener_text(name), "active": active, diff --git a/notamify_sdk/models.py b/notamify_sdk/models.py index 4fce77b..cd03868 100644 --- a/notamify_sdk/models.py +++ b/notamify_sdk/models.py @@ -129,7 +129,7 @@ def normalize_types(cls, value: Any) -> Any: class ListenerRequest(NotamifyModel): name: str | None = None webhook_url: str | None = None - email: str | None = None + emails: list[str] | None = None filters: ListenerFilters = Field(default_factory=ListenerFilters) lifecycle: ListenerLifecycleRequest | None = None active: bool | None = None @@ -155,7 +155,7 @@ class Listener(NotamifyModel): id: str = "" name: str = "" webhook_url: str = "" - email: str = "" + emails: list[str] = Field(default_factory=list) filters: ListenerFilters = Field(default_factory=ListenerFilters) lifecycle: ListenerLifecycle = Field(default_factory=ListenerLifecycle) metadata: ListenerMetadata = Field(default_factory=ListenerMetadata) diff --git a/notamify_watcher_sdk/client.py b/notamify_watcher_sdk/client.py index 425cd06..442ba67 100644 --- a/notamify_watcher_sdk/client.py +++ b/notamify_watcher_sdk/client.py @@ -34,7 +34,7 @@ def base_url(self, value: str) -> None: def create_listener( self, webhook_url: str, - email: str = "", + emails: list[str] | None = None, filters: Mapping[str, Any] | None = None, name: str = "", active: bool | None = None, @@ -44,7 +44,7 @@ def create_listener( ) -> Listener: return super().create_listener( webhook_url=webhook_url, - email=email, + emails=emails, filters=filters, name=name, active=active, @@ -57,7 +57,7 @@ def update_listener( self, listener_id: str, webhook_url: str, - email: str = "", + emails: list[str] | None = None, filters: Mapping[str, Any] | None = None, name: str = "", active: bool | None = None, @@ -68,7 +68,7 @@ def update_listener( return super().update_listener( listener_id=listener_id, webhook_url=webhook_url, - email=email, + emails=emails, filters=filters, name=name, active=active, diff --git a/tests/test_client.py b/tests/test_client.py index 372573a..cf2a517 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -57,6 +57,7 @@ def do_GET(self): "id": "l1", "name": "listener-1", "webhook_url": "https://x", + "emails": ["ops@example.com"], "filters": {"notam_icao": ["KJFK"]}, "lifecycle": {"enabled": False, "types": []}, "metadata": {"notams_shipped": 7}, @@ -189,6 +190,7 @@ def do_POST(self): "id": "new", "name": body.get("name", ""), "webhook_url": "https://x", + "emails": body.get("emails", []), "filters": body.get("filters", {}), "lifecycle": body.get("lifecycle", {"enabled": False, "types": []}), "metadata": {"notams_shipped": 0}, @@ -268,6 +270,7 @@ def do_PUT(self): "id": "l1", "name": body.get("name", ""), "webhook_url": "https://x2", + "emails": body.get("emails", []), "filters": body.get("filters", {}), "lifecycle": body.get("lifecycle", {"enabled": False, "types": []}), "metadata": {"notams_shipped": 10}, @@ -335,9 +338,11 @@ def test_watcher_methods(self): self.assertFalse(listeners[0].lifecycle.enabled) self.assertFalse(listeners[0].lifecycle_enabled) self.assertEqual(listeners[0].team.owner, "teammate-1") + self.assertEqual(listeners[0].emails, ["ops@example.com"]) created = client.create_listener( "https://x", + emails=["created@example.com"], mode="sandbox", lifecycle={"enabled": True, "types": ["cancelled"]}, ) @@ -347,6 +352,9 @@ def test_watcher_methods(self): self.assertEqual(created.lifecycle.types[0].value, "CANCELLED") self.assertTrue(created.lifecycle_enabled) self.assertEqual(created.webhook_secret, "nmf_wh_new_listener") + self.assertEqual(created.emails, ["created@example.com"]) + self.assertEqual(_Handler.last_create_body.get("emails"), ["created@example.com"]) + self.assertNotIn("email", _Handler.last_create_body) self.assertEqual(_Handler.last_create_body.get("mode"), "sandbox") self.assertEqual( _Handler.last_create_body.get("lifecycle"), @@ -375,10 +383,10 @@ def test_watcher_methods(self): def test_update_listener_preserves_explicit_empty_fields(self): client = NotamifyClient(token="t", watcher_base_url=self.base_url, api_base_url=self.base_url) - client.update_listener("l1", "https://x2", email="", name="") - self.assertIn("email", _Handler.last_update_body) + client.update_listener("l1", "https://x2", emails=[], name="") + self.assertIn("emails", _Handler.last_update_body) self.assertIn("name", _Handler.last_update_body) - self.assertEqual(_Handler.last_update_body["email"], "") + self.assertEqual(_Handler.last_update_body["emails"], []) self.assertEqual(_Handler.last_update_body["name"], "") def test_default_user_agent_tracks_sdk_version(self): diff --git a/tests/test_models.py b/tests/test_models.py index 76a7ecc..d282c50 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -210,6 +210,7 @@ def test_listener_request_accepts_nested_lifecycle_shape(self): model = ListenerRequest.model_validate( { "webhook_url": "https://example.com/hook", + "emails": ["ops@example.com"], "filters": {}, "lifecycle": { "enabled": True, @@ -217,13 +218,19 @@ def test_listener_request_accepts_nested_lifecycle_shape(self): }, } ) + self.assertEqual(model.emails, ["ops@example.com"]) self.assertTrue(model.lifecycle.enabled) self.assertEqual([item.value for item in model.lifecycle.types], ["CANCELLED", "REPLACED"]) + def test_listener_request_rejects_scalar_emails(self): + with self.assertRaises(ValidationError): + ListenerRequest.model_validate({"webhook_url": "https://example.com/hook", "emails": "ops@example.com"}) + def test_listener_model_maps_legacy_lifecycle_enabled_to_nested_shape(self): model = Listener.model_validate( { "id": "l1", + "emails": ["ops@example.com"], "filters": {}, "metadata": {"notams_shipped": 0}, "active": True, @@ -233,6 +240,7 @@ def test_listener_model_maps_legacy_lifecycle_enabled_to_nested_shape(self): "updated_at": "2026-03-01T10:01:00Z", } ) + self.assertEqual(model.emails, ["ops@example.com"]) self.assertTrue(model.lifecycle.enabled) self.assertTrue(model.lifecycle_enabled)