Repository: saleor/saleor

Issue:
Can't fetch unpublished product as App
As `App` I should be able to fetch unpublished product if I have `MANAGE_PRODUCT` permission. 


Discussion:
Hey, I tried to resolve this problem and my investigation followed me to method **get_node** within **Product** class. When we asked for an unpublish product as App in **get_node** was the only **visible_to_user** for parameter info.context.user - should be also info.context.app?  - so put there condition to handling both app and user. Maybe it is not an elegant way but IMO it's working. 
 
To resolve that issue we should use a similar approach like `saleor/graphql/product/resolvers.py::resolve_collection_by_slug`

Resolving patch:
diff --git a/saleor/graphql/product/types/products.py b/saleor/graphql/product/types/products.py
--- a/saleor/graphql/product/types/products.py
+++ b/saleor/graphql/product/types/products.py
@@ -412,10 +412,10 @@ def resolve_images(root: models.ProductVariant, info, *_args):
 
     @classmethod
     def get_node(cls, info, pk):
-        user = info.context.user
-        visible_products = models.Product.objects.visible_to_user(user).values_list(
-            "pk", flat=True
-        )
+        requestor = get_user_or_app_from_context(info.context)
+        visible_products = models.Product.objects.visible_to_user(
+            requestor
+        ).values_list("pk", flat=True)
         qs = cls._meta.model.objects.filter(product__id__in=visible_products)
         return qs.filter(pk=pk).first()
 
@@ -631,7 +631,8 @@ def resolve_collections(root: models.Product, *_args):
     @classmethod
     def get_node(cls, info, pk):
         if info.context:
-            qs = cls._meta.model.objects.visible_to_user(info.context.user)
+            requestor = get_user_or_app_from_context(info.context)
+            qs = cls._meta.model.objects.visible_to_user(requestor)
             return qs.filter(pk=pk).first()
         return None
 
@@ -793,8 +794,8 @@ def resolve_products(root: models.Collection, info, first=None, **kwargs):
     @classmethod
     def get_node(cls, info, id):
         if info.context:
-            user = info.context.user
-            qs = cls._meta.model.objects.visible_to_user(user)
+            requestor = get_user_or_app_from_context(info.context)
+            qs = cls._meta.model.objects.visible_to_user(requestor)
             return qs.filter(id=id).first()
         return None
 


Test patch:
diff --git a/saleor/graphql/core/tests/test_base_mutation.py b/saleor/graphql/core/tests/test_base_mutation.py
--- a/saleor/graphql/core/tests/test_base_mutation.py
+++ b/saleor/graphql/core/tests/test_base_mutation.py
@@ -21,6 +21,9 @@ class Meta:
 
     @classmethod
     def perform_mutation(cls, _root, info, product_id):
+        # Need to mock `app_middleware`
+        info.context.app = None
+
         product = cls.get_node_or_error(
             info, product_id, field="product_id", only_type=product_types.Product
         )
diff --git a/saleor/graphql/product/tests/test_collection.py b/saleor/graphql/product/tests/test_collection.py
--- a/saleor/graphql/product/tests/test_collection.py
+++ b/saleor/graphql/product/tests/test_collection.py
@@ -36,6 +36,29 @@ def test_collection_query_by_id(
     assert collection_data["name"] == collection.name
 
 
+def test_collection_query_unpublished_collection_by_id_as_app(
+    app_api_client, collection, permission_manage_products
+):
+    # given
+    collection.is_published = False
+    collection.save(update_fields=["is_published"])
+    variables = {"id": graphene.Node.to_global_id("Collection", collection.pk)}
+
+    # when
+    response = app_api_client.post_graphql(
+        QUERY_COLLECTION,
+        variables=variables,
+        permissions=[permission_manage_products],
+        check_no_permissions=False,
+    )
+
+    # then
+    content = get_graphql_content(response)
+    collection_data = content["data"]["collection"]
+    assert collection_data is not None
+    assert collection_data["name"] == collection.name
+
+
 def test_collection_query_by_slug(
     user_api_client, collection,
 ):
@@ -47,11 +70,11 @@ def test_collection_query_by_slug(
     assert collection_data["name"] == collection.name
 
 
-def test_collection_query_unpublished_collection_by_slug(
-    user_api_client, collection, permission_manage_products
+def test_collection_query_unpublished_collection_by_slug_as_staff(
+    staff_api_client, collection, permission_manage_products
 ):
     # given
-    user = user_api_client.user
+    user = staff_api_client.user
     user.user_permissions.add(permission_manage_products)
 
     collection.is_published = False
@@ -59,7 +82,7 @@ def test_collection_query_unpublished_collection_by_slug(
     variables = {"slug": collection.slug}
 
     # when
-    response = user_api_client.post_graphql(QUERY_COLLECTION, variables=variables)
+    response = staff_api_client.post_graphql(QUERY_COLLECTION, variables=variables)
 
     # then
     content = get_graphql_content(response)
diff --git a/saleor/graphql/product/tests/test_product.py b/saleor/graphql/product/tests/test_product.py
--- a/saleor/graphql/product/tests/test_product.py
+++ b/saleor/graphql/product/tests/test_product.py
@@ -153,6 +153,27 @@ def test_product_query_by_id(
     assert product_data["visibleInListings"] is True
 
 
+def test_product_unpublished_query_by_id_as_app(
+    app_api_client, unavailable_product, permission_manage_products
+):
+    # given
+    variables = {"id": graphene.Node.to_global_id("Product", unavailable_product.pk)}
+
+    # when
+    response = app_api_client.post_graphql(
+        QUERY_PRODUCT,
+        variables=variables,
+        permissions=[permission_manage_products],
+        check_no_permissions=False,
+    )
+
+    # then
+    content = get_graphql_content(response)
+    product_data = content["data"]["product"]
+    assert product_data is not None
+    assert product_data["name"] == unavailable_product.name
+
+
 def test_product_query_by_id_weight_returned_in_default_unit(
     user_api_client, product, site_settings
 ):
diff --git a/saleor/graphql/product/tests/test_variant_query.py b/saleor/graphql/product/tests/test_variant_query.py
--- a/saleor/graphql/product/tests/test_variant_query.py
+++ b/saleor/graphql/product/tests/test_variant_query.py
@@ -1,5 +1,4 @@
 import graphene
-import pytest
 
 from ...tests.utils import assert_graphql_error_with_message, get_graphql_content
 
@@ -68,7 +67,6 @@ def test_get_unpublished_variant_by_id_as_staff(
     assert data["sku"] == variant.sku
 
 
-@pytest.mark.skip(reason="Issue #5845")
 def test_get_unpublished_variant_by_id_as_app(
     app_api_client, permission_manage_products, unavailable_product_with_variant
 ):
@@ -220,7 +218,6 @@ def test_get_unpublished_variant_by_sku_as_staff(
     assert data["sku"] == variant.sku
 
 
-@pytest.mark.skip(reason="Issue #5845")
 def test_get_unpublished_variant_by_sku_as_app(
     app_api_client, permission_manage_products, unavailable_product_with_variant
 ):