From d5637cc7b4d014191a45a410c344f7ffc1d0ad60 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:24:01 -0800 Subject: [PATCH 01/12] chore(deps): update dependency werkzeug to v3.1.6 [security] (#3625) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- auth-api/poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/auth-api/poetry.lock b/auth-api/poetry.lock index b97ecf8ec..6dbd7cbde 100644 --- a/auth-api/poetry.lock +++ b/auth-api/poetry.lock @@ -3290,14 +3290,14 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "werkzeug" -version = "3.1.5" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, - {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] From 337a854630249c5858f971e7208518b45e00cec1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:24:23 -0800 Subject: [PATCH 02/12] Bump werkzeug from 3.1.5 to 3.1.6 in /queue_services/account-mailer (#3623) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- queue_services/account-mailer/poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/queue_services/account-mailer/poetry.lock b/queue_services/account-mailer/poetry.lock index f16578da9..42bb40a3f 100644 --- a/queue_services/account-mailer/poetry.lock +++ b/queue_services/account-mailer/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "aiofiles" @@ -3241,14 +3241,14 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [[package]] name = "werkzeug" -version = "3.1.5" +version = "3.1.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc"}, - {file = "werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67"}, + {file = "werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131"}, + {file = "werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25"}, ] [package.dependencies] From b58bf40b477ab171c77b384b12e84ca9d26877ee Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 11:33:58 -0800 Subject: [PATCH 03/12] add pay service for get account fees --- auth-api/src/auth_api/services/products.py | 9 ++-- auth-api/src/auth_api/utils/pay.py | 49 ++++++++++++++++++++ auth-api/tests/unit/services/test_pay.py | 52 ++++++++++++++++++++++ 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 auth-api/src/auth_api/utils/pay.py create mode 100644 auth-api/tests/unit/services/test_pay.py diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index 25b2e3149..4301732ad 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -17,6 +17,7 @@ from datetime import datetime from typing import Any +from auth_api.utils.pay import get_account_fees from flask import current_app from sqlalchemy import and_, case, func, literal, or_ from sqlalchemy.exc import SQLAlchemyError @@ -159,13 +160,14 @@ def resubmit_product_subscription(org_id, subscription_data: dict[str, Any], ski @staticmethod def _check_gov_org_add_product_previously_approved( org_id: int, - product_code: str + product_code: str, + account_fees: list[str] ) -> tuple[bool, Any]: """Check if GOV org's account fee product was previously approved (NEW_PRODUCT_FEE_REVIEW task).""" inactive_sub = ProductSubscriptionModel.find_by_org_id_product_code( org_id=org_id, product_code=product_code, valid_statuses=(ProductSubscriptionStatus.INACTIVE.value,) ) - if not inactive_sub: + if not inactive_sub or product_code not in account_fees: return False, None task_add_product = TaskModel.find_by_task_relationship_id( inactive_sub.id, TaskRelationshipType.PRODUCT.value, TaskStatus.COMPLETED.value @@ -233,6 +235,7 @@ def create_product_subscription( check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF), org_id=org_id) subscriptions_list = subscription_data.get("subscriptions") + account_fees = get_account_fees(org) for subscription in subscriptions_list: auto_approve_current = auto_approve product_code = subscription.get("productCode") @@ -246,7 +249,7 @@ def create_product_subscription( if org.access_type in GOV_ORG_TYPES and not staff_review_for_create_org: previously_approved, inactive_sub = Product._check_gov_org_add_product_previously_approved( - org.id, product_code + org.id, product_code, account_fees ) else: previously_approved, inactive_sub = Product._is_previously_approved(org_id, product_code) diff --git a/auth-api/src/auth_api/utils/pay.py b/auth-api/src/auth_api/utils/pay.py new file mode 100644 index 000000000..f294c5453 --- /dev/null +++ b/auth-api/src/auth_api/utils/pay.py @@ -0,0 +1,49 @@ +# Copyright © 2026 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Pay API utility functions.""" + +from flask import current_app + +from auth_api.exceptions.errors import Error +from auth_api.models import Org as OrgModel +from auth_api.services.rest_service import RestService +from auth_api.utils.roles import GOV_ORG_TYPES + + +def get_account_fees(org: OrgModel) -> dict: + """Fetch all account fees from pay-api and return a dict mapping product codes to fee existence.""" + if org.access_type not in GOV_ORG_TYPES: + return {} + pay_url = current_app.config.get("PAY_API_URL") + account_fees = [] + + try: + token = RestService.get_service_account_token() + response = RestService.get(endpoint=f"{pay_url}/accounts/{org.id}/fees", token=token, retry_on_failure=True) + + if response and response.status_code == 200: + response_data = response.json() + account_fees_obj = response_data.get("accountFees", []) + + for fee in account_fees_obj: + product_code = fee.get("product") + if product_code: + account_fees.append(product_code) + return account_fees + except Exception as e: # NOQA # pylint: disable=broad-except + # Log the error but don't fail the subscription creation + # Return empty dict so subscription can proceed without fee-based review logic + current_app.logger.info(f"{Error.ACCOUNT_FEES_FETCH_FAILED} for org {org.id}: {e}") + + return account_fees diff --git a/auth-api/tests/unit/services/test_pay.py b/auth-api/tests/unit/services/test_pay.py new file mode 100644 index 000000000..7f3c5ec86 --- /dev/null +++ b/auth-api/tests/unit/services/test_pay.py @@ -0,0 +1,52 @@ +"""Tests for the pay utility functions.""" + +# Copyright © 2026 Province of British Columbia +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from unittest.mock import Mock + +from flask import current_app + +from auth_api.utils.enums import AccessType, ProductCode +from auth_api.utils.pay import get_account_fees +from tests.conftest import mock_token +from tests.utilities.factory_utils import factory_org_model + + +def test_get_account_fees_govm_org_success(monkeypatch, session): # pylint:disable=unused-argument + """Test that GOVM org with successful response returns list of product codes.""" + org = factory_org_model(org_info={"name": "Org 1", "accessType": AccessType.GOVM.value}) + + mock_response = Mock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "accountFees": [ + {"product": ProductCode.BUSINESS.value}, + {"product": ProductCode.VS.value}, + {"product": ProductCode.BCA.value}, + ] + } + + current_app.config["PAY_API_URL"] = "http://pay-api.test" + + monkeypatch.setattr("auth_api.utils.pay.RestService.get_service_account_token", mock_token) + monkeypatch.setattr("auth_api.utils.pay.RestService.get", lambda *args, **kwargs: mock_response) # noqa: ARG005 + + result = get_account_fees(org) + + assert result == [ + ProductCode.BUSINESS.value, + ProductCode.VS.value, + ProductCode.BCA.value, + ] From 67dcbe52ca6a9a745d47530286e984f5b576393b Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 12:39:58 -0800 Subject: [PATCH 04/12] lint --- auth-api/src/auth_api/services/products.py | 2 +- auth-api/src/auth_api/utils/pay.py | 5 +++-- auth-api/tests/unit/api/test_org.py | 2 ++ auth-api/tests/utilities/factory_utils.py | 17 +++++++++++++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index 4301732ad..dd1a82a68 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -17,7 +17,6 @@ from datetime import datetime from typing import Any -from auth_api.utils.pay import get_account_fees from flask import current_app from sqlalchemy import and_, case, func, literal, or_ from sqlalchemy.exc import SQLAlchemyError @@ -57,6 +56,7 @@ get_product_notification_data, get_product_notification_type, ) +from auth_api.utils.pay import get_account_fees from auth_api.utils.roles import CLIENT_ADMIN_ROLES, CLIENT_AUTH_ROLES, GOV_ORG_TYPES, STAFF from auth_api.utils.user_context import UserContext, user_context diff --git a/auth-api/src/auth_api/utils/pay.py b/auth-api/src/auth_api/utils/pay.py index f294c5453..4673ed748 100644 --- a/auth-api/src/auth_api/utils/pay.py +++ b/auth-api/src/auth_api/utils/pay.py @@ -21,10 +21,10 @@ from auth_api.utils.roles import GOV_ORG_TYPES -def get_account_fees(org: OrgModel) -> dict: +def get_account_fees(org: OrgModel) -> list[str]: """Fetch all account fees from pay-api and return a dict mapping product codes to fee existence.""" if org.access_type not in GOV_ORG_TYPES: - return {} + return [] pay_url = current_app.config.get("PAY_API_URL") account_fees = [] @@ -45,5 +45,6 @@ def get_account_fees(org: OrgModel) -> dict: # Log the error but don't fail the subscription creation # Return empty dict so subscription can proceed without fee-based review logic current_app.logger.info(f"{Error.ACCOUNT_FEES_FETCH_FAILED} for org {org.id}: {e}") + return [] return account_fees diff --git a/auth-api/tests/unit/api/test_org.py b/auth-api/tests/unit/api/test_org.py index a37676b46..763695d04 100644 --- a/auth-api/tests/unit/api/test_org.py +++ b/auth-api/tests/unit/api/test_org.py @@ -79,6 +79,7 @@ factory_user_model, patch_pay_account_delete, patch_pay_account_delete_error, + patch_pay_account_fees, patch_pay_account_post, ) @@ -569,6 +570,7 @@ def test_create_govn_org_with_products_single_staff_review_task(client, jwt, ses after approval. """ patch_pay_account_post(monkeypatch) + patch_pay_account_fees(monkeypatch, [ProductCode.BUSINESS_SEARCH.value, ProductCode.PPR.value]) headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) client.post("/api/v1/users", headers=headers, content_type="application/json") diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index cff10bee0..73fee4f0f 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -534,3 +534,20 @@ def keycloak_delete_user_by_username(username): delete_user_url = f"{base_url}/auth/admin/realms/{realm}/users/{user.id}" response = requests.delete(delete_user_url, headers=headers, timeout=timeout) response.raise_for_status() + + +def patch_pay_account_fees(monkeypatch, product_codes: list[str]): + """Patch GET /accounts/{id}/fees to return given product codes.""" + class MockFeeResponse: + status_code = 200 + def json(self): + return {"accountFees": [{"product": c} for c in product_codes]} + + monkeypatch.setattr( + "auth_api.utils.pay.RestService.get", + lambda *args, **kwargs: MockFeeResponse(), + ) + monkeypatch.setattr( + "auth_api.utils.pay.RestService.get_service_account_token", + lambda *args, **kwargs: "mock-token", + ) From 2941e0c813fab89b8220a971f600eaeba1976a42 Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 12:42:20 -0800 Subject: [PATCH 05/12] lint --- auth-api/tests/utilities/factory_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index 73fee4f0f..92424cd59 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -545,9 +545,9 @@ def json(self): monkeypatch.setattr( "auth_api.utils.pay.RestService.get", - lambda *args, **kwargs: MockFeeResponse(), + lambda *_args, **_kwargs: MockFeeResponse(), ) monkeypatch.setattr( "auth_api.utils.pay.RestService.get_service_account_token", - lambda *args, **kwargs: "mock-token", + lambda *_args, **_kwargs: "mock-token", ) From bfcefb6c0da0e5aa06cb0b5e58fa209295ad91dc Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 13:13:01 -0800 Subject: [PATCH 06/12] tweaks --- auth-api/tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/auth-api/tests/conftest.py b/auth-api/tests/conftest.py index a050cadb1..9ef68ded5 100644 --- a/auth-api/tests/conftest.py +++ b/auth-api/tests/conftest.py @@ -29,6 +29,7 @@ from auth_api.models import db as _db from auth_api.models.org import receive_before_update from auth_api.utils.auth import jwt as _jwt +from tests.utilities.factory_utils import patch_pay_account_post def mock_token(config_id="", config_secret=""): @@ -175,6 +176,7 @@ def auth_mock(monkeypatch): monkeypatch.setattr("auth_api.services.org.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 monkeypatch.setattr("auth_api.services.invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 monkeypatch.setattr("auth_api.services.affiliation_invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 + patch_pay_account_post(monkeypatch) @pytest.fixture() From 2b78f4eb36f00201c5084c81398e2028501bc94f Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 13:31:33 -0800 Subject: [PATCH 07/12] unit test fix --- auth-api/tests/conftest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/auth-api/tests/conftest.py b/auth-api/tests/conftest.py index 9ef68ded5..51c4f38c1 100644 --- a/auth-api/tests/conftest.py +++ b/auth-api/tests/conftest.py @@ -29,7 +29,7 @@ from auth_api.models import db as _db from auth_api.models.org import receive_before_update from auth_api.utils.auth import jwt as _jwt -from tests.utilities.factory_utils import patch_pay_account_post +from tests.utilities.factory_utils import patch_pay_account_post, patch_pay_account_put def mock_token(config_id="", config_secret=""): @@ -176,7 +176,13 @@ def auth_mock(monkeypatch): monkeypatch.setattr("auth_api.services.org.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 monkeypatch.setattr("auth_api.services.invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 monkeypatch.setattr("auth_api.services.affiliation_invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 + + +@pytest.fixture(autouse=True) +def pay_api_mock(monkeypatch): + """Mock pay-api.""" patch_pay_account_post(monkeypatch) + patch_pay_account_put(monkeypatch) @pytest.fixture() From 8b1a65723321b1dd488eb9a805eec1463f6be063 Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 14:01:56 -0800 Subject: [PATCH 08/12] tweaks --- auth-api/src/auth_api/services/products.py | 4 ++-- auth-api/tests/conftest.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index dd1a82a68..b974a53a1 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -167,7 +167,7 @@ def _check_gov_org_add_product_previously_approved( inactive_sub = ProductSubscriptionModel.find_by_org_id_product_code( org_id=org_id, product_code=product_code, valid_statuses=(ProductSubscriptionStatus.INACTIVE.value,) ) - if not inactive_sub or product_code not in account_fees: + if not inactive_sub: return False, None task_add_product = TaskModel.find_by_task_relationship_id( inactive_sub.id, TaskRelationshipType.PRODUCT.value, TaskStatus.COMPLETED.value @@ -235,7 +235,7 @@ def create_product_subscription( check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF), org_id=org_id) subscriptions_list = subscription_data.get("subscriptions") - account_fees = get_account_fees(org) + account_fees = [] for subscription in subscriptions_list: auto_approve_current = auto_approve product_code = subscription.get("productCode") diff --git a/auth-api/tests/conftest.py b/auth-api/tests/conftest.py index 51c4f38c1..68ea695dc 100644 --- a/auth-api/tests/conftest.py +++ b/auth-api/tests/conftest.py @@ -178,9 +178,9 @@ def auth_mock(monkeypatch): monkeypatch.setattr("auth_api.services.affiliation_invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 -@pytest.fixture(autouse=True) +@pytest.fixture() def pay_api_mock(monkeypatch): - """Mock pay-api.""" + """Mock pay-api (RestService.post/put for pay). Use when test creates org but does not use auth_mock.""" patch_pay_account_post(monkeypatch) patch_pay_account_put(monkeypatch) From e6934cfc87b88dfb9fe192eea6bd0b2c7f3f672c Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 14:14:50 -0800 Subject: [PATCH 09/12] comment some code for unit test --- auth-api/src/auth_api/services/products.py | 2 +- auth-api/tests/conftest.py | 12 ++++----- auth-api/tests/unit/api/test_org.py | 4 +-- auth-api/tests/utilities/factory_utils.py | 30 +++++++++++----------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index b974a53a1..57e4b3b0e 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -56,7 +56,7 @@ get_product_notification_data, get_product_notification_type, ) -from auth_api.utils.pay import get_account_fees +#from auth_api.utils.pay import get_account_fees from auth_api.utils.roles import CLIENT_ADMIN_ROLES, CLIENT_AUTH_ROLES, GOV_ORG_TYPES, STAFF from auth_api.utils.user_context import UserContext, user_context diff --git a/auth-api/tests/conftest.py b/auth-api/tests/conftest.py index 68ea695dc..78fdee609 100644 --- a/auth-api/tests/conftest.py +++ b/auth-api/tests/conftest.py @@ -29,7 +29,7 @@ from auth_api.models import db as _db from auth_api.models.org import receive_before_update from auth_api.utils.auth import jwt as _jwt -from tests.utilities.factory_utils import patch_pay_account_post, patch_pay_account_put +#from tests.utilities.factory_utils import patch_pay_account_post, patch_pay_account_put def mock_token(config_id="", config_secret=""): @@ -178,11 +178,11 @@ def auth_mock(monkeypatch): monkeypatch.setattr("auth_api.services.affiliation_invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 -@pytest.fixture() -def pay_api_mock(monkeypatch): - """Mock pay-api (RestService.post/put for pay). Use when test creates org but does not use auth_mock.""" - patch_pay_account_post(monkeypatch) - patch_pay_account_put(monkeypatch) +#@pytest.fixture() +#def pay_api_mock(monkeypatch): +# """Mock pay-api (RestService.post/put for pay). Use when test creates org but does not use auth_mock.""" +# patch_pay_account_post(monkeypatch) +# patch_pay_account_put(monkeypatch) @pytest.fixture() diff --git a/auth-api/tests/unit/api/test_org.py b/auth-api/tests/unit/api/test_org.py index 763695d04..2817f19ae 100644 --- a/auth-api/tests/unit/api/test_org.py +++ b/auth-api/tests/unit/api/test_org.py @@ -79,7 +79,7 @@ factory_user_model, patch_pay_account_delete, patch_pay_account_delete_error, - patch_pay_account_fees, + #patch_pay_account_fees, patch_pay_account_post, ) @@ -570,7 +570,7 @@ def test_create_govn_org_with_products_single_staff_review_task(client, jwt, ses after approval. """ patch_pay_account_post(monkeypatch) - patch_pay_account_fees(monkeypatch, [ProductCode.BUSINESS_SEARCH.value, ProductCode.PPR.value]) + #patch_pay_account_fees(monkeypatch, [ProductCode.BUSINESS_SEARCH.value, ProductCode.PPR.value]) headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) client.post("/api/v1/users", headers=headers, content_type="application/json") diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index 92424cd59..a557f8cf7 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -536,18 +536,18 @@ def keycloak_delete_user_by_username(username): response.raise_for_status() -def patch_pay_account_fees(monkeypatch, product_codes: list[str]): - """Patch GET /accounts/{id}/fees to return given product codes.""" - class MockFeeResponse: - status_code = 200 - def json(self): - return {"accountFees": [{"product": c} for c in product_codes]} - - monkeypatch.setattr( - "auth_api.utils.pay.RestService.get", - lambda *_args, **_kwargs: MockFeeResponse(), - ) - monkeypatch.setattr( - "auth_api.utils.pay.RestService.get_service_account_token", - lambda *_args, **_kwargs: "mock-token", - ) +#def patch_pay_account_fees(monkeypatch, product_codes: list[str]): +# """Patch GET /accounts/{id}/fees to return given product codes.""" +# class MockFeeResponse: +# status_code = 200 +# def json(self): +# return {"accountFees": [{"product": c} for c in product_codes]} + +# monkeypatch.setattr( +# "auth_api.utils.pay.RestService.get", +# lambda *_args, **_kwargs: MockFeeResponse(), +# ) +# monkeypatch.setattr( +# "auth_api.utils.pay.RestService.get_service_account_token", +# lambda *_args, **_kwargs: "mock-token", +# ) From 940413dba6588d375abe9390e1f40fbb25739a00 Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 14:32:49 -0800 Subject: [PATCH 10/12] remove comment out code --- auth-api/src/auth_api/services/products.py | 7 ++----- auth-api/tests/conftest.py | 7 ------- auth-api/tests/unit/api/test_org.py | 2 -- auth-api/tests/utilities/factory_utils.py | 16 ---------------- 4 files changed, 2 insertions(+), 30 deletions(-) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index 57e4b3b0e..25b2e3149 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -56,7 +56,6 @@ get_product_notification_data, get_product_notification_type, ) -#from auth_api.utils.pay import get_account_fees from auth_api.utils.roles import CLIENT_ADMIN_ROLES, CLIENT_AUTH_ROLES, GOV_ORG_TYPES, STAFF from auth_api.utils.user_context import UserContext, user_context @@ -160,8 +159,7 @@ def resubmit_product_subscription(org_id, subscription_data: dict[str, Any], ski @staticmethod def _check_gov_org_add_product_previously_approved( org_id: int, - product_code: str, - account_fees: list[str] + product_code: str ) -> tuple[bool, Any]: """Check if GOV org's account fee product was previously approved (NEW_PRODUCT_FEE_REVIEW task).""" inactive_sub = ProductSubscriptionModel.find_by_org_id_product_code( @@ -235,7 +233,6 @@ def create_product_subscription( check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF), org_id=org_id) subscriptions_list = subscription_data.get("subscriptions") - account_fees = [] for subscription in subscriptions_list: auto_approve_current = auto_approve product_code = subscription.get("productCode") @@ -249,7 +246,7 @@ def create_product_subscription( if org.access_type in GOV_ORG_TYPES and not staff_review_for_create_org: previously_approved, inactive_sub = Product._check_gov_org_add_product_previously_approved( - org.id, product_code, account_fees + org.id, product_code ) else: previously_approved, inactive_sub = Product._is_previously_approved(org_id, product_code) diff --git a/auth-api/tests/conftest.py b/auth-api/tests/conftest.py index 78fdee609..77cc19b55 100644 --- a/auth-api/tests/conftest.py +++ b/auth-api/tests/conftest.py @@ -29,7 +29,6 @@ from auth_api.models import db as _db from auth_api.models.org import receive_before_update from auth_api.utils.auth import jwt as _jwt -#from tests.utilities.factory_utils import patch_pay_account_post, patch_pay_account_put def mock_token(config_id="", config_secret=""): @@ -178,12 +177,6 @@ def auth_mock(monkeypatch): monkeypatch.setattr("auth_api.services.affiliation_invitation.check_auth", lambda *args, **kwargs: None) # noqa: ARG005 -#@pytest.fixture() -#def pay_api_mock(monkeypatch): -# """Mock pay-api (RestService.post/put for pay). Use when test creates org but does not use auth_mock.""" -# patch_pay_account_post(monkeypatch) -# patch_pay_account_put(monkeypatch) - @pytest.fixture() def notify_mock(monkeypatch): diff --git a/auth-api/tests/unit/api/test_org.py b/auth-api/tests/unit/api/test_org.py index 2817f19ae..a37676b46 100644 --- a/auth-api/tests/unit/api/test_org.py +++ b/auth-api/tests/unit/api/test_org.py @@ -79,7 +79,6 @@ factory_user_model, patch_pay_account_delete, patch_pay_account_delete_error, - #patch_pay_account_fees, patch_pay_account_post, ) @@ -570,7 +569,6 @@ def test_create_govn_org_with_products_single_staff_review_task(client, jwt, ses after approval. """ patch_pay_account_post(monkeypatch) - #patch_pay_account_fees(monkeypatch, [ProductCode.BUSINESS_SEARCH.value, ProductCode.PPR.value]) headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) client.post("/api/v1/users", headers=headers, content_type="application/json") diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index a557f8cf7..8ba7e805d 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -535,19 +535,3 @@ def keycloak_delete_user_by_username(username): response = requests.delete(delete_user_url, headers=headers, timeout=timeout) response.raise_for_status() - -#def patch_pay_account_fees(monkeypatch, product_codes: list[str]): -# """Patch GET /accounts/{id}/fees to return given product codes.""" -# class MockFeeResponse: -# status_code = 200 -# def json(self): -# return {"accountFees": [{"product": c} for c in product_codes]} - -# monkeypatch.setattr( -# "auth_api.utils.pay.RestService.get", -# lambda *_args, **_kwargs: MockFeeResponse(), -# ) -# monkeypatch.setattr( -# "auth_api.utils.pay.RestService.get_service_account_token", -# lambda *_args, **_kwargs: "mock-token", -# ) From 0463f8b33a47639a44a93e977d7a39733128af08 Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 14:59:22 -0800 Subject: [PATCH 11/12] add back get_account_fees --- auth-api/src/auth_api/services/products.py | 9 ++++++--- auth-api/tests/unit/api/test_org.py | 2 ++ auth-api/tests/utilities/factory_utils.py | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/auth-api/src/auth_api/services/products.py b/auth-api/src/auth_api/services/products.py index 25b2e3149..dd1a82a68 100644 --- a/auth-api/src/auth_api/services/products.py +++ b/auth-api/src/auth_api/services/products.py @@ -56,6 +56,7 @@ get_product_notification_data, get_product_notification_type, ) +from auth_api.utils.pay import get_account_fees from auth_api.utils.roles import CLIENT_ADMIN_ROLES, CLIENT_AUTH_ROLES, GOV_ORG_TYPES, STAFF from auth_api.utils.user_context import UserContext, user_context @@ -159,13 +160,14 @@ def resubmit_product_subscription(org_id, subscription_data: dict[str, Any], ski @staticmethod def _check_gov_org_add_product_previously_approved( org_id: int, - product_code: str + product_code: str, + account_fees: list[str] ) -> tuple[bool, Any]: """Check if GOV org's account fee product was previously approved (NEW_PRODUCT_FEE_REVIEW task).""" inactive_sub = ProductSubscriptionModel.find_by_org_id_product_code( org_id=org_id, product_code=product_code, valid_statuses=(ProductSubscriptionStatus.INACTIVE.value,) ) - if not inactive_sub: + if not inactive_sub or product_code not in account_fees: return False, None task_add_product = TaskModel.find_by_task_relationship_id( inactive_sub.id, TaskRelationshipType.PRODUCT.value, TaskStatus.COMPLETED.value @@ -233,6 +235,7 @@ def create_product_subscription( check_auth(one_of_roles=(*CLIENT_ADMIN_ROLES, STAFF), org_id=org_id) subscriptions_list = subscription_data.get("subscriptions") + account_fees = get_account_fees(org) for subscription in subscriptions_list: auto_approve_current = auto_approve product_code = subscription.get("productCode") @@ -246,7 +249,7 @@ def create_product_subscription( if org.access_type in GOV_ORG_TYPES and not staff_review_for_create_org: previously_approved, inactive_sub = Product._check_gov_org_add_product_previously_approved( - org.id, product_code + org.id, product_code, account_fees ) else: previously_approved, inactive_sub = Product._is_previously_approved(org_id, product_code) diff --git a/auth-api/tests/unit/api/test_org.py b/auth-api/tests/unit/api/test_org.py index a37676b46..763695d04 100644 --- a/auth-api/tests/unit/api/test_org.py +++ b/auth-api/tests/unit/api/test_org.py @@ -79,6 +79,7 @@ factory_user_model, patch_pay_account_delete, patch_pay_account_delete_error, + patch_pay_account_fees, patch_pay_account_post, ) @@ -569,6 +570,7 @@ def test_create_govn_org_with_products_single_staff_review_task(client, jwt, ses after approval. """ patch_pay_account_post(monkeypatch) + patch_pay_account_fees(monkeypatch, [ProductCode.BUSINESS_SEARCH.value, ProductCode.PPR.value]) headers = factory_auth_header(jwt=jwt, claims=TestJwtClaims.public_user_role) client.post("/api/v1/users", headers=headers, content_type="application/json") diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index 8ba7e805d..73fee4f0f 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -535,3 +535,19 @@ def keycloak_delete_user_by_username(username): response = requests.delete(delete_user_url, headers=headers, timeout=timeout) response.raise_for_status() + +def patch_pay_account_fees(monkeypatch, product_codes: list[str]): + """Patch GET /accounts/{id}/fees to return given product codes.""" + class MockFeeResponse: + status_code = 200 + def json(self): + return {"accountFees": [{"product": c} for c in product_codes]} + + monkeypatch.setattr( + "auth_api.utils.pay.RestService.get", + lambda *args, **kwargs: MockFeeResponse(), + ) + monkeypatch.setattr( + "auth_api.utils.pay.RestService.get_service_account_token", + lambda *args, **kwargs: "mock-token", + ) From 4d3511430f46948c36cda0530ce2b6b28851b3c4 Mon Sep 17 00:00:00 2001 From: Jia Xu Date: Fri, 20 Feb 2026 15:04:12 -0800 Subject: [PATCH 12/12] lint --- auth-api/tests/utilities/factory_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth-api/tests/utilities/factory_utils.py b/auth-api/tests/utilities/factory_utils.py index 73fee4f0f..92424cd59 100644 --- a/auth-api/tests/utilities/factory_utils.py +++ b/auth-api/tests/utilities/factory_utils.py @@ -545,9 +545,9 @@ def json(self): monkeypatch.setattr( "auth_api.utils.pay.RestService.get", - lambda *args, **kwargs: MockFeeResponse(), + lambda *_args, **_kwargs: MockFeeResponse(), ) monkeypatch.setattr( "auth_api.utils.pay.RestService.get_service_account_token", - lambda *args, **kwargs: "mock-token", + lambda *_args, **_kwargs: "mock-token", )