Skip to content

Commit

Permalink
Merge branch '3.16' of github.com:saleor/saleor into fix-discounts-re…
Browse files Browse the repository at this point in the history
…solver-316
  • Loading branch information
tomaszszymanski129 committed May 10, 2024
2 parents f98e300 + cd445a1 commit 4037797
Show file tree
Hide file tree
Showing 20 changed files with 610 additions and 172 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "saleor",
"version": "3.16.45",
"version": "3.16.46",
"engines": {
"node": ">=16 <17",
"npm": ">=7"
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "saleor"
version = "3.16.45"
version = "3.16.46"
description = "A modular, high performance, headless e-commerce platform built with Python, GraphQL, Django, and React."
authors = [ "Saleor Commerce <hello@saleor.io>" ]
license = "BSD-3-Clause"
Expand Down
2 changes: 1 addition & 1 deletion saleor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .celeryconf import app as celery_app

__all__ = ["celery_app"]
__version__ = "3.16.45"
__version__ = "3.16.46"


class PatchedSubscriberExecutionContext(object):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@ def validate_external_url(cls, external_url: Optional[str], error_code: str):

@classmethod
def validate_metadata_keys( # type: ignore[override]
cls, metadata_list: List[dict], field_name, error_code
cls, metadata_list: Optional[List[dict]], field_name, error_code
):
if not metadata_list:
return
if metadata_contains_empty_key(metadata_list):
raise ValidationError(
{
Expand Down
118 changes: 118 additions & 0 deletions saleor/graphql/payment/tests/mutations/test_transaction_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,63 @@ def test_transaction_create_for_order_by_app(
assert transaction.external_url == external_url


def test_transaction_create_for_order_by_app_metadata_null_value(
order_with_lines, permission_manage_payments, app_api_client
):
# given
name = "Credit Card"
psp_reference = "PSP reference - 123"
available_actions = [
TransactionActionEnum.CHARGE.name,
TransactionActionEnum.CHARGE.name,
]
authorized_value = Decimal("10")
external_url = f"http://{TEST_SERVER_DOMAIN}/external-url"

variables = {
"id": graphene.Node.to_global_id("Order", order_with_lines.pk),
"transaction": {
"name": name,
"pspReference": psp_reference,
"availableActions": available_actions,
"amountAuthorized": {
"amount": authorized_value,
"currency": "USD",
},
"metadata": None,
"privateMetadata": None,
"externalUrl": external_url,
},
}

# when
response = app_api_client.post_graphql(
MUTATION_TRANSACTION_CREATE, variables, permissions=[permission_manage_payments]
)

# then
available_actions = list(set(available_actions))

transaction = order_with_lines.payment_transactions.first()
content = get_graphql_content(response)
data = content["data"]["transactionCreate"]["transaction"]
assert data["actions"] == available_actions
assert data["pspReference"] == psp_reference
assert data["authorizedAmount"]["amount"] == authorized_value
assert data["externalUrl"] == external_url
assert data["createdBy"]["id"] == to_global_id_or_none(app_api_client.app)

assert available_actions == list(map(str.upper, transaction.available_actions))
assert psp_reference == transaction.psp_reference
assert authorized_value == transaction.authorized_value
assert transaction.metadata == {}
assert transaction.private_metadata == {}
assert transaction.app_identifier == app_api_client.app.identifier
assert transaction.app == app_api_client.app
assert transaction.user is None
assert transaction.external_url == external_url


def test_transaction_create_for_order_updates_order_total_authorized_by_app(
order_with_lines, permission_manage_payments, app_api_client
):
Expand Down Expand Up @@ -351,6 +408,67 @@ def test_transaction_create_for_checkout_by_app(
assert transaction.user is None


def test_transaction_create_for_checkout_by_app_metadata_null_value(
checkout_with_items, permission_manage_payments, app_api_client
):
# given
name = "Credit Card"
psp_reference = "PSP reference - 123"
available_actions = [
TransactionActionEnum.CHARGE.name,
TransactionActionEnum.CHARGE.name,
]
authorized_value = Decimal("10")
external_url = f"http://{TEST_SERVER_DOMAIN}/external-url"

variables = {
"id": graphene.Node.to_global_id("Checkout", checkout_with_items.pk),
"transaction": {
"name": name,
"pspReference": psp_reference,
"availableActions": available_actions,
"amountAuthorized": {
"amount": authorized_value,
"currency": "USD",
},
"metadata": None,
"privateMetadata": None,
"externalUrl": external_url,
},
}

# when
response = app_api_client.post_graphql(
MUTATION_TRANSACTION_CREATE, variables, permissions=[permission_manage_payments]
)

# then
checkout_with_items.refresh_from_db()
assert checkout_with_items.charge_status == CheckoutChargeStatus.NONE
assert checkout_with_items.authorize_status == CheckoutAuthorizeStatus.PARTIAL

available_actions = list(set(available_actions))

transaction = checkout_with_items.payment_transactions.first()
content = get_graphql_content(response)
data = content["data"]["transactionCreate"]["transaction"]
assert data["actions"] == available_actions
assert data["pspReference"] == psp_reference
assert data["authorizedAmount"]["amount"] == authorized_value
assert data["externalUrl"] == external_url
assert data["createdBy"]["id"] == to_global_id_or_none(app_api_client.app)

assert available_actions == list(map(str.upper, transaction.available_actions))
assert psp_reference == transaction.psp_reference
assert authorized_value == transaction.authorized_value
assert transaction.metadata == {}
assert transaction.private_metadata == {}
assert transaction.external_url == external_url
assert transaction.app_identifier == app_api_client.app.identifier
assert transaction.app == app_api_client.app
assert transaction.user is None


@pytest.mark.parametrize(
"amount_field_name, amount_db_field",
[
Expand Down
52 changes: 52 additions & 0 deletions saleor/graphql/payment/tests/mutations/test_transaction_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,31 @@ def test_transaction_update_metadata_by_app(
assert transaction_item_created_by_app.metadata == {meta_key: meta_value}


def test_transaction_update_metadata_by_app_null_value(
transaction_item_created_by_app, permission_manage_payments, app_api_client
):
# given
transaction = transaction_item_created_by_app

variables = {
"id": graphene.Node.to_global_id("TransactionItem", transaction.token),
"transaction": {
"metadata": None,
},
}

# when
response = app_api_client.post_graphql(
MUTATION_TRANSACTION_UPDATE, variables, permissions=[permission_manage_payments]
)

# then
transaction.refresh_from_db()
content = get_graphql_content(response)
data = content["data"]["transactionUpdate"]["transaction"]
assert len(data["metadata"]) == 0


def test_transaction_update_metadata_incorrect_key_by_app(
transaction_item_created_by_app, permission_manage_payments, app_api_client
):
Expand Down Expand Up @@ -300,6 +325,33 @@ def test_transaction_update_private_metadata_by_app(
assert transaction_item_created_by_app.private_metadata == {meta_key: meta_value}


def test_transaction_update_private_metadata_by_app_null_value(
transaction_item_created_by_app, permission_manage_payments, app_api_client
):
# given
transaction = transaction_item_created_by_app
transaction.private_metadata = {"key": "value"}
transaction.save(update_fields=["private_metadata"])

variables = {
"id": graphene.Node.to_global_id("TransactionItem", transaction.token),
"transaction": {
"privateMetadata": None,
},
}

# when
response = app_api_client.post_graphql(
MUTATION_TRANSACTION_UPDATE, variables, permissions=[permission_manage_payments]
)

# then
transaction.refresh_from_db()
content = get_graphql_content(response)
data = content["data"]["transactionUpdate"]["transaction"]
assert len(data["privateMetadata"]) == 1


def test_transaction_update_private_metadata_incorrect_key_by_app(
transaction_item_created_by_app, permission_manage_payments, app_api_client
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@
from ...core.mutations import BaseMutation
from ...core.types import BulkStockError, NonNullList
from ...plugins.dataloaders import get_plugin_manager_promise
from ...warehouse.dataloaders import (
StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader,
)
from ...warehouse.dataloaders import StocksByProductVariantIdLoader
from ...warehouse.types import Warehouse
from ..mutations.product.product_create import StockInput
from ..types import ProductVariant
Expand Down Expand Up @@ -70,9 +68,7 @@ def perform_mutation(cls, _root, info: ResolveInfo, /, **data):
manager.product_variant_back_in_stock, stock, webhooks=webhooks
)

StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader(
info.context
).clear((variant.id, None, None))
StocksByProductVariantIdLoader(info.context).clear(variant.id)

variant = ChannelContext(node=variant, channel_slug=None)
return cls(product_variant=variant)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
from ...core.types import NonNullList, StockError
from ...core.validators import validate_one_of_args_is_in_mutation
from ...plugins.dataloaders import get_plugin_manager_promise
from ...warehouse.dataloaders import (
StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader,
)
from ...warehouse.dataloaders import StocksByProductVariantIdLoader
from ...warehouse.types import Warehouse
from ..types import ProductVariant

Expand Down Expand Up @@ -85,9 +83,7 @@ def perform_mutation(cls, _root, info: ResolveInfo, /, **data):

stocks_to_delete.delete()

StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader(
info.context
).clear((variant.id, None, None))
StocksByProductVariantIdLoader(info.context).clear(variant.id)

variant = ChannelContext(node=variant, channel_slug=None)
return cls(product_variant=variant)
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
from ...core.types import BulkStockError, NonNullList
from ...core.validators import validate_one_of_args_is_in_mutation
from ...plugins.dataloaders import get_plugin_manager_promise
from ...warehouse.dataloaders import (
StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader,
)
from ...warehouse.dataloaders import StocksByProductVariantIdLoader
from ...warehouse.types import Warehouse
from ..mutations.product.product_create import StockInput
from ..types import ProductVariant
Expand Down Expand Up @@ -82,9 +80,7 @@ def perform_mutation(cls, _root, info: ResolveInfo, /, **data):
manager = get_plugin_manager_promise(info.context).get()
cls.update_or_create_variant_stocks(variant, stocks, warehouses, manager)

StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader(
info.context
).clear((variant.id, None, None))
StocksByProductVariantIdLoader(info.context).clear(variant.id)

variant = ChannelContext(node=variant, channel_slug=None)
return cls(product_variant=variant)
Expand Down
36 changes: 36 additions & 0 deletions saleor/graphql/product/tests/queries/test_product_variant_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from measurement.measures import Weight

from .....core.units import WeightUnits
from .....warehouse import WarehouseClickAndCollectOption
from ....core.enums import WeightUnitsEnum
from ....tests.utils import assert_no_permission, get_graphql_content

Expand Down Expand Up @@ -167,6 +168,41 @@ def test_fetch_variant_no_stocks(
)


def test_fetch_variant_stocks_from_click_and_collect_warehouse(
staff_api_client,
product,
permission_manage_products,
channel_USD,
):
# given
query = QUERY_VARIANT
variant = product.variants.first()
stocks_count = variant.stocks.count()
warehouse = variant.stocks.first().warehouse

# remove the warehouse shipping zones and mark it as click and collect
# the stocks for this warehouse should be still returned
warehouse.shipping_zones.clear()
warehouse.click_and_collect_option = WarehouseClickAndCollectOption.LOCAL_STOCK
warehouse.save(update_fields=["click_and_collect_option"])

variant_id = graphene.Node.to_global_id("ProductVariant", variant.pk)
variables = {"id": variant_id, "countryCode": "EU", "channel": channel_USD.slug}
staff_api_client.user.user_permissions.add(permission_manage_products)

# when
response = staff_api_client.post_graphql(query, variables)

# then
content = get_graphql_content(response)
data = content["data"]["productVariant"]
assert data["name"] == variant.name
assert data["created"] == variant.created_at.isoformat()

assert len(data["stocksByAddress"]) == stocks_count
assert not data["deprecatedStocksByCountry"]


QUERY_PRODUCT_VARIANT_CHANNEL_LISTING = """
query ProductVariantDetails($id: ID!, $channel: String) {
productVariant(id: $id, channel: $channel) {
Expand Down
13 changes: 10 additions & 3 deletions saleor/graphql/product/types/products.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
from ...warehouse.dataloaders import (
AvailableQuantityByProductVariantIdCountryCodeAndChannelSlugLoader,
PreorderQuantityReservedByVariantChannelListingIdLoader,
StocksByProductVariantIdLoader,
StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader,
)
from ...warehouse.types import Stock
Expand Down Expand Up @@ -434,9 +435,15 @@ def resolve_stocks(
):
if address is not None:
country_code = address.country
return StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader(
info.context
).load((root.node.id, country_code, root.channel_slug))
channle_slug = root.channel_slug
if channle_slug or country_code:
return StocksWithAvailableQuantityByProductVariantIdCountryCodeAndChannelLoader( # noqa: E501
info.context
).load(
(root.node.id, country_code, root.channel_slug)
)
else:
return StocksByProductVariantIdLoader(info.context).load(root.node.id)

@staticmethod
@load_site_callback
Expand Down

0 comments on commit 4037797

Please sign in to comment.