-
Notifications
You must be signed in to change notification settings - Fork 22
fix: add pg8000 graceful shutdown on Cloud Run scale-down #1669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
panish16
wants to merge
3
commits into
bcgov:main
Choose a base branch
from
panish16:fix/pg8000-graceful-shutdown
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -379,3 +379,121 @@ def notify_callback(request): | |
| assert stored.registration_id == registration.id | ||
| assert stored.status == InteractionStatus.SENT | ||
| assert stored.meta_data["email_type"] == "STRATA_HOTEL_REGISTRATION_ACTIVE" | ||
|
|
||
|
|
||
| def test_email_no_cloud_event_data(client, queue_envelope, simple_cloud_event): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what are these unit & integration tests added for in this project? wasnt it already at 100% coverage? |
||
| """Cloud event with no data is a no-op returning 200.""" | ||
| ce = simple_cloud_event(type="email", data={}) | ||
| envelope = queue_envelope(cloud_event=ce) | ||
| response = client.post("/", json=envelope) | ||
| assert response.status_code == HTTPStatus.OK | ||
|
|
||
|
|
||
| def test_email_application_not_found(client, session, queue_envelope, simple_cloud_event): | ||
| """Returns 404 when the application_number in the event does not exist.""" | ||
| data = { | ||
| "email_type": "HOST_AUTO_APPROVED", | ||
| "application_number": "NONEXISTENT-APP-12345", | ||
| } | ||
| ce = simple_cloud_event(type="email", data=data) | ||
| envelope = queue_envelope(cloud_event=ce) | ||
| response = client.post("/", json=envelope) | ||
| assert response.status_code == HTTPStatus.NOT_FOUND | ||
|
|
||
|
|
||
| def test_email_registration_not_found(client, session, queue_envelope, simple_cloud_event): | ||
| """Returns 404 when the registration_number in the event does not exist.""" | ||
| data = { | ||
| "email_type": "HOST_RENEWAL_REMINDER", | ||
| "registration_number": "NONEXISTENT-REG-99999", | ||
| } | ||
| ce = simple_cloud_event(type="email", data=data) | ||
| envelope = queue_envelope(cloud_event=ce) | ||
| response = client.post("/", json=envelope) | ||
| assert response.status_code == HTTPStatus.NOT_FOUND | ||
|
|
||
|
|
||
| @pytest.mark.conf( | ||
| KEYCLOAK_AUTH_TOKEN_URL="http://my-auth-url", | ||
| NOTIFY_SVC_URL="http://my-notify-mock", | ||
| NOTIFY_API_TIMEOUT=30, | ||
| EMAIL_HOUSING_RECIPIENT_EMAIL="[email protected]", | ||
| ) | ||
| @responses.activate | ||
| def test_email_notify_api_failure( | ||
| app, | ||
| client, | ||
| session, | ||
| simple_cloud_event, | ||
| queue_envelope, | ||
| setup_parents, | ||
| inject_config, | ||
| ): | ||
| """Returns an error status when the notify API call fails.""" | ||
| responses.add( | ||
| responses.POST, | ||
| app.config.get("KEYCLOAK_AUTH_TOKEN_URL"), | ||
| json={"access_token": "token"}, | ||
| status=200, | ||
| ) | ||
| responses.add( | ||
| responses.POST, | ||
| app.config.get("NOTIFY_SVC_URL"), | ||
| json={"message": "Service unavailable"}, | ||
| status=503, | ||
| ) | ||
|
|
||
| registration = create_registration(session, setup_parents) | ||
|
|
||
| data = { | ||
| "email_type": "HOST_REGISTRATION_CANCELLED", | ||
| "registration_number": registration.registration_number, | ||
| } | ||
| ce = simple_cloud_event(type="email", data=data) | ||
| envelope = queue_envelope(cloud_event=ce) | ||
|
|
||
| response = client.post("/", json=envelope) | ||
| assert response.status_code == HTTPStatus.BAD_REQUEST | ||
|
|
||
|
|
||
| @pytest.mark.conf( | ||
| KEYCLOAK_AUTH_TOKEN_URL="http://my-auth-url", | ||
| NOTIFY_SVC_URL="http://my-notify-mock", | ||
| NOTIFY_API_TIMEOUT=30, | ||
| EMAIL_HOUSING_RECIPIENT_EMAIL="[email protected]", | ||
| ) | ||
| @responses.activate | ||
| def test_email_host_registration_cancelled( | ||
| app, | ||
| client, | ||
| session, | ||
| simple_cloud_event, | ||
| queue_envelope, | ||
| setup_parents, | ||
| inject_config, | ||
| ): | ||
| """Returns 200 and calls notify-api directly for non-renewal email types.""" | ||
| responses.add( | ||
| responses.POST, | ||
| app.config.get("KEYCLOAK_AUTH_TOKEN_URL"), | ||
| json={"access_token": "token"}, | ||
| status=200, | ||
| ) | ||
| responses.add( | ||
| responses.POST, | ||
| app.config.get("NOTIFY_SVC_URL"), | ||
| json={"id": "notif-123"}, | ||
| status=200, | ||
| ) | ||
|
|
||
| registration = create_registration(session, setup_parents) | ||
|
|
||
| data = { | ||
| "email_type": "HOST_REGISTRATION_CANCELLED", | ||
| "registration_number": registration.registration_number, | ||
| } | ||
| ce = simple_cloud_event(type="email", data=data) | ||
| envelope = queue_envelope(cloud_event=ce) | ||
|
|
||
| response = client.post("/", json=envelope) | ||
| assert response.status_code == HTTPStatus.OK | ||
207 changes: 207 additions & 0 deletions
207
queue_services/strr-email/tests/unit/test_email_listener_utils.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| """Unit tests for email_listener utility functions (no Flask or DB required).""" | ||
|
|
||
| import pytest | ||
| from simple_cloudevent import SimpleCloudEvent | ||
| from strr_api.models import Registration | ||
|
|
||
| from strr_email.resources.email_listener import _get_address_detail | ||
| from strr_email.resources.email_listener import _get_client_recipients | ||
| from strr_email.resources.email_listener import _get_expiry_date | ||
| from strr_email.resources.email_listener import _get_rental_nickname | ||
| from strr_email.resources.email_listener import _get_service_provider | ||
| from strr_email.resources.email_listener import dict_keys_to_snake_case | ||
| from strr_email.resources.email_listener import get_email_info | ||
|
|
||
|
|
||
| class TestDictKeysToSnakeCase: | ||
| def test_converts_camel_case(self): | ||
| result = dict_keys_to_snake_case({"emailType": "HOST", "applicationNumber": "APP-001"}) | ||
| assert result == {"email_type": "HOST", "application_number": "APP-001"} | ||
|
|
||
| def test_already_snake_case_unchanged(self): | ||
| result = dict_keys_to_snake_case({"email_type": "HOST"}) | ||
| assert result == {"email_type": "HOST"} | ||
|
|
||
| def test_empty_dict(self): | ||
| assert dict_keys_to_snake_case({}) == {} | ||
|
|
||
| def test_preserves_none_values(self): | ||
| result = dict_keys_to_snake_case({"customContent": None, "registrationNumber": "H123"}) | ||
| assert result["custom_content"] is None | ||
| assert result["registration_number"] == "H123" | ||
|
|
||
| def test_single_word_key(self): | ||
| result = dict_keys_to_snake_case({"type": "HOST"}) | ||
| assert result == {"type": "HOST"} | ||
|
|
||
|
|
||
| class TestGetEmailInfo: | ||
| def test_returns_email_info_from_valid_data(self): | ||
| ce = SimpleCloudEvent( | ||
| id="id", | ||
| source="src", | ||
| subject="sub", | ||
| type="email", | ||
| data={"emailType": "HOST_RENEWAL_REMINDER", "registrationNumber": "H123"}, | ||
| ) | ||
| info = get_email_info(ce) | ||
| assert info is not None | ||
| assert info.email_type == "HOST_RENEWAL_REMINDER" | ||
| assert info.registration_number == "H123" | ||
|
|
||
| def test_returns_none_when_no_data(self): | ||
| ce = SimpleCloudEvent(id="id", source="src", subject="sub", type="email") | ||
| assert get_email_info(ce) is None | ||
|
|
||
| def test_returns_none_when_data_is_not_dict(self): | ||
| ce = SimpleCloudEvent(id="id", source="src", subject="sub", type="email", data="not-a-dict") | ||
| assert get_email_info(ce) is None | ||
|
|
||
| def test_converts_camel_case_fields(self): | ||
| ce = SimpleCloudEvent( | ||
| id="id", | ||
| source="src", | ||
| subject="sub", | ||
| type="email", | ||
| data={"emailType": "NOC", "applicationNumber": "APP-999", "customContent": "hello"}, | ||
| ) | ||
| info = get_email_info(ce) | ||
| assert info.email_type == "NOC" | ||
| assert info.application_number == "APP-999" | ||
| assert info.custom_content == "hello" | ||
|
|
||
|
|
||
| class TestGetAddressDetail: | ||
| def test_host_returns_requested_field(self): | ||
| app_dict = {"registration": {"unitAddress": {"streetNumber": "123"}}} | ||
| result = _get_address_detail(app_dict, Registration.RegistrationType.HOST, "streetNumber") | ||
| assert result == "123" | ||
|
|
||
| def test_non_host_returns_empty_string(self): | ||
| app_dict = {"registration": {"unitAddress": {"streetNumber": "123"}}} | ||
| result = _get_address_detail( | ||
| app_dict, Registration.RegistrationType.PLATFORM, "streetNumber" | ||
| ) | ||
| assert result == "" | ||
|
|
||
| def test_missing_field_returns_empty_string(self): | ||
| app_dict = {"registration": {"unitAddress": {}}} | ||
| result = _get_address_detail(app_dict, Registration.RegistrationType.HOST, "unitNumber") | ||
| assert result == "" | ||
|
|
||
| def test_strata_hotel_returns_empty_string(self): | ||
| app_dict = {"registration": {"unitAddress": {"streetNumber": "456"}}} | ||
| result = _get_address_detail( | ||
| app_dict, Registration.RegistrationType.STRATA_HOTEL, "streetNumber" | ||
| ) | ||
| assert result == "" | ||
|
|
||
|
|
||
| class TestGetExpiryDate: | ||
| def test_formats_valid_iso_date(self): | ||
| app_dict = {"header": {"registrationEndDate": "2025-12-15T00:00:00+00:00"}} | ||
| result = _get_expiry_date(app_dict) | ||
| assert "December" in result | ||
| assert "2025" in result | ||
|
|
||
| def test_returns_empty_when_no_date(self): | ||
| assert _get_expiry_date({"header": {}}) == "" | ||
|
|
||
| def test_returns_empty_when_no_header(self): | ||
| assert _get_expiry_date({}) == "" | ||
|
|
||
| def test_returns_empty_when_date_is_none(self): | ||
| assert _get_expiry_date({"header": {"registrationEndDate": None}}) == "" | ||
|
|
||
|
|
||
| class TestGetServiceProvider: | ||
| def test_platform_returns_legal_name(self): | ||
| app_dict = {"registration": {"businessDetails": {"legalName": "Acme Rentals Ltd."}}} | ||
| result = _get_service_provider(app_dict, Registration.RegistrationType.PLATFORM) | ||
| assert result == "Acme Rentals Ltd." | ||
|
|
||
| def test_host_returns_empty_string(self): | ||
| app_dict = {"registration": {"businessDetails": {"legalName": "Acme Rentals Ltd."}}} | ||
| result = _get_service_provider(app_dict, Registration.RegistrationType.HOST) | ||
| assert result == "" | ||
|
|
||
| def test_strata_hotel_returns_empty_string(self): | ||
| app_dict = {"registration": {"businessDetails": {"legalName": "Acme"}}} | ||
| result = _get_service_provider(app_dict, Registration.RegistrationType.STRATA_HOTEL) | ||
| assert result == "" | ||
|
|
||
|
|
||
| class TestGetClientRecipients: | ||
| def test_host_returns_primary_contact_email(self): | ||
| app_dict = { | ||
| "registration": { | ||
| "registrationType": Registration.RegistrationType.HOST.value, | ||
| "primaryContact": {"emailAddress": "[email protected]"}, | ||
| } | ||
| } | ||
| result = _get_client_recipients(app_dict) | ||
| assert result == "[email protected]" | ||
|
|
||
| def test_host_with_property_manager_contact_email(self): | ||
| app_dict = { | ||
| "registration": { | ||
| "registrationType": Registration.RegistrationType.HOST.value, | ||
| "primaryContact": {"emailAddress": "[email protected]"}, | ||
| "propertyManager": {"contact": {"emailAddress": "[email protected]"}}, | ||
| } | ||
| } | ||
| result = _get_client_recipients(app_dict) | ||
| assert "[email protected]" in result | ||
| assert "[email protected]" in result | ||
|
|
||
| def test_host_with_property_manager_business_email(self): | ||
| app_dict = { | ||
| "registration": { | ||
| "registrationType": Registration.RegistrationType.HOST.value, | ||
| "primaryContact": {"emailAddress": "[email protected]"}, | ||
| "propertyManager": { | ||
| "business": {"primaryContact": {"emailAddress": "[email protected]"}} | ||
| }, | ||
| } | ||
| } | ||
| result = _get_client_recipients(app_dict) | ||
| assert "[email protected]" in result | ||
| assert "[email protected]" in result | ||
|
|
||
| def test_platform_returns_empty_string(self): | ||
| app_dict = { | ||
| "registration": { | ||
| "registrationType": Registration.RegistrationType.PLATFORM.value, | ||
| } | ||
| } | ||
| assert _get_client_recipients(app_dict) == "" | ||
|
|
||
| def test_strata_hotel_returns_empty_string(self): | ||
| app_dict = { | ||
| "registration": { | ||
| "registrationType": Registration.RegistrationType.STRATA_HOTEL.value, | ||
| } | ||
| } | ||
| assert _get_client_recipients(app_dict) == "" | ||
|
|
||
|
|
||
| class TestGetRentalNickname: | ||
| def test_host_with_nickname_returns_nickname(self): | ||
| app_dict = {"registration": {"unitAddress": {"nickname": "Beach House"}}} | ||
| result = _get_rental_nickname(app_dict, Registration.RegistrationType.HOST) | ||
| assert result == "Beach House" | ||
|
|
||
| def test_host_without_nickname_returns_none(self): | ||
| app_dict = {"registration": {"unitAddress": {}}} | ||
| result = _get_rental_nickname(app_dict, Registration.RegistrationType.HOST) | ||
| assert result is None | ||
|
|
||
| def test_platform_returns_none(self): | ||
| app_dict = {"registration": {"unitAddress": {"nickname": "Beach House"}}} | ||
| result = _get_rental_nickname(app_dict, Registration.RegistrationType.PLATFORM) | ||
| assert result is None | ||
|
|
||
| def test_strata_hotel_returns_none(self): | ||
| app_dict = {"registration": {"unitAddress": {"nickname": "Resort"}}} | ||
| result = _get_rental_nickname(app_dict, Registration.RegistrationType.STRATA_HOTEL) | ||
| assert result is None |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whats the reason for adding this?