diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py b/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py new file mode 100644 index 000000000000..f14a59829166 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest +from source_shopify.auth import NotImplementedAuth, ShopifyAuthenticator + +TEST_ACCESS_TOKEN = "test_access_token" +TEST_API_PASSWORD = "test_api_password" + + +@pytest.fixture +def config_access_token(): + return {"credentials": {"access_token": TEST_ACCESS_TOKEN, "auth_method": "access_token"}} + + +@pytest.fixture +def config_api_password(): + return {"credentials": {"api_password": TEST_API_PASSWORD, "auth_method": "api_password"}} + + +@pytest.fixture +def config_not_implemented_auth_method(): + return {"credentials": {"auth_method": "not_implemented_auth_method"}} + + +@pytest.fixture +def expected_auth_header_access_token(): + return {"X-Shopify-Access-Token": TEST_ACCESS_TOKEN} + + +@pytest.fixture +def expected_auth_header_api_password(): + return {"X-Shopify-Access-Token": TEST_API_PASSWORD} + + +def test_shopify_authenticator_access_token(config_access_token, expected_auth_header_access_token): + authenticator = ShopifyAuthenticator(config=config_access_token) + assert authenticator.get_auth_header() == expected_auth_header_access_token + + +def test_shopify_authenticator_api_password(config_api_password, expected_auth_header_api_password): + authenticator = ShopifyAuthenticator(config=config_api_password) + assert authenticator.get_auth_header() == expected_auth_header_api_password + + +def test_raises_notimplemented_auth(config_not_implemented_auth_method): + authenticator = ShopifyAuthenticator(config=(config_not_implemented_auth_method)) + with pytest.raises(NotImplementedAuth) as not_implemented_exc: + print(not_implemented_exc) + authenticator.get_auth_header() diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py b/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py new file mode 100644 index 000000000000..d57662efc6e8 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py @@ -0,0 +1,148 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from unittest.mock import MagicMock + +import pytest +from source_shopify.auth import ShopifyAuthenticator +from source_shopify.source import ( + AbandonedCheckouts, + Articles, + Blogs, + Collects, + CustomCollections, + Customers, + DiscountCodes, + DraftOrders, + FulfillmentOrders, + Fulfillments, + InventoryLevels, + Locations, + MetafieldArticles, + MetafieldBlogs, + MetafieldCollections, + MetafieldCustomers, + MetafieldDraftOrders, + MetafieldLocations, + MetafieldOrders, + MetafieldPages, + MetafieldProducts, + MetafieldProductVariants, + MetafieldShops, + MetafieldSmartCollections, + OrderRefunds, + OrderRisks, + Orders, + Pages, + PriceRules, + ProductImages, + Products, + ProductVariants, + Shop, + SourceShopify, + TenderTransactions, + Transactions, +) + + +@pytest.fixture +def config(basic_config): + basic_config["start_date"] = "2020-11-01" + basic_config["authenticator"] = ShopifyAuthenticator(basic_config) + return basic_config + + +@pytest.mark.parametrize( + "stream,stream_slice,expected_path", + [ + (Articles, None, "articles.json"), + (Blogs, None, "blogs.json"), + (MetafieldBlogs, {"id": 123}, "blogs/123/metafields.json"), + (MetafieldArticles, {"id": 123}, "articles/123/metafields.json"), + (MetafieldCustomers, {"id": 123}, "customers/123/metafields.json"), + (MetafieldOrders, {"id": 123}, "orders/123/metafields.json"), + (MetafieldDraftOrders, {"id": 123}, "draft_orders/123/metafields.json"), + (MetafieldProducts, {"id": 123}, "products/123/metafields.json"), + (MetafieldProductVariants, {"variants": 123}, "variants/123/metafields.json"), + (MetafieldSmartCollections, {"id": 123}, "smart_collections/123/metafields.json"), + (MetafieldCollections, {"collection_id": 123}, "collections/123/metafields.json"), + (MetafieldPages, {"id": 123}, "pages/123/metafields.json"), + (MetafieldLocations, {"id": 123}, "locations/123/metafields.json"), + (MetafieldShops, None, "metafields.json"), + (ProductImages, {"product_id": 123}, "products/123/images.json"), + (ProductVariants, {"product_id": 123}, "products/123/variants.json"), + (Customers, None, "customers.json"), + (Orders, None, "orders.json"), + (DraftOrders, None, "draft_orders.json"), + (Products, None, "products.json"), + (AbandonedCheckouts, None, "checkouts.json"), + (Collects, None, "collects.json"), + (TenderTransactions, None, "tender_transactions.json"), + (Pages, None, "pages.json"), + (PriceRules, None, "price_rules.json"), + (Locations, None, "locations.json"), + (Shop, None, "shop.json"), + (CustomCollections, None, "custom_collections.json"), + ], +) +def test_customers_path(stream, stream_slice, expected_path, config): + stream = stream(config) + if stream_slice: + result = stream.path(stream_slice) + else: + result = stream.path() + assert result == expected_path + + +@pytest.mark.parametrize( + "stream,stream_slice,expected_path", + [ + (OrderRefunds, {"order_id": 12345}, "orders/12345/refunds.json"), + (OrderRisks, {"order_id": 12345}, "orders/12345/risks.json"), + (Transactions, {"order_id": 12345}, "orders/12345/transactions.json"), + (DiscountCodes, {"price_rule_id": 12345}, "price_rules/12345/discount_codes.json"), + (InventoryLevels, {"location_id": 12345}, "locations/12345/inventory_levels.json"), + (FulfillmentOrders, {"order_id": 12345}, "orders/12345/fulfillment_orders.json"), + (Fulfillments, {"order_id": 12345}, "orders/12345/fulfillments.json"), + ], +) +def test_customers_path_with_stream_slice_param(stream, stream_slice, expected_path, config): + stream = stream(config) + assert stream.path(stream_slice) == expected_path + + +def test_check_connection(config, mocker): + mocker.patch("source_shopify.source.Shop.read_records", return_value=[{"id": 1}]) + source = SourceShopify() + logger_mock = MagicMock() + assert source.check_connection(logger_mock, config) == (True, None) + + +def test_read_records(config, mocker): + records = [{"created_at": "2022-10-10T06:21:53-07:00", "orders": {"updated_at": "2022-10-10T06:21:53-07:00"}}] + stream_slice = records[0] + stream = OrderRefunds(config) + mocker.patch("source_shopify.source.IncrementalShopifyStream.read_records", return_value=records) + assert next(stream.read_records(stream_slice=stream_slice)) == records[0] + + +@pytest.mark.parametrize( + "stream, expected", + [ + (OrderRefunds, {"limit": 250}), + (Orders, {"limit": 250, "status": "any", "order": "updated_at asc", "updated_at_min": "2020-11-01"}), + (AbandonedCheckouts, {"limit": 250, "status": "any", "order": "updated_at asc", "updated_at_min": "2020-11-01"}), + ], +) +def test_request_params(config, stream, expected): + assert stream(config).request_params() == expected + + +def test_get_updated_state(config): + current_stream_state = {"created_at": ""} + latest_record = {"created_at": "2022-10-10T06:21:53-07:00"} + updated_state = {"created_at": "2022-10-10T06:21:53-07:00", "orders": None} + stream = OrderRefunds(config) + assert stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) == updated_state