From 64a8b77a3f056a363e20c53ce8747e43c0a136f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Fri, 19 Apr 2024 15:55:59 +0200 Subject: [PATCH 01/10] Clear context after GraphQL view --- saleor/graphql/context.py | 6 ++++++ saleor/graphql/core/dataloaders.py | 5 +++++ saleor/graphql/core/tests/test_view.py | 25 ++++++++++++++++++++++++- saleor/graphql/tests/test_context.py | 21 ++++++++++++++++++++- saleor/graphql/views.py | 4 +++- saleor/plugins/base_plugin.py | 6 ++++++ saleor/plugins/manager.py | 8 ++++++++ saleor/tests/fixtures.py | 2 +- 8 files changed, 73 insertions(+), 4 deletions(-) diff --git a/saleor/graphql/context.py b/saleor/graphql/context.py index 79702cbcd23..ba059eb24a5 100644 --- a/saleor/graphql/context.py +++ b/saleor/graphql/context.py @@ -26,6 +26,12 @@ def get_context_value(request: HttpRequest) -> SaleorContext: return request +def clear_context(context: SaleorContext): + context.dataloaders.clear() + context.user = None + context.app = None + + class RequestWithUser(HttpRequest): _cached_user: Optional[User] app: Optional[App] diff --git a/saleor/graphql/core/dataloaders.py b/saleor/graphql/core/dataloaders.py index 48b464f87e1..7b800800fa2 100644 --- a/saleor/graphql/core/dataloaders.py +++ b/saleor/graphql/core/dataloaders.py @@ -39,6 +39,11 @@ def __init__(self, context: SaleorContext) -> None: self.database_connection_name = get_database_connection_name(context) super().__init__() + def __del__(self): + self._promise_cache.clear() + self._queue.clear() + self._scheduler = None + def batch_load_fn( # pylint: disable=method-hidden self, keys: Iterable[K] ) -> Promise[list[R]]: diff --git a/saleor/graphql/core/tests/test_view.py b/saleor/graphql/core/tests/test_view.py index 040430b0723..ad7b3964d81 100644 --- a/saleor/graphql/core/tests/test_view.py +++ b/saleor/graphql/core/tests/test_view.py @@ -1,3 +1,4 @@ +import json from unittest import mock import graphene @@ -7,10 +8,11 @@ from .... import __version__ as saleor_version from ....demo.views import EXAMPLE_QUERY +from ....graphql.api import schema from ....graphql.utils import INTERNAL_ERROR_MESSAGE from ...tests.fixtures import API_PATH from ...tests.utils import get_graphql_content, get_graphql_content_from_response -from ...views import generate_cache_key +from ...views import GraphQLView, generate_cache_key def test_batch_queries(category, product, api_client, channel_USD): @@ -332,3 +334,24 @@ def test_introspection_query_is_not_cached_in_debug_mode( def test_generate_cache_key_use_saleor_version(): cache_key = generate_cache_key(INTROSPECTION_QUERY) assert saleor_version in cache_key + + +def test_graphql_view_clears_context(rf, staff_user, product): + # given + product_id = graphene.Node.to_global_id("Product", product.pk) + data = {"query": '{ product(id: "%s") { name category { name } } }' % product_id} + request = rf.post(path="/", data=data, content_type="application/json") + request.app = None + request.user = staff_user + + # when + view = GraphQLView.as_view(schema=schema) + response = view(request) + + # then + json_data = json.loads(response.content) + assert json_data["data"]["product"]["name"] == product.name + assert json_data["data"]["product"]["category"]["name"] == product.category.name + assert response.status_code == 200 + assert request.dataloaders == {} + assert request.user is None diff --git a/saleor/graphql/tests/test_context.py b/saleor/graphql/tests/test_context.py index 81bfccb84dd..2ac12ece383 100644 --- a/saleor/graphql/tests/test_context.py +++ b/saleor/graphql/tests/test_context.py @@ -3,7 +3,7 @@ import graphene from django.utils import timezone -from ..context import get_context_value +from ..context import clear_context, get_context_value from .utils import get_graphql_content QUERY_ORDER_BY_ID = """ @@ -71,3 +71,22 @@ def test_get_context_value_uses_request_time_if_passed_already(rf): # then assert context.request_time == request_time + + +# {'query': '{\n me {\n id\n }\n}'} + + +def test_clear_context(rf, staff_user, app): + # given + context = get_context_value(rf.request()) + context.dataloaders = {"key": "value"} # type: ignore + context.user = staff_user + context.app = app + + # when + clear_context(context) + + # then + assert context.dataloaders == {} + assert context.user is None + assert context.app is None diff --git a/saleor/graphql/views.py b/saleor/graphql/views.py index 9c1e124b1e1..ed709078f83 100644 --- a/saleor/graphql/views.py +++ b/saleor/graphql/views.py @@ -24,7 +24,7 @@ from ..core.utils import is_valid_ipv4, is_valid_ipv6 from ..webhook import observability from .api import API_PATH, schema -from .context import get_context_value +from .context import clear_context, get_context_value from .core.validators.query_cost import validate_query_cost from .query_cost_map import COST_MAP from .utils import format_error, query_fingerprint, query_identifier @@ -352,6 +352,8 @@ def execute_graphql_request(self, request: HttpRequest, data: dict): if str(e).startswith(INT_ERROR_MSG) or isinstance(e, ValueError): e = GraphQLError(str(e)) return ExecutionResult(errors=[e], invalid=True) + finally: + clear_context(context) @staticmethod def parse_body(request: HttpRequest): diff --git a/saleor/plugins/base_plugin.py b/saleor/plugins/base_plugin.py index 96d6ffa83a5..4e84864f9f0 100644 --- a/saleor/plugins/base_plugin.py +++ b/saleor/plugins/base_plugin.py @@ -155,6 +155,12 @@ def __init__( self.db_config = db_config self.allow_replica = allow_replica + def __del__(self) -> None: + self.channel = None + self.db_config = None + self.configuration.clear() + self.requestor = None + def __str__(self): return self.PLUGIN_NAME diff --git a/saleor/plugins/manager.py b/saleor/plugins/manager.py index c62ade81ea0..0daf0d7b133 100644 --- a/saleor/plugins/manager.py +++ b/saleor/plugins/manager.py @@ -143,6 +143,14 @@ def __init__(self, plugins: list[str], requestor_getter=None, allow_replica=True self.loaded_global = False self.requestor_getter = requestor_getter + def __del__(self) -> None: + # remove references to plugins + self.all_plugins.clear() + self.global_plugins.clear() + for c in self.plugins_per_channel.values(): + c.clear() + self.loaded_channels.clear() + def _ensure_channel_plugins_loaded( self, channel_slug: Optional[str], channel: Optional[Channel] = None ): diff --git a/saleor/tests/fixtures.py b/saleor/tests/fixtures.py index 8f67a5a22a2..b467245b8fd 100644 --- a/saleor/tests/fixtures.py +++ b/saleor/tests/fixtures.py @@ -7764,7 +7764,7 @@ def checkout_with_prices( channel = checkout_with_items.channel lines = checkout_with_items.lines.all() lines_info, _ = fetch_checkout_lines(checkout_with_items) - checkout_info = fetch_checkout_info(checkout_with_items, lines, manager) + checkout_info = fetch_checkout_info(checkout_with_items, lines_info, manager) for line, line_info in zip(lines, lines_info): line.total_price_net_amount = base_calculations.calculate_base_line_total_price( From 129c9aa9acc4e550b5e012cfef4620fa7f38a871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Mon, 22 Apr 2024 17:46:16 +0200 Subject: [PATCH 02/10] Refactor lazy object properties into cached properties in CheckoutInfo --- saleor/checkout/fetch.py | 243 ++++++------------ saleor/checkout/utils.py | 10 +- saleor/graphql/checkout/dataloaders.py | 37 +-- .../mutations/checkout_add_promo_code.py | 11 - .../checkout/mutations/checkout_lines_add.py | 11 - saleor/graphql/checkout/types.py | 3 +- 6 files changed, 96 insertions(+), 219 deletions(-) diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index 2dd718a4029..c9c93936c5a 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -1,7 +1,7 @@ import itertools from collections.abc import Iterable -from dataclasses import dataclass -from functools import singledispatch +from dataclasses import dataclass, field +from functools import cached_property, singledispatch from typing import ( TYPE_CHECKING, Any, @@ -11,7 +11,6 @@ ) from uuid import UUID -from ..core.utils.lazyobjects import lazy_no_retry from ..discount import DiscountType, VoucherType from ..discount.interface import fetch_variant_rules_info, fetch_voucher_info from ..shipping.interface import ShippingMethodData @@ -65,18 +64,88 @@ def get_promotion_discounts(self) -> list["CheckoutLineDiscount"]: @dataclass class CheckoutInfo: + # compare=False used to avoid comparing the manager attribute as it's irrelevant + # for the equality checkout infos; this is used in tests + manager: "PluginsManager" = field(compare=False) + lines: Iterable[CheckoutLineInfo] = field(compare=False) + checkout: "Checkout" user: Optional["User"] channel: "Channel" billing_address: Optional["Address"] shipping_address: Optional["Address"] - delivery_method_info: "DeliveryMethodBase" - all_shipping_methods: list["ShippingMethodData"] tax_configuration: "TaxConfiguration" - valid_pick_up_points: list["Warehouse"] + shipping_channel_listings: Iterable["ShippingMethodChannelListing"] + shipping_method: Optional["ShippingMethod"] = None + collection_point: Optional["Warehouse"] = None voucher: Optional["Voucher"] = None voucher_code: Optional["VoucherCode"] = None + @cached_property + def all_shipping_methods(self) -> list["ShippingMethodData"]: + all_methods = get_all_shipping_methods_list( + self, + self.shipping_address, + self.lines, + self.shipping_channel_listings, + self.manager, + ) + # Filter shipping methods using sync webhooks + excluded_methods = self.manager.excluded_shipping_methods_for_checkout( + self.checkout, all_methods + ) + initialize_shipping_method_active_status(all_methods, excluded_methods) + return all_methods + + @cached_property + def valid_pick_up_points(self) -> Iterable["Warehouse"]: + from .utils import get_valid_collection_points_for_checkout + + return list( + get_valid_collection_points_for_checkout( + self.lines, self.channel.id, quantity_check=False + ) + ) + + @property + def delivery_method_info(self) -> "DeliveryMethodBase": + from ..webhook.transport.shipping import convert_to_app_id_with_identifier + from .utils import get_external_shipping_id + + delivery_method: Optional[Union[ShippingMethodData, Warehouse, Callable]] = None + + if self.shipping_method: + # Find listing for the currently selected shipping method + shipping_channel_listing = None + for listing in self.shipping_channel_listings: + if listing.shipping_method_id == self.shipping_method.id: + shipping_channel_listing = listing + break + + if shipping_channel_listing: + delivery_method = convert_to_shipping_method_data( + self.shipping_method, shipping_channel_listing + ) + + elif external_shipping_method_id := get_external_shipping_id(self.checkout): + # A local function is used to delay evaluation + # of the lazy `all_shipping_methods` attribute + def _resolve_external_method(): + methods = {method.id: method for method in self.all_shipping_methods} + if method := methods.get(external_shipping_method_id): + return method + new_shipping_method_id = convert_to_app_id_with_identifier( + external_shipping_method_id + ) + return methods.get(new_shipping_method_id) + + delivery_method = _resolve_external_method + + else: + delivery_method = self.collection_point + + return get_delivery_method_info(delivery_method, self.shipping_address) + @property def valid_shipping_methods(self) -> list["ShippingMethodData"]: return [method for method in self.all_shipping_methods if method.active] @@ -428,109 +497,24 @@ def fetch_checkout_info( checkout, channel_slug=channel.slug ) - delivery_method_info = get_delivery_method_info(None, shipping_address) checkout_info = CheckoutInfo( checkout=checkout, user=checkout.user, channel=channel, billing_address=checkout.billing_address, shipping_address=shipping_address, - delivery_method_info=delivery_method_info, tax_configuration=tax_configuration, - all_shipping_methods=[], - valid_pick_up_points=[], + lines=lines, + manager=manager, + shipping_channel_listings=list(shipping_channel_listings), + shipping_method=checkout.shipping_method, + collection_point=checkout.collection_point, voucher=voucher, voucher_code=voucher_code, ) - if fetch_delivery_methods: - update_delivery_method_lists_for_checkout_info( - checkout_info, - checkout.shipping_method, - checkout.collection_point, - shipping_address, - lines, - manager, - shipping_channel_listings, - ) - return checkout_info -def update_checkout_info_delivery_method_info( - checkout_info: CheckoutInfo, - shipping_method: Optional[ShippingMethod], - collection_point: Optional[Warehouse], - shipping_channel_listings: Iterable[ShippingMethodChannelListing], -): - """Update delivery_method_attribute for CheckoutInfo. - - The attribute is lazy-evaluated avoid external API calls unless accessed. - """ - from ..webhook.transport.shipping import convert_to_app_id_with_identifier - from .utils import get_external_shipping_id - - delivery_method: Optional[Union[ShippingMethodData, Warehouse, Callable]] = None - checkout = checkout_info.checkout - if shipping_method: - # Find listing for the currently selected shipping method - shipping_channel_listing = None - for listing in shipping_channel_listings: - if listing.shipping_method_id == shipping_method.id: - shipping_channel_listing = listing - break - - if shipping_channel_listing: - delivery_method = convert_to_shipping_method_data( - shipping_method, shipping_channel_listing - ) - - elif external_shipping_method_id := get_external_shipping_id(checkout): - # A local function is used to delay evaluation - # of the lazy `all_shipping_methods` attribute - def _resolve_external_method(): - methods = { - method.id: method for method in checkout_info.all_shipping_methods - } - if method := methods.get(external_shipping_method_id): - return method - new_shipping_method_id = convert_to_app_id_with_identifier( - external_shipping_method_id - ) - return methods.get(new_shipping_method_id) - - delivery_method = _resolve_external_method - - else: - delivery_method = collection_point - - checkout_info.delivery_method_info = lazy_no_retry( - lambda: get_delivery_method_info( - delivery_method, - checkout_info.shipping_address, - ) - ) # type: ignore[assignment] # using SimpleLazyObject breaks protocol - - -def update_checkout_info_shipping_address( - checkout_info: CheckoutInfo, - address: Optional["Address"], - lines: Iterable[CheckoutLineInfo], - manager: "PluginsManager", - shipping_channel_listings: Iterable["ShippingMethodChannelListing"], -): - checkout_info.shipping_address = address - - update_delivery_method_lists_for_checkout_info( - checkout_info, - checkout_info.checkout.shipping_method, - checkout_info.checkout.collection_point, - address, - lines, - manager, - shipping_channel_listings, - ) - - def get_valid_internal_shipping_method_list_for_checkout_info( checkout_info: "CheckoutInfo", shipping_address: Optional["Address"], @@ -605,70 +589,3 @@ def get_all_shipping_methods_list( ), ) ) - - -def update_delivery_method_lists_for_checkout_info( - checkout_info: "CheckoutInfo", - shipping_method: Optional["ShippingMethod"], - collection_point: Optional["Warehouse"], - shipping_address: Optional["Address"], - lines: Iterable[CheckoutLineInfo], - manager: "PluginsManager", - shipping_channel_listings: Iterable[ShippingMethodChannelListing], -): - """Update the list of shipping methods for checkout info. - - Shipping methods excluded by Saleor's own business logic are not present - in the result list. - - Availability of shipping methods according to plugins is indicated - by the `active` field. - """ - - def _resolve_all_shipping_methods(): - # Fetch all shipping method from all sources, including sync webhooks - all_methods = get_all_shipping_methods_list( - checkout_info, - shipping_address, - lines, - shipping_channel_listings, - manager, - ) - # Filter shipping methods using sync webhooks - excluded_methods = manager.excluded_shipping_methods_for_checkout( - checkout_info.checkout, all_methods - ) - initialize_shipping_method_active_status(all_methods, excluded_methods) - return all_methods - - checkout_info.all_shipping_methods = lazy_no_retry(_resolve_all_shipping_methods) # type: ignore[assignment] # using lazy object breaks protocol - checkout_info.valid_pick_up_points = lazy_no_retry( - lambda: (get_valid_collection_points_for_checkout_info(lines, checkout_info)) - ) # type: ignore[assignment] # using lazy object breaks protocol - update_checkout_info_delivery_method_info( - checkout_info, - shipping_method, - collection_point, - shipping_channel_listings, - ) - - -def get_valid_collection_points_for_checkout_info( - lines: Iterable[CheckoutLineInfo], - checkout_info: CheckoutInfo, -): - from .utils import get_valid_collection_points_for_checkout - - valid_collection_points = get_valid_collection_points_for_checkout( - lines, checkout_info.channel.id, quantity_check=False - ) - return list(valid_collection_points) - - -def update_checkout_info_delivery_method( - checkout_info: CheckoutInfo, - delivery_method: Optional[Union["ShippingMethodData", "Warehouse"]], -): - checkout_info.delivery_method_info = get_delivery_method_info( - delivery_method, checkout_info.shipping_address - ) diff --git a/saleor/checkout/utils.py b/saleor/checkout/utils.py index 7509945e23a..65c088bcadf 100644 --- a/saleor/checkout/utils.py +++ b/saleor/checkout/utils.py @@ -41,10 +41,6 @@ from ..warehouse.reservations import reserve_stocks_and_preorders from . import AddressType, base_calculations, calculations from .error_codes import CheckoutErrorCode -from .fetch import ( - update_checkout_info_delivery_method, - update_checkout_info_shipping_address, -) from .models import Checkout, CheckoutLine, CheckoutMetadata if TYPE_CHECKING: @@ -370,9 +366,7 @@ def change_shipping_address_in_checkout( if remove and checkout.shipping_address: checkout.shipping_address.delete() checkout.shipping_address = address - update_checkout_info_shipping_address( - checkout_info, address, lines, manager, shipping_channel_listings - ) + checkout_info.shipping_address = address updated_fields = ["shipping_address", "last_change"] return updated_fields @@ -855,7 +849,7 @@ def clear_delivery_method(checkout_info: "CheckoutInfo"): checkout = checkout_info.checkout checkout.collection_point = None checkout.shipping_method = None - update_checkout_info_delivery_method(checkout_info, None) + checkout_info.shipping_method = None delete_external_shipping_id(checkout=checkout) checkout.save( update_fields=[ diff --git a/saleor/graphql/checkout/dataloaders.py b/saleor/graphql/checkout/dataloaders.py index f8a31d50747..9c277869c14 100644 --- a/saleor/graphql/checkout/dataloaders.py +++ b/saleor/graphql/checkout/dataloaders.py @@ -8,8 +8,6 @@ CheckoutInfo, CheckoutLineInfo, apply_voucher_to_checkout_line, - get_delivery_method_info, - update_delivery_method_lists_for_checkout_info, ) from ...checkout.models import Checkout, CheckoutLine, CheckoutMetadata from ...checkout.problems import ( @@ -519,11 +517,15 @@ def with_checkout_info(results): collection_point = collection_points_map.get( checkout.collection_point_id ) - shipping_address = address_map.get(checkout.shipping_address_id) - delivery_method_info = get_delivery_method_info( - None, shipping_address - ) voucher_code = voucher_code_map.get(checkout.voucher_code) + + shipping_channel_listings = [ + listing + for channel_listings in listings_for_channels + for listing in channel_listings + if listing.channel_id == channel.id + ] + checkout_info = CheckoutInfo( checkout=checkout, user=user_map.get(checkout.user_id), @@ -534,30 +536,17 @@ def with_checkout_info(results): shipping_address=address_map.get( checkout.shipping_address_id ), - delivery_method_info=delivery_method_info, tax_configuration=tax_configuration_by_channel_map[ channel.id ], - valid_pick_up_points=[], - all_shipping_methods=[], + lines=checkout_lines, + manager=manager, + shipping_channel_listings=shipping_channel_listings, + shipping_method=shipping_method, + collection_point=collection_point, voucher=voucher_code.voucher if voucher_code else None, voucher_code=voucher_code, ) - shipping_method_listings = [ - listing - for channel_listings in listings_for_channels - for listing in channel_listings - if listing.channel_id == channel.id - ] - update_delivery_method_lists_for_checkout_info( - checkout_info, - shipping_method, - collection_point, - shipping_address, - checkout_lines, - manager, - shipping_method_listings, - ) checkout_info_map[key] = checkout_info return [checkout_info_map[key] for key in keys] diff --git a/saleor/graphql/checkout/mutations/checkout_add_promo_code.py b/saleor/graphql/checkout/mutations/checkout_add_promo_code.py index 855a84d8bcd..17319145fd1 100644 --- a/saleor/graphql/checkout/mutations/checkout_add_promo_code.py +++ b/saleor/graphql/checkout/mutations/checkout_add_promo_code.py @@ -5,7 +5,6 @@ from ....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, - update_delivery_method_lists_for_checkout_info, ) from ....checkout.utils import add_promo_code_to_checkout, invalidate_checkout_prices from ....webhook.event_types import WebhookEventAsyncType @@ -103,16 +102,6 @@ def perform_mutation( # type: ignore[override] promo_code, ) - update_delivery_method_lists_for_checkout_info( - checkout_info, - checkout_info.checkout.shipping_method, - checkout_info.checkout.collection_point, - checkout_info.shipping_address, - lines, - manager, - shipping_channel_listings, - ) - update_checkout_shipping_method_if_invalid(checkout_info, lines) invalidate_checkout_prices( checkout_info, diff --git a/saleor/graphql/checkout/mutations/checkout_lines_add.py b/saleor/graphql/checkout/mutations/checkout_lines_add.py index d08ac645b07..6bdec1bc255 100644 --- a/saleor/graphql/checkout/mutations/checkout_lines_add.py +++ b/saleor/graphql/checkout/mutations/checkout_lines_add.py @@ -4,7 +4,6 @@ from ....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, - update_delivery_method_lists_for_checkout_info, ) from ....checkout.utils import add_variants_to_checkout, invalidate_checkout_prices from ....warehouse.reservations import get_reservation_length, is_reservation_enabled @@ -163,16 +162,6 @@ def clean_input( ) lines, _ = fetch_checkout_lines(checkout) - shipping_channel_listings = checkout.channel.shipping_method_listings.all() - update_delivery_method_lists_for_checkout_info( - checkout_info, - checkout_info.checkout.shipping_method, - checkout_info.checkout.collection_point, - checkout_info.shipping_address, - lines, - manager, - shipping_channel_listings, - ) return lines @classmethod diff --git a/saleor/graphql/checkout/types.py b/saleor/graphql/checkout/types.py index a43aa0e8a5b..a3d365d50ac 100644 --- a/saleor/graphql/checkout/types.py +++ b/saleor/graphql/checkout/types.py @@ -12,7 +12,6 @@ from ...checkout.calculations import fetch_checkout_data from ...checkout.utils import get_valid_collection_points_for_checkout from ...core.taxes import zero_money, zero_taxed_money -from ...core.utils.lazyobjects import unwrap_lazy from ...payment.interface import ListStoredPaymentMethodsRequestData from ...permission.auth_filters import AuthorizationFilters from ...permission.enums import ( @@ -851,7 +850,7 @@ def resolve_shipping_methods(root: models.Checkout, info: ResolveInfo): return ( CheckoutInfoByCheckoutTokenLoader(info.context) .load(root.token) - .then(lambda checkout_info: unwrap_lazy(checkout_info.all_shipping_methods)) + .then(lambda checkout_info: checkout_info.all_shipping_methods) ) @staticmethod From eac4ea7d6be3f7bc83327008c31d0adbdcb4d560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Tue, 23 Apr 2024 15:02:28 +0200 Subject: [PATCH 03/10] Adjust tests --- saleor/checkout/fetch.py | 25 ++++- saleor/checkout/tests/test_checkout.py | 103 ++++++++++-------- saleor/checkout/utils.py | 20 +++- .../mutations/checkout_add_promo_code.py | 10 ++ .../checkout/mutations/checkout_lines_add.py | 10 ++ .../benchmark/test_checkout_mutations.py | 8 +- .../test_checkout_shipping_method_update.py | 23 +--- .../test_checkout_shipping_method_update.py | 7 -- saleor/graphql/context.py | 2 - saleor/graphql/core/tests/test_view.py | 1 - .../tests/test_mutation_plugin_update.py | 20 ++-- saleor/graphql/tests/test_context.py | 6 +- ...roduct_with_charge_taxes_set_to_false.yaml | 74 +++++++++++++ saleor/plugins/avatax/tests/conftest.py | 16 +-- saleor/plugins/avatax/tests/test_avatax.py | 62 ++++------- 15 files changed, 228 insertions(+), 159 deletions(-) create mode 100644 saleor/plugins/avatax/tests/cassettes/test_avatax/test_get_checkout_line_tax_rate_for_product_with_charge_taxes_set_to_false.yaml diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index c9c93936c5a..3ff9516e993 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -64,18 +64,15 @@ def get_promotion_discounts(self) -> list["CheckoutLineDiscount"]: @dataclass class CheckoutInfo: - # compare=False used to avoid comparing the manager attribute as it's irrelevant - # for the equality checkout infos; this is used in tests manager: "PluginsManager" = field(compare=False) - lines: Iterable[CheckoutLineInfo] = field(compare=False) - checkout: "Checkout" user: Optional["User"] channel: "Channel" billing_address: Optional["Address"] shipping_address: Optional["Address"] tax_configuration: "TaxConfiguration" - shipping_channel_listings: Iterable["ShippingMethodChannelListing"] + lines: Iterable[CheckoutLineInfo] + shipping_channel_listings: list["ShippingMethodChannelListing"] shipping_method: Optional["ShippingMethod"] = None collection_point: Optional["Warehouse"] = None voucher: Optional["Voucher"] = None @@ -97,7 +94,7 @@ def all_shipping_methods(self) -> list["ShippingMethodData"]: initialize_shipping_method_active_status(all_methods, excluded_methods) return all_methods - @cached_property + @property def valid_pick_up_points(self) -> Iterable["Warehouse"]: from .utils import get_valid_collection_points_for_checkout @@ -589,3 +586,19 @@ def get_all_shipping_methods_list( ), ) ) + + +def update_delivery_method_lists_for_checkout_info( + checkout_info: "CheckoutInfo", + shipping_method: Optional["ShippingMethod"], + collection_point: Optional["Warehouse"], + shipping_address: Optional["Address"], + lines: Iterable[CheckoutLineInfo], + shipping_channel_listings: Iterable[ShippingMethodChannelListing], +): + # Update checkout info fields with new data + checkout_info.shipping_method = shipping_method + checkout_info.collection_point = collection_point + checkout_info.shipping_address = shipping_address + checkout_info.lines = lines + checkout_info.shipping_channel_listings = list(shipping_channel_listings) diff --git a/saleor/checkout/tests/test_checkout.py b/saleor/checkout/tests/test_checkout.py index 3cc59f1b31a..473900c5d23 100644 --- a/saleor/checkout/tests/test_checkout.py +++ b/saleor/checkout/tests/test_checkout.py @@ -33,7 +33,6 @@ DeliveryMethodBase, fetch_checkout_info, fetch_checkout_lines, - get_delivery_method_info, ) from ..models import Checkout, CheckoutLine from ..utils import ( @@ -317,17 +316,7 @@ def test_get_discount_for_checkout_value_entire_order_voucher( "saleor.checkout.base_calculations.base_checkout_subtotal", lambda *args: subtotal, ) - checkout_info = CheckoutInfo( - checkout=checkout, - shipping_address=None, - billing_address=None, - channel=channel_USD, - user=None, - tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - delivery_method_info=get_delivery_method_info(None, None), - all_shipping_methods=[], - ) + manager = get_plugins_manager(allow_replica=False) lines = [ CheckoutLineInfo( line=line, @@ -342,7 +331,17 @@ def test_get_discount_for_checkout_value_entire_order_voucher( ) for line in checkout_with_items.lines.all() ] - manager = get_plugins_manager(allow_replica=False) + checkout_info = CheckoutInfo( + checkout=checkout, + shipping_address=None, + billing_address=None, + channel=channel_USD, + user=None, + tax_configuration=channel_USD.tax_configuration, + manager=manager, + lines=lines, + shipping_channel_listings=[], + ) # when discount = get_voucher_discount_for_checkout( @@ -481,17 +480,6 @@ def test_get_discount_for_checkout_value_specific_product_voucher( "saleor.checkout.base_calculations.base_checkout_subtotal", lambda *args: subtotal, ) - checkout_info = CheckoutInfo( - checkout=checkout, - shipping_address=None, - billing_address=None, - channel=channel_USD, - user=None, - tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - delivery_method_info=get_delivery_method_info(None, None), - all_shipping_methods=[], - ) lines = [ CheckoutLineInfo( line=line, @@ -507,6 +495,17 @@ def test_get_discount_for_checkout_value_specific_product_voucher( for line in checkout_with_items.lines.all() ] manager = get_plugins_manager(allow_replica=False) + checkout_info = CheckoutInfo( + checkout=checkout, + shipping_address=None, + billing_address=None, + channel=channel_USD, + user=None, + tax_configuration=channel_USD.tax_configuration, + manager=manager, + lines=lines, + shipping_channel_listings=[], + ) # when discount = get_voucher_discount_for_checkout( @@ -596,18 +595,18 @@ def test_get_discount_for_checkout_entire_order_voucher_not_applicable( "saleor.checkout.base_calculations.base_checkout_subtotal", lambda *args: subtotal, ) + manager = get_plugins_manager(allow_replica=False) checkout_info = CheckoutInfo( checkout=checkout, - delivery_method_info=None, shipping_address=None, billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=[], + shipping_channel_listings=[], ) - manager = get_plugins_manager(allow_replica=False) with pytest.raises(NotApplicable): get_voucher_discount_for_checkout(manager, voucher, checkout_info, [], None) @@ -777,14 +776,14 @@ def test_get_discount_for_checkout_specific_products_voucher_not_applicable( checkout = Mock(quantity=total_quantity, spec=Checkout, channel=channel_USD) checkout_info = CheckoutInfo( checkout=checkout, - delivery_method_info=get_delivery_method_info(None, None), shipping_address=None, billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=[], + shipping_channel_listings=[], ) with pytest.raises(NotApplicable): get_voucher_discount_for_checkout(manager, voucher, checkout_info, [], None) @@ -844,7 +843,6 @@ def test_get_discount_for_checkout_shipping_voucher( monkeypatch, channel_USD, shipping_method, - shipping_method_data, ): manager = get_plugins_manager(allow_replica=False) tax = Decimal("1.23") @@ -883,15 +881,14 @@ def test_get_discount_for_checkout_shipping_voucher( checkout_info = CheckoutInfo( checkout=checkout, shipping_address=shipping_address, - delivery_method_info=get_delivery_method_info( - shipping_method_data, shipping_address - ), billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + shipping_method=shipping_method, + lines=[], + shipping_channel_listings=shipping_method.channel_listings.all(), ) discount = get_voucher_discount_for_checkout( @@ -901,7 +898,7 @@ def test_get_discount_for_checkout_shipping_voucher( def test_get_discount_for_checkout_shipping_voucher_all_countries( - monkeypatch, channel_USD, shipping_method, shipping_method_data + monkeypatch, channel_USD, shipping_method ): subtotal = Money(100, "USD") monkeypatch.setattr( @@ -936,14 +933,15 @@ def test_get_discount_for_checkout_shipping_voucher_all_countries( manager = get_plugins_manager(allow_replica=False) checkout_info = CheckoutInfo( checkout=checkout, - delivery_method_info=get_delivery_method_info(shipping_method_data), shipping_address=Mock(spec=Address, country=Mock(code="PL")), billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + shipping_method=shipping_method, + shipping_channel_listings=shipping_method.channel_listings.all(), + manager=manager, + lines=[], ) discount = get_voucher_discount_for_checkout( manager, voucher, checkout_info, [], None @@ -980,18 +978,18 @@ def test_get_discount_for_checkout_shipping_voucher_limited_countries( discount=Money(50, channel_USD.currency_code), ) + manager = get_plugins_manager(allow_replica=False) checkout_info = CheckoutInfo( checkout=checkout, - delivery_method_info=get_delivery_method_info(None, None), shipping_address=Mock(spec=Address, country=Mock(code="PL")), billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=[], + shipping_channel_listings=[], ) - manager = get_plugins_manager(allow_replica=False) with pytest.raises(NotApplicable): get_voucher_discount_for_checkout( manager, @@ -1112,6 +1110,13 @@ def test_get_discount_for_checkout_shipping_voucher_not_applicable( monkeypatch.setattr( "saleor.checkout.utils.is_shipping_required", lambda lines: is_shipping_required ) + + if shipping_method_data: + shipping_channel_listings = shipping_method.channel_listings.all() + else: + shipping_method = None + shipping_channel_listings = [] + checkout = Mock( is_shipping_required=Mock(return_value=is_shipping_required), shipping_method=shipping_method, @@ -1119,6 +1124,7 @@ def test_get_discount_for_checkout_shipping_voucher_not_applicable( quantity=total_quantity, spec=Checkout, channel=channel_USD, + get_value_from_private_metadata=Mock(return_value=None), ) voucher = Voucher.objects.create( @@ -1137,14 +1143,15 @@ def test_get_discount_for_checkout_shipping_voucher_not_applicable( ) checkout_info = CheckoutInfo( checkout=checkout, - delivery_method_info=get_delivery_method_info(shipping_method_data), shipping_address=Mock(spec=Address, country=Mock(code="PL")), billing_address=None, channel=channel_USD, user=None, tax_configuration=channel_USD.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=[], + shipping_method=shipping_method, + shipping_channel_listings=shipping_channel_listings, ) with pytest.raises(NotApplicable) as e: get_voucher_discount_for_checkout( diff --git a/saleor/checkout/utils.py b/saleor/checkout/utils.py index 65c088bcadf..0defe767f80 100644 --- a/saleor/checkout/utils.py +++ b/saleor/checkout/utils.py @@ -10,6 +10,7 @@ from prices import Money from ..account.models import User +from ..checkout.fetch import update_delivery_method_lists_for_checkout_info from ..core.exceptions import ProductNotPublished from ..core.taxes import zero_taxed_money from ..core.utils.promo_code import ( @@ -366,7 +367,14 @@ def change_shipping_address_in_checkout( if remove and checkout.shipping_address: checkout.shipping_address.delete() checkout.shipping_address = address - checkout_info.shipping_address = address + update_delivery_method_lists_for_checkout_info( + checkout_info=checkout_info, + shipping_method=checkout_info.checkout.shipping_method, + collection_point=checkout_info.checkout.collection_point, + shipping_address=address, + lines=lines, + shipping_channel_listings=shipping_channel_listings, + ) updated_fields = ["shipping_address", "last_change"] return updated_fields @@ -850,6 +858,16 @@ def clear_delivery_method(checkout_info: "CheckoutInfo"): checkout.collection_point = None checkout.shipping_method = None checkout_info.shipping_method = None + + update_delivery_method_lists_for_checkout_info( + checkout_info=checkout_info, + shipping_method=None, + collection_point=None, + shipping_address=checkout_info.shipping_address, + lines=checkout_info.lines, + shipping_channel_listings=checkout_info.shipping_channel_listings, + ) + delete_external_shipping_id(checkout=checkout) checkout.save( update_fields=[ diff --git a/saleor/graphql/checkout/mutations/checkout_add_promo_code.py b/saleor/graphql/checkout/mutations/checkout_add_promo_code.py index 17319145fd1..7eb4a5bc89c 100644 --- a/saleor/graphql/checkout/mutations/checkout_add_promo_code.py +++ b/saleor/graphql/checkout/mutations/checkout_add_promo_code.py @@ -5,6 +5,7 @@ from ....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, + update_delivery_method_lists_for_checkout_info, ) from ....checkout.utils import add_promo_code_to_checkout, invalidate_checkout_prices from ....webhook.event_types import WebhookEventAsyncType @@ -102,6 +103,15 @@ def perform_mutation( # type: ignore[override] promo_code, ) + update_delivery_method_lists_for_checkout_info( + checkout_info=checkout_info, + shipping_method=checkout_info.checkout.shipping_method, + collection_point=checkout_info.checkout.collection_point, + shipping_address=checkout_info.shipping_address, + lines=lines, + shipping_channel_listings=shipping_channel_listings, + ) + update_checkout_shipping_method_if_invalid(checkout_info, lines) invalidate_checkout_prices( checkout_info, diff --git a/saleor/graphql/checkout/mutations/checkout_lines_add.py b/saleor/graphql/checkout/mutations/checkout_lines_add.py index 6bdec1bc255..ba6ef269a3f 100644 --- a/saleor/graphql/checkout/mutations/checkout_lines_add.py +++ b/saleor/graphql/checkout/mutations/checkout_lines_add.py @@ -4,6 +4,7 @@ from ....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, + update_delivery_method_lists_for_checkout_info, ) from ....checkout.utils import add_variants_to_checkout, invalidate_checkout_prices from ....warehouse.reservations import get_reservation_length, is_reservation_enabled @@ -162,6 +163,15 @@ def clean_input( ) lines, _ = fetch_checkout_lines(checkout) + shipping_channel_listings = checkout.channel.shipping_method_listings.all() + update_delivery_method_lists_for_checkout_info( + checkout_info=checkout_info, + shipping_method=checkout_info.checkout.shipping_method, + collection_point=checkout_info.checkout.collection_point, + shipping_address=checkout_info.shipping_address, + lines=lines, + shipping_channel_listings=shipping_channel_listings, + ) return lines @classmethod diff --git a/saleor/graphql/checkout/tests/benchmark/test_checkout_mutations.py b/saleor/graphql/checkout/tests/benchmark/test_checkout_mutations.py index d8e4e749f6f..53fee413360 100644 --- a/saleor/graphql/checkout/tests/benchmark/test_checkout_mutations.py +++ b/saleor/graphql/checkout/tests/benchmark/test_checkout_mutations.py @@ -681,7 +681,7 @@ def test_update_checkout_lines_with_reservations( reservation_length=5, ) - with django_assert_num_queries(76): + with django_assert_num_queries(78): variant_id = graphene.Node.to_global_id("ProductVariant", variants[0].pk) variables = { "id": to_global_id_or_none(checkout), @@ -695,7 +695,7 @@ def test_update_checkout_lines_with_reservations( assert not data["errors"] # Updating multiple lines in checkout has same query count as updating one - with django_assert_num_queries(76): + with django_assert_num_queries(78): variables = { "id": to_global_id_or_none(checkout), "lines": [], @@ -940,7 +940,7 @@ def test_add_checkout_lines_with_reservations( new_lines.append({"quantity": 2, "variantId": variant_id}) # Adding multiple lines to checkout has same query count as adding one - with django_assert_num_queries(75): + with django_assert_num_queries(77): variables = { "id": Node.to_global_id("Checkout", checkout.pk), "lines": [new_lines[0]], @@ -953,7 +953,7 @@ def test_add_checkout_lines_with_reservations( checkout.lines.exclude(id=line.id).delete() - with django_assert_num_queries(75): + with django_assert_num_queries(77): variables = { "id": Node.to_global_id("Checkout", checkout.pk), "lines": new_lines, diff --git a/saleor/graphql/checkout/tests/deprecated/test_checkout_shipping_method_update.py b/saleor/graphql/checkout/tests/deprecated/test_checkout_shipping_method_update.py index 36b2a19bc86..a7fc4a6107b 100644 --- a/saleor/graphql/checkout/tests/deprecated/test_checkout_shipping_method_update.py +++ b/saleor/graphql/checkout/tests/deprecated/test_checkout_shipping_method_update.py @@ -6,7 +6,6 @@ from .....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, - get_delivery_method_info, ) from .....plugins.manager import get_plugins_manager from .....shipping.utils import convert_to_shipping_method_data @@ -45,7 +44,6 @@ def test_checkout_shipping_method_update_by_id( checkout_with_item_and_shipping_method, ): checkout = checkout_with_item_and_shipping_method - old_shipping_method = checkout.shipping_method query = MUTATION_UPDATE_SHIPPING_METHOD mock_clean_shipping.return_value = True @@ -62,12 +60,6 @@ def test_checkout_shipping_method_update_by_id( manager = get_plugins_manager(allow_replica=False) lines, _ = fetch_checkout_lines(checkout) checkout_info = fetch_checkout_info(checkout, lines, manager) - checkout_info.delivery_method_info = get_delivery_method_info( - convert_to_shipping_method_data( - old_shipping_method, old_shipping_method.channel_listings.first() - ), - None, - ) mock_clean_shipping.assert_called_once_with( checkout_info=checkout_info, @@ -93,7 +85,6 @@ def test_checkout_shipping_method_update_by_token( checkout_with_item_and_shipping_method, ): checkout = checkout_with_item_and_shipping_method - old_shipping_method = checkout.shipping_method query = MUTATION_UPDATE_SHIPPING_METHOD mock_clean_shipping.return_value = True @@ -110,12 +101,7 @@ def test_checkout_shipping_method_update_by_token( manager = get_plugins_manager(allow_replica=False) lines, _ = fetch_checkout_lines(checkout) checkout_info = fetch_checkout_info(checkout, lines, manager) - checkout_info.delivery_method_info = get_delivery_method_info( - convert_to_shipping_method_data( - old_shipping_method, old_shipping_method.channel_listings.first() - ), - None, - ) + mock_clean_shipping.assert_called_once_with( checkout_info=checkout_info, lines=lines, @@ -189,7 +175,6 @@ def test_checkout_shipping_method_update_by_id_no_checkout_metadata( ): # given checkout = checkout_with_item_and_shipping_method - old_shipping_method = checkout.shipping_method query = MUTATION_UPDATE_SHIPPING_METHOD mock_clean_shipping.return_value = True @@ -211,12 +196,6 @@ def test_checkout_shipping_method_update_by_id_no_checkout_metadata( manager = get_plugins_manager(allow_replica=False) lines, _ = fetch_checkout_lines(checkout) checkout_info = fetch_checkout_info(checkout, lines, manager) - checkout_info.delivery_method_info = get_delivery_method_info( - convert_to_shipping_method_data( - old_shipping_method, old_shipping_method.channel_listings.first() - ), - None, - ) mock_clean_shipping.assert_called_once_with( checkout_info=checkout_info, diff --git a/saleor/graphql/checkout/tests/mutations/test_checkout_shipping_method_update.py b/saleor/graphql/checkout/tests/mutations/test_checkout_shipping_method_update.py index 456ecf929a6..669d63e0af2 100644 --- a/saleor/graphql/checkout/tests/mutations/test_checkout_shipping_method_update.py +++ b/saleor/graphql/checkout/tests/mutations/test_checkout_shipping_method_update.py @@ -9,7 +9,6 @@ from .....checkout.fetch import ( fetch_checkout_info, fetch_checkout_lines, - get_delivery_method_info, ) from .....checkout.utils import PRIVATE_META_APP_SHIPPING_ID, invalidate_checkout_prices from .....plugins.base_plugin import ExcludedShippingMethod @@ -73,12 +72,6 @@ def test_checkout_shipping_method_update( manager = get_plugins_manager(allow_replica=False) lines, _ = fetch_checkout_lines(checkout) checkout_info = fetch_checkout_info(checkout, lines, manager) - checkout_info.delivery_method_info = get_delivery_method_info( - convert_to_shipping_method_data( - old_shipping_method, old_shipping_method.channel_listings.first() - ), - None, - ) mock_clean_shipping.assert_called_once_with( checkout_info=checkout_info, lines=lines, diff --git a/saleor/graphql/context.py b/saleor/graphql/context.py index ba059eb24a5..f50d02e2418 100644 --- a/saleor/graphql/context.py +++ b/saleor/graphql/context.py @@ -28,8 +28,6 @@ def get_context_value(request: HttpRequest) -> SaleorContext: def clear_context(context: SaleorContext): context.dataloaders.clear() - context.user = None - context.app = None class RequestWithUser(HttpRequest): diff --git a/saleor/graphql/core/tests/test_view.py b/saleor/graphql/core/tests/test_view.py index ad7b3964d81..b7c72f8e991 100644 --- a/saleor/graphql/core/tests/test_view.py +++ b/saleor/graphql/core/tests/test_view.py @@ -354,4 +354,3 @@ def test_graphql_view_clears_context(rf, staff_user, product): assert json_data["data"]["product"]["category"]["name"] == product.category.name assert response.status_code == 200 assert request.dataloaders == {} - assert request.user is None diff --git a/saleor/graphql/plugins/tests/test_mutation_plugin_update.py b/saleor/graphql/plugins/tests/test_mutation_plugin_update.py index 5ef7f1a17c0..2b010f23f91 100644 --- a/saleor/graphql/plugins/tests/test_mutation_plugin_update.py +++ b/saleor/graphql/plugins/tests/test_mutation_plugin_update.py @@ -326,9 +326,9 @@ def test_cannot_update_configuration_of_hidden_plugin( settings.PLUGINS = ["saleor.plugins.tests.sample_plugins.PluginSample"] plugin_id = PluginSample.PLUGIN_ID - original_config = ( - get_plugins_manager(allow_replica=False).get_plugin(plugin_id).configuration - ) + manager = get_plugins_manager(allow_replica=False) + plugin = manager.get_plugin(plugin_id) + original_config = plugin.configuration variables = { "id": plugin_id, @@ -347,7 +347,7 @@ def test_cannot_update_configuration_of_hidden_plugin( ] # Hidden plugin should be untouched - plugin = get_plugins_manager(allow_replica=False).get_plugin(plugin_id) + plugin = manager.get_plugin(plugin_id) assert plugin.active is True assert plugin.configuration == original_config @@ -370,11 +370,9 @@ def test_cannot_update_configuration_of_hidden_multichannel_plugin( settings.PLUGINS = ["saleor.plugins.tests.sample_plugins.ChannelPluginSample"] plugin_id = ChannelPluginSample.PLUGIN_ID - original_config = ( - get_plugins_manager(allow_replica=False) - .get_plugin(plugin_id, channel_slug=channel_USD.slug) - .configuration - ) + manager = get_plugins_manager(allow_replica=False) + plugin = manager.get_plugin(plugin_id, channel_slug=channel_USD.slug) + original_config = plugin.configuration variables = { "id": plugin_id, @@ -393,9 +391,7 @@ def test_cannot_update_configuration_of_hidden_multichannel_plugin( ] # Hidden plugin should be untouched - plugin = get_plugins_manager(allow_replica=False).get_plugin( - plugin_id, channel_slug=channel_USD.slug - ) + plugin = manager.get_plugin(plugin_id, channel_slug=channel_USD.slug) assert plugin.active is True assert plugin.configuration == original_config diff --git a/saleor/graphql/tests/test_context.py b/saleor/graphql/tests/test_context.py index 2ac12ece383..60d330f811e 100644 --- a/saleor/graphql/tests/test_context.py +++ b/saleor/graphql/tests/test_context.py @@ -76,17 +76,13 @@ def test_get_context_value_uses_request_time_if_passed_already(rf): # {'query': '{\n me {\n id\n }\n}'} -def test_clear_context(rf, staff_user, app): +def test_clear_context(rf): # given context = get_context_value(rf.request()) context.dataloaders = {"key": "value"} # type: ignore - context.user = staff_user - context.app = app # when clear_context(context) # then assert context.dataloaders == {} - assert context.user is None - assert context.app is None diff --git a/saleor/plugins/avatax/tests/cassettes/test_avatax/test_get_checkout_line_tax_rate_for_product_with_charge_taxes_set_to_false.yaml b/saleor/plugins/avatax/tests/cassettes/test_avatax/test_get_checkout_line_tax_rate_for_product_with_charge_taxes_set_to_false.yaml new file mode 100644 index 00000000000..4e307463390 --- /dev/null +++ b/saleor/plugins/avatax/tests/cassettes/test_avatax/test_get_checkout_line_tax_rate_for_product_with_charge_taxes_set_to_false.yaml @@ -0,0 +1,74 @@ +interactions: +- request: + body: '{"createTransactionModel": {"companyCode": "DEFAULT", "type": "SalesOrder", + "lines": [{"quantity": 3, "amount": "30.00", "taxCode": "NT", "taxIncluded": + false, "itemCode": "123", "discounted": false, "description": "Test product", + "ref1": "123"}, {"quantity": 1, "amount": "10.000", "taxCode": "FR000000", "taxIncluded": + false, "itemCode": "Shipping", "discounted": false, "description": null}], "code": + "db304eb2-b138-47fc-b9ee-ec979e3328dd", "date": "2024-04-23", "customerCode": + 0, "discount": null, "addresses": {"shipFrom": {"line1": "Teczowa 7", "line2": + "", "city": "Wroclaw", "region": "", "country": "PL", "postalCode": "53-601"}, + "shipTo": {"line1": "T\u0119czowa 7", "line2": "", "city": "WROC\u0141AW", "region": + "", "country": "PL", "postalCode": "53-601"}}, "commit": false, "currencyCode": + "USD", "email": "user@email.com"}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Authorization: + - Basic Og== + Connection: + - keep-alive + Content-Length: + - '839' + User-Agent: + - Saleor/3.18 + method: POST + uri: https://sandbox-rest.avatax.com/api/v2/transactions/createoradjust + response: + body: + string: '{"id":0,"code":"db304eb2-b138-47fc-b9ee-ec979e3328dd","companyId":7799660,"date":"2024-04-23","paymentDate":"2024-04-23","status":"Temporary","type":"SalesOrder","batchCode":"","currencyCode":"USD","exchangeRateCurrencyCode":"USD","customerUsageType":"","entityUseCode":"","customerVendorCode":"0","customerCode":"0","exemptNo":"","reconciled":false,"locationCode":"","reportingLocationCode":"","purchaseOrderNo":"","referenceCode":"","salespersonCode":"","totalAmount":40.0,"totalExempt":30.0,"totalDiscount":0.0,"totalTax":2.3,"totalTaxable":10.0,"totalTaxCalculated":2.3,"adjustmentReason":"NotAdjusted","locked":false,"version":1,"exchangeRateEffectiveDate":"2024-04-23","exchangeRate":1.0,"email":"user@email.com","modifiedDate":"2024-04-23T12:05:15.612755Z","modifiedUserId":6479978,"taxDate":"2024-04-23","lines":[{"id":0,"transactionId":0,"lineNumber":"1","customerUsageType":"","entityUseCode":"","description":"Test + product","discountAmount":0.0,"exemptAmount":30.0,"exemptCertId":0,"exemptNo":"","isItemTaxable":false,"itemCode":"123","lineAmount":30.0,"quantity":3.0,"ref1":"123","ref2":"","reportingDate":"2024-04-23","sourcing":"Origin","tax":0.0,"taxableAmount":0.0,"taxCalculated":0.0,"taxCode":"NT","taxCodeId":16740,"taxDate":"2024-04-23","taxIncluded":false,"details":[{"id":0,"transactionLineId":0,"transactionId":0,"country":"PL","region":"PL","stateFIPS":"PL","exemptAmount":0.0,"jurisCode":"PL","jurisName":"POLAND","jurisdictionId":200102,"stateAssignedNo":"","jurisType":"CNT","jurisdictionType":"Country","nonTaxableAmount":30.0,"rate":0.230000,"sourcing":"Origin","tax":0.0,"taxableAmount":0.0,"taxType":"Output","taxSubTypeId":"O","taxName":"Standard + Rate","taxAuthorityTypeId":45,"taxCalculated":0.0,"rateType":"Standard","rateTypeCode":"S","taxableUnits":0.0000,"nonTaxableUnits":30.0000,"exemptUnits":0.0000,"unitOfBasis":"PerCurrencyUnit","isNonPassThru":false,"isFee":false,"reportingTaxableUnits":0.0,"reportingNonTaxableUnits":30.0,"reportingExemptUnits":0.0,"reportingTax":0.0,"reportingTaxCalculated":0.0,"avtUserBIN":"","liabilityType":"Seller","chargedTo":"Buyer"}],"nonPassthroughDetails":[],"hsCode":"","costInsuranceFreight":0.0,"vatCode":"PLSL230C","vatNumberTypeId":0},{"id":0,"transactionId":0,"lineNumber":"2","customerUsageType":"","entityUseCode":"","discountAmount":0.0,"exemptAmount":0.0,"exemptCertId":0,"exemptNo":"","isItemTaxable":true,"itemCode":"Shipping","lineAmount":10.0,"quantity":1.0,"ref1":"","ref2":"","reportingDate":"2024-04-23","sourcing":"Destination","tax":2.3,"taxableAmount":10.0,"taxCalculated":2.3,"taxCode":"FR000000","taxCodeId":8550,"taxDate":"2024-04-23","taxIncluded":false,"details":[{"id":0,"transactionLineId":0,"transactionId":0,"country":"PL","region":"PL","stateFIPS":"PL","exemptAmount":0.0,"jurisCode":"PL","jurisName":"POLAND","jurisdictionId":200102,"stateAssignedNo":"","jurisType":"CNT","jurisdictionType":"Country","nonTaxableAmount":0.0,"rate":0.230000,"sourcing":"Destination","tax":2.3,"taxableAmount":10.0,"taxType":"Output","taxSubTypeId":"O","taxName":"Standard + Rate","taxAuthorityTypeId":45,"taxCalculated":2.3,"rateType":"Standard","rateTypeCode":"S","taxableUnits":10.0000,"nonTaxableUnits":0.0000,"exemptUnits":0.0000,"unitOfBasis":"PerCurrencyUnit","isNonPassThru":false,"isFee":false,"reportingTaxableUnits":10.0,"reportingNonTaxableUnits":0.0,"reportingExemptUnits":0.0,"reportingTax":2.3,"reportingTaxCalculated":2.3,"liabilityType":"Seller","chargedTo":"Buyer"}],"nonPassthroughDetails":[],"hsCode":"","costInsuranceFreight":0.0,"vatCode":"PLS-230D","vatNumberTypeId":0}],"addresses":[{"id":0,"transactionId":0,"boundaryLevel":"Zip5","line1":"Teczowa + 7","line2":"","line3":"","city":"WROCLAW","region":"","postalCode":"53-601","country":"PL","taxRegionId":205102,"latitude":"","longitude":""},{"id":0,"transactionId":0,"boundaryLevel":"Zip5","line1":"Teczowa + 7","line2":"","line3":"","city":"Wroclaw","region":"","postalCode":"53-601","country":"PL","taxRegionId":205102,"latitude":"","longitude":""}],"summary":[{"country":"PL","region":"PL","jurisType":"Country","jurisCode":"PL","jurisName":"POLAND","taxAuthorityType":45,"stateAssignedNo":"","taxType":"Output","taxSubType":"O","taxName":"Standard + Rate","rateType":"Standard","taxable":10.0,"rate":0.230000,"tax":2.3,"taxCalculated":2.3,"nonTaxable":30.0,"exemption":0.0}]}' + headers: + Connection: + - keep-alive + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 23 Apr 2024 12:05:15 GMT + Location: + - /api/v2/companies/7799660/transactions/0 + ServerDuration: + - '00:00:00.0214048' + Transfer-Encoding: + - chunked + X-Content-Type-Options: + - nosniff + api-supported-versions: + - '2.0' + cache-control: + - private, no-cache, no-store + referrer-policy: + - same-origin + strict-transport-security: + - max-age=31536000; includeSubdomains + x-avalara-uid: + - da7eea91-8b85-4703-a667-686f1ca2c3d9 + x-correlation-id: + - da7eea91-8b85-4703-a667-686f1ca2c3d9 + x-frame-options: + - sameorigin + x-permitted-cross-domain-policies: + - none + x-xss-protection: + - 1; mode=block + status: + code: 201 + message: Created +version: 1 diff --git a/saleor/plugins/avatax/tests/conftest.py b/saleor/plugins/avatax/tests/conftest.py index aa9a6558f68..50dd62fa1f5 100644 --- a/saleor/plugins/avatax/tests/conftest.py +++ b/saleor/plugins/avatax/tests/conftest.py @@ -3,9 +3,9 @@ import pytest from ....account.models import Address -from ....checkout.fetch import CheckoutInfo, get_delivery_method_info +from ....checkout.fetch import CheckoutInfo, fetch_checkout_lines from ....shipping.models import ShippingMethodChannelListing -from ....shipping.utils import convert_to_shipping_method_data +from ...manager import get_plugins_manager from ...models import PluginConfiguration from .. import AvataxConfiguration from ..plugin import AvataxPlugin @@ -109,19 +109,19 @@ def checkout_with_items_and_shipping_info(checkout_with_items_and_shipping): channel=channel, shipping_method=shipping_method, ) + manager = get_plugins_manager(allow_replica=False) + lines, _ = fetch_checkout_lines(checkout) checkout_info = CheckoutInfo( checkout=checkout, user=checkout.user, channel=channel, billing_address=checkout.billing_address, shipping_address=shipping_address, - delivery_method_info=get_delivery_method_info( - convert_to_shipping_method_data(shipping_method, shipping_channel_listing), - shipping_address, - ), + shipping_method=shipping_method, + shipping_channel_listings=[shipping_channel_listing], tax_configuration=channel.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=lines, ) return checkout_info diff --git a/saleor/plugins/avatax/tests/test_avatax.py b/saleor/plugins/avatax/tests/test_avatax.py index b88d42cbff4..6069a4f9f5f 100644 --- a/saleor/plugins/avatax/tests/test_avatax.py +++ b/saleor/plugins/avatax/tests/test_avatax.py @@ -16,8 +16,6 @@ CheckoutInfo, fetch_checkout_info, fetch_checkout_lines, - get_delivery_method_info, - update_delivery_method_lists_for_checkout_info, ) from ....checkout.utils import add_variant_to_checkout from ....core.prices import quantize_price @@ -31,7 +29,6 @@ from ....product import ProductTypeKind from ....product.models import Product, ProductType from ....product.utils.variant_prices import update_discounted_prices_for_promotion -from ....shipping.utils import convert_to_shipping_method_data from ....tax import TaxCalculationStrategy from ....tax.models import TaxClass from ...manager import get_plugins_manager @@ -2058,15 +2055,6 @@ def test_calculate_checkout_subtotal_for_product_without_tax( lines, _ = fetch_checkout_lines(checkout) assert len(lines) == 1 - update_delivery_method_lists_for_checkout_info( - checkout_info, - checkout_info.checkout.shipping_method, - checkout_info.checkout.collection_point, - ship_to_pl_address, - lines, - manager, - checkout.channel.shipping_method_listings.all(), - ) total = manager.calculate_checkout_subtotal(checkout_info, lines, address) total = quantize_price(total, total.currency) @@ -3355,25 +3343,20 @@ def test_get_checkout_line_tax_rate( checkout_with_item.shipping_address = address checkout_with_item.shipping_method = shipping_zone.shipping_methods.get() checkout_with_item.save(update_fields=["shipping_address", "shipping_method"]) - delivery_method = checkout_with_item.shipping_method + lines, _ = fetch_checkout_lines(checkout_with_item) + shipping_method = checkout_with_item.shipping_method checkout_info = CheckoutInfo( checkout=checkout_with_item, - delivery_method_info=get_delivery_method_info( - convert_to_shipping_method_data( - delivery_method, - delivery_method.channel_listings.first(), - ), - address, - ), shipping_address=address, billing_address=None, channel=checkout_with_item.channel, user=None, tax_configuration=checkout_with_item.channel.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=lines, + shipping_method=shipping_method, + shipping_channel_listings=shipping_method.channel_listings.all(), ) - lines, _ = fetch_checkout_lines(checkout_with_item) checkout_line_info = lines[0] # when @@ -3420,25 +3403,21 @@ def test_get_checkout_line_tax_rate_for_product_with_charge_taxes_set_to_false( checkout_with_item.shipping_address = address checkout_with_item.shipping_method = shipping_zone.shipping_methods.get() checkout_with_item.save(update_fields=["shipping_address", "shipping_method"]) - delivery_method = checkout_with_item.shipping_method + lines, _ = fetch_checkout_lines(checkout_with_item) + shipping_method = checkout_with_item.shipping_method checkout_info = CheckoutInfo( checkout=checkout_with_item, - delivery_method_info=get_delivery_method_info( - convert_to_shipping_method_data( - delivery_method, - delivery_method.channel_listings.first(), - ) - ), shipping_address=address, billing_address=None, channel=checkout_with_item.channel, user=None, tax_configuration=checkout_with_item.channel.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + lines=lines, + shipping_method=shipping_method, + shipping_channel_listings=shipping_method.channel_listings.all(), ) - lines, _ = fetch_checkout_lines(checkout_with_item) checkout_line_info = lines[0] product = checkout_line_info.product product.tax_class = tax_class_zero_rates @@ -3492,25 +3471,22 @@ def test_get_checkout_line_tax_rate_for_product_type_with_non_taxable_product( checkout_with_item.shipping_address = address checkout_with_item.shipping_method = shipping_zone.shipping_methods.get() checkout_with_item.save(update_fields=["shipping_address", "shipping_method"]) - delivery_method = checkout_with_item.shipping_method + + lines, _ = fetch_checkout_lines(checkout_with_item) + shipping_method = checkout_with_item.shipping_method variant2 = product2.variants.first() checkout_info = CheckoutInfo( checkout=checkout_with_item, - delivery_method_info=get_delivery_method_info( - convert_to_shipping_method_data( - delivery_method, - delivery_method.channel_listings.first(), - ), - address, - ), shipping_address=address, billing_address=None, channel=checkout_with_item.channel, user=None, tax_configuration=checkout_with_item.channel.tax_configuration, - valid_pick_up_points=[], - all_shipping_methods=[], + manager=manager, + shipping_method=shipping_method, + shipping_channel_listings=shipping_method.channel_listings.all(), + lines=lines, ) add_variant_to_checkout(checkout_info, variant2, 1) From 088b94baff5337f83bfdca1e35d3941701da5d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Tue, 23 Apr 2024 17:46:30 +0200 Subject: [PATCH 04/10] Clean cached property if fetched before --- saleor/checkout/fetch.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index 3ff9516e993..fc5996b8c03 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -555,17 +555,6 @@ def get_valid_internal_shipping_method_list_for_checkout_info( return valid_shipping_methods -def get_valid_external_shipping_method_list_for_checkout_info( - checkout_info: "CheckoutInfo", - shipping_address: Optional["Address"], - lines: Iterable[CheckoutLineInfo], - manager: "PluginsManager", -) -> list["ShippingMethodData"]: - return manager.list_shipping_methods_for_checkout( - checkout=checkout_info.checkout, channel_slug=checkout_info.channel.slug - ) - - def get_all_shipping_methods_list( checkout_info, shipping_address, @@ -581,8 +570,8 @@ def get_all_shipping_methods_list( lines, shipping_channel_listings, ), - get_valid_external_shipping_method_list_for_checkout_info( - checkout_info, shipping_address, lines, manager + manager.list_shipping_methods_for_checkout( + checkout=checkout_info.checkout, channel_slug=checkout_info.channel.slug ), ) ) @@ -602,3 +591,9 @@ def update_delivery_method_lists_for_checkout_info( checkout_info.shipping_address = shipping_address checkout_info.lines = lines checkout_info.shipping_channel_listings = list(shipping_channel_listings) + + # Clear cached property if it was already calculated, so it can be recalculated. + try: + del checkout_info.all_shipping_methods + except AttributeError: + pass From 09acb860deae988892b9ba240431b491c82cfa1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Wed, 24 Apr 2024 14:24:20 +0200 Subject: [PATCH 05/10] Add E2E test for checkout voucher and shipping with total limits --- .../tests/e2e/checkout/shipping/__init__.py | 0 ...ate_price_based_shipping_on_voucher_add.py | 227 ++++++++++++++++++ ...th_shipping_method_with_min_order_value.py | 2 +- .../checkout/utils/checkout_add_promo_code.py | 10 + .../utils/shipping_method_channel_listing.py | 8 +- 5 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 saleor/tests/e2e/checkout/shipping/__init__.py create mode 100644 saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py diff --git a/saleor/tests/e2e/checkout/shipping/__init__.py b/saleor/tests/e2e/checkout/shipping/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py new file mode 100644 index 00000000000..f7de63cae58 --- /dev/null +++ b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py @@ -0,0 +1,227 @@ +import pytest + +from ...channel.utils import create_channel +from ...product.utils.preparing_product import prepare_product +from ...shipping_zone.utils import ( + create_shipping_method, + create_shipping_method_channel_listing, + create_shipping_zone, +) +from ...utils import assign_permissions +from ...vouchers.utils import create_voucher, create_voucher_channel_listing +from ...warehouse.utils import create_warehouse +from ..utils import ( + checkout_complete, + checkout_create, + checkout_delivery_method_update, + checkout_dummy_payment_create, + checkout_lines_add, + raw_checkout_add_promo_code, +) + + +def prepare_entire_order_voucher(e2e_staff_api_client, channel_id, voucher_amount): + voucher_code = "ENTIRE_ORDER" + input = { + "name": "Entire order voucher", + "addCodes": [voucher_code], + "applyOncePerCustomer": False, + "applyOncePerOrder": False, + "onlyForStaff": False, + "discountValueType": "FIXED", + "endDate": None, + "minCheckoutItemsQuantity": 0, + "startDate": None, + "type": "ENTIRE_ORDER", + "usageLimit": None, + "singleUse": False, + } + voucher_data = create_voucher(e2e_staff_api_client, input) + voucher_id = voucher_data["id"] + channel_listing = [ + { + "channelId": channel_id, + "discountValue": voucher_amount, + }, + ] + create_voucher_channel_listing( + e2e_staff_api_client, + voucher_id, + channel_listing, + ) + + return voucher_code + + +def prepare_shop_with_shipping_with_order_limits(e2e_staff_api_client): + warehouse_data = create_warehouse(e2e_staff_api_client) + warehouse_id = warehouse_data["id"] + channel_slug = "test" + warehouse_ids = [warehouse_id] + channel_data = create_channel( + e2e_staff_api_client, + slug=channel_slug, + warehouse_ids=warehouse_ids, + ) + channel_id = channel_data["id"] + channel_ids = [channel_id] + shipping_zone_data = create_shipping_zone( + e2e_staff_api_client, + warehouse_ids=warehouse_ids, + channel_ids=channel_ids, + ) + shipping_zone_id = shipping_zone_data["id"] + + shipping_method_data = create_shipping_method( + e2e_staff_api_client, + shipping_zone_id, + ) + shipping_method_id = shipping_method_data["id"] + create_shipping_method_channel_listing( + e2e_staff_api_client, + shipping_method_id, + channel_id, + minimum_order_price="18.00", + ) + return ( + warehouse_id, + channel_id, + channel_slug, + shipping_method_id, + ) + + +@pytest.mark.e2e +def test_checkout_invalidate_price_based_shipping_on_voucher_add( + e2e_not_logged_api_client, + e2e_staff_api_client, + permission_manage_products, + permission_manage_channels, + permission_manage_shipping, + permission_manage_product_types_and_attributes, + permission_manage_discounts, +): + # Before + permissions = [ + permission_manage_products, + permission_manage_channels, + permission_manage_shipping, + permission_manage_product_types_and_attributes, + permission_manage_discounts, + ] + assign_permissions(e2e_staff_api_client, permissions) + + ( + warehouse_id, + channel_id, + channel_slug, + shipping_method_id, + ) = prepare_shop_with_shipping_with_order_limits(e2e_staff_api_client) + + ( + _product_id, + product_variant_id, + product_variant_price, + ) = prepare_product( + e2e_staff_api_client, + warehouse_id, + channel_id, + variant_price=18, + ) + + voucher_amount = 1 + voucher_code = prepare_entire_order_voucher( + e2e_staff_api_client, channel_id, voucher_amount + ) + + # Step 1 - Create checkout for product + lines = [ + { + "variantId": product_variant_id, + "quantity": 1, + }, + ] + checkout_data = checkout_create( + e2e_not_logged_api_client, + lines, + channel_slug, + email="testEmail@example.com", + set_default_billing_address=True, + set_default_shipping_address=True, + ) + checkout_id = checkout_data["id"] + checkout_lines = checkout_data["lines"][0] + shipping_method_id = checkout_data["shippingMethods"][0]["id"] + + assert checkout_data["isShippingRequired"] is True + assert checkout_lines["unitPrice"]["gross"]["amount"] == float( + product_variant_price + ) + assert checkout_lines["undiscountedUnitPrice"]["amount"] == product_variant_price + + # Step 2 - Set DeliveryMethod for checkout. + checkout_data = checkout_delivery_method_update( + e2e_not_logged_api_client, + checkout_id, + shipping_method_id, + ) + assert checkout_data["deliveryMethod"]["id"] == shipping_method_id + + # Step 3 Add voucher code to checkout. Selected delivery method should be unset as + # the price is lower than the minimum total price for this method. + checkout_data = raw_checkout_add_promo_code( + e2e_not_logged_api_client, checkout_id, voucher_code + ) + + assert checkout_data["checkout"]["voucherCode"] == voucher_code + assert checkout_data["checkout"]["discount"]["amount"] == voucher_amount + assert checkout_data["checkout"]["deliveryMethod"] is None + + # Step 4 Add lines to the checkout to increase the total amount + lines_add = [ + { + "variantId": product_variant_id, + "quantity": 2, + }, + ] + checkout_data = checkout_lines_add( + e2e_staff_api_client, + checkout_id, + lines_add, + ) + checkout_lines = checkout_data["lines"][0] + assert checkout_lines["quantity"] == 3 + subtotal_amount = float(product_variant_price) * 3 - voucher_amount + assert checkout_lines["totalPrice"]["gross"]["amount"] == subtotal_amount + + # Step 5 Add shipping method again. + checkout_data = checkout_delivery_method_update( + e2e_not_logged_api_client, + checkout_id, + shipping_method_id, + ) + assert checkout_data["deliveryMethod"]["id"] == shipping_method_id + shipping_price = float(checkout_data["shippingPrice"]["gross"]["amount"]) + total_gross_amount = checkout_data["totalPrice"]["gross"]["amount"] + + # Step 6 - Create payment for checkout. + checkout_dummy_payment_create( + e2e_not_logged_api_client, + checkout_id, + total_gross_amount, + ) + + # Step 7 - Complete checkout. + order_data = checkout_complete( + e2e_not_logged_api_client, + checkout_id, + ) + assert order_data["status"] == "UNFULFILLED" + assert order_data["discounts"][0]["type"] == "VOUCHER" + assert order_data["discounts"][0]["value"] == voucher_amount + assert order_data["voucher"]["code"] == voucher_code + # assert order_data["total"]["gross"]["amount"] == subtotal_amount + assert order_data["deliveryMethod"]["id"] == shipping_method_id + assert order_data["shippingPrice"]["gross"]["amount"] == shipping_price + # order_line = order_data["lines"][0] + # assert order_line["unitPrice"]["gross"]["amount"] == float(product_variant_price) diff --git a/saleor/tests/e2e/checkout/test_checkout_with_shipping_method_with_min_order_value.py b/saleor/tests/e2e/checkout/test_checkout_with_shipping_method_with_min_order_value.py index 8edf1d8b56b..435591cb0c4 100644 --- a/saleor/tests/e2e/checkout/test_checkout_with_shipping_method_with_min_order_value.py +++ b/saleor/tests/e2e/checkout/test_checkout_with_shipping_method_with_min_order_value.py @@ -129,7 +129,7 @@ def test_checkout_with_shipping_method_with_min_order_value_CORE_0501( shipping_method_id, channel_id, price="5.00", - minimumOrderPrice=minimum_order_price, + minimum_order_price=minimum_order_price, ) shipping_method_min_order_value = data["channelListings"][0]["minimumOrderPrice"][ "amount" diff --git a/saleor/tests/e2e/checkout/utils/checkout_add_promo_code.py b/saleor/tests/e2e/checkout/utils/checkout_add_promo_code.py index 346f67cd913..f18d9c60b70 100644 --- a/saleor/tests/e2e/checkout/utils/checkout_add_promo_code.py +++ b/saleor/tests/e2e/checkout/utils/checkout_add_promo_code.py @@ -20,6 +20,16 @@ amount } } + shippingMethods { + id + name + } + deliveryMethod { + ... on ShippingMethod { + id + name + } + } voucherCode discount { amount diff --git a/saleor/tests/e2e/shipping_zone/utils/shipping_method_channel_listing.py b/saleor/tests/e2e/shipping_zone/utils/shipping_method_channel_listing.py index 47cc1f680d4..b95bafb357f 100644 --- a/saleor/tests/e2e/shipping_zone/utils/shipping_method_channel_listing.py +++ b/saleor/tests/e2e/shipping_zone/utils/shipping_method_channel_listing.py @@ -28,8 +28,8 @@ def create_shipping_method_channel_listing( shipping_method_id, channel_id, price="10.00", - minimumOrderPrice=None, - maximumOrderPrice=None, + minimum_order_price=None, + maximum_order_price=None, ): variables = { "id": shipping_method_id, @@ -38,8 +38,8 @@ def create_shipping_method_channel_listing( { "channelId": channel_id, "price": price, - "maximumOrderPrice": maximumOrderPrice, - "minimumOrderPrice": minimumOrderPrice, + "maximumOrderPrice": maximum_order_price, + "minimumOrderPrice": minimum_order_price, } ] }, From e9c30d198b7a22998a25930830e0dbf507e80714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Wed, 24 Apr 2024 14:30:36 +0200 Subject: [PATCH 06/10] [Test] Use external shipping methods in the checkout (#14820) --- saleor/tests/e2e/apps/__init__.py | 0 saleor/tests/e2e/apps/utils/__init__.py | 5 + saleor/tests/e2e/apps/utils/app_create.py | 35 +++++ ...hipping_methods_in_checkout_core_1652.yaml | 100 ++++++++++++ ...e_external_shipping_methods_in_checkout.py | 147 ++++++++++++++++++ .../e2e/checkout/utils/checkout_complete.py | 1 + .../e2e/checkout/utils/checkout_create.py | 6 + saleor/tests/e2e/test_utils.py | 124 +++++++++++++++ saleor/tests/e2e/utils.py | 22 +++ saleor/tests/e2e/webhooks/__init__.py | 0 saleor/tests/e2e/webhooks/utils.py | 50 ++++++ 11 files changed, 490 insertions(+) create mode 100644 saleor/tests/e2e/apps/__init__.py create mode 100644 saleor/tests/e2e/apps/utils/__init__.py create mode 100644 saleor/tests/e2e/apps/utils/app_create.py create mode 100644 saleor/tests/e2e/checkout/shipping/cassettes/test_use_external_shipping_methods_in_checkout/test_use_external_shipping_methods_in_checkout_core_1652.yaml create mode 100644 saleor/tests/e2e/checkout/shipping/test_use_external_shipping_methods_in_checkout.py create mode 100644 saleor/tests/e2e/test_utils.py create mode 100644 saleor/tests/e2e/webhooks/__init__.py create mode 100644 saleor/tests/e2e/webhooks/utils.py diff --git a/saleor/tests/e2e/apps/__init__.py b/saleor/tests/e2e/apps/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/saleor/tests/e2e/apps/utils/__init__.py b/saleor/tests/e2e/apps/utils/__init__.py new file mode 100644 index 00000000000..c7cab1d63cf --- /dev/null +++ b/saleor/tests/e2e/apps/utils/__init__.py @@ -0,0 +1,5 @@ +from .app_create import add_app + +__all__ = [ + "add_app", +] diff --git a/saleor/tests/e2e/apps/utils/app_create.py b/saleor/tests/e2e/apps/utils/app_create.py new file mode 100644 index 00000000000..7d58581fb97 --- /dev/null +++ b/saleor/tests/e2e/apps/utils/app_create.py @@ -0,0 +1,35 @@ +from ...utils import get_graphql_content + +APP_CREATE_MUTATION = """ +mutation AppCreate($input: AppInput!) { + appCreate(input: $input) { + errors { + field + code + message + } + authToken + app { + id + isActive + } + } +} + +""" + + +def add_app( + staff_api_client, + input, +): + variables = {"input": input} + + response = staff_api_client.post_graphql(APP_CREATE_MUTATION, variables) + content = get_graphql_content(response) + + assert content["data"]["appCreate"]["errors"] == [] + data = content["data"]["appCreate"] + assert data["app"]["id"] is not None + + return data diff --git a/saleor/tests/e2e/checkout/shipping/cassettes/test_use_external_shipping_methods_in_checkout/test_use_external_shipping_methods_in_checkout_core_1652.yaml b/saleor/tests/e2e/checkout/shipping/cassettes/test_use_external_shipping_methods_in_checkout/test_use_external_shipping_methods_in_checkout_core_1652.yaml new file mode 100644 index 00000000000..f8a94165b88 --- /dev/null +++ b/saleor/tests/e2e/checkout/shipping/cassettes/test_use_external_shipping_methods_in_checkout/test_use_external_shipping_methods_in_checkout_core_1652.yaml @@ -0,0 +1,100 @@ +interactions: +- request: + body: '{"__typename": "ShippingListMethodsForCheckout", "checkout": {"shippingAddress": + {"country": {"code": "US"}}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '110' + Content-Type: + - application/json + Saleor-Api-Url: + - http://mirumee.com/graphql/ + Saleor-Domain: + - mirumee.com + Saleor-Event: + - shipping_list_methods_for_checkout + Saleor-Signature: + - '' + User-Agent: + - Saleor/3.19 + X-Saleor-Domain: + - mirumee.com + X-Saleor-Event: + - shipping_list_methods_for_checkout + X-Saleor-Signature: + - '' + method: POST + uri: http://localhost:8080/ + response: + body: + string: '[{"id": "Provider", "name": "Provider - Economy", "amount": "100", + "currency": "USD", "maximum_delivery_days": "7"}, {"id": 1, "name": "Pocztex", + "amount": "21.37", "currency": "USD", "maximum_delivery_days": 7}]' + headers: + Content-Length: + - '212' + Content-Type: + - application/json + Date: + - Tue, 21 Nov 2023 16:14:43 GMT + Server: + - TwistedWeb/21.7.0 + status: + code: 200 + message: OK +- request: + body: '{"__typename": "ShippingListMethodsForCheckout", "checkout": {"shippingAddress": + {"country": {"code": "US"}}}}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Connection: + - keep-alive + Content-Length: + - '110' + Content-Type: + - application/json + Saleor-Api-Url: + - http://mirumee.com/graphql/ + Saleor-Domain: + - mirumee.com + Saleor-Event: + - shipping_list_methods_for_checkout + Saleor-Signature: + - '' + User-Agent: + - Saleor/3.19 + X-Saleor-Domain: + - mirumee.com + X-Saleor-Event: + - shipping_list_methods_for_checkout + X-Saleor-Signature: + - '' + method: POST + uri: http://localhost:8080/ + response: + body: + string: '[{"id": "Provider", "name": "Provider - Economy", "amount": "100", + "currency": "USD", "maximum_delivery_days": "7"}, {"id": 1, "name": "Pocztex", + "amount": "21.37", "currency": "USD", "maximum_delivery_days": 7}]' + headers: + Content-Length: + - '212' + Content-Type: + - application/json + Date: + - Tue, 21 Nov 2023 16:14:44 GMT + Server: + - TwistedWeb/21.7.0 + status: + code: 200 + message: OK +version: 1 diff --git a/saleor/tests/e2e/checkout/shipping/test_use_external_shipping_methods_in_checkout.py b/saleor/tests/e2e/checkout/shipping/test_use_external_shipping_methods_in_checkout.py new file mode 100644 index 00000000000..8259f04ebbe --- /dev/null +++ b/saleor/tests/e2e/checkout/shipping/test_use_external_shipping_methods_in_checkout.py @@ -0,0 +1,147 @@ +import pytest +import vcr + +from ...apps.utils import add_app +from ...product.utils.preparing_product import prepare_product +from ...shop.utils.preparing_shop import prepare_shop +from ...utils import assign_permissions, request_matcher +from ...webhooks.utils import create_webhook +from ..utils import ( + checkout_complete, + checkout_create, + checkout_delivery_method_update, + checkout_dummy_payment_create, +) + + +@pytest.mark.vcr +@pytest.mark.e2e +def test_use_external_shipping_methods_in_checkout_core_1652( + e2e_not_logged_api_client, + e2e_staff_api_client, + permission_manage_products, + permission_manage_channels, + permission_manage_shipping, + permission_manage_product_types_and_attributes, + permission_manage_apps, + settings, + vcr_cassette_dir, +): + # Before + settings.PLUGINS = [ + "saleor.plugins.webhook.plugin.WebhookPlugin", + "saleor.payment.gateways.dummy.plugin.DummyGatewayPlugin", + ] + permissions = [ + permission_manage_products, + permission_manage_channels, + permission_manage_shipping, + permission_manage_product_types_and_attributes, + permission_manage_apps, + ] + assign_permissions(e2e_staff_api_client, permissions) + + ( + warehouse_id, + channel_id, + channel_slug, + shipping_method_id, + ) = prepare_shop(e2e_staff_api_client) + + app_input = {"name": "external_shipping", "permissions": ["MANAGE_SHIPPING"]} + app_data = add_app(e2e_staff_api_client, app_input) + app_id = app_data["app"]["id"] + + target_url = "http://localhost:8080" + webhook_input = { + "app": app_id, + "syncEvents": ["SHIPPING_LIST_METHODS_FOR_CHECKOUT"], + "asyncEvents": [], + "isActive": True, + "name": "external shipping methods", + "targetUrl": target_url, + "query": "subscription {\n event {\n ... on ShippingListMethodsForCheckout {\n __typename\n checkout{\n shippingAddress{\n country{\n code\n }\n }\n }\n }\n }\n}\n", + "customHeaders": "{}", + } + create_webhook(e2e_staff_api_client, webhook_input) + + ( + _product_id, + product_variant_id, + product_variant_price, + ) = prepare_product( + e2e_staff_api_client, + warehouse_id, + channel_id, + variant_price=9.99, + ) + product_variant_price = float(product_variant_price) + + # Step 1 - Create checkout for product + lines = [ + { + "variantId": product_variant_id, + "quantity": 4, + }, + ] + my_vcr = vcr.VCR() + my_vcr.register_matcher("shipping_cassette", request_matcher) + with my_vcr.use_cassette( + f"{vcr_cassette_dir}/test_use_external_shipping_methods_in_checkout_core_1652.yaml", + match_on=["shipping_cassette"], + ): + checkout_data = checkout_create( + e2e_not_logged_api_client, + lines, + channel_slug, + email="testEmail@example.com", + set_default_billing_address=True, + set_default_shipping_address=True, + ) + checkout_id = checkout_data["id"] + assert checkout_data["isShippingRequired"] is True + calculated_subtotal = product_variant_price * 4 + assert checkout_data["totalPrice"]["gross"]["amount"] == calculated_subtotal + shipping_methods = checkout_data["shippingMethods"] + assert len(shipping_methods) == 3 + saleor_shipping = shipping_methods[0] + assert saleor_shipping["name"] == "Test shipping method" + assert saleor_shipping["price"]["amount"] == 10 + external_shipping_1 = shipping_methods[1] + assert external_shipping_1["name"] == "Provider - Economy" + assert external_shipping_1["price"]["amount"] == 100 + assert external_shipping_1["maximumDeliveryDays"] == 7 + external_shipping_2 = shipping_methods[2] + assert external_shipping_2["name"] == "Pocztex" + assert external_shipping_2["price"]["amount"] == 21.37 + assert external_shipping_2["maximumDeliveryDays"] == 7 + + external_shipping_2_id = external_shipping_2["id"] + shipping_price = external_shipping_2["price"]["amount"] + + # Step 2 - Set DeliveryMethod for checkout. + checkout_data = checkout_delivery_method_update( + e2e_not_logged_api_client, + checkout_id, + external_shipping_2_id, + ) + assert checkout_data["deliveryMethod"]["id"] == external_shipping_2_id + total_gross_amount = checkout_data["totalPrice"]["gross"]["amount"] + calculated_total = calculated_subtotal + shipping_price + assert total_gross_amount == calculated_total + + # Step 3 - Create payment for checkout. + checkout_dummy_payment_create( + e2e_not_logged_api_client, checkout_id, total_gross_amount + ) + + # Step 4 - Complete checkout and check total + order_data = checkout_complete( + e2e_not_logged_api_client, + checkout_id, + ) + assert order_data["status"] == "UNFULFILLED" + assert order_data["total"]["gross"]["amount"] == calculated_total + assert order_data["deliveryMethod"]["id"] == external_shipping_2_id + assert order_data["deliveryMethod"]["price"]["amount"] == shipping_price + assert order_data["deliveryMethod"]["name"] == "Pocztex" diff --git a/saleor/tests/e2e/checkout/utils/checkout_complete.py b/saleor/tests/e2e/checkout/utils/checkout_complete.py index 00c8c4b4a2d..f338f0f9eb5 100644 --- a/saleor/tests/e2e/checkout/utils/checkout_complete.py +++ b/saleor/tests/e2e/checkout/utils/checkout_complete.py @@ -38,6 +38,7 @@ deliveryMethod { ... on ShippingMethod { id + name price { amount } diff --git a/saleor/tests/e2e/checkout/utils/checkout_create.py b/saleor/tests/e2e/checkout/utils/checkout_create.py index cacc715104f..0427a0b495c 100644 --- a/saleor/tests/e2e/checkout/utils/checkout_create.py +++ b/saleor/tests/e2e/checkout/utils/checkout_create.py @@ -35,6 +35,12 @@ isShippingRequired shippingMethods { id + name + price { + amount + currency + } + maximumDeliveryDays } deliveryMethod { ... on ShippingMethod { diff --git a/saleor/tests/e2e/test_utils.py b/saleor/tests/e2e/test_utils.py new file mode 100644 index 00000000000..d391b0d020e --- /dev/null +++ b/saleor/tests/e2e/test_utils.py @@ -0,0 +1,124 @@ +import json +from unittest.mock import Mock + +from .utils import request_matcher + + +def test_equal_dicts(): + r1 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + r2 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + + result = request_matcher(r1, r2) + assert result is True + + +def test_unequal_dicts(): + r1 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + r2 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value3"})) + ) + ) + + result = request_matcher(r1, r2) + assert result is False + + +def test_missing_key_in_r2(): + r1 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + r2 = Mock(body=Mock(decode=Mock(return_value=json.dumps({"key1": "value1"})))) + + result = request_matcher(r1, r2) + assert result is False + + +def test_extra_key_in_r2(): + r1 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + r2 = Mock( + body=Mock( + decode=Mock( + return_value=json.dumps( + {"key1": "value1", "key2": "value2", "key3": "value3"} + ) + ) + ) + ) + + result = request_matcher(r1, r2) + assert result is False + + +def test_different_sorting_order_in_r2(): + r1 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key1": "value1", "key2": "value2"})) + ) + ) + r2 = Mock( + body=Mock( + decode=Mock(return_value=json.dumps({"key2": "value2", "key1": "value1"})) + ) + ) + + result = request_matcher(r1, r2) + assert result is True + + +def test_nested_keys(): + r1 = Mock( + body=Mock( + decode=Mock( + return_value=json.dumps( + { + "key1": "value1", + "key2": { + "key3": "value3", + "key4": { + "key5": "value5", + }, + }, + } + ) + ) + ) + ) + r2 = Mock( + body=Mock( + decode=Mock( + return_value=json.dumps( + { + "key1": "value1", + "key2": { + "key3": "value3", + "key4": { + "key5": "value5", + }, + }, + } + ) + ) + ) + ) + + result = request_matcher(r1, r2) + assert result is True diff --git a/saleor/tests/e2e/utils.py b/saleor/tests/e2e/utils.py index 7fe08febdd7..e877359aaae 100644 --- a/saleor/tests/e2e/utils.py +++ b/saleor/tests/e2e/utils.py @@ -1,3 +1,5 @@ +import json + from ...account.models import Group from ...graphql.tests.utils import ( get_graphql_content, # noqa: F401 @@ -18,3 +20,23 @@ def assign_permissions(api_client, permissions): app = api_client.app if app: app.permissions.add(*permissions) + + +def request_matcher(r1, r2): + body1 = json.loads(r1.body.decode("utf-8")) + body2 = json.loads(r2.body.decode("utf-8")) + + # Check if all key-value pairs in body1 are present in body2 + if set(body1.keys()) != set(body2.keys()): + return False + + for key, value in body1.items(): + if key not in body2 or body2[key] != value: + return False + + # Check if there are any extra key-value pairs in body2 + for key in body2: + if key not in body1: + return False + + return True diff --git a/saleor/tests/e2e/webhooks/__init__.py b/saleor/tests/e2e/webhooks/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/saleor/tests/e2e/webhooks/utils.py b/saleor/tests/e2e/webhooks/utils.py new file mode 100644 index 00000000000..e7366d13b98 --- /dev/null +++ b/saleor/tests/e2e/webhooks/utils.py @@ -0,0 +1,50 @@ +from ..utils import get_graphql_content + +WEBHOOK_CREATE_MUTATION = """ +mutation WebhookCreate($input: WebhookCreateInput!) { + webhookCreate(input: $input) { + errors { + field + code + message + } + webhook { + id + name + isActive + targetUrl + syncEvents{ + eventType + } + asyncEvents{ + eventType + } + subscriptionQuery + customHeaders + } + __typename + } +} +""" + + +def create_webhook( + staff_api_client, + input, +): + variables = { + "input": input, + } + + response = staff_api_client.post_graphql( + WEBHOOK_CREATE_MUTATION, + variables, + ) + + content = get_graphql_content(response) + + assert content["data"]["webhookCreate"]["errors"] == [] + data = content["data"]["webhookCreate"]["webhook"] + assert data["id"] is not None + + return data From 495db41e06d6d95a607ebadadf42dd5d962e9113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Thu, 25 Apr 2024 10:51:27 +0200 Subject: [PATCH 07/10] Update test case nr --- saleor/checkout/fetch.py | 3 +-- ..._checkout_invalidate_price_based_shipping_on_voucher_add.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index fc5996b8c03..482be3f37a0 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -125,8 +125,7 @@ def delivery_method_info(self) -> "DeliveryMethodBase": ) elif external_shipping_method_id := get_external_shipping_id(self.checkout): - # A local function is used to delay evaluation - # of the lazy `all_shipping_methods` attribute + def _resolve_external_method(): methods = {method.id: method for method in self.all_shipping_methods} if method := methods.get(external_shipping_method_id): diff --git a/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py index f7de63cae58..16201401fb1 100644 --- a/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py +++ b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py @@ -92,7 +92,7 @@ def prepare_shop_with_shipping_with_order_limits(e2e_staff_api_client): @pytest.mark.e2e -def test_checkout_invalidate_price_based_shipping_on_voucher_add( +def test_checkout_should_invalidate_shipping_methods_when_adding_entire_order_voucher_0116( e2e_not_logged_api_client, e2e_staff_api_client, permission_manage_products, From 9e1872526e904862886b7b4e40d6f8a71d64d1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Thu, 25 Apr 2024 12:36:07 +0200 Subject: [PATCH 08/10] Add test for change_shipping_address_in_checkout --- saleor/checkout/fetch.py | 1 - saleor/checkout/tests/test_checkout.py | 37 ++++++++++++++++++++++++++ saleor/graphql/webhook/resolvers.py | 1 - 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index 482be3f37a0..e2c102aeffc 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -475,7 +475,6 @@ def fetch_checkout_info( shipping_channel_listings: Optional[ Iterable["ShippingMethodChannelListing"] ] = None, - fetch_delivery_methods=True, voucher: Optional["Voucher"] = None, voucher_code: Optional["VoucherCode"] = None, ) -> CheckoutInfo: diff --git a/saleor/checkout/tests/test_checkout.py b/saleor/checkout/tests/test_checkout.py index 473900c5d23..4eef31f8954 100644 --- a/saleor/checkout/tests/test_checkout.py +++ b/saleor/checkout/tests/test_checkout.py @@ -1636,6 +1636,43 @@ def test_change_address_in_checkout_from_user_address_to_other( assert checkout_info.shipping_address == other_address +def test_change_address_in_checkout_invalidates_shipping_methods( + checkout_with_items, address, shipping_method, shipping_zone +): + # given + checkout = checkout_with_items + + manager = get_plugins_manager(allow_replica=False) + lines, _ = fetch_checkout_lines(checkout) + checkout_info = fetch_checkout_info( + checkout=checkout, + lines=lines, + manager=manager, + shipping_channel_listings=shipping_method.channel_listings.all(), + ) + + all_shipping_methods = checkout_info.all_shipping_methods + assert all_shipping_methods == [] + + # when + shipping_updated_fields = change_shipping_address_in_checkout( + checkout_info, + address, + lines, + manager, + checkout.channel.shipping_method_listings.all(), + ) + billing_updated_fields = change_billing_address_in_checkout(checkout, address) + checkout.save(update_fields=shipping_updated_fields + billing_updated_fields) + checkout.refresh_from_db() + + # then + assert checkout.shipping_address == address + assert checkout.billing_address == address + assert checkout_info.shipping_address == address + assert checkout_info.all_shipping_methods + + def test_add_voucher_to_checkout(checkout_with_item, voucher): assert checkout_with_item.voucher_code is None manager = get_plugins_manager(allow_replica=False) diff --git a/saleor/graphql/webhook/resolvers.py b/saleor/graphql/webhook/resolvers.py index ff5ede36ebc..cb8cbf2609e 100644 --- a/saleor/graphql/webhook/resolvers.py +++ b/saleor/graphql/webhook/resolvers.py @@ -66,7 +66,6 @@ def resolve_shipping_methods_for_checkout(info: ResolveInfo, checkout, manager): lines, manager, shipping_channel_listings, - fetch_delivery_methods=False, ) all_shipping_methods = get_all_shipping_methods_list( checkout_info, From f23a3ddbf674973e76eb9ca3fe5408e3b96e2651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Fri, 26 Apr 2024 13:16:51 +0200 Subject: [PATCH 09/10] Fix commented code --- saleor/graphql/tests/test_context.py | 3 --- ...checkout_invalidate_price_based_shipping_on_voucher_add.py | 4 +--- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/saleor/graphql/tests/test_context.py b/saleor/graphql/tests/test_context.py index 60d330f811e..6c5d0b592a6 100644 --- a/saleor/graphql/tests/test_context.py +++ b/saleor/graphql/tests/test_context.py @@ -73,9 +73,6 @@ def test_get_context_value_uses_request_time_if_passed_already(rf): assert context.request_time == request_time -# {'query': '{\n me {\n id\n }\n}'} - - def test_clear_context(rf): # given context = get_context_value(rf.request()) diff --git a/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py index 16201401fb1..56e4f7652f5 100644 --- a/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py +++ b/saleor/tests/e2e/checkout/shipping/test_checkout_invalidate_price_based_shipping_on_voucher_add.py @@ -220,8 +220,6 @@ def test_checkout_should_invalidate_shipping_methods_when_adding_entire_order_vo assert order_data["discounts"][0]["type"] == "VOUCHER" assert order_data["discounts"][0]["value"] == voucher_amount assert order_data["voucher"]["code"] == voucher_code - # assert order_data["total"]["gross"]["amount"] == subtotal_amount + assert order_data["total"]["gross"]["amount"] == total_gross_amount assert order_data["deliveryMethod"]["id"] == shipping_method_id assert order_data["shippingPrice"]["gross"]["amount"] == shipping_price - # order_line = order_data["lines"][0] - # assert order_line["unitPrice"]["gross"]["amount"] == float(product_variant_price) From 444c437693c845f8bb8aa2ceb16a79658c989178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20G=C4=99bala?= Date: Fri, 26 Apr 2024 16:40:02 +0200 Subject: [PATCH 10/10] Change property to cached one --- saleor/checkout/fetch.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/saleor/checkout/fetch.py b/saleor/checkout/fetch.py index e2c102aeffc..1d61c6277a6 100644 --- a/saleor/checkout/fetch.py +++ b/saleor/checkout/fetch.py @@ -94,7 +94,7 @@ def all_shipping_methods(self) -> list["ShippingMethodData"]: initialize_shipping_method_active_status(all_methods, excluded_methods) return all_methods - @property + @cached_property def valid_pick_up_points(self) -> Iterable["Warehouse"]: from .utils import get_valid_collection_points_for_checkout @@ -590,8 +590,14 @@ def update_delivery_method_lists_for_checkout_info( checkout_info.lines = lines checkout_info.shipping_channel_listings = list(shipping_channel_listings) - # Clear cached property if it was already calculated, so it can be recalculated. + # Clear cached properties if they were already calculated, so they can be + # recalculated. try: del checkout_info.all_shipping_methods except AttributeError: pass + + try: + del checkout_info.valid_pick_up_points + except AttributeError: + pass